add RBAC for HsOfficeSepaMandateEntity, improved DSL and Postgres-generator

This commit is contained in:
Michael Hoennig 2024-02-28 13:58:55 +01:00
parent 59ea077a4e
commit dff9803dc3
13 changed files with 227 additions and 121 deletions

View File

@ -58,7 +58,7 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
public static RbacView rbac() {
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
.withIdentityView(SQL.query("target.iban || ':' || target.holder"))
.withIdentityView(SQL.projection("iban || ':' || holder"))
.withUpdatableColumns("holder", "iban", "bic")
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);

View File

@ -5,6 +5,7 @@ import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
@ -61,7 +62,7 @@ public class HsOfficeContactEntity implements Stringifyable, HasUuid {
public static RbacView rbac() {
return rbacViewFor("contact", HsOfficeContactEntity.class)
.withIdentityView(RbacView.SQL.query("target.label"))
.withIdentityView(SQL.projection("label"))
.withUpdatableColumns("label", "postalAddress", "emailAddresses", "phoneNumbers")
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);

View File

@ -144,22 +144,22 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
.createPermission(VIEW).grantedTo("debitorRel", TENANT)
.importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class,
fetchedBySql("""
dependsOnColumn("bankAccountUuid"), fetchedBySql("""
SELECT *
FROM hs_office_relationship AS r
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid
"""),
dependsOnColumn("bankAccountUuid"))
""")
)
.toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
.importEntityAlias("partnerRel", HsOfficeRelationshipEntity.class,
fetchedBySql("""
dependsOnColumn("debitorRelUuid"), fetchedBySql("""
SELECT *
FROM hs_office_relationship AS partnerRel
WHERE ${debitorRel}.relAnchorUuid = partnerRel.relHolderUuid
"""),
dependsOnColumn("debitorRelUuid"))
""")
)
.toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN)
.toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT)

View File

@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
@ -69,7 +70,7 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
public static RbacView rbac() {
return rbacViewFor("partnerDetails", HsOfficePartnerDetailsEntity.class)
.withIdentityView(RbacView.SQL.query("""
.withIdentityView(SQL.query("""
SELECT partner_iv.idName || '-details'
FROM hs_office_partner_details AS partnerDetails
JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid

View File

@ -7,8 +7,7 @@ import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacViewMermaidFlowchart;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacViewPostgresGenerator;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.NotFound;
@ -81,11 +80,11 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
public static RbacView rbac() {
return rbacViewFor("partner", HsOfficePartnerEntity.class)
.withIdentityView(RbacView.SQL.query("""
.withIdentityView(SQL.query("""
SELECT partner.partnerNumber
|| ':' || (SELECT idName FROM hs_office_person_iv p WHERE p.uuid = partner.personuuid)
|| '-' || (SELECT idName FROM hs_office_contact_iv c WHERE c.uuid = partner.contactuuid)
FROM hs_office_partner AD partner
|| ':' || (SELECT idName FROM hs_office_person_iv p WHERE p.uuid = partner.personUuid)
|| '-' || (SELECT idName FROM hs_office_contact_iv c WHERE c.uuid = partner.contactUuid)
FROM hs_office_partner AS partner
"""))
.withUpdatableColumns(
"partnerRoleUuid",
@ -109,9 +108,6 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
}
public static void main(String[] args) throws IOException {
final RbacView rbac = HsOfficePartnerEntity.rbac();
new RbacViewMermaidFlowchart(rbac).generateToMarkdownFile();
new RbacViewPostgresGenerator(rbac).generateToChangeLog("233-hs-office-partner-rbac.sql");
HsOfficePartnerEntity.rbac().generateWithBaseFileName("233-hs-office-partner-rbac");
}
}

View File

@ -16,7 +16,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.query;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.projection;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@ -66,7 +66,7 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
public static RbacView rbac() {
return rbacViewFor("person", HsOfficePersonEntity.class)
.withIdentityView(query("concat(target.tradeName, target.familyName, target.givenName)"))
.withIdentityView(projection("concat(tradeName, familyName, givenName)"))
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
.createRole(OWNER, (with) -> {
with.permission(ALL);

View File

@ -79,21 +79,21 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
public static RbacView rbac() {
return rbacViewFor("relationship", HsOfficeRelationshipEntity.class)
.withIdentityView(SQL.query("""
(select idName from hs_office_person_iv p where p.uuid = target.relAnchorUuid)
.withIdentityView(SQL.projection("""
(select idName from hs_office_person_iv p where p.uuid = relAnchorUuid)
|| '-with-' || target.relType || '-'
|| (select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)
|| (select idName from hs_office_person_iv p where p.uuid = relHolderUuid)
"""))
.withUpdatableColumns("contactUuid")
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relAnchorUuid"),
dependsOnColumn("relAnchorUuid"))
dependsOnColumn("relAnchorUuid"), fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relAnchorUuid")
)
.importEntityAlias("holderPerson", HsOfficePersonEntity.class,
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relHolderUuid"),
dependsOnColumn("relHolderUuid"))
dependsOnColumn("relHolderUuid"), fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relHolderUuid")
)
.importEntityAlias("contact", HsOfficeContactEntity.class,
fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid"),
dependsOnColumn("contactUuid"))
dependsOnColumn("contactUuid"), fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid")
)
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);

View File

@ -6,16 +6,26 @@ import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;
import jakarta.persistence.*;
import java.io.IOException;
import java.time.LocalDate;
import java.util.UUID;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity
@ -84,4 +94,33 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
return reference;
}
public static RbacView rbac() {
return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class)
.withIdentityView(projection("concat(tradeName, familyName, givenName)"))
.withUpdatableColumns("reference", "agreement", "validity")
.importEntityAlias("debitorRel", HsOfficeRelationshipEntity.class, dependsOnColumn("debitorRelUuid"))
.importEntityAlias("bankAccount", HsOfficeBankAccountEntity.class, dependsOnColumn("bankAccountUuid"))
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
with.outgoingSubRole("bankAccount", REFERRER);
with.permission(ALL);
})
.createSubRole(ADMIN, (with) -> {
with.permission(EDIT);
})
.createSubRole(AGENT, (with) -> {
with.outgoingSubRole("debitorRel", AGENT);
})
.createSubRole(REFERRER, (with) -> {
with.incomingSuperRole("debitorRel", AGENT);
with.permission(VIEW);
});
}
public static void main(String[] args) throws IOException {
HsOfficeSepaMandateEntity.rbac().generateWithBaseFileName("253-hs-office-sepamandate-rbac");
}
}

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.rbac.rbacdef;
import java.nio.file.Path;
import java.util.function.Consumer;
import lombok.EqualsAndHashCode;
@ -7,17 +8,21 @@ import lombok.Getter;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.autoFetched;
import static org.apache.commons.lang3.StringUtils.uncapitalize;
@Getter
public class RbacView {
public static final String GLOBAL = "global";
public static final String OUTPUT_BASEDIR = "src/main/resources/db/changelog";
private final EntityAlias rootEntityAlias;
@ -123,11 +128,18 @@ public class RbacView {
public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final SQL fetchSql, final Column dependsOnColum) {
final Column dependsOnColum, final SQL fetchSql) {
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false);
return this;
}
public RbacView importEntityAlias(
final String aliasName, final Class<? extends HasUuid> entityClass,
final Column dependsOnColum) {
importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false);
return this;
}
private EntityAlias importEntityAliasImpl(
final String aliasName, final Class<? extends HasUuid> entityClass,
final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity) {
@ -200,6 +212,11 @@ public class RbacView {
return entityAlias == rootEntityAliasProxy;
}
public void generateWithBaseFileName(final String baseFileName) {
new RbacViewMermaidFlowchart(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + "-generated.md"));
new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + "-generated.sql"));
}
public class RbacGrantBuilder {
private final RbacRoleDefinition superRoleDef;
@ -463,6 +480,18 @@ public class RbacView {
return entityClass == null;
}
@Override
public SQL fetchSql() {
if ( fetchSql == null ) {
return null;
}
return switch (fetchSql.part) {
case SQL_QUERY -> fetchSql;
case AUTO_FETCH -> SQL.query("SELECT * FROM " + getRawTableName(entityClass) + " WHERE uuid = ${ref}." + dependsOnColum.column);
default -> throw new IllegalStateException("unexpected SQL definition: " + fetchSql);
};
}
private String withoutEntitySuffix(final String simpleEntityName) {
return simpleEntityName.substring(0, simpleEntityName.length()-"Entity".length());
}
@ -474,6 +503,13 @@ public class RbacView {
}
}
public static String getRawTableName(final Class<?> entityClass) {
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
}
public static String withoutRvSuffix(final String tableName) {
return tableName.substring(0, tableName.length()-"_rv".length());
}
public record Role(String roleName) {
public static final Role OWNER = new Role("owner");
public static final Role ADMIN = new Role("admin");
@ -510,7 +546,7 @@ public class RbacView {
public static class SQL {
/**
* DSL methid to specify an SQL SELECT expression which fetches the related entity,
* DSL method to specify an SQL SELECT expression which fetches the related entity,
* using the reference `${ref}` of the root entity.
* `${ref}` is going to be replaced by either `NEW` or `OLD` of the trigger function.
* `into ...` will be added with a variable name prefixed with either `new` or `old`.
@ -520,7 +556,18 @@ public class RbacView {
*/
public static SQL fetchedBySql(final String sql) {
validateExpression(sql);
return new SQL(sql);
return new SQL(sql, Part.SQL_QUERY);
}
/**
* DSL method to specify that a related entity is to be fetched by a simple SELECT statement
* using the raw table from the @Table statement of the entity to fetch
* and the dependent column of the root entity.
*
* @return the wrapped SQL definition object
*/
public static SQL autoFetched() {
return new SQL(null, Part.AUTO_FETCH);
}
/** Generic DSL method to specify an SQL SELECT expression.
@ -530,13 +577,39 @@ public class RbacView {
*/
public static SQL query(final String sql) {
validateExpression(sql);
return new SQL(sql);
return new SQL(sql, Part.SQL_QUERY);
}
public final String sql;
/** Generic DSL method to specify an SQL SELECT expression by just the projection part.
*
* @param projection an SQL SELECT expression, the list of columns after 'SELECT'
* @return the wrapped SQL projection
*/
public static SQL projection(final String projection) {
validateProjection(projection);
return new SQL(projection, Part.SQL_PROJECTION);
}
private SQL(final String sql) {
enum Part {
SQL_QUERY,
AUTO_FETCH, SQL_PROJECTION
}
final String sql;
final Part part;
private SQL(final String sql, final Part part) {
this.sql = sql;
this.part = part;
}
private static void validateProjection(final String projection) {
if (projection.toUpperCase().matches("[ \t]*$SELECT[ \t]")) {
throw new IllegalArgumentException("SQL projection must not start with 'SELECT': " + projection);
}
if (projection.matches(";[ \t]*$")) {
throw new IllegalArgumentException("SQL projection must not end with ';': " + projection);
}
}
private static void validateExpression(final String sql) {

View File

@ -1,13 +1,8 @@
package net.hostsharing.hsadminng.rbac.rbacdef;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.nio.file.*;
import java.time.LocalDateTime;
@ -46,10 +41,11 @@ public class RbacViewMermaidFlowchart {
flowchart.writeLn("""
subgraph %{aliasName}["`**%{aliasName}**`"]
direction TB
style %{aliasName} fill:%{color},stroke:darkblue,stroke-width:8px
style %{aliasName} fill:%{fillColor},stroke:%{strokeColor},stroke-width:8px
"""
.replace("%{aliasName}", entity.aliasName())
.replace("%{color}", color ));
.replace("%{fillColor}", color )
.replace("%{strokeColor}", HOSTSHARING_DARK_BLUE ));
flowchart.indented( () -> {
rbacDef.getEntityAliases().values().stream()
@ -83,9 +79,9 @@ public class RbacViewMermaidFlowchart {
flowchart.ensureEmptyLine();
flowchart.writeLn("subgraph " + name + "[ ]\n");
flowchart.indented(() -> {
flowchart.writeLn("style %{aliasName} fill: %{color}"
flowchart.writeLn("style %{aliasName} fill:%{fillColor},stroke:white"
.replace("%{aliasName}", name)
.replace("%{color}", color));
.replace("%{fillColor}", color));
flowchart.writeLn();
flowchart.writeLn(content);
});
@ -147,8 +143,8 @@ public class RbacViewMermaidFlowchart {
return flowchart.toString();
}
public void generateToMarkdownFile() throws IOException {
final Path path = Paths.get("doc", rbacDef.getRootEntityAlias().simpleName() + ".md");
@SneakyThrows
public void generateToMarkdownFile(final Path path) {
Files.writeString(
path,
"""
@ -164,12 +160,4 @@ public class RbacViewMermaidFlowchart {
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
System.out.println("Markdown-File: " + path.toAbsolutePath());
}
public static void main(String[] args) throws IOException {
new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.rbac()).generateToMarkdownFile();
new RbacViewMermaidFlowchart(HsOfficeRelationshipEntity.rbac()).generateToMarkdownFile();
new RbacViewMermaidFlowchart(HsOfficePartnerEntity.rbac()).generateToMarkdownFile();
new RbacViewMermaidFlowchart(HsOfficePartnerDetailsEntity.rbac()).generateToMarkdownFile();
new RbacViewMermaidFlowchart(HsOfficeDebitorEntity.rbac()).generateToMarkdownFile();
}
}

View File

@ -1,10 +1,7 @@
package net.hostsharing.hsadminng.rbac.rbacdef;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import lombok.SneakyThrows;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -37,7 +34,8 @@ public class RbacViewPostgresGenerator {
return plPgSql.toString();
}
private static void generatePostgres(final RbacView rbac) throws IOException {
@SneakyThrows
private static void generatePostgres(final RbacView rbac) {
final Path outputPath = Paths.get("doc", rbac.getRootEntityAlias().simpleName() + ".sql");
Files.writeString(
outputPath,
@ -47,8 +45,8 @@ public class RbacViewPostgresGenerator {
System.out.println(outputPath.toAbsolutePath());
}
public void generateToChangeLog(final String fileName) throws IOException {
final Path outputPath = Path.of("src/main/resources/db/changelog", fileName);
@SneakyThrows
public void generateToChangeLog(final Path outputPath) {
Files.writeString(
outputPath,
toString(),
@ -56,10 +54,4 @@ public class RbacViewPostgresGenerator {
StandardOpenOption.TRUNCATE_EXISTING);
System.out.println(outputPath.toAbsolutePath());
}
public static void main(String[] args) throws IOException {
generatePostgres(HsOfficeRelationshipEntity.rbac());
generatePostgres(HsOfficePartnerEntity.rbac());
generatePostgres(HsOfficeDebitorEntity.rbac());
}
}

View File

@ -2,14 +2,15 @@ package net.hostsharing.hsadminng.rbac.rbacdef;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition;
import jakarta.persistence.Table;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static java.util.stream.Collectors.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.getRawTableName;
import static org.apache.commons.lang3.StringUtils.capitalize;
import static org.apache.commons.lang3.StringUtils.uncapitalize;
@ -141,10 +142,6 @@ class RolesGrantsAndPermissionsGenerator {
return ref.name().toLowerCase() + capitalize(entityAlias.aliasName());
}
private String getRawTableName(final Class<?> entityClass) {
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
}
private String roleRef(final PostgresTriggerReference rootRefVar, final RbacView.RbacRoleDefinition roleDef) {
if ( roleDef == null ) {
System.out.println("null");
@ -179,51 +176,13 @@ class RolesGrantsAndPermissionsGenerator {
.replace("${simpleVarName)", simpleEntityVarName)
.replace("${roleSuffix}", capitalize(role.roleName())));
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
if (!permissionGrantsForRole.isEmpty()) {
final var permissionsForRoleInPlPgSql = permissionGrantsForRole.stream()
.map(RbacView.RbacGrantDefinition::getPermDef)
.map(RbacPermissionDefinition::getPermission)
.map(RbacView.Permission::permission)
.map(p -> "'" + p + "'")
.collect(joining(", "));
plPgSql.indented( () ->
plPgSql.writeLn("permissions => array[" + permissionsForRoleInPlPgSql + "],\n"));
rbacGrants.removeAll(permissionGrantsForRole);
}
generatePermissionsForRole(plPgSql, role);
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
if (!grantsToUsers.isEmpty()) {
final var grantsToUsersPlPgSql = grantsToUsers.stream()
.map(RbacView.RbacGrantDefinition::getUserDef)
.map(this::toPlPgSqlReference)
.collect(joining(", "));
plPgSql.indented(() ->
plPgSql.writeLn("userUuids => array[" + grantsToUsersPlPgSql + "],\n"));
rbacGrants.removeAll(grantsToUsers);
}
generateUserGrantsForRole(plPgSql, role);
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
if (!incomingGrants.isEmpty()) {
final var incomingGrantsInPlPgSql = incomingGrants.stream()
.map(RbacView.RbacGrantDefinition::getSuperRoleDef)
.map(r -> toPlPgSqlReference(NEW, r))
.collect(joining(", "));
plPgSql.indented(() ->
plPgSql.writeLn("incomingSuperRoles => array[" + incomingGrantsInPlPgSql + "],\n"));
rbacGrants.removeAll(incomingGrants);
}
generateIncomingSuperRolesForRole(plPgSql, role);
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
if (!outgoingGrants.isEmpty()) {
final var outgoingGrantsInPlPgSql = outgoingGrants.stream()
.map(RbacView.RbacGrantDefinition::getSuperRoleDef)
.map(r -> toPlPgSqlReference(NEW, r))
.collect(joining(", "));
plPgSql.indented(() ->
plPgSql.writeLn("outgoingSubRoles => array[" + outgoingGrantsInPlPgSql + "],\n"));
rbacGrants.removeAll(outgoingGrants);
}
generateOutgoingSubRolesForRole(plPgSql, role);
plPgSql.chopTail(",\n");
plPgSql.writeLn();
@ -232,6 +191,66 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.writeLn(");");
}
private void generateUserGrantsForRole(final StringWriter plPgSql, final RbacView.Role role) {
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
if (!grantsToUsers.isEmpty()) {
final var arrayElements = grantsToUsers.stream()
.map(RbacView.RbacGrantDefinition::getUserDef)
.map(this::toPlPgSqlReference)
.toList();
plPgSql.indented(() ->
plPgSql.writeLn("userUuids => array[" + joinArrayElements(arrayElements, 2) + "],\n"));
rbacGrants.removeAll(grantsToUsers);
}
}
private void generatePermissionsForRole(final StringWriter plPgSql, final RbacView.Role role) {
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
if (!permissionGrantsForRole.isEmpty()) {
final var arrayElements = permissionGrantsForRole.stream()
.map(RbacView.RbacGrantDefinition::getPermDef)
.map(RbacPermissionDefinition::getPermission)
.map(RbacView.Permission::permission)
.map(p -> "'" + p + "'")
.toList();
plPgSql.indented( () ->
plPgSql.writeLn("permissions => array[" + joinArrayElements(arrayElements, 3) + "],\n"));
rbacGrants.removeAll(permissionGrantsForRole);
}
}
private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
if (!incomingGrants.isEmpty()) {
final var arraElements = incomingGrants.stream()
.map(RbacView.RbacGrantDefinition::getSuperRoleDef)
.map(r -> toPlPgSqlReference(NEW, r))
.toList();
plPgSql.indented(() ->
plPgSql.writeLn("incomingSuperRoles => array[" + joinArrayElements(arraElements, 1) + "],\n"));
rbacGrants.removeAll(incomingGrants);
}
}
private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
if (!outgoingGrants.isEmpty()) {
final var arrayElements = outgoingGrants.stream()
.map(RbacView.RbacGrantDefinition::getSubRoleDef)
.map(r -> toPlPgSqlReference(NEW, r))
.toList();
plPgSql.indented(() ->
plPgSql.writeLn("outgoingSubRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n"));
rbacGrants.removeAll(outgoingGrants);
}
}
private String joinArrayElements(final List<String> arrayElements, final int singleLineLimit) {
return arrayElements.size() <= singleLineLimit
? String.join(", ", arrayElements)
: arrayElements.stream().collect(joining(",\n\t", "\n\t", ""));
}
private Set<RbacView.RbacGrantDefinition> findPermissionsGrantsForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
return rbacGrants.stream()
@ -284,10 +303,6 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.writeLn();
}
private String withoutRvSuffix(final String tableName) {
return tableName.substring(0, tableName.length()-"_rv".length());
}
private String toPlPgSqlReference(final RbacView.RbacUserReference userRef) {
return switch (userRef.role) {
case CREATOR -> "currentUserUuid()";

View File

@ -5,6 +5,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import jakarta.persistence.*;
@ -39,7 +40,7 @@ public class TestCustomerEntity implements RbacObject {
public static RbacView rbac() {
return rbacViewFor("customer", TestCustomerEntity.class)
.withIdentityView(RbacView.SQL.query("target.prefix"))
.withIdentityView(SQL.projection("prefix"))
.withUpdatableColumns("reference", "prefix", "adminUserName")
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);