From 0decfe1132e7682432397d5e2a43b72c1d8d444c Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 21 Mar 2024 10:00:20 +0100 Subject: [PATCH 01/17] copy improved generators from branch remove-direct-partner-person-and-contact --- .../office/debitor/HsOfficeDebitorEntity.java | 2 +- .../partner/HsOfficePartnerDetailsEntity.java | 2 +- .../office/partner/HsOfficePartnerEntity.java | 2 +- .../rbac/rbacdef/InsertTriggerGenerator.java | 155 ++++++++++++++---- .../rbacdef/RbacIdentityViewGenerator.java | 2 +- .../rbacdef/RbacRestrictedViewGenerator.java | 6 +- .../hsadminng/rbac/rbacdef/RbacView.java | 76 +++++---- .../rbacdef/RbacViewPostgresGenerator.java | 7 +- .../RolesGrantsAndPermissionsGenerator.java | 96 +++++++++-- .../hsadminng/rbac/rbacdef/StringWriter.java | 18 +- .../rbacgrant/RbacGrantsDiagramService.java | 46 ++++-- .../hsadminng/test/dom/TestDomainEntity.java | 2 +- .../hsadminng/test/pac/TestPackageEntity.java | 2 +- .../db/changelog/113-test-customer-rbac.md | 2 +- .../db/changelog/113-test-customer-rbac.sql | 20 ++- .../db/changelog/123-test-package-rbac.md | 2 +- .../db/changelog/123-test-package-rbac.sql | 58 ++++--- .../db/changelog/133-test-domain-rbac.md | 2 +- .../db/changelog/133-test-domain-rbac.sql | 58 ++++--- 19 files changed, 405 insertions(+), 153 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java index 66f82f95..b010ed9b 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java @@ -131,7 +131,7 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { "vatBusiness", "vatReverseCharge", "defaultPrefix" /* TODO: do we want that updatable? */) - .createPermission(custom("new-debitor")).grantedTo("global", ADMIN) + .createPermission(INSERT).grantedTo("global", ADMIN) .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, fetchedBySql(""" diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java index 435357fe..31ff56ab 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java @@ -84,7 +84,7 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable { "birthName", "birthday", "dateOfDeath") - .createPermission(custom("new-partner-details")).grantedTo("global", ADMIN) + .createPermission(INSERT).grantedTo("global", ADMIN) .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, fetchedBySql(""" diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java index 8e35e9b0..5aaa5318 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java @@ -90,7 +90,7 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid { "partnerRelUuid", "personUuid", "contactUuid") - .createPermission(custom("new-partner")).grantedTo("global", ADMIN) + .createPermission(INSERT).grantedTo("global", ADMIN) .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, fetchedBySql("SELECT * FROM hs_office_relation AS r WHERE r.uuid = ${ref}.partnerRelUuid"), diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index 5303c27e..88d07efa 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -4,6 +4,7 @@ import java.util.Optional; import java.util.function.BinaryOperator; import java.util.stream.Stream; +import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE; import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with; @@ -53,16 +54,16 @@ public class InsertTriggerGenerator { FOR row IN SELECT * FROM ${rawSuperTableName} LOOP - roleUuid := findRoleId(${rawSuperRoleDescriptor}(row)); + roleUuid := findRoleId(${rawSuperRoleDescriptor}); permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}'); - call grantPermissionToRole(roleUuid, permissionUuid); + call grantPermissionToRole(permissionUuid, roleUuid); END LOOP; END; $$; """, with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()), - with("rawSuperRoleDescriptor", toVar(superRoleDef)) + with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, "row")) ); }); } @@ -79,50 +80,50 @@ public class InsertTriggerGenerator { strict as $$ begin call grantPermissionToRole( - ${rawSuperRoleDescriptor}(NEW), - createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}')); + createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}'), + ${rawSuperRoleDescriptor}); return NEW; end; $$; - create trigger ${rawSubTableName}_${rawSuperTableName}_insert_tg + -- z_... is to put it at the end of after insert triggers, to make sure the roles exist + create trigger z_${rawSubTableName}_${rawSuperTableName}_insert_tg after insert on ${rawSuperTableName} for each row execute procedure ${rawSubTableName}_${rawSuperTableName}_insert_tf(); """, with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()), - with("rawSuperRoleDescriptor", toVar(superRoleDef)) + with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, NEW.name())) ); }); } private void generateInsertCheckTrigger(final StringWriter plPgSql) { - plPgSql.writeLn(""" - /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. - */ - create or replace function ${rawSubTable}_insert_permission_missing_tf() - returns trigger - language plpgsql as $$ - begin - raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); - end; $$; - """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); getOptionalInsertGrant().ifPresentOrElse(g -> { - plPgSql.writeLn(""" - create trigger ${rawSubTable}_insert_permission_check_tg - before insert on ${rawSubTable} - for each row - when ( not hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') ) - execute procedure ${rawSubTable}_insert_permission_missing_tf(); - """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), - with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName() )); + if (!g.getSuperRoleDef().getEntityAlias().isGlobal()) { + if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) { + generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g); + } else { + generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g); + } + } else { + switch (g.getSuperRoleDef().getRole()) { + case ADMIN -> { + generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql); + } + case GUEST -> { + // no permission check trigger generated, as anybody can insert rows into this table + } + default -> { + throw new IllegalArgumentException( + "invalid global role for INSERT permission: " + g.getSuperRoleDef().getRole()); + } + } + } }, () -> { - plPgSql.writeLn(""" + plPgSql.writeLn(""" + -- FIXME: Where is this case necessary? create trigger ${rawSubTable}_insert_permission_check_tg before insert on ${rawSubTable} for each row @@ -135,6 +136,92 @@ public class InsertTriggerGenerator { }); } + private void generateInsertPermissionTriggerAllowByDirectRole(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) { + plPgSql.writeLn(""" + /** + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + */ + create or replace function ${rawSubTable}_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ + begin + raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end; $$; + + create trigger ${rawSubTable}_insert_permission_check_tg + before insert on ${rawSubTable} + for each row + when ( not hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') ) + execute procedure ${rawSubTable}_insert_permission_missing_tf(); + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), + with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName())); + } + + private void generateInsertPermissionTriggerAllowByIndirectRole( + final StringWriter plPgSql, + final RbacView.RbacGrantDefinition g) { + plPgSql.writeLn(""" + /** + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + */ + create or replace function ${rawSubTable}_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ + begin + if ( not hasInsertPermission( + ( SELECT ${varName}.uuid FROM + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), + with("varName", g.getSuperRoleDef().getEntityAlias().aliasName())); + plPgSql.indented(3, () -> { + plPgSql.writeLn( + "(" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ") AS ${varName}", + with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()), + with("ref", NEW.name())); + }); + plPgSql.writeLn(""" + + ), 'INSERT', '${rawSubTable}') ) then + raise exception + '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end if; + return NEW; + end; $$; + + create trigger ${rawSubTable}_insert_permission_check_tg + before insert on ${rawSubTable} + for each row + execute procedure ${rawSubTable}_insert_permission_missing_tf(); + + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); + } + + private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) { + plPgSql.writeLn(""" + /** + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + */ + create or replace function ${rawSubTable}_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ + begin + raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end; $$; + + create trigger ${rawSubTable}_insert_permission_check_tg + before insert on ${rawSubTable} + for each row + when ( not isGlobalAdmin() ) + execute procedure ${rawSubTable}_insert_permission_missing_tf(); + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); + } + private Stream getInsertGrants() { return rbacDef.getGrantDefs().stream() .filter(g -> g.grantType() == PERM_TO_ROLE) @@ -162,4 +249,12 @@ public class InsertTriggerGenerator { return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName()); } + + private String toRoleDescriptor(final RbacView.RbacRoleDefinition roleDef, final String ref) { + final var functionName = toVar(roleDef); + if (roleDef.getEntityAlias().isGlobal()) { + return functionName + "()"; + } + return functionName + "(" + ref + ")"; + } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java index d664a83b..7e3c6a3b 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java @@ -31,7 +31,7 @@ public class RbacIdentityViewGenerator { $idName$); """; case SQL_QUERY -> """ - call generateRbacIdentityViewFromProjection('${rawTableName}', $idName$ + call generateRbacIdentityViewFromQuery('${rawTableName}', $idName$ ${identityViewSqlPart} $idName$); """; diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java index f8f6e890..a2d53d39 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java @@ -24,9 +24,11 @@ public class RbacRestrictedViewGenerator { --changeset ${liquibaseTagPrefix}-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('${rawTableName}', - '${orderBy}', + $orderBy$ + ${orderBy} + $orderBy$, $updates$ - ${updates} + ${updates} $updates$); --// diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index 2d5cd93c..2ce71379 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -32,6 +32,7 @@ import java.util.stream.Stream; import static java.lang.reflect.Modifier.isStatic; import static java.util.Arrays.stream; import static java.util.Optional.ofNullable; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; 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; @@ -141,35 +142,42 @@ public class RbacView { if (rootEntityAliasProxy != null) { throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy); } - rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false); + rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL); return this; } public RbacView importSubEntityAlias( final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) { - importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, true); + importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, true, NOT_NULL); + return this; + } + + public RbacView importEntityAlias( + final String aliasName, final Class entityClass, + final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) { + importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, nullable); return this; } public RbacView importEntityAlias( final String aliasName, final Class entityClass, final Column dependsOnColum, final SQL fetchSql) { - importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false); + importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL); return this; } public RbacView importEntityAlias( final String aliasName, final Class entityClass, final Column dependsOnColum) { - importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false); + importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false, null); return this; } private EntityAlias importEntityAliasImpl( final String aliasName, final Class entityClass, - final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity) { - final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum, asSubEntity); + final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity, final Nullable nullable) { + final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum, asSubEntity, nullable); entityAliases.put(aliasName, entityAlias); try { importAsAlias(aliasName, rbacDefinition(entityClass), asSubEntity); @@ -281,15 +289,19 @@ public class RbacView { return RbacView.this; } - public RbacView grantPermission(final String entityAliasName, final Permission perm) { - final var entityAlias = findEntityAlias(entityAliasName); - final var forTable = entityAlias.getRawTableName(); - findOrCreateGrantDef(findRbacPerm(entityAlias, perm, forTable), superRoleDef).toCreate(); + public RbacView grantPermission(final Permission perm) { + final var forTable = rootEntityAlias.getRawTableName(); + findOrCreateGrantDef(findRbacPerm(rootEntityAlias, perm, forTable), superRoleDef).toCreate(); return RbacView.this; } } + public enum Nullable { + NOT_NULL, // DEFAULT + NULLABLE + } + @Getter @EqualsAndHashCode public class RbacGrantDefinition { @@ -560,14 +572,14 @@ public class RbacView { .orElseGet(() -> new RbacGrantDefinition(subRoleDefinition, superRoleDefinition)); } - record EntityAlias(String aliasName, Class entityClass, SQL fetchSql, Column dependsOnColum, boolean isSubEntity) { + record EntityAlias(String aliasName, Class entityClass, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) { public EntityAlias(final String aliasName) { - this(aliasName, null, null, null, false); + this(aliasName, null, null, null, false, null); } public EntityAlias(final String aliasName, final Class entityClass) { - this(aliasName, entityClass, null, null, false); + this(aliasName, entityClass, null, null, false, null); } boolean isGlobal() { @@ -626,39 +638,35 @@ public class RbacView { return tableName.substring(0, tableName.length() - "_rv".length()); } - public record Role(String roleName) { + public enum Role { - public static final Role OWNER = new Role("owner"); - public static final Role ADMIN = new Role("admin"); - public static final Role AGENT = new Role("agent"); - public static final Role TENANT = new Role("tenant"); - public static final Role REFERRER = new Role("referrer"); + OWNER, + ADMIN, + AGENT, + TENANT, + REFERRER, + + GUEST; @Override public String toString() { - return ":" + roleName; + return ":" + roleName(); } - @Override - public boolean equals(final Object obj) { - return ((obj instanceof Role) && ((Role) obj).roleName.equals(this.roleName)); + String roleName() { + return name().toLowerCase(); } } - public record Permission(String permission) { - - public static final Permission INSERT = new Permission("INSERT"); - public static final Permission DELETE = new Permission("DELETE"); - public static final Permission UPDATE = new Permission("UPDATE"); - public static final Permission SELECT = new Permission("SELECT"); - - public static Permission custom(final String permission) { - return new Permission(permission); - } + public enum Permission { + INSERT, + DELETE, + UPDATE, + SELECT; @Override public String toString() { - return ":" + permission; + return ":" + name(); } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java index eb8f3534..9850d942 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java @@ -37,8 +37,11 @@ public class RbacViewPostgresGenerator { @Override public String toString() { - return plPgSql.toString(); -} + return plPgSql.toString() + .replace("\n\n\n", "\n\n") + .replace("-- ====", "\n-- ====") + .replace("\n\n--//", "\n--//"); + } @SneakyThrows public void generateToChangeLog(final Path outputPath) { diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java index edb1f609..87a730a3 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java @@ -82,6 +82,7 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.writeLn("begin"); plPgSql.indented(() -> { plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);"); + plPgSql.writeLn(); generateCreateRolesAndGrantsAfterInsert(plPgSql); plPgSql.ensureSingleEmptyLine(); plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);"); @@ -90,6 +91,37 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.writeLn(); } + + private void generateSimplifiedUpdateTriggerFunction(final StringWriter plPgSql) { + + final var updateConditions = updatableEntityAliases() + .map(RbacView.EntityAlias::dependsOnColumName) + .distinct() + .map(columnName -> "NEW." + columnName + " is distinct from OLD." + columnName) + .collect(joining( "\n or ")); + plPgSql.writeLn(""" + /* + Called from the AFTER UPDATE TRIGGER to re-wire the grants. + */ + + create or replace procedure updateRbacRulesFor${simpleEntityName}( + OLD ${rawTableName}, + NEW ${rawTableName} + ) + language plpgsql as $$ + begin + + if ${updateConditions} then + delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid; + call buildRbacSystemFor${simpleEntityName}(NEW); + end if; + end; $$; + """, + with("simpleEntityName", simpleEntityName), + with("rawTableName", rawTableName), + with("updateConditions", updateConditions)); + } + private void generateUpdateTriggerFunction(final StringWriter plPgSql) { plPgSql.writeLn(""" /* @@ -109,7 +141,7 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.chopEmptyLines(); plPgSql.indented(() -> { - updatableEntityAliases() + referencedEntityAliases() .forEach((ea) -> { plPgSql.writeLn(entityRefVar(OLD, ea) + " " + ea.getRawTableName() + ";"); plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";"); @@ -120,6 +152,7 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.writeLn("begin"); plPgSql.indented(() -> { plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);"); + plPgSql.writeLn(); generateUpdateRolesAndGrantsAfterUpdate(plPgSql); plPgSql.ensureSingleEmptyLine(); plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);"); @@ -132,11 +165,18 @@ class RolesGrantsAndPermissionsGenerator { return updatableEntityAliases().anyMatch(e -> true); } + private boolean hasAnyUpdatableAndNullableEntityAliases() { + return updatableEntityAliases() + .filter(ea -> ea.nullable() == RbacView.Nullable.NULLABLE) + .anyMatch(e -> true); + } + private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) { referencedEntityAliases() - .forEach((ea) -> plPgSql.writeLn( - ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";", - with("ref", NEW.name()))); + .forEach((ea) -> { + generateFetchedVars(plPgSql, ea, NEW); + plPgSql.writeLn(); + }); createRolesWithGrantsSql(plPgSql, OWNER); createRolesWithGrantsSql(plPgSql, ADMIN); @@ -165,14 +205,11 @@ class RolesGrantsAndPermissionsGenerator { private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) { plPgSql.ensureSingleEmptyLine(); - updatableEntityAliases() + referencedEntityAliases() .forEach((ea) -> { - plPgSql.writeLn( - ea.fetchSql().sql + " into " + entityRefVar(OLD, ea) + ";", - with("ref", OLD.name())); - plPgSql.writeLn( - ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";", - with("ref", NEW.name())); + generateFetchedVars(plPgSql, ea, OLD); + generateFetchedVars(plPgSql, ea, NEW); + plPgSql.writeLn(); }); updatableEntityAliases() @@ -190,6 +227,23 @@ class RolesGrantsAndPermissionsGenerator { }); } + private void generateFetchedVars( + final StringWriter plPgSql, + final RbacView.EntityAlias ea, + final PostgresTriggerReference old) { + plPgSql.writeLn( + ea.fetchSql().sql + " INTO " + entityRefVar(old, ea) + ";", + with("ref", old.name())); + if (ea.nullable() == RbacView.Nullable.NOT_NULL) { + plPgSql.writeLn( + "assert ${entityRefVar}.uuid is not null, format('${entityRefVar} must not be null for ${REF}.${dependsOnColumn} = %s', ${REF}.${dependsOnColumn});", + with("entityRefVar", entityRefVar(old, ea)), + with("dependsOnColumn", ea.dependsOnColumName()), + with("ref", old.name())); + plPgSql.writeLn(); + } + } + private boolean isUpdatable(final RbacView.Column c) { return rbacDef.getUpdatableColumns().contains(c); } @@ -222,7 +276,7 @@ class RolesGrantsAndPermissionsGenerator { .replace("${subRoleRef}", roleRef(OLD, grantDef.getSubRoleDef())) .replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef())); case PERM_TO_ROLE -> "call revokePermissionFromRole(${permRef}, ${superRoleRef});" - .replace("${permRef}", findPerm(OLD, grantDef.getPermDef())) + .replace("${permRef}", getPerm(OLD, grantDef.getPermDef())) .replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef())); }; } @@ -246,6 +300,10 @@ class RolesGrantsAndPermissionsGenerator { return permRef("findPermissionId", ref, permDef); } + private String getPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { + return permRef("getPermissionId", ref, permDef); + } + private String createPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { return permRef("createPermission", ref, permDef); } @@ -256,7 +314,7 @@ class RolesGrantsAndPermissionsGenerator { .replace("${entityRef}", rbacDef.isRootEntityAlias(permDef.entityAlias) ? ref.name() : refVarName(ref, permDef.entityAlias)) - .replace("${perm}", permDef.permission.permission()); + .replace("${perm}", permDef.permission.name()); } private String refVarName(final PostgresTriggerReference ref, final RbacView.EntityAlias entityAlias) { @@ -301,12 +359,12 @@ class RolesGrantsAndPermissionsGenerator { generatePermissionsForRole(plPgSql, role); - generateUserGrantsForRole(plPgSql, role); - generateIncomingSuperRolesForRole(plPgSql, role); generateOutgoingSubRolesForRole(plPgSql, role); + generateUserGrantsForRole(plPgSql, role); + plPgSql.chopTail(",\n"); plPgSql.writeLn(); }); @@ -333,7 +391,7 @@ class RolesGrantsAndPermissionsGenerator { final var arrayElements = permissionGrantsForRole.stream() .map(RbacView.RbacGrantDefinition::getPermDef) .map(RbacPermissionDefinition::getPermission) - .map(RbacView.Permission::permission) + .map(RbacView.Permission::name) .map(p -> "'" + p + "'") .sorted() .toList(); @@ -444,7 +502,11 @@ class RolesGrantsAndPermissionsGenerator { private void generateUpdateTrigger(final StringWriter plPgSql) { generateHeader(plPgSql, "update"); - generateUpdateTriggerFunction(plPgSql); + if ( hasAnyUpdatableAndNullableEntityAliases() ) { + generateSimplifiedUpdateTriggerFunction(plPgSql); + } else { + generateUpdateTriggerFunction(plPgSql); + } plPgSql.writeLn(""" /* diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java index 512ec72d..5a5e0699 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java @@ -38,12 +38,26 @@ public class StringWriter { --indentLevel; } + void indent(int levels) { + indentLevel += levels; + } + + void unindent(int levels) { + indentLevel -= levels; + } + void indented(final Runnable indented) { indent(); indented.run(); unindent(); } + void indented(int levels, final Runnable indented) { + indent(levels); + indented.run(); + unindent(levels); + } + boolean chopTail(final String tail) { if (string.toString().endsWith(tail)) { string.setLength(string.length() - tail.length()); @@ -103,8 +117,8 @@ public class StringWriter { text = matcher.replaceAll(varDef.value()); }); return text; - } catch (Exception exc) { - throw exc; + } catch (final RuntimeException exc) { + throw exc; // FIXME: just for debugging, remove try/catch before merging to master } } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java index 0296cd61..1f0ae950 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java @@ -21,6 +21,8 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService. @Service public class RbacGrantsDiagramService { + private static final int GRANT_LIMIT = 500; + public static void writeToFile(final String title, final String graph, final String fileName) { try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) { @@ -42,7 +44,11 @@ public class RbacGrantsDiagramService { PERMISSIONS, NOT_ASSUMED, TEST_ENTITIES, - NON_TEST_ENTITIES + NON_TEST_ENTITIES; + + public static final EnumSet ALL = EnumSet.allOf(Include.class); + public static final EnumSet ALL_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, TEST_ENTITIES, PERMISSIONS); + public static final EnumSet ALL_NON_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, NON_TEST_ENTITIES, PERMISSIONS); } @Autowired @@ -55,7 +61,7 @@ public class RbacGrantsDiagramService { private EntityManager em; public String allGrantsToCurrentUser(final EnumSet includes) { - final var graph = new HashSet(); + final var graph = new LimitedHashSet(); for ( UUID subjectUuid: context.currentSubjectsUuids() ) { traverseGrantsTo(graph, subjectUuid, includes); } @@ -65,6 +71,10 @@ public class RbacGrantsDiagramService { private void traverseGrantsTo(final Set graph, final UUID refUuid, final EnumSet includes) { final var grants = rawGrantRepo.findByAscendingUuid(refUuid); grants.forEach(g -> { + if ( g.getDescendantIdName() == null ) { + // FIXME: what's that? + return; + } if (!includes.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm ")) { return; } @@ -88,7 +98,7 @@ public class RbacGrantsDiagramService { .setParameter("targetObject", targetObject) .setParameter("op", op) .getSingleResult(); - final var graph = new HashSet(); + final var graph = new LimitedHashSet(); traverseGrantsFrom(graph, refUuid, includes); return toMermaidFlowchart(graph, includes); } @@ -116,7 +126,7 @@ public class RbacGrantsDiagramService { ) .collect(groupingBy(RbacGrantsDiagramService::renderEntityIdName)) .entrySet().stream() - .map(entity -> "subgraph " + quoted(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n " + .map(entity -> "subgraph " + cleanId(entity.getKey()) + renderSubgraph(entity.getKey()) + "\n\n " + entity.getValue().stream() .map(n -> renderNode(n.idName(), n.uuid()).replace("\n", "\n ")) .sorted() @@ -127,14 +137,15 @@ public class RbacGrantsDiagramService { : ""; final var grants = graph.stream() - .map(g -> quoted(g.getAscendantIdName()) + .map(g -> cleanId(g.getAscendantIdName()) + " -->" + (g.isAssumed() ? " " : "|XX| ") - + quoted(g.getDescendantIdName())) + + cleanId(g.getDescendantIdName())) .sorted() .collect(joining("\n")); final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n"; return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "") + + (grants.length() > GRANT_LIMIT ? "%% too many grants, graph is cropped\n" : "") + "flowchart TB\n\n" + entities + grants; @@ -151,7 +162,7 @@ public class RbacGrantsDiagramService { // } // return "[" + table + "\n" + entity + "]"; // } - return "[" + entityId + "]"; + return "[" + cleanId(entityId) + "]"; } private static String renderEntityIdName(final Node node) { @@ -170,7 +181,7 @@ public class RbacGrantsDiagramService { } private String renderNode(final String idName, final UUID uuid) { - return quoted(idName) + renderNodeContent(idName, uuid); + return cleanId(idName) + renderNodeContent(idName, uuid); } private String renderNodeContent(final String idName, final UUID uuid) { @@ -196,9 +207,24 @@ public class RbacGrantsDiagramService { } @NotNull - private static String quoted(final String idName) { - return idName.replace(" ", ":").replaceAll("@.*", ""); + private static String cleanId(final String idName) { + return idName.replace(" ", ":").replaceAll("@.*", "") + .replace("[", "").replace("]", "").replace("(", "").replace(")", "").replace(",", ""); } + + + class LimitedHashSet extends HashSet { + + @Override + public boolean add(final T t) { + if (size() < GRANT_LIMIT ) { + return super.add(t); + } else { + return false; + } + } + } + } record Node(String idName, UUID uuid) { diff --git a/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java b/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java index 6a031df7..fe053f1f 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java @@ -53,7 +53,7 @@ public class TestDomainEntity implements HasUuid { SELECT * FROM test_package p WHERE p.uuid= ${ref}.packageUuid """)) - .toRole("package", ADMIN).grantPermission("domain", INSERT) + .toRole("package", ADMIN).grantPermission(INSERT) .createRole(OWNER, (with) -> { with.incomingSuperRole("package", ADMIN); diff --git a/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java b/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java index 757fcf05..9dc0d5d9 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java @@ -54,7 +54,7 @@ public class TestPackageEntity implements HasUuid { SELECT * FROM test_customer c WHERE c.uuid= ${ref}.customerUuid """)) - .toRole("customer", ADMIN).grantPermission("package", INSERT) + .toRole("customer", ADMIN).grantPermission(INSERT) .createRole(OWNER, (with) -> { with.incomingSuperRole("customer", ADMIN); diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.md b/src/main/resources/db/changelog/113-test-customer-rbac.md index 7770e470..99083bec 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.md +++ b/src/main/resources/db/changelog/113-test-customer-rbac.md @@ -1,6 +1,6 @@ ### rbac customer -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.571772062. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:18.451453701. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index 6ae19710..2d8436a8 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -1,5 +1,6 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.584886824. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:18.467932975. + -- ============================================================================ --changeset test-customer-rbac-OBJECT:1 endDelimiter:--// @@ -36,8 +37,8 @@ begin perform createRoleWithGrants( testCustomerOwner(NEW), permissions => array['DELETE'], - userUuids => array[currentUserUuid()], - incomingSuperRoles => array[globalAdmin(unassumed())] + incomingSuperRoles => array[globalAdmin(unassumed())], + userUuids => array[currentUserUuid()] ); perform createRoleWithGrants( @@ -72,9 +73,9 @@ create trigger insertTriggerForTestCustomer_tg after insert on test_customer for each row execute procedure insertTriggerForTestCustomer_tf(); - --// + -- ============================================================================ --changeset test-customer-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -97,8 +98,8 @@ create trigger test_customer_insert_permission_check_tg -- only global admins are allowed to insert any rows. when ( not isGlobalAdmin() ) execute procedure test_customer_insert_permission_missing_tf(); - --// + -- ============================================================================ --changeset test-customer-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -106,18 +107,19 @@ create trigger test_customer_insert_permission_check_tg call generateRbacIdentityViewFromProjection('test_customer', $idName$ prefix $idName$); - --// + -- ============================================================================ --changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('test_customer', - 'reference', + $orderBy$ + reference + $orderBy$, $updates$ - reference = new.reference, + reference = new.reference, prefix = new.prefix, adminUserName = new.adminUserName $updates$); --// - diff --git a/src/main/resources/db/changelog/123-test-package-rbac.md b/src/main/resources/db/changelog/123-test-package-rbac.md index 78da4439..2ba8560e 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.md +++ b/src/main/resources/db/changelog/123-test-package-rbac.md @@ -1,6 +1,6 @@ ### rbac package -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.624847792. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:51.758424330. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index 20562642..b3d20bac 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -1,5 +1,6 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.625353859. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:51.767062425. + -- ============================================================================ --changeset test-package-rbac-OBJECT:1 endDelimiter:--// @@ -33,9 +34,12 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); + SELECT * FROM test_customer c WHERE c.uuid= NEW.customerUuid - into newCustomer; + INTO newCustomer; + assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid); + perform createRoleWithGrants( testPackageOwner(NEW), @@ -75,9 +79,9 @@ create trigger insertTriggerForTestPackage_tg after insert on test_package for each row execute procedure insertTriggerForTestPackage_tf(); - --// + -- ============================================================================ --changeset test-package-rbac-update-trigger:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -101,14 +105,18 @@ begin SELECT * FROM test_customer c WHERE c.uuid= OLD.customerUuid - into oldCustomer; + INTO oldCustomer; + assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s', OLD.customerUuid); + SELECT * FROM test_customer c WHERE c.uuid= NEW.customerUuid - into newCustomer; + INTO newCustomer; + assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid); + if NEW.customerUuid <> OLD.customerUuid then - call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testCustomerAdmin(oldCustomer)); + call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testCustomerAdmin(oldCustomer)); call revokeRoleFromRole(testPackageOwner(OLD), testCustomerAdmin(oldCustomer)); call grantRoleToRole(testPackageOwner(NEW), testCustomerAdmin(newCustomer)); @@ -138,9 +146,9 @@ create trigger updateTriggerForTestPackage_tg after update on test_package for each row execute procedure updateTriggerForTestPackage_tf(); - --// + -- ============================================================================ --changeset test-package-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -160,7 +168,7 @@ do language plpgsql $$ LOOP roleUuid := findRoleId(testCustomerAdmin(row)); permissionUuid := createPermission(row.uuid, 'INSERT', 'test_package'); - call grantPermissionToRole(roleUuid, permissionUuid); + call grantPermissionToRole(permissionUuid, roleUuid); END LOOP; END; $$; @@ -174,12 +182,13 @@ create or replace function test_package_test_customer_insert_tf() strict as $$ begin call grantPermissionToRole( - testCustomerAdmin(NEW), - createPermission(NEW.uuid, 'INSERT', 'test_package')); + createPermission(NEW.uuid, 'INSERT', 'test_package'), + testCustomerAdmin(NEW)); return NEW; end; $$; -create trigger test_package_test_customer_insert_tg +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_test_package_test_customer_insert_tg after insert on test_customer for each row execute procedure test_package_test_customer_insert_tf(); @@ -191,17 +200,27 @@ create or replace function test_package_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - raise exception '[403] insert into test_package not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); + if ( not hasInsertPermission( + ( SELECT customer.uuid FROM + + (SELECT * FROM test_customer c + WHERE c.uuid= NEW.customerUuid + ) AS customer + + ), 'INSERT', 'test_package') ) then + raise exception + '[403] insert into test_package not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end if; + return NEW; end; $$; create trigger test_package_insert_permission_check_tg before insert on test_package for each row - when ( not hasInsertPermission(NEW.customerUuid, 'INSERT', 'test_package') ) execute procedure test_package_insert_permission_missing_tf(); - --// + -- ============================================================================ --changeset test-package-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -209,18 +228,19 @@ create trigger test_package_insert_permission_check_tg call generateRbacIdentityViewFromProjection('test_package', $idName$ name $idName$); - --// + -- ============================================================================ --changeset test-package-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('test_package', - 'name', + $orderBy$ + name + $orderBy$, $updates$ - version = new.version, + version = new.version, customerUuid = new.customerUuid, description = new.description $updates$); --// - diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.md b/src/main/resources/db/changelog/133-test-domain-rbac.md index bd5cf706..800a6fe5 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.md +++ b/src/main/resources/db/changelog/133-test-domain-rbac.md @@ -1,6 +1,6 @@ ### rbac domain -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.644658132. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:31.860490657. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index e686dada..6fb9cc73 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -1,5 +1,6 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.645391647. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:31.873124905. + -- ============================================================================ --changeset test-domain-rbac-OBJECT:1 endDelimiter:--// @@ -33,9 +34,12 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); + SELECT * FROM test_package p WHERE p.uuid= NEW.packageUuid - into newPackage; + INTO newPackage; + assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid); + perform createRoleWithGrants( testDomainOwner(NEW), @@ -71,9 +75,9 @@ create trigger insertTriggerForTestDomain_tg after insert on test_domain for each row execute procedure insertTriggerForTestDomain_tf(); - --// + -- ============================================================================ --changeset test-domain-rbac-update-trigger:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -97,14 +101,18 @@ begin SELECT * FROM test_package p WHERE p.uuid= OLD.packageUuid - into oldPackage; + INTO oldPackage; + assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s', OLD.packageUuid); + SELECT * FROM test_package p WHERE p.uuid= NEW.packageUuid - into newPackage; + INTO newPackage; + assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid); + if NEW.packageUuid <> OLD.packageUuid then - call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage)); + call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage)); call revokeRoleFromRole(testDomainOwner(OLD), testPackageAdmin(oldPackage)); call grantRoleToRole(testDomainOwner(NEW), testPackageAdmin(newPackage)); @@ -137,9 +145,9 @@ create trigger updateTriggerForTestDomain_tg after update on test_domain for each row execute procedure updateTriggerForTestDomain_tf(); - --// + -- ============================================================================ --changeset test-domain-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -159,7 +167,7 @@ do language plpgsql $$ LOOP roleUuid := findRoleId(testPackageAdmin(row)); permissionUuid := createPermission(row.uuid, 'INSERT', 'test_domain'); - call grantPermissionToRole(roleUuid, permissionUuid); + call grantPermissionToRole(permissionUuid, roleUuid); END LOOP; END; $$; @@ -173,12 +181,13 @@ create or replace function test_domain_test_package_insert_tf() strict as $$ begin call grantPermissionToRole( - testPackageAdmin(NEW), - createPermission(NEW.uuid, 'INSERT', 'test_domain')); + createPermission(NEW.uuid, 'INSERT', 'test_domain'), + testPackageAdmin(NEW)); return NEW; end; $$; -create trigger test_domain_test_package_insert_tg +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_test_domain_test_package_insert_tg after insert on test_package for each row execute procedure test_domain_test_package_insert_tf(); @@ -190,17 +199,27 @@ create or replace function test_domain_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - raise exception '[403] insert into test_domain not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); + if ( not hasInsertPermission( + ( SELECT package.uuid FROM + + (SELECT * FROM test_package p + WHERE p.uuid= NEW.packageUuid + ) AS package + + ), 'INSERT', 'test_domain') ) then + raise exception + '[403] insert into test_domain not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end if; + return NEW; end; $$; create trigger test_domain_insert_permission_check_tg before insert on test_domain for each row - when ( not hasInsertPermission(NEW.packageUuid, 'INSERT', 'test_domain') ) execute procedure test_domain_insert_permission_missing_tf(); - --// + -- ============================================================================ --changeset test-domain-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -208,18 +227,19 @@ create trigger test_domain_insert_permission_check_tg call generateRbacIdentityViewFromProjection('test_domain', $idName$ name $idName$); - --// + -- ============================================================================ --changeset test-domain-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('test_domain', - 'name', + $orderBy$ + name + $orderBy$, $updates$ - version = new.version, + version = new.version, packageUuid = new.packageUuid, description = new.description $updates$); --// - -- 2.39.5 From 3551ef087bfbe0db683f884806d978ff144165f6 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 22 Mar 2024 09:48:39 +0100 Subject: [PATCH 02/17] fix RbacGrantsDiagramService grant limit treatment --- .../hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java index 1f0ae950..e643fe8d 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java @@ -145,7 +145,7 @@ public class RbacGrantsDiagramService { final var avoidCroppedNodeLabels = "%%{init:{'flowchart':{'htmlLabels':false}}}%%\n\n"; return (includes.contains(DETAILS) ? avoidCroppedNodeLabels : "") - + (grants.length() > GRANT_LIMIT ? "%% too many grants, graph is cropped\n" : "") + + (graph.size() >= GRANT_LIMIT ? "%% too many grants, graph is cropped\n" : "") + "flowchart TB\n\n" + entities + grants; -- 2.39.5 From 20cc98b48e005c8c04f9eb7330db1c8608761722 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 22 Mar 2024 10:00:16 +0100 Subject: [PATCH 03/17] reverse arguments of grantPermissionToRole according to reading order --- src/main/resources/db/changelog/050-rbac-base.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/db/changelog/050-rbac-base.sql b/src/main/resources/db/changelog/050-rbac-base.sql index 2992d6a9..0c64866c 100644 --- a/src/main/resources/db/changelog/050-rbac-base.sql +++ b/src/main/resources/db/changelog/050-rbac-base.sql @@ -588,7 +588,7 @@ select exists( ); $$; -create or replace procedure grantPermissionToRole(roleUuid uuid, permissionUuid uuid) +create or replace procedure grantPermissionToRole(permissionUuid uuid, roleUuid uuid) language plpgsql as $$ begin perform assertReferenceType('roleId (ascendant)', roleUuid, 'RbacRole'); @@ -601,10 +601,10 @@ begin end; $$; -create or replace procedure grantPermissionToRole(roleDesc RbacRoleDescriptor, permissionUuid uuid) +create or replace procedure grantPermissionToRole(permissionUuid uuid, roleDesc RbacRoleDescriptor) language plpgsql as $$ begin - call grantPermissionToRole(findRoleId(roleDesc), permissionUuid); + call grantPermissionToRole(permissionUuid, findRoleId(roleDesc)); end; $$; -- 2.39.5 From a991c45bc95243161611351a7ee083d1b91a0b8b Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 22 Mar 2024 11:28:56 +0100 Subject: [PATCH 04/17] code improvements and documentation in InsertTriggerGenerator --- .../rbac/rbacdef/InsertTriggerGenerator.java | 31 ++++++++++++------- .../db/changelog/113-test-customer-rbac.md | 2 +- .../db/changelog/113-test-customer-rbac.sql | 14 ++------- .../db/changelog/123-test-package-rbac.md | 2 +- .../db/changelog/123-test-package-rbac.sql | 7 +++-- .../db/changelog/133-test-domain-rbac.md | 2 +- .../db/changelog/133-test-domain-rbac.sql | 7 +++-- 7 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index 88d07efa..faa3d565 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -23,7 +23,7 @@ public class InsertTriggerGenerator { void generateTo(final StringWriter plPgSql) { generateLiquibaseChangesetHeader(plPgSql); - generateGrantInsertRoleToExistingCustomers(plPgSql); + generateGrantInsertRoleToExistingObjects(plPgSql); generateInsertPermissionGrantTrigger(plPgSql); generateInsertCheckTrigger(plPgSql); plPgSql.writeLn("--//"); @@ -38,7 +38,7 @@ public class InsertTriggerGenerator { with("liquibaseTagPrefix", liquibaseTagPrefix)); } - private void generateGrantInsertRoleToExistingCustomers(final StringWriter plPgSql) { + private void generateGrantInsertRoleToExistingObjects(final StringWriter plPgSql) { getOptionalInsertSuperRole().ifPresent( superRoleDef -> { plPgSql.writeLn(""" /* @@ -100,13 +100,7 @@ public class InsertTriggerGenerator { private void generateInsertCheckTrigger(final StringWriter plPgSql) { getOptionalInsertGrant().ifPresentOrElse(g -> { - if (!g.getSuperRoleDef().getEntityAlias().isGlobal()) { - if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) { - generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g); - } else { - generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g); - } - } else { + if (g.getSuperRoleDef().getEntityAlias().isGlobal()) { switch (g.getSuperRoleDef().getRole()) { case ADMIN -> { generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql); @@ -119,6 +113,12 @@ public class InsertTriggerGenerator { "invalid global role for INSERT permission: " + g.getSuperRoleDef().getRole()); } } + } else { + if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) { + generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g); + } else { + generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g); + } } }, () -> { @@ -139,7 +139,10 @@ public class InsertTriggerGenerator { private void generateInsertPermissionTriggerAllowByDirectRole(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) { plPgSql.writeLn(""" /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, + where the check is performed by a direct role. + + A direct role is a role depending on a foreign key directly available in the NEW row. */ create or replace function ${rawSubTable}_insert_permission_missing_tf() returns trigger @@ -164,7 +167,10 @@ public class InsertTriggerGenerator { final RbacView.RbacGrantDefinition g) { plPgSql.writeLn(""" /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, + where the check is performed by an indirect role. + + An indirect role is a role FIXME. */ create or replace function ${rawSubTable}_insert_permission_missing_tf() returns trigger @@ -203,7 +209,8 @@ public class InsertTriggerGenerator { private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) { plPgSql.writeLn(""" /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, + where only global-admin has that permission. */ create or replace function ${rawSubTable}_insert_permission_missing_tf() returns trigger diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.md b/src/main/resources/db/changelog/113-test-customer-rbac.md index 99083bec..14057c2a 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.md +++ b/src/main/resources/db/changelog/113-test-customer-rbac.md @@ -1,6 +1,6 @@ ### rbac customer -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:18.451453701. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.310302721. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index 2d8436a8..2b3fda9f 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:18.467932975. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.329089492. -- ============================================================================ @@ -80,17 +80,7 @@ execute procedure insertTriggerForTestCustomer_tf(); --changeset test-customer-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -/** - Checks if the user or assumed roles are allowed to insert a row to test_customer. -*/ -create or replace function test_customer_insert_permission_missing_tf() - returns trigger - language plpgsql as $$ -begin - raise exception '[403] insert into test_customer not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); -end; $$; - +-- FIXME: Where is this case necessary? create trigger test_customer_insert_permission_check_tg before insert on test_customer for each row diff --git a/src/main/resources/db/changelog/123-test-package-rbac.md b/src/main/resources/db/changelog/123-test-package-rbac.md index 2ba8560e..0ca13fc4 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.md +++ b/src/main/resources/db/changelog/123-test-package-rbac.md @@ -1,6 +1,6 @@ ### rbac package -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:51.758424330. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.365161640. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index b3d20bac..1e79ac4b 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:51.767062425. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.365610181. -- ============================================================================ @@ -194,7 +194,10 @@ create trigger z_test_package_test_customer_insert_tg execute procedure test_package_test_customer_insert_tf(); /** - Checks if the user or assumed roles are allowed to insert a row to test_package. + Checks if the user or assumed roles are allowed to insert a row to test_package, + where the check is performed by an indirect role. + + An indirect role is a role FIXME. */ create or replace function test_package_insert_permission_missing_tf() returns trigger diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.md b/src/main/resources/db/changelog/133-test-domain-rbac.md index 800a6fe5..71fb6074 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.md +++ b/src/main/resources/db/changelog/133-test-domain-rbac.md @@ -1,6 +1,6 @@ ### rbac domain -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-21T09:53:31.860490657. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.391784384. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index 6fb9cc73..8bd6b8df 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-21T09:53:31.873124905. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.392306652. -- ============================================================================ @@ -193,7 +193,10 @@ create trigger z_test_domain_test_package_insert_tg execute procedure test_domain_test_package_insert_tf(); /** - Checks if the user or assumed roles are allowed to insert a row to test_domain. + Checks if the user or assumed roles are allowed to insert a row to test_domain, + where the check is performed by an indirect role. + + An indirect role is a role FIXME. */ create or replace function test_domain_insert_permission_missing_tf() returns trigger -- 2.39.5 From e118cfac731ab3f48402ead0141764ccedcd3af8 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 22 Mar 2024 13:49:45 +0100 Subject: [PATCH 05/17] simplify InsertTriggerGenerator cases --- .../rbac/rbacdef/InsertTriggerGenerator.java | 65 ++----------------- .../rbacdef/RbacRestrictedViewGenerator.java | 4 +- .../hsadminng/rbac/rbacdef/RbacView.java | 2 +- .../db/changelog/113-test-customer-rbac.md | 2 +- .../db/changelog/113-test-customer-rbac.sql | 19 ++++-- .../db/changelog/123-test-package-rbac.md | 2 +- .../db/changelog/123-test-package-rbac.sql | 24 ++----- .../db/changelog/133-test-domain-rbac.md | 2 +- .../db/changelog/133-test-domain-rbac.sql | 24 ++----- 9 files changed, 37 insertions(+), 107 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index faa3d565..d625c0d7 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -114,29 +114,16 @@ public class InsertTriggerGenerator { } } } else { - if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) { - generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g); - } else { - generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g); - } + generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g); } }, () -> { - plPgSql.writeLn(""" - -- FIXME: Where is this case necessary? - create trigger ${rawSubTable}_insert_permission_check_tg - before insert on ${rawSubTable} - for each row - -- As there is no explicit INSERT grant specified for this table, - -- only global admins are allowed to insert any rows. - when ( not isGlobalAdmin() ) - execute procedure ${rawSubTable}_insert_permission_missing_tf(); - """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); + System.err.println("WARNING: no explicit INSERT grant for " + rbacDef.getRootEntityAlias().simpleName() + " => implicitly grant INSERT to global.admin"); + generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql); }); } - private void generateInsertPermissionTriggerAllowByDirectRole(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) { + private void generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) { plPgSql.writeLn(""" /** Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, @@ -162,50 +149,6 @@ public class InsertTriggerGenerator { with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName())); } - private void generateInsertPermissionTriggerAllowByIndirectRole( - final StringWriter plPgSql, - final RbacView.RbacGrantDefinition g) { - plPgSql.writeLn(""" - /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, - where the check is performed by an indirect role. - - An indirect role is a role FIXME. - */ - create or replace function ${rawSubTable}_insert_permission_missing_tf() - returns trigger - language plpgsql as $$ - begin - if ( not hasInsertPermission( - ( SELECT ${varName}.uuid FROM - """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), - with("varName", g.getSuperRoleDef().getEntityAlias().aliasName())); - plPgSql.indented(3, () -> { - plPgSql.writeLn( - "(" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ") AS ${varName}", - with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()), - with("ref", NEW.name())); - }); - plPgSql.writeLn(""" - - ), 'INSERT', '${rawSubTable}') ) then - raise exception - '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); - end if; - return NEW; - end; $$; - - create trigger ${rawSubTable}_insert_permission_check_tg - before insert on ${rawSubTable} - for each row - execute procedure ${rawSubTable}_insert_permission_missing_tf(); - - """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); - } - private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) { plPgSql.writeLn(""" /** diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java index a2d53d39..0a6d23ac 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java @@ -8,13 +8,11 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with; public class RbacRestrictedViewGenerator { private final RbacView rbacDef; private final String liquibaseTagPrefix; - private final String simpleEntityVarName; private final String rawTableName; public RbacRestrictedViewGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) { this.rbacDef = rbacDef; this.liquibaseTagPrefix = liquibaseTagPrefix; - this.simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName(); this.rawTableName = rbacDef.getRootEntityAlias().getRawTableName(); } @@ -28,7 +26,7 @@ public class RbacRestrictedViewGenerator { ${orderBy} $orderBy$, $updates$ - ${updates} + ${updates} $updates$); --// diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index 2ce71379..57499bb2 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -831,7 +831,7 @@ public class RbacView { throw new RuntimeException(e); } } else { - System.err.println("no main method in: " + c.getName()); + System.err.println("WARNING: no main method in: " + c.getName() + " => no RBAC rules generated"); } }); } diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.md b/src/main/resources/db/changelog/113-test-customer-rbac.md index 14057c2a..997c5e97 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.md +++ b/src/main/resources/db/changelog/113-test-customer-rbac.md @@ -1,6 +1,6 @@ ### rbac customer -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.310302721. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.041634681. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index 2b3fda9f..9472f71e 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.329089492. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.059763974. -- ============================================================================ @@ -80,12 +80,21 @@ execute procedure insertTriggerForTestCustomer_tf(); --changeset test-customer-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- --- FIXME: Where is this case necessary? +/** + Checks if the user or assumed roles are allowed to insert a row to test_customer, + where only global-admin has that permission. +*/ +create or replace function test_customer_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into test_customer not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + create trigger test_customer_insert_permission_check_tg before insert on test_customer for each row - -- As there is no explicit INSERT grant specified for this table, - -- only global admins are allowed to insert any rows. when ( not isGlobalAdmin() ) execute procedure test_customer_insert_permission_missing_tf(); --// @@ -107,7 +116,7 @@ call generateRbacRestrictedView('test_customer', reference $orderBy$, $updates$ - reference = new.reference, + reference = new.reference, prefix = new.prefix, adminUserName = new.adminUserName $updates$); diff --git a/src/main/resources/db/changelog/123-test-package-rbac.md b/src/main/resources/db/changelog/123-test-package-rbac.md index 0ca13fc4..b4281d6d 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.md +++ b/src/main/resources/db/changelog/123-test-package-rbac.md @@ -1,6 +1,6 @@ ### rbac package -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.365161640. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.107522933. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index 1e79ac4b..26a2d1af 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.365610181. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.108087056. -- ============================================================================ @@ -195,32 +195,22 @@ execute procedure test_package_test_customer_insert_tf(); /** Checks if the user or assumed roles are allowed to insert a row to test_package, - where the check is performed by an indirect role. + where the check is performed by a direct role. - An indirect role is a role FIXME. + A direct role is a role depending on a foreign key directly available in the NEW row. */ create or replace function test_package_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - if ( not hasInsertPermission( - ( SELECT customer.uuid FROM - - (SELECT * FROM test_customer c - WHERE c.uuid= NEW.customerUuid - ) AS customer - - ), 'INSERT', 'test_package') ) then - raise exception - '[403] insert into test_package not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); - end if; - return NEW; + raise exception '[403] insert into test_package not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); end; $$; create trigger test_package_insert_permission_check_tg before insert on test_package for each row + when ( not hasInsertPermission(NEW.customerUuid, 'INSERT', 'test_package') ) execute procedure test_package_insert_permission_missing_tf(); --// @@ -241,7 +231,7 @@ call generateRbacRestrictedView('test_package', name $orderBy$, $updates$ - version = new.version, + version = new.version, customerUuid = new.customerUuid, description = new.description $updates$); diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.md b/src/main/resources/db/changelog/133-test-domain-rbac.md index 71fb6074..f856c2f7 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.md +++ b/src/main/resources/db/changelog/133-test-domain-rbac.md @@ -1,6 +1,6 @@ ### rbac domain -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T11:19:38.391784384. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.134524089. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index 8bd6b8df..c98a9bb4 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T11:19:38.392306652. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.134964758. -- ============================================================================ @@ -194,32 +194,22 @@ execute procedure test_domain_test_package_insert_tf(); /** Checks if the user or assumed roles are allowed to insert a row to test_domain, - where the check is performed by an indirect role. + where the check is performed by a direct role. - An indirect role is a role FIXME. + A direct role is a role depending on a foreign key directly available in the NEW row. */ create or replace function test_domain_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - if ( not hasInsertPermission( - ( SELECT package.uuid FROM - - (SELECT * FROM test_package p - WHERE p.uuid= NEW.packageUuid - ) AS package - - ), 'INSERT', 'test_domain') ) then - raise exception - '[403] insert into test_domain not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); - end if; - return NEW; + raise exception '[403] insert into test_domain not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); end; $$; create trigger test_domain_insert_permission_check_tg before insert on test_domain for each row + when ( not hasInsertPermission(NEW.packageUuid, 'INSERT', 'test_domain') ) execute procedure test_domain_insert_permission_missing_tf(); --// @@ -240,7 +230,7 @@ call generateRbacRestrictedView('test_domain', name $orderBy$, $updates$ - version = new.version, + version = new.version, packageUuid = new.packageUuid, description = new.description $updates$); -- 2.39.5 From 83b16dfe5ed8649012798fa73ba3bc7f5f0bfe14 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 22 Mar 2024 14:46:29 +0100 Subject: [PATCH 06/17] imroved indentation --- .../hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java | 6 +++--- src/main/resources/db/changelog/113-test-customer-rbac.md | 2 +- src/main/resources/db/changelog/113-test-customer-rbac.sql | 4 ++-- src/main/resources/db/changelog/123-test-package-rbac.md | 2 +- src/main/resources/db/changelog/123-test-package-rbac.sql | 4 ++-- src/main/resources/db/changelog/133-test-domain-rbac.md | 2 +- src/main/resources/db/changelog/133-test-domain-rbac.sql | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java index 0a6d23ac..f0bd50a0 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java @@ -23,16 +23,16 @@ public class RbacRestrictedViewGenerator { -- ---------------------------------------------------------------------------- call generateRbacRestrictedView('${rawTableName}', $orderBy$ - ${orderBy} + ${orderBy} $orderBy$, $updates$ - ${updates} + ${updates} $updates$); --// """, with("liquibaseTagPrefix", liquibaseTagPrefix), - with("orderBy", rbacDef.getOrderBySqlExpression().sql), + with("orderBy", indented(rbacDef.getOrderBySqlExpression().sql, 2)), with("updates", indented(rbacDef.getUpdatableColumns().stream() .map(c -> c + " = new." + c) .collect(joining(",\n")), 2)), diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.md b/src/main/resources/db/changelog/113-test-customer-rbac.md index 997c5e97..438b6254 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.md +++ b/src/main/resources/db/changelog/113-test-customer-rbac.md @@ -1,6 +1,6 @@ ### rbac customer -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.041634681. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.425403022. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index 9472f71e..dccb3a26 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.059763974. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.441879428. -- ============================================================================ @@ -116,7 +116,7 @@ call generateRbacRestrictedView('test_customer', reference $orderBy$, $updates$ - reference = new.reference, + reference = new.reference, prefix = new.prefix, adminUserName = new.adminUserName $updates$); diff --git a/src/main/resources/db/changelog/123-test-package-rbac.md b/src/main/resources/db/changelog/123-test-package-rbac.md index b4281d6d..895d3269 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.md +++ b/src/main/resources/db/changelog/123-test-package-rbac.md @@ -1,6 +1,6 @@ ### rbac package -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.107522933. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.484173294. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index 26a2d1af..fd375e2d 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.108087056. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.484728385. -- ============================================================================ @@ -231,7 +231,7 @@ call generateRbacRestrictedView('test_package', name $orderBy$, $updates$ - version = new.version, + version = new.version, customerUuid = new.customerUuid, description = new.description $updates$); diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.md b/src/main/resources/db/changelog/133-test-domain-rbac.md index f856c2f7..4f507312 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.md +++ b/src/main/resources/db/changelog/133-test-domain-rbac.md @@ -1,6 +1,6 @@ ### rbac domain -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:02:48.134524089. +This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.510830235. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index c98a9bb4..511b2de4 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:02:48.134964758. +-- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.511320177. -- ============================================================================ @@ -230,7 +230,7 @@ call generateRbacRestrictedView('test_domain', name $orderBy$, $updates$ - version = new.version, + version = new.version, packageUuid = new.packageUuid, description = new.description $updates$); -- 2.39.5 From cb6b491bfffe75abee87a8b3bc003e40a55efd57 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 23 Mar 2024 13:59:43 +0100 Subject: [PATCH 07/17] remove unnecessary try/catch --- .../hsadminng/rbac/rbacdef/StringWriter.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java index 5a5e0699..0376236c 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java @@ -109,17 +109,13 @@ public class StringWriter { } String apply(final String textToAppend) { - try { - text = textToAppend; - stream(varDefs).forEach(varDef -> { - final var pattern = Pattern.compile("\\$\\{" + varDef.name() + "}", Pattern.CASE_INSENSITIVE); - final var matcher = pattern.matcher(text); - text = matcher.replaceAll(varDef.value()); - }); - return text; - } catch (final RuntimeException exc) { - throw exc; // FIXME: just for debugging, remove try/catch before merging to master - } - } + text = textToAppend; + stream(varDefs).forEach(varDef -> { + final var pattern = Pattern.compile("\\$\\{" + varDef.name() + "}", Pattern.CASE_INSENSITIVE); + final var matcher = pattern.matcher(text); + text = matcher.replaceAll(varDef.value()); + }); + return text; } + } } -- 2.39.5 From e8dd5b7935381132fbd1ca35327f8ce597e97f29 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 23 Mar 2024 14:09:40 +0100 Subject: [PATCH 08/17] remove generated timestamp and explicitly grant INSERT for customer --- .../RbacViewMermaidFlowchartGenerator.java | 3 +- .../rbacdef/RbacViewPostgresGenerator.java | 3 +- .../test/cust/TestCustomerEntity.java | 3 +- .../db/changelog/113-test-customer-rbac.md | 4 +- .../db/changelog/113-test-customer-rbac.sql | 42 ++++++++++++++++++- .../db/changelog/123-test-package-rbac.md | 2 +- .../db/changelog/123-test-package-rbac.sql | 2 +- .../db/changelog/133-test-domain-rbac.md | 2 +- .../db/changelog/133-test-domain-rbac.sql | 2 +- .../test/cust/TestCustomerEntityUnitTest.java | 2 + 10 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java index ccef566d..898a0273 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java @@ -149,14 +149,13 @@ public class RbacViewMermaidFlowchartGenerator { """ ### rbac %{entityAlias} - This code generated was by RbacViewMermaidFlowchartGenerator at %{timestamp}. + This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. ```mermaid %{flowchart} ``` """ .replace("%{entityAlias}", rbacDef.getRootEntityAlias().aliasName()) - .replace("%{timestamp}", LocalDateTime.now().toString()) .replace("%{flowchart}", flowchart.toString()), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); System.out.println("Markdown-File: " + path.toAbsolutePath()); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java index 9850d942..dc56a286 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java @@ -21,10 +21,9 @@ public class RbacViewPostgresGenerator { liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableName().replace("_", "-"); plPgSql.writeLn(""" --liquibase formatted sql - -- This code generated was by ${generator} at ${timestamp}. + -- This code generated was by ${generator}, do not amend manually. """, with("generator", getClass().getSimpleName()), - with("timestamp", LocalDateTime.now().toString()), with("ref", NEW.name())); new RbacObjectGenerator(rbacDef, liqibaseTagPrefix).generateTo(plPgSql); diff --git a/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java b/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java index 99b0fb3c..b4152fa9 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java @@ -41,8 +41,7 @@ public class TestCustomerEntity implements HasUuid { .withIdentityView(SQL.projection("prefix")) .withRestrictedViewOrderBy(SQL.expression("reference")) .withUpdatableColumns("reference", "prefix", "adminUserName") - // TODO: do we want explicit specification of parent-independent insert permissions? - // .toRole("global", ADMIN).grantPermission("customer", INSERT) + .toRole("global", ADMIN).grantPermission(INSERT) .createRole(OWNER, (with) -> { with.owningUser(CREATOR).unassumed(); diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.md b/src/main/resources/db/changelog/113-test-customer-rbac.md index 438b6254..4d63eeac 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.md +++ b/src/main/resources/db/changelog/113-test-customer-rbac.md @@ -1,6 +1,6 @@ ### rbac customer -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.425403022. +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% @@ -21,6 +21,7 @@ subgraph customer["`**customer**`"] subgraph customer:permissions[ ] style customer:permissions fill:#dd4901,stroke:white + perm:customer:INSERT{{customer:INSERT}} perm:customer:DELETE{{customer:DELETE}} perm:customer:UPDATE{{customer:UPDATE}} perm:customer:SELECT{{customer:SELECT}} @@ -36,6 +37,7 @@ role:customer:owner ==> role:customer:admin role:customer:admin ==> role:customer:tenant %% granting permissions to roles +role:global:admin ==> perm:customer:INSERT role:customer:owner ==> perm:customer:DELETE role:customer:admin ==> perm:customer:UPDATE role:customer:tenant ==> perm:customer:SELECT diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index dccb3a26..b98fc949 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.441879428. +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. -- ============================================================================ @@ -80,6 +80,46 @@ execute procedure insertTriggerForTestCustomer_tf(); --changeset test-customer-rbac-INSERT:1 endDelimiter:--// -- ---------------------------------------------------------------------------- +/* + Creates INSERT INTO test_customer permissions for the related global rows. + */ +do language plpgsql $$ + declare + row global; + permissionUuid uuid; + roleUuid uuid; + begin + call defineContext('create INSERT INTO test_customer permissions for the related global rows'); + + FOR row IN SELECT * FROM global + LOOP + roleUuid := findRoleId(globalAdmin()); + permissionUuid := createPermission(row.uuid, 'INSERT', 'test_customer'); + call grantPermissionToRole(permissionUuid, roleUuid); + END LOOP; + END; +$$; + +/** + Adds test_customer INSERT permission to specified role of new global rows. +*/ +create or replace function test_customer_global_insert_tf() + returns trigger + language plpgsql + strict as $$ +begin + call grantPermissionToRole( + createPermission(NEW.uuid, 'INSERT', 'test_customer'), + globalAdmin()); + return NEW; +end; $$; + +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_test_customer_global_insert_tg + after insert on global + for each row +execute procedure test_customer_global_insert_tf(); + /** Checks if the user or assumed roles are allowed to insert a row to test_customer, where only global-admin has that permission. diff --git a/src/main/resources/db/changelog/123-test-package-rbac.md b/src/main/resources/db/changelog/123-test-package-rbac.md index 895d3269..34b8c7c7 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.md +++ b/src/main/resources/db/changelog/123-test-package-rbac.md @@ -1,6 +1,6 @@ ### rbac package -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.484173294. +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index fd375e2d..912430bb 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.484728385. +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. -- ============================================================================ diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.md b/src/main/resources/db/changelog/133-test-domain-rbac.md index 4f507312..6954e9b8 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.md +++ b/src/main/resources/db/changelog/133-test-domain-rbac.md @@ -1,6 +1,6 @@ ### rbac domain -This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.510830235. +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. ```mermaid %%{init:{'flowchart':{'htmlLabels':false}}}%% diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index 511b2de4..6344e43d 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -1,5 +1,5 @@ --liquibase formatted sql --- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.511320177. +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. -- ============================================================================ diff --git a/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityUnitTest.java index eca0aec1..d576396a 100644 --- a/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityUnitTest.java @@ -29,6 +29,7 @@ class TestCustomerEntityUnitTest { subgraph customer:permissions[ ] style customer:permissions fill:#dd4901,stroke:white + perm:customer:INSERT{{customer:INSERT}} perm:customer:DELETE{{customer:DELETE}} perm:customer:UPDATE{{customer:UPDATE}} perm:customer:SELECT{{customer:SELECT}} @@ -44,6 +45,7 @@ class TestCustomerEntityUnitTest { role:customer:admin ==> role:customer:tenant %% granting permissions to roles + role:global:admin ==> perm:customer:INSERT role:customer:owner ==> perm:customer:DELETE role:customer:admin ==> perm:customer:UPDATE role:customer:tenant ==> perm:customer:SELECT -- 2.39.5 From 87c23d2aa96f65cc6239e674af2f5f19d2eab539 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 23 Mar 2024 14:11:23 +0100 Subject: [PATCH 09/17] remove generated timestamp and explicitly grant INSERT for customer --- .../203-hs-office-contact-rbac-generated.md | 43 +++ .../203-hs-office-contact-rbac-generated.sql | 125 ++++++++ .../213-hs-office-person-rbac-generated.md | 43 +++ .../213-hs-office-person-rbac-generated.sql | 125 ++++++++ .../223-hs-office-relation-rbac-generated.md | 100 ++++++ .../223-hs-office-relation-rbac-generated.sql | 231 ++++++++++++++ .../233-hs-office-partner-rbac-generated.md | 158 ++++++++++ .../233-hs-office-partner-rbac-generated.sql | 248 +++++++++++++++ ...s-office-partner-details-rbac-generated.md | 136 ++++++++ ...-office-partner-details-rbac-generated.sql | 164 ++++++++++ ...43-hs-office-bankaccount-rbac-generated.md | 43 +++ ...3-hs-office-bankaccount-rbac-generated.sql | 124 ++++++++ ...53-hs-office-sepamandate-rbac-generated.md | 178 +++++++++++ ...3-hs-office-sepamandate-rbac-generated.sql | 142 +++++++++ .../273-hs-office-debitor-rbac-generated.md | 275 ++++++++++++++++ .../273-hs-office-debitor-rbac-generated.sql | 296 ++++++++++++++++++ 16 files changed, 2431 insertions(+) create mode 100644 src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.md create mode 100644 src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.sql create mode 100644 src/main/resources/db/changelog/213-hs-office-person-rbac-generated.md create mode 100644 src/main/resources/db/changelog/213-hs-office-person-rbac-generated.sql create mode 100644 src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.md create mode 100644 src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql create mode 100644 src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.md create mode 100644 src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql create mode 100644 src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.md create mode 100644 src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql create mode 100644 src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.md create mode 100644 src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.sql create mode 100644 src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.md create mode 100644 src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql create mode 100644 src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.md create mode 100644 src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql diff --git a/src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.md b/src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.md new file mode 100644 index 00000000..f3547312 --- /dev/null +++ b/src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.md @@ -0,0 +1,43 @@ +### rbac contact + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph contact["`**contact**`"] + direction TB + style contact fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph contact:roles[ ] + style contact:roles fill:#dd4901,stroke:white + + role:contact:owner[[contact:owner]] + role:contact:admin[[contact:admin]] + role:contact:referrer[[contact:referrer]] + end + + subgraph contact:permissions[ ] + style contact:permissions fill:#dd4901,stroke:white + + perm:contact:DELETE{{contact:DELETE}} + perm:contact:UPDATE{{contact:UPDATE}} + perm:contact:SELECT{{contact:SELECT}} + end +end + +%% granting roles to users +user:creator ==> role:contact:owner + +%% granting roles to roles +role:global:admin ==> role:contact:owner +role:contact:owner ==> role:contact:admin +role:contact:admin ==> role:contact:referrer + +%% granting permissions to roles +role:contact:owner ==> perm:contact:DELETE +role:contact:admin ==> perm:contact:UPDATE +role:contact:referrer ==> perm:contact:SELECT + +``` diff --git a/src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.sql b/src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.sql new file mode 100644 index 00000000..ac0b5114 --- /dev/null +++ b/src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.sql @@ -0,0 +1,125 @@ +--liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. + + +-- ============================================================================ +--changeset hs-office-contact-rbac-OBJECT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRelatedRbacObject('hs_office_contact'); +--// + + +-- ============================================================================ +--changeset hs-office-contact-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRoleDescriptors('hsOfficeContact', 'hs_office_contact'); +--// + + +-- ============================================================================ +--changeset hs-office-contact-rbac-insert-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates the roles, grants and permission for the AFTER INSERT TRIGGER. + */ + +create or replace procedure buildRbacSystemForHsOfficeContact( + NEW hs_office_contact +) + language plpgsql as $$ + +declare + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + perform createRoleWithGrants( + hsOfficeContactOwner(NEW), + permissions => array['DELETE'], + incomingSuperRoles => array[globalAdmin()], + userUuids => array[currentUserUuid()] + ); + + perform createRoleWithGrants( + hsOfficeContactAdmin(NEW), + permissions => array['UPDATE'], + incomingSuperRoles => array[hsOfficeContactOwner(NEW)] + ); + + perform createRoleWithGrants( + hsOfficeContactReferrer(NEW), + permissions => array['SELECT'], + incomingSuperRoles => array[hsOfficeContactAdmin(NEW)] + ); + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_contact row. + */ + +create or replace function insertTriggerForHsOfficeContact_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForHsOfficeContact(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForHsOfficeContact_tg + after insert on hs_office_contact + for each row +execute procedure insertTriggerForHsOfficeContact_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-contact-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/** + Checks if the user or assumed roles are allowed to insert a row to hs_office_contact, + where only global-admin has that permission. +*/ +create or replace function hs_office_contact_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into hs_office_contact not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + +create trigger hs_office_contact_insert_permission_check_tg + before insert on hs_office_contact + for each row + when ( not isGlobalAdmin() ) + execute procedure hs_office_contact_insert_permission_missing_tf(); +--// + +-- ============================================================================ +--changeset hs-office-contact-rbac-IDENTITY-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +call generateRbacIdentityViewFromProjection('hs_office_contact', $idName$ + label + $idName$); +--// + +-- ============================================================================ +--changeset hs-office-contact-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRestrictedView('hs_office_contact', + $orderBy$ + label + $orderBy$, + $updates$ + label = new.label, + postalAddress = new.postalAddress, + emailAddresses = new.emailAddresses, + phoneNumbers = new.phoneNumbers + $updates$); +--// + diff --git a/src/main/resources/db/changelog/213-hs-office-person-rbac-generated.md b/src/main/resources/db/changelog/213-hs-office-person-rbac-generated.md new file mode 100644 index 00000000..aa971642 --- /dev/null +++ b/src/main/resources/db/changelog/213-hs-office-person-rbac-generated.md @@ -0,0 +1,43 @@ +### rbac person + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph person["`**person**`"] + direction TB + style person fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph person:roles[ ] + style person:roles fill:#dd4901,stroke:white + + role:person:owner[[person:owner]] + role:person:admin[[person:admin]] + role:person:referrer[[person:referrer]] + end + + subgraph person:permissions[ ] + style person:permissions fill:#dd4901,stroke:white + + perm:person:DELETE{{person:DELETE}} + perm:person:UPDATE{{person:UPDATE}} + perm:person:SELECT{{person:SELECT}} + end +end + +%% granting roles to users +user:creator ==> role:person:owner + +%% granting roles to roles +role:global:admin ==> role:person:owner +role:person:owner ==> role:person:admin +role:person:admin ==> role:person:referrer + +%% granting permissions to roles +role:person:owner ==> perm:person:DELETE +role:person:admin ==> perm:person:UPDATE +role:person:referrer ==> perm:person:SELECT + +``` diff --git a/src/main/resources/db/changelog/213-hs-office-person-rbac-generated.sql b/src/main/resources/db/changelog/213-hs-office-person-rbac-generated.sql new file mode 100644 index 00000000..afd7ed66 --- /dev/null +++ b/src/main/resources/db/changelog/213-hs-office-person-rbac-generated.sql @@ -0,0 +1,125 @@ +--liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. + + +-- ============================================================================ +--changeset hs-office-person-rbac-OBJECT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRelatedRbacObject('hs_office_person'); +--// + + +-- ============================================================================ +--changeset hs-office-person-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRoleDescriptors('hsOfficePerson', 'hs_office_person'); +--// + + +-- ============================================================================ +--changeset hs-office-person-rbac-insert-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates the roles, grants and permission for the AFTER INSERT TRIGGER. + */ + +create or replace procedure buildRbacSystemForHsOfficePerson( + NEW hs_office_person +) + language plpgsql as $$ + +declare + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + perform createRoleWithGrants( + hsOfficePersonOwner(NEW), + permissions => array['DELETE'], + incomingSuperRoles => array[globalAdmin()], + userUuids => array[currentUserUuid()] + ); + + perform createRoleWithGrants( + hsOfficePersonAdmin(NEW), + permissions => array['UPDATE'], + incomingSuperRoles => array[hsOfficePersonOwner(NEW)] + ); + + perform createRoleWithGrants( + hsOfficePersonReferrer(NEW), + permissions => array['SELECT'], + incomingSuperRoles => array[hsOfficePersonAdmin(NEW)] + ); + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_person row. + */ + +create or replace function insertTriggerForHsOfficePerson_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForHsOfficePerson(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForHsOfficePerson_tg + after insert on hs_office_person + for each row +execute procedure insertTriggerForHsOfficePerson_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-person-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/** + Checks if the user or assumed roles are allowed to insert a row to hs_office_person, + where only global-admin has that permission. +*/ +create or replace function hs_office_person_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into hs_office_person not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + +create trigger hs_office_person_insert_permission_check_tg + before insert on hs_office_person + for each row + when ( not isGlobalAdmin() ) + execute procedure hs_office_person_insert_permission_missing_tf(); +--// + +-- ============================================================================ +--changeset hs-office-person-rbac-IDENTITY-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +call generateRbacIdentityViewFromProjection('hs_office_person', $idName$ + concat(tradeName, familyName, givenName) + $idName$); +--// + +-- ============================================================================ +--changeset hs-office-person-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRestrictedView('hs_office_person', + $orderBy$ + concat(tradeName, familyName, givenName) + $orderBy$, + $updates$ + personType = new.personType, + tradeName = new.tradeName, + givenName = new.givenName, + familyName = new.familyName + $updates$); +--// + diff --git a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.md b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.md new file mode 100644 index 00000000..14f797eb --- /dev/null +++ b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.md @@ -0,0 +1,100 @@ +### rbac relation + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph holderPerson["`**holderPerson**`"] + direction TB + style holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph holderPerson:roles[ ] + style holderPerson:roles fill:#99bcdb,stroke:white + + role:holderPerson:owner[[holderPerson:owner]] + role:holderPerson:admin[[holderPerson:admin]] + role:holderPerson:referrer[[holderPerson:referrer]] + end +end + +subgraph anchorPerson["`**anchorPerson**`"] + direction TB + style anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph anchorPerson:roles[ ] + style anchorPerson:roles fill:#99bcdb,stroke:white + + role:anchorPerson:owner[[anchorPerson:owner]] + role:anchorPerson:admin[[anchorPerson:admin]] + role:anchorPerson:referrer[[anchorPerson:referrer]] + end +end + +subgraph contact["`**contact**`"] + direction TB + style contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph contact:roles[ ] + style contact:roles fill:#99bcdb,stroke:white + + role:contact:owner[[contact:owner]] + role:contact:admin[[contact:admin]] + role:contact:referrer[[contact:referrer]] + end +end + +subgraph relation["`**relation**`"] + direction TB + style relation fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph relation:roles[ ] + style relation:roles fill:#dd4901,stroke:white + + role:relation:owner[[relation:owner]] + role:relation:admin[[relation:admin]] + role:relation:agent[[relation:agent]] + role:relation:tenant[[relation:tenant]] + end + + subgraph relation:permissions[ ] + style relation:permissions fill:#dd4901,stroke:white + + perm:relation:DELETE{{relation:DELETE}} + perm:relation:UPDATE{{relation:UPDATE}} + perm:relation:SELECT{{relation:SELECT}} + end +end + +%% granting roles to users +user:creator ==> role:relation:owner + +%% granting roles to roles +role:global:admin -.-> role:anchorPerson:owner +role:anchorPerson:owner -.-> role:anchorPerson:admin +role:anchorPerson:admin -.-> role:anchorPerson:referrer +role:global:admin -.-> role:holderPerson:owner +role:holderPerson:owner -.-> role:holderPerson:admin +role:holderPerson:admin -.-> role:holderPerson:referrer +role:global:admin -.-> role:contact:owner +role:contact:owner -.-> role:contact:admin +role:contact:admin -.-> role:contact:referrer +role:global:admin ==> role:relation:owner +role:relation:owner ==> role:relation:admin +role:anchorPerson:admin ==> role:relation:admin +role:relation:admin ==> role:relation:agent +role:holderPerson:admin ==> role:relation:agent +role:relation:agent ==> role:relation:tenant +role:holderPerson:admin ==> role:relation:tenant +role:contact:admin ==> role:relation:tenant +role:relation:tenant ==> role:anchorPerson:referrer +role:relation:tenant ==> role:holderPerson:referrer +role:relation:tenant ==> role:contact:referrer + +%% granting permissions to roles +role:relation:owner ==> perm:relation:DELETE +role:relation:admin ==> perm:relation:UPDATE +role:relation:tenant ==> perm:relation:SELECT + +``` diff --git a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql new file mode 100644 index 00000000..9a1f7d1b --- /dev/null +++ b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql @@ -0,0 +1,231 @@ +--liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. + + +-- ============================================================================ +--changeset hs-office-relation-rbac-OBJECT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRelatedRbacObject('hs_office_relation'); +--// + + +-- ============================================================================ +--changeset hs-office-relation-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRoleDescriptors('hsOfficeRelation', 'hs_office_relation'); +--// + + +-- ============================================================================ +--changeset hs-office-relation-rbac-insert-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates the roles, grants and permission for the AFTER INSERT TRIGGER. + */ + +create or replace procedure buildRbacSystemForHsOfficeRelation( + NEW hs_office_relation +) + language plpgsql as $$ + +declare + newHolderPerson hs_office_person; + newAnchorPerson hs_office_person; + newContact hs_office_contact; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + select * from hs_office_person as p where p.uuid = NEW.holderUuid INTO newHolderPerson; + assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s', NEW.holderUuid); + + select * from hs_office_person as p where p.uuid = NEW.anchorUuid INTO newAnchorPerson; + assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s', NEW.anchorUuid); + + select * from hs_office_contact as c where c.uuid = NEW.contactUuid INTO newContact; + assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s', NEW.contactUuid); + + + perform createRoleWithGrants( + hsOfficeRelationOwner(NEW), + permissions => array['DELETE'], + incomingSuperRoles => array[globalAdmin()], + userUuids => array[currentUserUuid()] + ); + + perform createRoleWithGrants( + hsOfficeRelationAdmin(NEW), + permissions => array['UPDATE'], + incomingSuperRoles => array[ + hsOfficePersonAdmin(newAnchorPerson), + hsOfficeRelationOwner(NEW)] + ); + + perform createRoleWithGrants( + hsOfficeRelationAgent(NEW), + incomingSuperRoles => array[ + hsOfficePersonAdmin(newHolderPerson), + hsOfficeRelationAdmin(NEW)] + ); + + perform createRoleWithGrants( + hsOfficeRelationTenant(NEW), + permissions => array['SELECT'], + incomingSuperRoles => array[ + hsOfficeRelationAgent(NEW), + hsOfficeContactAdmin(newContact), + hsOfficePersonAdmin(newHolderPerson)], + outgoingSubRoles => array[ + hsOfficePersonReferrer(newHolderPerson), + hsOfficeContactReferrer(newContact), + hsOfficePersonReferrer(newAnchorPerson)] + ); + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_relation row. + */ + +create or replace function insertTriggerForHsOfficeRelation_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForHsOfficeRelation(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForHsOfficeRelation_tg + after insert on hs_office_relation + for each row +execute procedure insertTriggerForHsOfficeRelation_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-relation-rbac-update-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Called from the AFTER UPDATE TRIGGER to re-wire the grants. + */ + +create or replace procedure updateRbacRulesForHsOfficeRelation( + OLD hs_office_relation, + NEW hs_office_relation +) + language plpgsql as $$ + +declare + oldHolderPerson hs_office_person; + newHolderPerson hs_office_person; + oldAnchorPerson hs_office_person; + newAnchorPerson hs_office_person; + oldContact hs_office_contact; + newContact hs_office_contact; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + select * from hs_office_person as p where p.uuid = OLD.holderUuid INTO oldHolderPerson; + assert oldHolderPerson.uuid is not null, format('oldHolderPerson must not be null for OLD.holderUuid = %s', OLD.holderUuid); + + select * from hs_office_person as p where p.uuid = NEW.holderUuid INTO newHolderPerson; + assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s', NEW.holderUuid); + + select * from hs_office_person as p where p.uuid = OLD.anchorUuid INTO oldAnchorPerson; + assert oldAnchorPerson.uuid is not null, format('oldAnchorPerson must not be null for OLD.anchorUuid = %s', OLD.anchorUuid); + + select * from hs_office_person as p where p.uuid = NEW.anchorUuid INTO newAnchorPerson; + assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s', NEW.anchorUuid); + + select * from hs_office_contact as c where c.uuid = OLD.contactUuid INTO oldContact; + assert oldContact.uuid is not null, format('oldContact must not be null for OLD.contactUuid = %s', OLD.contactUuid); + + select * from hs_office_contact as c where c.uuid = NEW.contactUuid INTO newContact; + assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s', NEW.contactUuid); + + + if NEW.contactUuid <> OLD.contactUuid then + + call revokeRoleFromRole(hsOfficeRelationTenant(OLD), hsOfficeContactAdmin(oldContact)); + call grantRoleToRole(hsOfficeRelationTenant(NEW), hsOfficeContactAdmin(newContact)); + + call revokeRoleFromRole(hsOfficeContactReferrer(oldContact), hsOfficeRelationTenant(OLD)); + call grantRoleToRole(hsOfficeContactReferrer(newContact), hsOfficeRelationTenant(NEW)); + + end if; + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_office_relation row. + */ + +create or replace function updateTriggerForHsOfficeRelation_tf() + returns trigger + language plpgsql + strict as $$ +begin + call updateRbacRulesForHsOfficeRelation(OLD, NEW); + return NEW; +end; $$; + +create trigger updateTriggerForHsOfficeRelation_tg + after update on hs_office_relation + for each row +execute procedure updateTriggerForHsOfficeRelation_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-relation-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/** + Checks if the user or assumed roles are allowed to insert a row to hs_office_relation, + where only global-admin has that permission. +*/ +create or replace function hs_office_relation_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into hs_office_relation not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + +create trigger hs_office_relation_insert_permission_check_tg + before insert on hs_office_relation + for each row + when ( not isGlobalAdmin() ) + execute procedure hs_office_relation_insert_permission_missing_tf(); +--// + +-- ============================================================================ +--changeset hs-office-relation-rbac-IDENTITY-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +call generateRbacIdentityViewFromProjection('hs_office_relation', $idName$ + (select idName from hs_office_person_iv p where p.uuid = anchorUuid) + || '-with-' || target.type || '-' + || (select idName from hs_office_person_iv p where p.uuid = holderUuid) + + $idName$); +--// + +-- ============================================================================ +--changeset hs-office-relation-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRestrictedView('hs_office_relation', + $orderBy$ + (select idName from hs_office_person_iv p where p.uuid = target.holderUuid) + $orderBy$, + $updates$ + contactUuid = new.contactUuid + $updates$); +--// + diff --git a/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.md b/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.md new file mode 100644 index 00000000..98bd276d --- /dev/null +++ b/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.md @@ -0,0 +1,158 @@ +### rbac partner + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph partnerRel.contact["`**partnerRel.contact**`"] + direction TB + style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.contact:roles[ ] + style partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:partnerRel.contact:owner[[partnerRel.contact:owner]] + role:partnerRel.contact:admin[[partnerRel.contact:admin]] + role:partnerRel.contact:referrer[[partnerRel.contact:referrer]] + end +end + +subgraph partner["`**partner**`"] + direction TB + style partner fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph partner:permissions[ ] + style partner:permissions fill:#dd4901,stroke:white + + perm:partner:INSERT{{partner:INSERT}} + perm:partner:DELETE{{partner:DELETE}} + perm:partner:UPDATE{{partner:UPDATE}} + perm:partner:SELECT{{partner:SELECT}} + end + + subgraph partnerRel["`**partnerRel**`"] + direction TB + style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + subgraph partnerRel.contact["`**partnerRel.contact**`"] + direction TB + style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.contact:roles[ ] + style partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:partnerRel.contact:owner[[partnerRel.contact:owner]] + role:partnerRel.contact:admin[[partnerRel.contact:admin]] + role:partnerRel.contact:referrer[[partnerRel.contact:referrer]] + end + end + + subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"] + direction TB + style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.anchorPerson:roles[ ] + style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]] + role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]] + role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]] + end + end + + subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"] + direction TB + style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.holderPerson:roles[ ] + style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]] + role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]] + role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]] + end + end + + subgraph partnerRel:roles[ ] + style partnerRel:roles fill:#99bcdb,stroke:white + + role:partnerRel:owner[[partnerRel:owner]] + role:partnerRel:admin[[partnerRel:admin]] + role:partnerRel:agent[[partnerRel:agent]] + role:partnerRel:tenant[[partnerRel:tenant]] + end + end +end + +subgraph partnerDetails["`**partnerDetails**`"] + direction TB + style partnerDetails fill:#feb28c,stroke:#274d6e,stroke-width:8px + + subgraph partnerDetails:permissions[ ] + style partnerDetails:permissions fill:#feb28c,stroke:white + + perm:partnerDetails:DELETE{{partnerDetails:DELETE}} + perm:partnerDetails:UPDATE{{partnerDetails:UPDATE}} + perm:partnerDetails:SELECT{{partnerDetails:SELECT}} + end +end + +subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"] + direction TB + style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.anchorPerson:roles[ ] + style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]] + role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]] + role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]] + end +end + +subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"] + direction TB + style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.holderPerson:roles[ ] + style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]] + role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]] + role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]] + end +end + +%% granting roles to roles +role:global:admin -.-> role:partnerRel.anchorPerson:owner +role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin +role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer +role:global:admin -.-> role:partnerRel.holderPerson:owner +role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin +role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer +role:global:admin -.-> role:partnerRel.contact:owner +role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin +role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer +role:global:admin -.-> role:partnerRel:owner +role:partnerRel:owner -.-> role:partnerRel:admin +role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin +role:partnerRel:admin -.-> role:partnerRel:agent +role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent +role:partnerRel:agent -.-> role:partnerRel:tenant +role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant +role:partnerRel.contact:admin -.-> role:partnerRel:tenant +role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer +role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer +role:partnerRel:tenant -.-> role:partnerRel.contact:referrer + +%% granting permissions to roles +role:global:admin ==> perm:partner:INSERT +role:partnerRel:admin ==> perm:partner:DELETE +role:partnerRel:agent ==> perm:partner:UPDATE +role:partnerRel:tenant ==> perm:partner:SELECT +role:partnerRel:admin ==> perm:partnerDetails:DELETE +role:partnerRel:agent ==> perm:partnerDetails:UPDATE +role:partnerRel:agent ==> perm:partnerDetails:SELECT + +``` diff --git a/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql b/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql new file mode 100644 index 00000000..683a8892 --- /dev/null +++ b/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql @@ -0,0 +1,248 @@ +--liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. + + +-- ============================================================================ +--changeset hs-office-partner-rbac-OBJECT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRelatedRbacObject('hs_office_partner'); +--// + + +-- ============================================================================ +--changeset hs-office-partner-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRoleDescriptors('hsOfficePartner', 'hs_office_partner'); +--// + + +-- ============================================================================ +--changeset hs-office-partner-rbac-insert-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates the roles, grants and permission for the AFTER INSERT TRIGGER. + */ + +create or replace procedure buildRbacSystemForHsOfficePartner( + NEW hs_office_partner +) + language plpgsql as $$ + +declare + newPartnerRel hs_office_relation; + newPartnerDetails hs_office_partner_details; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + SELECT * FROM hs_office_relation AS r WHERE r.uuid = NEW.partnerRelUuid INTO newPartnerRel; + assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid); + + SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = NEW.detailsUuid INTO newPartnerDetails; + assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid); + + call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTenant(newPartnerRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel)); + call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel)); + call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAgent(newPartnerRel)); + call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel)); + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_partner row. + */ + +create or replace function insertTriggerForHsOfficePartner_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForHsOfficePartner(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForHsOfficePartner_tg + after insert on hs_office_partner + for each row +execute procedure insertTriggerForHsOfficePartner_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-partner-rbac-update-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Called from the AFTER UPDATE TRIGGER to re-wire the grants. + */ + +create or replace procedure updateRbacRulesForHsOfficePartner( + OLD hs_office_partner, + NEW hs_office_partner +) + language plpgsql as $$ + +declare + oldPartnerRel hs_office_relation; + newPartnerRel hs_office_relation; + oldPartnerDetails hs_office_partner_details; + newPartnerDetails hs_office_partner_details; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + SELECT * FROM hs_office_relation AS r WHERE r.uuid = OLD.partnerRelUuid INTO oldPartnerRel; + assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRelUuid = %s', OLD.partnerRelUuid); + + SELECT * FROM hs_office_relation AS r WHERE r.uuid = NEW.partnerRelUuid INTO newPartnerRel; + assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid); + + SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = OLD.detailsUuid INTO oldPartnerDetails; + assert oldPartnerDetails.uuid is not null, format('oldPartnerDetails must not be null for OLD.detailsUuid = %s', OLD.detailsUuid); + + SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = NEW.detailsUuid INTO newPartnerDetails; + assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid); + + + if NEW.partnerRelUuid <> OLD.partnerRelUuid then + + call revokePermissionFromRole(getPermissionId(OLD.uuid, 'DELETE'), hsOfficeRelationAdmin(oldPartnerRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel)); + + call revokePermissionFromRole(getPermissionId(OLD.uuid, 'UPDATE'), hsOfficeRelationAgent(oldPartnerRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel)); + + call revokePermissionFromRole(getPermissionId(OLD.uuid, 'SELECT'), hsOfficeRelationTenant(oldPartnerRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTenant(newPartnerRel)); + + call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'DELETE'), hsOfficeRelationAdmin(oldPartnerRel)); + call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel)); + + call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAgent(oldPartnerRel)); + call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAgent(newPartnerRel)); + + call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAgent(oldPartnerRel)); + call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAgent(newPartnerRel)); + + end if; + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_office_partner row. + */ + +create or replace function updateTriggerForHsOfficePartner_tf() + returns trigger + language plpgsql + strict as $$ +begin + call updateRbacRulesForHsOfficePartner(OLD, NEW); + return NEW; +end; $$; + +create trigger updateTriggerForHsOfficePartner_tg + after update on hs_office_partner + for each row +execute procedure updateTriggerForHsOfficePartner_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-partner-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates INSERT INTO hs_office_partner permissions for the related global rows. + */ +do language plpgsql $$ + declare + row global; + permissionUuid uuid; + roleUuid uuid; + begin + call defineContext('create INSERT INTO hs_office_partner permissions for the related global rows'); + + FOR row IN SELECT * FROM global + LOOP + roleUuid := findRoleId(globalAdmin()); + permissionUuid := createPermission(row.uuid, 'INSERT', 'hs_office_partner'); + call grantPermissionToRole(permissionUuid, roleUuid); + END LOOP; + END; +$$; + +/** + Adds hs_office_partner INSERT permission to specified role of new global rows. +*/ +create or replace function hs_office_partner_global_insert_tf() + returns trigger + language plpgsql + strict as $$ +begin + call grantPermissionToRole( + createPermission(NEW.uuid, 'INSERT', 'hs_office_partner'), + globalAdmin()); + return NEW; +end; $$; + +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_hs_office_partner_global_insert_tg + after insert on global + for each row +execute procedure hs_office_partner_global_insert_tf(); + +/** + Checks if the user or assumed roles are allowed to insert a row to hs_office_partner, + where only global-admin has that permission. +*/ +create or replace function hs_office_partner_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into hs_office_partner not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + +create trigger hs_office_partner_insert_permission_check_tg + before insert on hs_office_partner + for each row + when ( not isGlobalAdmin() ) + execute procedure hs_office_partner_insert_permission_missing_tf(); +--// + +-- ============================================================================ +--changeset hs-office-partner-rbac-IDENTITY-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + + call generateRbacIdentityViewFromQuery('hs_office_partner', $idName$ + 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 AS partner + + $idName$); +--// + +-- ============================================================================ +--changeset hs-office-partner-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRestrictedView('hs_office_partner', + $orderBy$ + 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 AS partner + $orderBy$, + $updates$ + partnerRelUuid = new.partnerRelUuid, + personUuid = new.personUuid, + contactUuid = new.contactUuid + $updates$); +--// + diff --git a/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.md b/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.md new file mode 100644 index 00000000..ece32f9c --- /dev/null +++ b/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.md @@ -0,0 +1,136 @@ +### rbac partnerDetails + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph partnerRel.contact["`**partnerRel.contact**`"] + direction TB + style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.contact:roles[ ] + style partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:partnerRel.contact:owner[[partnerRel.contact:owner]] + role:partnerRel.contact:admin[[partnerRel.contact:admin]] + role:partnerRel.contact:referrer[[partnerRel.contact:referrer]] + end +end + +subgraph partnerDetails["`**partnerDetails**`"] + direction TB + style partnerDetails fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph partnerDetails:permissions[ ] + style partnerDetails:permissions fill:#dd4901,stroke:white + + perm:partnerDetails:INSERT{{partnerDetails:INSERT}} + end + + subgraph partnerRel["`**partnerRel**`"] + direction TB + style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + subgraph partnerRel.contact["`**partnerRel.contact**`"] + direction TB + style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.contact:roles[ ] + style partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:partnerRel.contact:owner[[partnerRel.contact:owner]] + role:partnerRel.contact:admin[[partnerRel.contact:admin]] + role:partnerRel.contact:referrer[[partnerRel.contact:referrer]] + end + end + + subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"] + direction TB + style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.anchorPerson:roles[ ] + style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]] + role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]] + role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]] + end + end + + subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"] + direction TB + style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.holderPerson:roles[ ] + style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]] + role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]] + role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]] + end + end + + subgraph partnerRel:roles[ ] + style partnerRel:roles fill:#99bcdb,stroke:white + + role:partnerRel:owner[[partnerRel:owner]] + role:partnerRel:admin[[partnerRel:admin]] + role:partnerRel:agent[[partnerRel:agent]] + role:partnerRel:tenant[[partnerRel:tenant]] + end + end +end + +subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"] + direction TB + style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.anchorPerson:roles[ ] + style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]] + role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]] + role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]] + end +end + +subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"] + direction TB + style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.holderPerson:roles[ ] + style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]] + role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]] + role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]] + end +end + +%% granting roles to roles +role:global:admin -.-> role:partnerRel.anchorPerson:owner +role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin +role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer +role:global:admin -.-> role:partnerRel.holderPerson:owner +role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin +role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer +role:global:admin -.-> role:partnerRel.contact:owner +role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin +role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer +role:global:admin -.-> role:partnerRel:owner +role:partnerRel:owner -.-> role:partnerRel:admin +role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin +role:partnerRel:admin -.-> role:partnerRel:agent +role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent +role:partnerRel:agent -.-> role:partnerRel:tenant +role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant +role:partnerRel.contact:admin -.-> role:partnerRel:tenant +role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer +role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer +role:partnerRel:tenant -.-> role:partnerRel.contact:referrer + +%% granting permissions to roles +role:global:admin ==> perm:partnerDetails:INSERT + +``` diff --git a/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql b/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql new file mode 100644 index 00000000..04fc7be7 --- /dev/null +++ b/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql @@ -0,0 +1,164 @@ +--liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. + + +-- ============================================================================ +--changeset hs-office-partner-details-rbac-OBJECT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRelatedRbacObject('hs_office_partner_details'); +--// + + +-- ============================================================================ +--changeset hs-office-partner-details-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRoleDescriptors('hsOfficePartnerDetails', 'hs_office_partner_details'); +--// + + +-- ============================================================================ +--changeset hs-office-partner-details-rbac-insert-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates the roles, grants and permission for the AFTER INSERT TRIGGER. + */ + +create or replace procedure buildRbacSystemForHsOfficePartnerDetails( + NEW hs_office_partner_details +) + language plpgsql as $$ + +declare + newPartnerRel hs_office_relation; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + SELECT partnerRel.* + FROM hs_office_relation AS partnerRel + JOIN hs_office_partner AS partner + ON partner.detailsUuid = NEW.uuid + WHERE partnerRel.uuid = partner.partnerRelUuid + INTO newPartnerRel; + assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid); + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_partner_details row. + */ + +create or replace function insertTriggerForHsOfficePartnerDetails_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForHsOfficePartnerDetails(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForHsOfficePartnerDetails_tg + after insert on hs_office_partner_details + for each row +execute procedure insertTriggerForHsOfficePartnerDetails_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-partner-details-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates INSERT INTO hs_office_partner_details permissions for the related global rows. + */ +do language plpgsql $$ + declare + row global; + permissionUuid uuid; + roleUuid uuid; + begin + call defineContext('create INSERT INTO hs_office_partner_details permissions for the related global rows'); + + FOR row IN SELECT * FROM global + LOOP + roleUuid := findRoleId(globalAdmin()); + permissionUuid := createPermission(row.uuid, 'INSERT', 'hs_office_partner_details'); + call grantPermissionToRole(permissionUuid, roleUuid); + END LOOP; + END; +$$; + +/** + Adds hs_office_partner_details INSERT permission to specified role of new global rows. +*/ +create or replace function hs_office_partner_details_global_insert_tf() + returns trigger + language plpgsql + strict as $$ +begin + call grantPermissionToRole( + createPermission(NEW.uuid, 'INSERT', 'hs_office_partner_details'), + globalAdmin()); + return NEW; +end; $$; + +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_hs_office_partner_details_global_insert_tg + after insert on global + for each row +execute procedure hs_office_partner_details_global_insert_tf(); + +/** + Checks if the user or assumed roles are allowed to insert a row to hs_office_partner_details, + where only global-admin has that permission. +*/ +create or replace function hs_office_partner_details_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into hs_office_partner_details not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + +create trigger hs_office_partner_details_insert_permission_check_tg + before insert on hs_office_partner_details + for each row + when ( not isGlobalAdmin() ) + execute procedure hs_office_partner_details_insert_permission_missing_tf(); +--// + +-- ============================================================================ +--changeset hs-office-partner-details-rbac-IDENTITY-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + + call generateRbacIdentityViewFromQuery('hs_office_partner_details', $idName$ + SELECT partner_iv.idName || '-details' + FROM hs_office_partner_details AS partnerDetails + JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid + JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid + + $idName$); +--// + +-- ============================================================================ +--changeset hs-office-partner-details-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRestrictedView('hs_office_partner_details', + $orderBy$ + SELECT partner_iv.idName || '-details' + FROM hs_office_partner_details AS partnerDetails + JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid + JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid + $orderBy$, + $updates$ + registrationOffice = new.registrationOffice, + registrationNumber = new.registrationNumber, + birthPlace = new.birthPlace, + birthName = new.birthName, + birthday = new.birthday, + dateOfDeath = new.dateOfDeath + $updates$); +--// + diff --git a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.md b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.md new file mode 100644 index 00000000..4f1604fb --- /dev/null +++ b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.md @@ -0,0 +1,43 @@ +### rbac bankAccount + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph bankAccount["`**bankAccount**`"] + direction TB + style bankAccount fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph bankAccount:roles[ ] + style bankAccount:roles fill:#dd4901,stroke:white + + role:bankAccount:owner[[bankAccount:owner]] + role:bankAccount:admin[[bankAccount:admin]] + role:bankAccount:referrer[[bankAccount:referrer]] + end + + subgraph bankAccount:permissions[ ] + style bankAccount:permissions fill:#dd4901,stroke:white + + perm:bankAccount:DELETE{{bankAccount:DELETE}} + perm:bankAccount:UPDATE{{bankAccount:UPDATE}} + perm:bankAccount:SELECT{{bankAccount:SELECT}} + end +end + +%% granting roles to users +user:creator ==> role:bankAccount:owner + +%% granting roles to roles +role:global:admin ==> role:bankAccount:owner +role:bankAccount:owner ==> role:bankAccount:admin +role:bankAccount:admin ==> role:bankAccount:referrer + +%% granting permissions to roles +role:bankAccount:owner ==> perm:bankAccount:DELETE +role:bankAccount:admin ==> perm:bankAccount:UPDATE +role:bankAccount:referrer ==> perm:bankAccount:SELECT + +``` diff --git a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.sql b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.sql new file mode 100644 index 00000000..9d83aad5 --- /dev/null +++ b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.sql @@ -0,0 +1,124 @@ +--liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. + + +-- ============================================================================ +--changeset hs-office-bankaccount-rbac-OBJECT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRelatedRbacObject('hs_office_bankaccount'); +--// + + +-- ============================================================================ +--changeset hs-office-bankaccount-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRoleDescriptors('hsOfficeBankAccount', 'hs_office_bankaccount'); +--// + + +-- ============================================================================ +--changeset hs-office-bankaccount-rbac-insert-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates the roles, grants and permission for the AFTER INSERT TRIGGER. + */ + +create or replace procedure buildRbacSystemForHsOfficeBankAccount( + NEW hs_office_bankaccount +) + language plpgsql as $$ + +declare + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + perform createRoleWithGrants( + hsOfficeBankAccountOwner(NEW), + permissions => array['DELETE'], + incomingSuperRoles => array[globalAdmin()], + userUuids => array[currentUserUuid()] + ); + + perform createRoleWithGrants( + hsOfficeBankAccountAdmin(NEW), + permissions => array['UPDATE'], + incomingSuperRoles => array[hsOfficeBankAccountOwner(NEW)] + ); + + perform createRoleWithGrants( + hsOfficeBankAccountReferrer(NEW), + permissions => array['SELECT'], + incomingSuperRoles => array[hsOfficeBankAccountAdmin(NEW)] + ); + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_bankaccount row. + */ + +create or replace function insertTriggerForHsOfficeBankAccount_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForHsOfficeBankAccount(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForHsOfficeBankAccount_tg + after insert on hs_office_bankaccount + for each row +execute procedure insertTriggerForHsOfficeBankAccount_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-bankaccount-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/** + Checks if the user or assumed roles are allowed to insert a row to hs_office_bankaccount, + where only global-admin has that permission. +*/ +create or replace function hs_office_bankaccount_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into hs_office_bankaccount not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + +create trigger hs_office_bankaccount_insert_permission_check_tg + before insert on hs_office_bankaccount + for each row + when ( not isGlobalAdmin() ) + execute procedure hs_office_bankaccount_insert_permission_missing_tf(); +--// + +-- ============================================================================ +--changeset hs-office-bankaccount-rbac-IDENTITY-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +call generateRbacIdentityViewFromProjection('hs_office_bankaccount', $idName$ + iban || ':' || holder + $idName$); +--// + +-- ============================================================================ +--changeset hs-office-bankaccount-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRestrictedView('hs_office_bankaccount', + $orderBy$ + iban || ':' || holder + $orderBy$, + $updates$ + holder = new.holder, + iban = new.iban, + bic = new.bic + $updates$); +--// + diff --git a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.md b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.md new file mode 100644 index 00000000..f542e78c --- /dev/null +++ b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.md @@ -0,0 +1,178 @@ +### rbac sepaMandate + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph bankAccount["`**bankAccount**`"] + direction TB + style bankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph bankAccount:roles[ ] + style bankAccount:roles fill:#99bcdb,stroke:white + + role:bankAccount:owner[[bankAccount:owner]] + role:bankAccount:admin[[bankAccount:admin]] + role:bankAccount:referrer[[bankAccount:referrer]] + end +end + +subgraph debitorRel.contact["`**debitorRel.contact**`"] + direction TB + style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.contact:roles[ ] + style debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:debitorRel.contact:owner[[debitorRel.contact:owner]] + role:debitorRel.contact:admin[[debitorRel.contact:admin]] + role:debitorRel.contact:referrer[[debitorRel.contact:referrer]] + end +end + +subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"] + direction TB + style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.anchorPerson:roles[ ] + style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]] + role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]] + role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]] + end +end + +subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"] + direction TB + style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.holderPerson:roles[ ] + style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]] + role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]] + role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]] + end +end + +subgraph sepaMandate["`**sepaMandate**`"] + direction TB + style sepaMandate fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph sepaMandate:roles[ ] + style sepaMandate:roles fill:#dd4901,stroke:white + + role:sepaMandate:owner[[sepaMandate:owner]] + role:sepaMandate:admin[[sepaMandate:admin]] + role:sepaMandate:agent[[sepaMandate:agent]] + role:sepaMandate:referrer[[sepaMandate:referrer]] + end + + subgraph sepaMandate:permissions[ ] + style sepaMandate:permissions fill:#dd4901,stroke:white + + perm:sepaMandate:DELETE{{sepaMandate:DELETE}} + perm:sepaMandate:UPDATE{{sepaMandate:UPDATE}} + perm:sepaMandate:SELECT{{sepaMandate:SELECT}} + end +end + +subgraph debitorRel["`**debitorRel**`"] + direction TB + style debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.contact["`**debitorRel.contact**`"] + direction TB + style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.contact:roles[ ] + style debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:debitorRel.contact:owner[[debitorRel.contact:owner]] + role:debitorRel.contact:admin[[debitorRel.contact:admin]] + role:debitorRel.contact:referrer[[debitorRel.contact:referrer]] + end + end + + subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"] + direction TB + style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.anchorPerson:roles[ ] + style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]] + role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]] + role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]] + end + end + + subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"] + direction TB + style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.holderPerson:roles[ ] + style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]] + role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]] + role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]] + end + end + + subgraph debitorRel:roles[ ] + style debitorRel:roles fill:#99bcdb,stroke:white + + role:debitorRel:owner[[debitorRel:owner]] + role:debitorRel:admin[[debitorRel:admin]] + role:debitorRel:agent[[debitorRel:agent]] + role:debitorRel:tenant[[debitorRel:tenant]] + end +end + +%% granting roles to users +user:creator ==> role:sepaMandate:owner + +%% granting roles to roles +role:global:admin -.-> role:debitorRel.anchorPerson:owner +role:debitorRel.anchorPerson:owner -.-> role:debitorRel.anchorPerson:admin +role:debitorRel.anchorPerson:admin -.-> role:debitorRel.anchorPerson:referrer +role:global:admin -.-> role:debitorRel.holderPerson:owner +role:debitorRel.holderPerson:owner -.-> role:debitorRel.holderPerson:admin +role:debitorRel.holderPerson:admin -.-> role:debitorRel.holderPerson:referrer +role:global:admin -.-> role:debitorRel.contact:owner +role:debitorRel.contact:owner -.-> role:debitorRel.contact:admin +role:debitorRel.contact:admin -.-> role:debitorRel.contact:referrer +role:global:admin -.-> role:debitorRel:owner +role:debitorRel:owner -.-> role:debitorRel:admin +role:debitorRel.anchorPerson:admin -.-> role:debitorRel:admin +role:debitorRel:admin -.-> role:debitorRel:agent +role:debitorRel.holderPerson:admin -.-> role:debitorRel:agent +role:debitorRel:agent -.-> role:debitorRel:tenant +role:debitorRel.holderPerson:admin -.-> role:debitorRel:tenant +role:debitorRel.contact:admin -.-> role:debitorRel:tenant +role:debitorRel:tenant -.-> role:debitorRel.anchorPerson:referrer +role:debitorRel:tenant -.-> role:debitorRel.holderPerson:referrer +role:debitorRel:tenant -.-> role:debitorRel.contact:referrer +role:global:admin -.-> role:bankAccount:owner +role:bankAccount:owner -.-> role:bankAccount:admin +role:bankAccount:admin -.-> role:bankAccount:referrer +role:global:admin ==> role:sepaMandate:owner +role:sepaMandate:owner ==> role:sepaMandate:admin +role:sepaMandate:admin ==> role:sepaMandate:agent +role:sepaMandate:agent ==> role:bankAccount:referrer +role:sepaMandate:agent ==> role:debitorRel:agent +role:sepaMandate:agent ==> role:sepaMandate:referrer +role:bankAccount:admin ==> role:sepaMandate:referrer +role:debitorRel:agent ==> role:sepaMandate:referrer +role:sepaMandate:referrer ==> role:debitorRel:tenant + +%% granting permissions to roles +role:sepaMandate:owner ==> perm:sepaMandate:DELETE +role:sepaMandate:admin ==> perm:sepaMandate:UPDATE +role:sepaMandate:referrer ==> perm:sepaMandate:SELECT + +``` diff --git a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql new file mode 100644 index 00000000..f0718272 --- /dev/null +++ b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql @@ -0,0 +1,142 @@ +--liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. + + +-- ============================================================================ +--changeset hs-office-sepamandate-rbac-OBJECT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRelatedRbacObject('hs_office_sepamandate'); +--// + + +-- ============================================================================ +--changeset hs-office-sepamandate-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRoleDescriptors('hsOfficeSepaMandate', 'hs_office_sepamandate'); +--// + + +-- ============================================================================ +--changeset hs-office-sepamandate-rbac-insert-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates the roles, grants and permission for the AFTER INSERT TRIGGER. + */ + +create or replace procedure buildRbacSystemForHsOfficeSepaMandate( + NEW hs_office_sepamandate +) + language plpgsql as $$ + +declare + newBankAccount hs_office_bankaccount; + newDebitorRel hs_office_relation; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + SELECT * FROM hs_office_bankaccount WHERE uuid = NEW.bankAccountUuid INTO newBankAccount; + + SELECT * FROM hs_office_relation WHERE uuid = NEW.debitorRelUuid INTO newDebitorRel; + + perform createRoleWithGrants( + hsOfficeSepaMandateOwner(NEW), + permissions => array['DELETE'], + incomingSuperRoles => array[globalAdmin()], + userUuids => array[currentUserUuid()] + ); + + perform createRoleWithGrants( + hsOfficeSepaMandateAdmin(NEW), + permissions => array['UPDATE'], + incomingSuperRoles => array[hsOfficeSepaMandateOwner(NEW)] + ); + + perform createRoleWithGrants( + hsOfficeSepaMandateAgent(NEW), + incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)], + outgoingSubRoles => array[ + hsOfficeBankAccountReferrer(newBankAccount), + hsOfficeRelationAgent(newDebitorRel)] + ); + + perform createRoleWithGrants( + hsOfficeSepaMandateReferrer(NEW), + permissions => array['SELECT'], + incomingSuperRoles => array[ + hsOfficeSepaMandateAgent(NEW), + hsOfficeRelationAgent(newDebitorRel), + hsOfficeBankAccountAdmin(newBankAccount)], + outgoingSubRoles => array[hsOfficeRelationTenant(newDebitorRel)] + ); + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_sepamandate row. + */ + +create or replace function insertTriggerForHsOfficeSepaMandate_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForHsOfficeSepaMandate(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForHsOfficeSepaMandate_tg + after insert on hs_office_sepamandate + for each row +execute procedure insertTriggerForHsOfficeSepaMandate_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-sepamandate-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/** + Checks if the user or assumed roles are allowed to insert a row to hs_office_sepamandate, + where only global-admin has that permission. +*/ +create or replace function hs_office_sepamandate_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into hs_office_sepamandate not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + +create trigger hs_office_sepamandate_insert_permission_check_tg + before insert on hs_office_sepamandate + for each row + when ( not isGlobalAdmin() ) + execute procedure hs_office_sepamandate_insert_permission_missing_tf(); +--// + +-- ============================================================================ +--changeset hs-office-sepamandate-rbac-IDENTITY-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +call generateRbacIdentityViewFromProjection('hs_office_sepamandate', $idName$ + concat(tradeName, familyName, givenName) + $idName$); +--// + +-- ============================================================================ +--changeset hs-office-sepamandate-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRestrictedView('hs_office_sepamandate', + $orderBy$ + concat(tradeName, familyName, givenName) + $orderBy$, + $updates$ + reference = new.reference, + agreement = new.agreement, + validity = new.validity + $updates$); +--// + diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.md b/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.md new file mode 100644 index 00000000..a1baa702 --- /dev/null +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.md @@ -0,0 +1,275 @@ +### rbac debitor + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"] + direction TB + style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.anchorPerson:roles[ ] + style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]] + role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]] + role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]] + end +end + +subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"] + direction TB + style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.holderPerson:roles[ ] + style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]] + role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]] + role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]] + end +end + +subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"] + direction TB + style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.holderPerson:roles[ ] + style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]] + role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]] + role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]] + end +end + +subgraph debitor["`**debitor**`"] + direction TB + style debitor fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph debitor:permissions[ ] + style debitor:permissions fill:#dd4901,stroke:white + + perm:debitor:INSERT{{debitor:INSERT}} + perm:debitor:DELETE{{debitor:DELETE}} + perm:debitor:UPDATE{{debitor:UPDATE}} + perm:debitor:SELECT{{debitor:SELECT}} + end + + subgraph debitorRel["`**debitorRel**`"] + direction TB + style debitorRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + subgraph debitorRel.anchorPerson["`**debitorRel.anchorPerson**`"] + direction TB + style debitorRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.anchorPerson:roles[ ] + style debitorRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:debitorRel.anchorPerson:owner[[debitorRel.anchorPerson:owner]] + role:debitorRel.anchorPerson:admin[[debitorRel.anchorPerson:admin]] + role:debitorRel.anchorPerson:referrer[[debitorRel.anchorPerson:referrer]] + end + end + + subgraph debitorRel.holderPerson["`**debitorRel.holderPerson**`"] + direction TB + style debitorRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.holderPerson:roles[ ] + style debitorRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:debitorRel.holderPerson:owner[[debitorRel.holderPerson:owner]] + role:debitorRel.holderPerson:admin[[debitorRel.holderPerson:admin]] + role:debitorRel.holderPerson:referrer[[debitorRel.holderPerson:referrer]] + end + end + + subgraph debitorRel.contact["`**debitorRel.contact**`"] + direction TB + style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.contact:roles[ ] + style debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:debitorRel.contact:owner[[debitorRel.contact:owner]] + role:debitorRel.contact:admin[[debitorRel.contact:admin]] + role:debitorRel.contact:referrer[[debitorRel.contact:referrer]] + end + end + + subgraph debitorRel:roles[ ] + style debitorRel:roles fill:#99bcdb,stroke:white + + role:debitorRel:owner[[debitorRel:owner]] + role:debitorRel:admin[[debitorRel:admin]] + role:debitorRel:agent[[debitorRel:agent]] + role:debitorRel:tenant[[debitorRel:tenant]] + end + end +end + +subgraph partnerRel["`**partnerRel**`"] + direction TB + style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"] + direction TB + style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.holderPerson:roles[ ] + style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]] + role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]] + role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]] + end + end + + subgraph partnerRel.contact["`**partnerRel.contact**`"] + direction TB + style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.contact:roles[ ] + style partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:partnerRel.contact:owner[[partnerRel.contact:owner]] + role:partnerRel.contact:admin[[partnerRel.contact:admin]] + role:partnerRel.contact:referrer[[partnerRel.contact:referrer]] + end + end + + subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"] + direction TB + style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.anchorPerson:roles[ ] + style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]] + role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]] + role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]] + end + end + + subgraph partnerRel:roles[ ] + style partnerRel:roles fill:#99bcdb,stroke:white + + role:partnerRel:owner[[partnerRel:owner]] + role:partnerRel:admin[[partnerRel:admin]] + role:partnerRel:agent[[partnerRel:agent]] + role:partnerRel:tenant[[partnerRel:tenant]] + end +end + +subgraph partnerRel.contact["`**partnerRel.contact**`"] + direction TB + style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.contact:roles[ ] + style partnerRel.contact:roles fill:#99bcdb,stroke:white + + role:partnerRel.contact:owner[[partnerRel.contact:owner]] + role:partnerRel.contact:admin[[partnerRel.contact:admin]] + role:partnerRel.contact:referrer[[partnerRel.contact:referrer]] + end +end + +subgraph debitorRel.contact["`**debitorRel.contact**`"] + direction TB + style debitorRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph debitorRel.contact:roles[ ] + style debitorRel.contact:roles fill:#99bcdb,stroke:white + + role:debitorRel.contact:owner[[debitorRel.contact:owner]] + role:debitorRel.contact:admin[[debitorRel.contact:admin]] + role:debitorRel.contact:referrer[[debitorRel.contact:referrer]] + end +end + +subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"] + direction TB + style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph partnerRel.anchorPerson:roles[ ] + style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white + + role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]] + role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]] + role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]] + end +end + +subgraph refundBankAccount["`**refundBankAccount**`"] + direction TB + style refundBankAccount fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph refundBankAccount:roles[ ] + style refundBankAccount:roles fill:#99bcdb,stroke:white + + role:refundBankAccount:owner[[refundBankAccount:owner]] + role:refundBankAccount:admin[[refundBankAccount:admin]] + role:refundBankAccount:referrer[[refundBankAccount:referrer]] + end +end + +%% granting roles to roles +role:global:admin -.-> role:debitorRel.anchorPerson:owner +role:debitorRel.anchorPerson:owner -.-> role:debitorRel.anchorPerson:admin +role:debitorRel.anchorPerson:admin -.-> role:debitorRel.anchorPerson:referrer +role:global:admin -.-> role:debitorRel.holderPerson:owner +role:debitorRel.holderPerson:owner -.-> role:debitorRel.holderPerson:admin +role:debitorRel.holderPerson:admin -.-> role:debitorRel.holderPerson:referrer +role:global:admin -.-> role:debitorRel.contact:owner +role:debitorRel.contact:owner -.-> role:debitorRel.contact:admin +role:debitorRel.contact:admin -.-> role:debitorRel.contact:referrer +role:global:admin -.-> role:debitorRel:owner +role:debitorRel:owner -.-> role:debitorRel:admin +role:debitorRel.anchorPerson:admin -.-> role:debitorRel:admin +role:debitorRel:admin -.-> role:debitorRel:agent +role:debitorRel.holderPerson:admin -.-> role:debitorRel:agent +role:debitorRel:agent -.-> role:debitorRel:tenant +role:debitorRel.holderPerson:admin -.-> role:debitorRel:tenant +role:debitorRel.contact:admin -.-> role:debitorRel:tenant +role:debitorRel:tenant -.-> role:debitorRel.anchorPerson:referrer +role:debitorRel:tenant -.-> role:debitorRel.holderPerson:referrer +role:debitorRel:tenant -.-> role:debitorRel.contact:referrer +role:global:admin -.-> role:refundBankAccount:owner +role:refundBankAccount:owner -.-> role:refundBankAccount:admin +role:refundBankAccount:admin -.-> role:refundBankAccount:referrer +role:refundBankAccount:admin ==> role:debitorRel:agent +role:debitorRel:agent ==> role:refundBankAccount:referrer +role:global:admin -.-> role:partnerRel.anchorPerson:owner +role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin +role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer +role:global:admin -.-> role:partnerRel.holderPerson:owner +role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin +role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer +role:global:admin -.-> role:partnerRel.contact:owner +role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin +role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer +role:global:admin -.-> role:partnerRel:owner +role:partnerRel:owner -.-> role:partnerRel:admin +role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin +role:partnerRel:admin -.-> role:partnerRel:agent +role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent +role:partnerRel:agent -.-> role:partnerRel:tenant +role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant +role:partnerRel.contact:admin -.-> role:partnerRel:tenant +role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer +role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer +role:partnerRel:tenant -.-> role:partnerRel.contact:referrer +role:partnerRel:admin ==> role:debitorRel:admin +role:partnerRel:agent ==> role:debitorRel:agent +role:debitorRel:agent ==> role:partnerRel:tenant + +%% granting permissions to roles +role:global:admin ==> perm:debitor:INSERT +role:debitorRel:owner ==> perm:debitor:DELETE +role:debitorRel:admin ==> perm:debitor:UPDATE +role:debitorRel:tenant ==> perm:debitor:SELECT + +``` diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql b/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql new file mode 100644 index 00000000..760f1fe9 --- /dev/null +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql @@ -0,0 +1,296 @@ +--liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator, do not amend manually. + + +-- ============================================================================ +--changeset hs-office-debitor-rbac-OBJECT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRelatedRbacObject('hs_office_debitor'); +--// + + +-- ============================================================================ +--changeset hs-office-debitor-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRoleDescriptors('hsOfficeDebitor', 'hs_office_debitor'); +--// + + +-- ============================================================================ +--changeset hs-office-debitor-rbac-insert-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates the roles, grants and permission for the AFTER INSERT TRIGGER. + */ + +create or replace procedure buildRbacSystemForHsOfficeDebitor( + NEW hs_office_debitor +) + language plpgsql as $$ + +declare + newPartnerRel hs_office_relation; + newDebitorRel hs_office_relation; + newRefundBankAccount hs_office_bankaccount; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + SELECT * + FROM hs_office_relation AS partnerRel + WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid + INTO newPartnerRel; + assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid); + + SELECT * + FROM hs_office_relation AS r + WHERE r.type = 'DEBITOR' AND r.holderUuid = NEW.debitorRelUuid + INTO newDebitorRel; + assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid); + + SELECT * + FROM hs_office_relation AS r + WHERE r.type = 'DEBITOR' AND r.holderUuid = NEW.debitorRelUuid + INTO newRefundBankAccount; + assert newRefundBankAccount.uuid is not null, format('newRefundBankAccount must not be null for NEW.refundBankAccountUuid = %s', NEW.refundBankAccountUuid); + + call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationAgent(newDebitorRel)); + call grantRoleToRole(hsOfficeRelationAdmin(newDebitorRel), hsOfficeRelationAdmin(newPartnerRel)); + call grantRoleToRole(hsOfficeRelationAgent(newDebitorRel), hsOfficeBankAccountAdmin(newRefundBankAccount)); + call grantRoleToRole(hsOfficeRelationAgent(newDebitorRel), hsOfficeRelationAgent(newPartnerRel)); + call grantRoleToRole(hsOfficeRelationTenant(newPartnerRel), hsOfficeRelationAgent(newDebitorRel)); + + call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationOwner(newDebitorRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTenant(newDebitorRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAdmin(newDebitorRel)); + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_debitor row. + */ + +create or replace function insertTriggerForHsOfficeDebitor_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForHsOfficeDebitor(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForHsOfficeDebitor_tg + after insert on hs_office_debitor + for each row +execute procedure insertTriggerForHsOfficeDebitor_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-debitor-rbac-update-trigger:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Called from the AFTER UPDATE TRIGGER to re-wire the grants. + */ + +create or replace procedure updateRbacRulesForHsOfficeDebitor( + OLD hs_office_debitor, + NEW hs_office_debitor +) + language plpgsql as $$ + +declare + oldPartnerRel hs_office_relation; + newPartnerRel hs_office_relation; + oldDebitorRel hs_office_relation; + newDebitorRel hs_office_relation; + oldRefundBankAccount hs_office_bankaccount; + newRefundBankAccount hs_office_bankaccount; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + SELECT * + FROM hs_office_relation AS partnerRel + WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid + INTO oldPartnerRel; + assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRelUuid = %s', OLD.partnerRelUuid); + + SELECT * + FROM hs_office_relation AS partnerRel + WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid + INTO newPartnerRel; + assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid); + + SELECT * + FROM hs_office_relation AS r + WHERE r.type = 'DEBITOR' AND r.holderUuid = OLD.debitorRelUuid + INTO oldDebitorRel; + assert oldDebitorRel.uuid is not null, format('oldDebitorRel must not be null for OLD.debitorRelUuid = %s', OLD.debitorRelUuid); + + SELECT * + FROM hs_office_relation AS r + WHERE r.type = 'DEBITOR' AND r.holderUuid = NEW.debitorRelUuid + INTO newDebitorRel; + assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid); + + SELECT * + FROM hs_office_relation AS r + WHERE r.type = 'DEBITOR' AND r.holderUuid = OLD.debitorRelUuid + INTO oldRefundBankAccount; + assert oldRefundBankAccount.uuid is not null, format('oldRefundBankAccount must not be null for OLD.refundBankAccountUuid = %s', OLD.refundBankAccountUuid); + + SELECT * + FROM hs_office_relation AS r + WHERE r.type = 'DEBITOR' AND r.holderUuid = NEW.debitorRelUuid + INTO newRefundBankAccount; + assert newRefundBankAccount.uuid is not null, format('newRefundBankAccount must not be null for NEW.refundBankAccountUuid = %s', NEW.refundBankAccountUuid); + + + if NEW.refundBankAccountUuid <> OLD.refundBankAccountUuid then + + call revokeRoleFromRole(hsOfficeRelationAgent(oldDebitorRel), hsOfficeBankAccountAdmin(oldRefundBankAccount)); + call grantRoleToRole(hsOfficeRelationAgent(newDebitorRel), hsOfficeBankAccountAdmin(newRefundBankAccount)); + + call revokeRoleFromRole(hsOfficeBankAccountReferrer(oldRefundBankAccount), hsOfficeRelationAgent(oldDebitorRel)); + call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationAgent(newDebitorRel)); + + end if; + + call leaveTriggerForObjectUuid(NEW.uuid); +end; $$; + +/* + AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_office_debitor row. + */ + +create or replace function updateTriggerForHsOfficeDebitor_tf() + returns trigger + language plpgsql + strict as $$ +begin + call updateRbacRulesForHsOfficeDebitor(OLD, NEW); + return NEW; +end; $$; + +create trigger updateTriggerForHsOfficeDebitor_tg + after update on hs_office_debitor + for each row +execute procedure updateTriggerForHsOfficeDebitor_tf(); +--// + + +-- ============================================================================ +--changeset hs-office-debitor-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates INSERT INTO hs_office_debitor permissions for the related global rows. + */ +do language plpgsql $$ + declare + row global; + permissionUuid uuid; + roleUuid uuid; + begin + call defineContext('create INSERT INTO hs_office_debitor permissions for the related global rows'); + + FOR row IN SELECT * FROM global + LOOP + roleUuid := findRoleId(globalAdmin()); + permissionUuid := createPermission(row.uuid, 'INSERT', 'hs_office_debitor'); + call grantPermissionToRole(permissionUuid, roleUuid); + END LOOP; + END; +$$; + +/** + Adds hs_office_debitor INSERT permission to specified role of new global rows. +*/ +create or replace function hs_office_debitor_global_insert_tf() + returns trigger + language plpgsql + strict as $$ +begin + call grantPermissionToRole( + createPermission(NEW.uuid, 'INSERT', 'hs_office_debitor'), + globalAdmin()); + return NEW; +end; $$; + +-- z_... is to put it at the end of after insert triggers, to make sure the roles exist +create trigger z_hs_office_debitor_global_insert_tg + after insert on global + for each row +execute procedure hs_office_debitor_global_insert_tf(); + +/** + Checks if the user or assumed roles are allowed to insert a row to hs_office_debitor, + where only global-admin has that permission. +*/ +create or replace function hs_office_debitor_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception '[403] insert into hs_office_debitor not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); +end; $$; + +create trigger hs_office_debitor_insert_permission_check_tg + before insert on hs_office_debitor + for each row + when ( not isGlobalAdmin() ) + execute procedure hs_office_debitor_insert_permission_missing_tf(); +--// + +-- ============================================================================ +--changeset hs-office-debitor-rbac-IDENTITY-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + + call generateRbacIdentityViewFromQuery('hs_office_debitor', $idName$ + SELECT debitor.uuid, + 'D-' || (SELECT partner.partnerNumber + FROM hs_office_partner partner + JOIN hs_office_relation partnerRel + ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER' + JOIN hs_office_relation debitorRel + ON debitorRel.anchorUuid = partnerRel.holderUuid AND partnerRel.type = 'DEBITOR' + WHERE debitorRel.uuid = debitor.debitorRelUuid) + || to_char(debitorNumberSuffix, 'fm00') + from hs_office_debitor as debitor + + $idName$); +--// + +-- ============================================================================ +--changeset hs-office-debitor-rbac-RESTRICTED-VIEW:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +call generateRbacRestrictedView('hs_office_debitor', + $orderBy$ + SELECT debitor.uuid, + 'D-' || (SELECT partner.partnerNumber + FROM hs_office_partner partner + JOIN hs_office_relation partnerRel + ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER' + JOIN hs_office_relation debitorRel + ON debitorRel.anchorUuid = partnerRel.holderUuid AND partnerRel.type = 'DEBITOR' + WHERE debitorRel.uuid = debitor.debitorRelUuid) + || to_char(debitorNumberSuffix, 'fm00') + from hs_office_debitor as debitor + $orderBy$, + $updates$ + debitorRel = new.debitorRel, + billable = new.billable, + debitorUuid = new.debitorUuid, + refundBankAccountUuid = new.refundBankAccountUuid, + vatId = new.vatId, + vatCountryCode = new.vatCountryCode, + vatBusiness = new.vatBusiness, + vatReverseCharge = new.vatReverseCharge, + defaultPrefix = new.defaultPrefix + $updates$); +--// + -- 2.39.5 From fbe2204d7277358e2a33863bb2529c24db75a7c4 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sat, 23 Mar 2024 15:00:07 +0100 Subject: [PATCH 10/17] JavaDoc for RbacView --- .../office/debitor/HsOfficeDebitorEntity.java | 9 +- .../relation/HsOfficeRelationEntity.java | 13 +- .../hsadminng/rbac/rbacdef/RbacView.java | 252 +++++++++++++++++- .../RbacViewMermaidFlowchartGenerator.java | 1 - .../rbacdef/RbacViewPostgresGenerator.java | 1 - .../hsadminng/test/dom/TestDomainEntity.java | 4 +- .../hsadminng/test/pac/TestPackageEntity.java | 4 +- .../resources/db/changelog/010-context.sql | 1 + 8 files changed, 264 insertions(+), 21 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java index b010ed9b..3c9f0481 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java @@ -19,6 +19,7 @@ import java.util.Optional; import java.util.UUID; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql; @@ -149,8 +150,8 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { SELECT * FROM hs_office_relation AS r WHERE r.type = 'DEBITOR' AND r.holderUuid = ${REF}.debitorRelUuid - """) - ) + """), + NULLABLE) .toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT) .toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER) @@ -159,8 +160,8 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { SELECT * FROM hs_office_relation AS partnerRel WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid - """) - ) + """), + NULLABLE) .toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN) .toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT) .toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java index 71e2b11a..0452c0df 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java @@ -16,6 +16,7 @@ import java.util.UUID; 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.Nullable.NULLABLE; 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.*; @@ -90,16 +91,16 @@ public class HsOfficeRelationEntity implements HasUuid, Stringifyable { .withUpdatableColumns("contactUuid") .importEntityAlias("anchorPerson", HsOfficePersonEntity.class, dependsOnColumn("anchorUuid"), - fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.anchorUuid") - ) + fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.anchorUuid"), + NULLABLE) .importEntityAlias("holderPerson", HsOfficePersonEntity.class, dependsOnColumn("holderUuid"), - fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.holderUuid") - ) + fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.holderUuid"), + NULLABLE) .importEntityAlias("contact", HsOfficeContactEntity.class, dependsOnColumn("contactUuid"), - fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid") - ) + fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid"), + NULLABLE) .createRole(OWNER, (with) -> { with.owningUser(CREATOR); with.incomingSuperRole(GLOBAL, ADMIN); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index 57499bb2..cf4e3d18 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -66,6 +66,21 @@ public class RbacView { private EntityAlias rootEntityAliasProxy; private RbacRoleDefinition previousRoleDef; + /** Crates an RBAC definition template for the given entity class and defining the given alias. + * + * @param alias + * an alias name for this entity/table, which can be used in further grants + * + * @param entityClass + * the Java class for which this RBAC definition is to be defined + * (the class to which the calling method belongs) + * + * @return + * the newly created RBAC definition template + * + * @param + * a JPA entity class extending RbacObject + */ public static RbacView rbacViewFor(final String alias, final Class entityClass) { return new RbacView(alias, entityClass); } @@ -77,22 +92,71 @@ public class RbacView { entityAliases.put("global", new EntityAlias("global")); } + /** + * Specifies, which columns of the restricted view are updatable at all. + * + * @param columnNames + * A list of the updatable columns. + * + * @return + * the `this` instance itself to allow chained calls. + */ public RbacView withUpdatableColumns(final String... columnNames) { Collections.addAll(updatableColumns, columnNames); verifyVersionColumnExists(); return this; } + /** Specifies the SQL query which creates the identity view for this entity. + * + *

An identity view is a view which maps an objectUuid to an idName. + * The idName should be a human-readable representation of the row, but as short as possible. + * The idName must only consist of letters (A-Z, a-z), digits (0-9), dash (-), dot (.) and unserscore '_'. + * It's used to create the object-specific-role-names like test_customer#abc.admin - here 'abc' is the idName. + * The idName not necessarily unique in a table, but it should be avoided. + *

+ * + * @param sqlExpression + * Either specify an SQL projection (the part between SELECT and FROM), e.g. `SQL.projection("columnName") + * or the whole SELECT query returning the uuid and idName columns, + * e.g. `SQL.query("SELECT ... AS uuid, ... AS idName FROM ... JOIN ..."). + * Only add really important columns, just enough to create a short human-readable representation. + * + * @return + * the `this` instance itself to allow chained calls. + */ public RbacView withIdentityView(final SQL sqlExpression) { this.identityViewSqlQuery = sqlExpression; return this; } + /** + * Specifies a ORDER BY clause for the generated restricted view. + * + *

A restricted view is generated, no matter if the order was specified or not.

+ * + * @param orderBySqlExpression + * That's the part behind `ORDER BY`, e.g. `SQL.expression("prefix"). + * + * @return + * the `this` instance itself to allow chained calls. + */ public RbacView withRestrictedViewOrderBy(final SQL orderBySqlExpression) { this.orderBySqlExpression = orderBySqlExpression; return this; } + /** + * Specifies that the given role (OWNER, ADMIN, ...) is to be created for new/updated roles in this table. + * + * @param role + * OWNER, ADMIN, AGENT etc. + * @param with + * a lambda which receives the created role to create grants and permissions to and from the newly created role, + * e.g. the owning user, incoming superroles, outgoing subroles + * @return + * the `this` instance itself to allow chained calls. + */ public RbacView createRole(final Role role, final Consumer with) { final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate(); with.accept(newRoleDef); @@ -100,6 +164,15 @@ public class RbacView { return this; } + /** + * Specifies that the given role (OWNER, ADMIN, ...) is to be created for new/updated roles in this table, + * which is becomes sub-role of the previously created role. + * + * @param role + * OWNER, ADMIN, AGENT etc. + * @return + * the `this` instance itself to allow chained calls. + */ public RbacView createSubRole(final Role role) { final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate(); findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate(); @@ -107,6 +180,19 @@ public class RbacView { return this; } + + /** + * Specifies that the given role (OWNER, ADMIN, ...) is to be created for new/updated roles in this table, + * which is becomes sub-role of the previously created role. + * + * @param role + * OWNER, ADMIN, AGENT etc. + * @param with + * a lambda which receives the created role to create grants and permissions to and from the newly created role, + * e.g. the owning user, incoming superroles, outgoing subroles + * @return + * the `this` instance itself to allow chained calls. + */ public RbacView createSubRole(final Role role, final Consumer with) { final RbacRoleDefinition newRoleDef = findRbacRole(rootEntityAlias, role).toCreate(); findOrCreateGrantDef(newRoleDef, previousRoleDef).toCreate(); @@ -115,10 +201,38 @@ public class RbacView { return this; } + /** + * Specifies that the given permission is to be created for each new row in the target table. + * + *

Grants to permissions created by this method have to be specified separately, + * often it's easier to read to use createRole/createSubRole and use with.permission(...).

+ * + * @param permission + * e.g. INSERT, SELECT, UPDATE, DELETE + * + * @return + * the newly created permission definition + */ public RbacPermissionDefinition createPermission(final Permission permission) { return createPermission(rootEntityAlias, permission); } + /** + * Specifies that the given permission is to be created for each new row in the target table, + * but for another table, e.g. a table with details data with different access rights. + * + *

Grants to permissions created by this method have to be specified separately, + * often it's easier to read to use createRole/createSubRole and use with.permission(...).

+ * + * @param entityAliasName + * A previously defined entity alias name. + * + * @param permission + * e.g. INSERT, SELECT, UPDATE, DELETE + * + * @return + * the newly created permission definition + */ public RbacPermissionDefinition createPermission(final String entityAliasName, final Permission permission) { return createPermission(findEntityAlias(entityAliasName), permission); } @@ -134,6 +248,32 @@ public class RbacView { return this; } + /** + * Imports the RBAC template from the given entity class and defines an alias name for it. + * This method is especially for proxy-entities, if the root entity does not have its own + * roles, a proxy-entity can be specified and its roles can be used instead. + * + * @param aliasName + * An alias name for the entity class. The same entity class can be imported multiple times, + * if multiple references to its table exist, then distinct alias names habe to be defined. + * + * @param entityClass + * A JPA entity class extending RbacObject which also implements an `rbac` method returning + * its RBAC specification. + * + * @param fetchSql + * An SQL SELECT statement which fetches the referenced row. Use `${REF}` to speficiy the + * newly created or updated row (will be replaced by NEW/OLD from the trigger method). + * + * @param dependsOnColum + * The column, usually containing an uuid, on which this other table depends. + * + * @return + * the newly created permission definition + * + * @param + * a JPA entity class extending RbacObject + */ public RbacView importRootEntityAliasProxy( final String aliasName, final Class entityClass, @@ -146,6 +286,18 @@ public class RbacView { return this; } + /** + * Imports the RBAC template from the given entity class and defines an alias name for it. + * This method is especially to declare sub-entities, e.g. details to a main object. + * + * @see {@link} + * + * @return + * the newly created permission definition + * + * @param + * a JPA entity class extending RbacObject + */ public RbacView importSubEntityAlias( final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) { @@ -153,6 +305,33 @@ public class RbacView { return this; } + /** + * Imports the RBAC template from the given entity class and defines an anlias name for it. + * + * @param aliasName + * An alias name for the entity class. The same entity class can be imported multiple times, + * if multiple references to its table exist, then distinct alias names habe to be defined. + * + * @param entityClass + * A JPA entity class extending RbacObject which also implements an `rbac` method returning + * its RBAC specification. + * + * @param fetchSql + * An SQL SELECT statement which fetches the referenced row. Use `${REF}` to speficiy the + * newly created or updated row (will be replaced by NEW/OLD from the trigger method). + * + * @param dependsOnColum + * The column, usually containing an uuid, on which this other table depends. + * + * @param nullable + * Specifies whether the dependsOnColum is nullable or not. + * + * @return + * the newly created permission definition + * + * @param + * a JPA entity class extending RbacObject + */ public RbacView importEntityAlias( final String aliasName, final Class entityClass, final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) { @@ -160,13 +339,7 @@ public class RbacView { return this; } - public RbacView importEntityAlias( - final String aliasName, final Class entityClass, - final Column dependsOnColum, final SQL fetchSql) { - importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL); - return this; - } - + // TODO: remove once it's not used in HsOffice...Entity anymore public RbacView importEntityAlias( final String aliasName, final Class entityClass, final Column dependsOnColum) { @@ -232,6 +405,16 @@ public class RbacView { } } + /** + * Starts declaring a grant to a given role. + * + * @param entityAlias + * A previously speciried entity alias name. + * @param role + * OWNER, ADMIN, AGENT, ... + * @return + * a grant builder + */ public RbacGrantBuilder toRole(final String entityAlias, final Role role) { return new RbacGrantBuilder(entityAlias, role); } @@ -430,6 +613,16 @@ public class RbacView { permDefs.add(this); } + /** + * Grants the permission under definition to the given role. + * + * @param entityAlias + * A previously declared entity alias name. + * @param role + * OWNER, ADMIN, ... + * @return + * The RbacView specification to which this permission definition belongs. + */ public RbacView grantedTo(final String entityAlias, final Role role) { findOrCreateGrantDef(this, findRbacRole(entityAlias, role)).toCreate(); return RbacView.this; @@ -460,19 +653,61 @@ public class RbacView { return this; } + /** + * Specifies which user becomes the owner of newly created objects. + * @param userRole + * GLOBAL_ADMIN, CREATOR, ... + * @return + * The grant definition for further chained calls. + */ public RbacGrantDefinition owningUser(final RbacUserReference.UserRole userRole) { return grantRoleToUser(this, findUserRef(userRole)); } + /** + * Specifies which permission is to be created for newly created objects. + * @param permission + * INSERT, SELECT, ... + * @return + * The grant definition for further chained calls. + */ public RbacGrantDefinition permission(final Permission permission) { return grantPermissionToRole(createPermission(entityAlias, permission), this); } + /** + * Specifies in incoming super role which gets granted the role under definition. + * + *

Incoming means an incoming grant arrow in our grant-diagrams. + * Super-role means that it's the role to which another role is granted. + * Both means actually the same, just in different aspects.

+ * + * @param entityAlias + * A previously declared entity alias name. + * @param role + * OWNER, ADMIN, ... + * @return + * The grant definition for further chained calls. + */ public RbacGrantDefinition incomingSuperRole(final String entityAlias, final Role role) { final var incomingSuperRole = findRbacRole(entityAlias, role); return grantSubRoleToSuperRole(this, incomingSuperRole); } + /** + * Specifies in outgoing sub role which gets granted the role under definition. + * + *

Outgoing means an outgoing grant arrow in our grant-diagrams. + * Sub-role means which is granted to another role. + * Both means actually the same, just in different aspects.

+ * + * @param entityAlias + * A previously declared entity alias name. + * @param role + * OWNER, ADMIN, ... + * @return + * The grant definition for further chained calls. + */ public RbacGrantDefinition outgoingSubRole(final String entityAlias, final Role role) { final var outgoingSubRole = findRbacRole(entityAlias, role); return grantSubRoleToSuperRole(outgoingSubRole, this); @@ -802,6 +1037,9 @@ public class RbacView { } } + /** + * This main method generates the RbacViews (PostgreSQL+diagram) for all given entity classes. + */ public static void main(String[] args) { Stream.of( TestCustomerEntity.class, diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java index 898a0273..d6a9bc28 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java @@ -4,7 +4,6 @@ import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import java.nio.file.*; -import java.time.LocalDateTime; import static java.util.stream.Collectors.joining; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*; diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java index dc56a286..5a3b2be8 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java @@ -5,7 +5,6 @@ import lombok.SneakyThrows; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.time.LocalDateTime; import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW; import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with; diff --git a/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java b/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java index fe053f1f..778c71ec 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.util.UUID; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql; @@ -52,7 +53,8 @@ public class TestDomainEntity implements HasUuid { fetchedBySql(""" SELECT * FROM test_package p WHERE p.uuid= ${ref}.packageUuid - """)) + """), + NULLABLE) .toRole("package", ADMIN).grantPermission(INSERT) .createRole(OWNER, (with) -> { diff --git a/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java b/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java index 9dc0d5d9..27546cf2 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.util.UUID; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.*; @@ -53,7 +54,8 @@ public class TestPackageEntity implements HasUuid { fetchedBySql(""" SELECT * FROM test_customer c WHERE c.uuid= ${ref}.customerUuid - """)) + """), + NOT_NULL) .toRole("customer", ADMIN).grantPermission(INSERT) .createRole(OWNER, (with) -> { diff --git a/src/main/resources/db/changelog/010-context.sql b/src/main/resources/db/changelog/010-context.sql index 8de41891..0e5cc457 100644 --- a/src/main/resources/db/changelog/010-context.sql +++ b/src/main/resources/db/changelog/010-context.sql @@ -160,6 +160,7 @@ create or replace function cleanIdentifier(rawIdentifier varchar) declare cleanIdentifier varchar; begin + -- TODO: remove the ':' from the list of allowed characters as soon as it's not used anymore cleanIdentifier := regexp_replace(rawIdentifier, '[^A-Za-z0-9\-._:]+', '', 'g'); return cleanIdentifier; end; $$; -- 2.39.5 From c9f7d8ec2da6e03f0b78b863cb49e4806674d883 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sun, 24 Mar 2024 11:01:45 +0100 Subject: [PATCH 11/17] introduce referrer role and support explict columns for restricted view --- .../rbac/rbacgrant/RbacGrantController.java | 13 +++ .../hsadminng/rbac/rbacrole/RbacRoleType.java | 2 +- .../db/changelog/007-table-columns.sql | 20 ++++ .../resources/db/changelog/050-rbac-base.sql | 91 +++++++++++++++++-- .../db/changelog/054-rbac-context.sql | 7 +- .../db/changelog/057-rbac-role-builder.sql | 21 +++-- .../db/changelog/058-rbac-generators.sql | 42 ++++++--- .../db/changelog/080-rbac-global.sql | 27 +++++- .../db/changelog/133-test-domain-rbac.sql | 37 +------- .../223-hs-office-relation-rbac-generated.sql | 52 ++--------- ...3-hs-office-sepamandate-rbac-generated.sql | 4 +- .../273-hs-office-debitor-rbac-generated.sql | 62 +------------ .../db/changelog/db.changelog-master.yaml | 2 + 13 files changed, 202 insertions(+), 178 deletions(-) create mode 100644 src/main/resources/db/changelog/007-table-columns.sql diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java index 29bdc2d8..9dfaea74 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantController.java @@ -94,4 +94,17 @@ public class RbacGrantController implements RbacGrantsApi { return ResponseEntity.noContent().build(); } + +// TODO: implement an endpoint to create a Mermaid flowchart with all grants of a given user +// @GetMapping( +// path = "/api/rbac/users/{userUuid}/grants", +// produces = {"text/vnd.mermaid"}) +// @Transactional(readOnly = true) +// public ResponseEntity allGrantsOfUserAsMermaid( +// @RequestHeader(name = "current-user") String currentUser, +// @RequestHeader(name = "assumed-roles", required = false) String assumedRoles) { +// final var graph = RbacGrantsDiagramService.allGrantsToUser(currentUser); +// return ResponseEntity.ok(graph); +// } + } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java index 153344fa..fa5b16aa 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java @@ -1,5 +1,5 @@ package net.hostsharing.hsadminng.rbac.rbacrole; public enum RbacRoleType { - owner, admin, agent, tenant, guest + owner, admin, agent, tenant, guest, referrer } diff --git a/src/main/resources/db/changelog/007-table-columns.sql b/src/main/resources/db/changelog/007-table-columns.sql new file mode 100644 index 00000000..588defba --- /dev/null +++ b/src/main/resources/db/changelog/007-table-columns.sql @@ -0,0 +1,20 @@ +--liquibase formatted sql + + +-- ============================================================================ +-- TABLE-COLUMNS-FUNCTION +--changeset table-columns-function:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +create or replace function columnsNames( tableName text ) + returns text + stable + language 'plpgsql' as $$ +declare columns text[]; +begin + columns := (select array(select column_name::text + from information_schema.columns + where table_name = tableName)); + return array_to_string(columns, ', '); +end; $$ +--// diff --git a/src/main/resources/db/changelog/050-rbac-base.sql b/src/main/resources/db/changelog/050-rbac-base.sql index 0c64866c..735f1932 100644 --- a/src/main/resources/db/changelog/050-rbac-base.sql +++ b/src/main/resources/db/changelog/050-rbac-base.sql @@ -164,7 +164,7 @@ end; $$; */ -create type RbacRoleType as enum ('owner', 'admin', 'agent', 'tenant', 'guest'); +create type RbacRoleType as enum ('owner', 'admin', 'agent', 'tenant', 'guest', 'referrer'); create table RbacRole ( @@ -373,10 +373,12 @@ create table RbacPermission uuid uuid primary key references RbacReference (uuid) on delete cascade, objectUuid uuid not null references RbacObject, op RbacOp not null, - opTableName varchar(60), - unique (objectUuid, op) + opTableName varchar(60) ); +ALTER TABLE RbacPermission + ADD CONSTRAINT RbacPermission_uc UNIQUE NULLS NOT DISTINCT (objectUuid, op, opTableName); + call create_journal('RbacPermission'); create or replace function createPermission(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null) @@ -395,7 +397,10 @@ begin raise exception 'forOpTableName must only be specified for ops: [INSERT]'; -- currently no other end if; - permissionUuid = (select uuid from RbacPermission where objectUuid = forObjectUuid and op = forOp and opTableName = forOpTableName); + permissionUuid := ( + select uuid from RbacPermission + where objectUuid = forObjectUuid + and op = forOp and opTableName is not distinct from forOpTableName); if (permissionUuid is null) then insert into RbacReference ("type") values ('RbacPermission') @@ -466,8 +471,44 @@ select uuid and p.op = forOp and p.opTableName = forOpTableName $$; + +create or replace function getPermissionId(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null) + returns uuid + stable -- leakproof + language plpgsql as $$ +declare + permissionUuid uuid; +begin + select uuid into permissionUuid + from RbacPermission p + where p.objectUuid = forObjectUuid + and p.op = forOp + and forOpTableName is null or p.opTableName = forOpTableName; + assert permissionUuid is not null, + format('permission %s %s for object UUID %s cannot be found', forOp, forOpTableName, forObjectUuid); + return permissionUuid; +end; $$; --// + +-- ============================================================================ +--changeset rbac-base-duplicate-role-grant-exception:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +create or replace procedure raiseDuplicateRoleGrantException(subRoleId uuid, superRoleId uuid) + language plpgsql as $$ +declare + subRoleIdName text; + superRoleIdName text; +begin + select roleIdName from rbacRole_ev where uuid=subRoleId into subRoleIdName; + select roleIdName from rbacRole_ev where uuid=superRoleId into superRoleIdName; + raise exception '[400] Duplicate role grant detected: role % (%) already granted to % (%)', subRoleId, subRoleIdName, superRoleId, superRoleIdName; +end; +$$; +--// + + -- ============================================================================ --changeset rbac-base-GRANTS:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -634,7 +675,7 @@ begin perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole'); if isGranted(subRoleId, superRoleId) then - raise exception '[400] Cyclic role grant detected between % and %', subRoleId, superRoleId; + call raiseDuplicateRoleGrantException(subRoleId, superRoleId); end if; insert @@ -650,6 +691,11 @@ declare superRoleId uuid; subRoleId uuid; begin + -- FIXME: maybe separate method grantRoleToRoleIfNotNull(...)? + if superRole.objectUuid is null or subRole.objectuuid is null then + return; + end if; + superRoleId := findRoleId(superRole); subRoleId := findRoleId(subRole); @@ -657,7 +703,7 @@ begin perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole'); if isGranted(subRoleId, superRoleId) then - raise exception '[400] Cyclic role grant detected between % and %', subRoleId, superRoleId; + call raiseDuplicateRoleGrantException(subRoleId, superRoleId); end if; insert @@ -672,6 +718,7 @@ declare superRoleId uuid; subRoleId uuid; begin + if ( superRoleId is null ) then return; end if; superRoleId := findRoleId(superRole); if ( subRoleId is null ) then return; end if; subRoleId := findRoleId(subRole); @@ -680,7 +727,7 @@ begin perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole'); if isGranted(subRoleId, superRoleId) then - raise exception '[400] Cyclic role grant detected between % and %', subRoleId, superRoleId; + call raiseDuplicateRoleGrantException(subRoleId, superRoleId); end if; insert @@ -704,11 +751,39 @@ begin if (isGranted(superRoleId, subRoleId)) then delete from RbacGrants where ascendantUuid = superRoleId and descendantUuid = subRoleId; else - raise exception 'cannot revoke role % (%) from % (% because it is not granted', + raise exception 'cannot revoke role % (%) from % (%) because it is not granted', subRole, subRoleId, superRole, superRoleId; end if; end; $$; +create or replace procedure revokePermissionFromRole(permissionId UUID, superRole RbacRoleDescriptor) + language plpgsql as $$ +declare + superRoleId uuid; + permissionOp text; + objectTable text; + objectUuid uuid; +begin + superRoleId := findRoleId(superRole); + + perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole'); + perform assertReferenceType('permission (descendant)', permissionId, 'RbacPermission'); + + if (isGranted(superRoleId, permissionId)) then + delete from RbacGrants where ascendantUuid = superRoleId and descendantUuid = permissionId; + else + select p.op, o.objectTable, o.uuid + from rbacGrants g + join rbacPermission p on p.uuid=g.descendantUuid + join rbacobject o on o.uuid=p.objectUuid + where g.uuid=permissionId + into permissionOp, objectTable, objectUuid; + + raise exception 'cannot revoke permission % (% on %#% (%) from % (%)) because it is not granted', + permissionId, permissionOp, objectTable, objectUuid, permissionId, superRole, superRoleId; + end if; +end; $$; + -- ============================================================================ --changeset rbac-base-QUERY-ACCESSIBLE-OBJECT-UUIDS:1 endDelimiter:--// -- ---------------------------------------------------------------------------- diff --git a/src/main/resources/db/changelog/054-rbac-context.sql b/src/main/resources/db/changelog/054-rbac-context.sql index ede86057..5437131f 100644 --- a/src/main/resources/db/changelog/054-rbac-context.sql +++ b/src/main/resources/db/changelog/054-rbac-context.sql @@ -56,14 +56,17 @@ begin roleTypeToAssume = split_part(roleNameParts, '#', 3); objectUuidToAssume = findObjectUuidByIdName(objectTableToAssume, objectNameToAssume); + if objectUuidToAssume is null then + raise exception '[401] object % cannot be found in table %', objectNameToAssume, objectTableToAssume; + end if; - select uuid as roleuuidToAssume + select uuid from RbacRole r where r.objectUuid = objectUuidToAssume and r.roleType = roleTypeToAssume into roleUuidToAssume; if roleUuidToAssume is null then - raise exception '[403] role % not accessible for user %', roleName, currentSubjects(); + raise exception '[403] role % does not exist or is not accessible for user %', roleName, currentUser(); end if; if not isGranted(currentUserUuid, roleUuidToAssume) then raise exception '[403] user % has no permission to assume role %', currentUser(), roleName; diff --git a/src/main/resources/db/changelog/057-rbac-role-builder.sql b/src/main/resources/db/changelog/057-rbac-role-builder.sql index 1a7da953..b3ddbecd 100644 --- a/src/main/resources/db/changelog/057-rbac-role-builder.sql +++ b/src/main/resources/db/changelog/057-rbac-role-builder.sql @@ -31,13 +31,13 @@ create or replace function createRoleWithGrants( called on null input language plpgsql as $$ declare - roleUuid uuid; - subRoleDesc RbacRoleDescriptor; - superRoleDesc RbacRoleDescriptor; - subRoleUuid uuid; - superRoleUuid uuid; - userUuid uuid; - grantedByRoleUuid uuid; + roleUuid uuid; + subRoleDesc RbacRoleDescriptor; + superRoleDesc RbacRoleDescriptor; + subRoleUuid uuid; + superRoleUuid uuid; + userUuid uuid; + userGrantsByRoleUuid uuid; begin roleUuid := createRole(roleDescriptor); @@ -58,14 +58,15 @@ begin end loop; if cardinality(userUuids) > 0 then + -- direct grants to users need a grantedByRole which can revoke the grant if grantedByRole is null then - grantedByRoleUuid := roleUuid; + userGrantsByRoleUuid := roleUuid; -- FIXME: or do we want to require an explicit userGrantsByRoleUuid? else - grantedByRoleUuid := getRoleId(grantedByRole); + userGrantsByRoleUuid := getRoleId(grantedByRole); end if; foreach userUuid in array userUuids loop - call grantRoleToUserUnchecked(grantedByRoleUuid, roleUuid, userUuid); + call grantRoleToUserUnchecked(userGrantsByRoleUuid, roleUuid, userUuid); end loop; end if; diff --git a/src/main/resources/db/changelog/058-rbac-generators.sql b/src/main/resources/db/changelog/058-rbac-generators.sql index 89d585ea..efe71b1b 100644 --- a/src/main/resources/db/changelog/058-rbac-generators.sql +++ b/src/main/resources/db/changelog/058-rbac-generators.sql @@ -73,6 +73,7 @@ begin return roleDescriptor('%2$s', entity.uuid, 'tenant', assumed); end; $f$; + -- TODO: remove guest role create or replace function %1$sGuest(entity %2$s, assumed boolean = true) returns RbacRoleDescriptor language plpgsql @@ -81,6 +82,14 @@ begin return roleDescriptor('%2$s', entity.uuid, 'guest', assumed); end; $f$; + create or replace function %1$sReferrer(entity %2$s) + returns RbacRoleDescriptor + language plpgsql + strict as $f$ + begin + return roleDescriptor('%2$s', entity.uuid, 'referrer'); + end; $f$; + $sql$, prefix, targetTable); execute sql; end; $$; @@ -148,12 +157,16 @@ end; $$; --changeset rbac-generators-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -create or replace procedure generateRbacRestrictedView(targetTable text, orderBy text, columnUpdates text = null) +create or replace procedure generateRbacRestrictedView(targetTable text, orderBy text, columnUpdates text = null, columnNames text = '*') language plpgsql as $$ declare sql text; + newColumns text; begin targetTable := lower(targetTable); + if columnNames = '*' then + columnNames := columnsNames(targetTable); + end if; /* Creates a restricted view based on the 'SELECT' permission of the current subject. @@ -175,20 +188,21 @@ begin /** Instead of insert trigger function for the restricted view. */ + newColumns := 'new.' || replace(columnNames, ',', ', new.'); sql := format($sql$ - create or replace function %1$sInsert() - returns trigger - language plpgsql as $f$ - declare - newTargetRow %1$s; - begin - insert - into %1$s - values (new.*) - returning * into newTargetRow; - return newTargetRow; - end; $f$; - $sql$, targetTable); + create or replace function %1$sInsert() + returns trigger + language plpgsql as $f$ + declare + newTargetRow %1$s; + begin + insert + into %1$s (%2$s) + values (%3$s) + returning * into newTargetRow; + return newTargetRow; + end; $f$; + $sql$, targetTable, columnNames, newColumns); execute sql; /* diff --git a/src/main/resources/db/changelog/080-rbac-global.sql b/src/main/resources/db/changelog/080-rbac-global.sql index 8313d05d..f8058113 100644 --- a/src/main/resources/db/changelog/080-rbac-global.sql +++ b/src/main/resources/db/changelog/080-rbac-global.sql @@ -118,9 +118,32 @@ select 'global', (select uuid from RbacObject where objectTable = 'global'), 'ad $$; begin transaction; -call defineContext('creating global admin role', null, null, null); -select createRole(globalAdmin()); + call defineContext('creating global admin role', null, null, null); + select createRole(globalAdmin()); commit; +--// + + +-- ============================================================================ +--changeset rbac-global-GUEST-ROLE:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- +/* + A global guest role. + */ +create or replace function globalGuest(assumed boolean = true) + returns RbacRoleDescriptor + returns null on null input + stable -- leakproof + language sql as $$ +select 'global', (select uuid from RbacObject where objectTable = 'global'), 'guest'::RbacRoleType, assumed; +$$; + +begin transaction; + call defineContext('creating global guest role', null, null, null); + select createRole(globalGuest()); +commit; +--// + -- ============================================================================ --changeset rbac-global-ADMIN-USERS:1 context:dev,tc endDelimiter:--// diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index 6344e43d..e20f21c4 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -38,8 +38,6 @@ begin SELECT * FROM test_package p WHERE p.uuid= NEW.packageUuid INTO newPackage; - assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid); - perform createRoleWithGrants( testDomainOwner(NEW), @@ -91,41 +89,12 @@ create or replace procedure updateRbacRulesForTestDomain( NEW test_domain ) language plpgsql as $$ - -declare - oldPackage test_package; - newPackage test_package; - begin - call enterTriggerForObjectUuid(NEW.uuid); - - SELECT * FROM test_package p - WHERE p.uuid= OLD.packageUuid - INTO oldPackage; - assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s', OLD.packageUuid); - - SELECT * FROM test_package p - WHERE p.uuid= NEW.packageUuid - INTO newPackage; - assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid); - - - if NEW.packageUuid <> OLD.packageUuid then - - call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage)); - - call revokeRoleFromRole(testDomainOwner(OLD), testPackageAdmin(oldPackage)); - call grantRoleToRole(testDomainOwner(NEW), testPackageAdmin(newPackage)); - - call revokeRoleFromRole(testPackageTenant(oldPackage), testDomainOwner(OLD)); - call grantRoleToRole(testPackageTenant(newPackage), testDomainOwner(NEW)); - - call revokeRoleFromRole(testPackageTenant(oldPackage), testDomainAdmin(OLD)); - call grantRoleToRole(testPackageTenant(newPackage), testDomainAdmin(NEW)); + if NEW.packageUuid is distinct from OLD.packageUuid then + delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid; + call buildRbacSystemForTestDomain(NEW); end if; - - call leaveTriggerForObjectUuid(NEW.uuid); end; $$; /* diff --git a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql index 9a1f7d1b..65645895 100644 --- a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql +++ b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql @@ -38,14 +38,10 @@ begin call enterTriggerForObjectUuid(NEW.uuid); select * from hs_office_person as p where p.uuid = NEW.holderUuid INTO newHolderPerson; - assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s', NEW.holderUuid); select * from hs_office_person as p where p.uuid = NEW.anchorUuid INTO newAnchorPerson; - assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s', NEW.anchorUuid); select * from hs_office_contact as c where c.uuid = NEW.contactUuid INTO newContact; - assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s', NEW.contactUuid); - perform createRoleWithGrants( hsOfficeRelationOwner(NEW), @@ -73,13 +69,13 @@ begin hsOfficeRelationTenant(NEW), permissions => array['SELECT'], incomingSuperRoles => array[ - hsOfficeRelationAgent(NEW), hsOfficeContactAdmin(newContact), + hsOfficeRelationAgent(NEW), hsOfficePersonAdmin(newHolderPerson)], outgoingSubRoles => array[ - hsOfficePersonReferrer(newHolderPerson), hsOfficeContactReferrer(newContact), - hsOfficePersonReferrer(newAnchorPerson)] + hsOfficePersonReferrer(newAnchorPerson), + hsOfficePersonReferrer(newHolderPerson)] ); call leaveTriggerForObjectUuid(NEW.uuid); @@ -118,48 +114,12 @@ create or replace procedure updateRbacRulesForHsOfficeRelation( NEW hs_office_relation ) language plpgsql as $$ - -declare - oldHolderPerson hs_office_person; - newHolderPerson hs_office_person; - oldAnchorPerson hs_office_person; - newAnchorPerson hs_office_person; - oldContact hs_office_contact; - newContact hs_office_contact; - begin - call enterTriggerForObjectUuid(NEW.uuid); - - select * from hs_office_person as p where p.uuid = OLD.holderUuid INTO oldHolderPerson; - assert oldHolderPerson.uuid is not null, format('oldHolderPerson must not be null for OLD.holderUuid = %s', OLD.holderUuid); - - select * from hs_office_person as p where p.uuid = NEW.holderUuid INTO newHolderPerson; - assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s', NEW.holderUuid); - - select * from hs_office_person as p where p.uuid = OLD.anchorUuid INTO oldAnchorPerson; - assert oldAnchorPerson.uuid is not null, format('oldAnchorPerson must not be null for OLD.anchorUuid = %s', OLD.anchorUuid); - - select * from hs_office_person as p where p.uuid = NEW.anchorUuid INTO newAnchorPerson; - assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s', NEW.anchorUuid); - - select * from hs_office_contact as c where c.uuid = OLD.contactUuid INTO oldContact; - assert oldContact.uuid is not null, format('oldContact must not be null for OLD.contactUuid = %s', OLD.contactUuid); - - select * from hs_office_contact as c where c.uuid = NEW.contactUuid INTO newContact; - assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s', NEW.contactUuid); - - - if NEW.contactUuid <> OLD.contactUuid then - - call revokeRoleFromRole(hsOfficeRelationTenant(OLD), hsOfficeContactAdmin(oldContact)); - call grantRoleToRole(hsOfficeRelationTenant(NEW), hsOfficeContactAdmin(newContact)); - - call revokeRoleFromRole(hsOfficeContactReferrer(oldContact), hsOfficeRelationTenant(OLD)); - call grantRoleToRole(hsOfficeContactReferrer(newContact), hsOfficeRelationTenant(NEW)); + if NEW.contactUuid is distinct from OLD.contactUuid then + delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid; + call buildRbacSystemForHsOfficeRelation(NEW); end if; - - call leaveTriggerForObjectUuid(NEW.uuid); end; $$; /* diff --git a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql index f0718272..ac4e9fee 100644 --- a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql +++ b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql @@ -57,8 +57,8 @@ begin hsOfficeSepaMandateAgent(NEW), incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)], outgoingSubRoles => array[ - hsOfficeBankAccountReferrer(newBankAccount), - hsOfficeRelationAgent(newDebitorRel)] + hsOfficeRelationAgent(newDebitorRel), + hsOfficeBankAccountReferrer(newBankAccount)] ); perform createRoleWithGrants( diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql b/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql index 760f1fe9..7f6afc13 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql @@ -41,7 +41,6 @@ begin FROM hs_office_relation AS partnerRel WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid INTO newPartnerRel; - assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid); SELECT * FROM hs_office_relation AS r @@ -53,7 +52,6 @@ begin FROM hs_office_relation AS r WHERE r.type = 'DEBITOR' AND r.holderUuid = NEW.debitorRelUuid INTO newRefundBankAccount; - assert newRefundBankAccount.uuid is not null, format('newRefundBankAccount must not be null for NEW.refundBankAccountUuid = %s', NEW.refundBankAccountUuid); call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationAgent(newDebitorRel)); call grantRoleToRole(hsOfficeRelationAdmin(newDebitorRel), hsOfficeRelationAdmin(newPartnerRel)); @@ -101,66 +99,12 @@ create or replace procedure updateRbacRulesForHsOfficeDebitor( NEW hs_office_debitor ) language plpgsql as $$ - -declare - oldPartnerRel hs_office_relation; - newPartnerRel hs_office_relation; - oldDebitorRel hs_office_relation; - newDebitorRel hs_office_relation; - oldRefundBankAccount hs_office_bankaccount; - newRefundBankAccount hs_office_bankaccount; - begin - call enterTriggerForObjectUuid(NEW.uuid); - - SELECT * - FROM hs_office_relation AS partnerRel - WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid - INTO oldPartnerRel; - assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRelUuid = %s', OLD.partnerRelUuid); - - SELECT * - FROM hs_office_relation AS partnerRel - WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid - INTO newPartnerRel; - assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid); - - SELECT * - FROM hs_office_relation AS r - WHERE r.type = 'DEBITOR' AND r.holderUuid = OLD.debitorRelUuid - INTO oldDebitorRel; - assert oldDebitorRel.uuid is not null, format('oldDebitorRel must not be null for OLD.debitorRelUuid = %s', OLD.debitorRelUuid); - - SELECT * - FROM hs_office_relation AS r - WHERE r.type = 'DEBITOR' AND r.holderUuid = NEW.debitorRelUuid - INTO newDebitorRel; - assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid); - - SELECT * - FROM hs_office_relation AS r - WHERE r.type = 'DEBITOR' AND r.holderUuid = OLD.debitorRelUuid - INTO oldRefundBankAccount; - assert oldRefundBankAccount.uuid is not null, format('oldRefundBankAccount must not be null for OLD.refundBankAccountUuid = %s', OLD.refundBankAccountUuid); - - SELECT * - FROM hs_office_relation AS r - WHERE r.type = 'DEBITOR' AND r.holderUuid = NEW.debitorRelUuid - INTO newRefundBankAccount; - assert newRefundBankAccount.uuid is not null, format('newRefundBankAccount must not be null for NEW.refundBankAccountUuid = %s', NEW.refundBankAccountUuid); - - - if NEW.refundBankAccountUuid <> OLD.refundBankAccountUuid then - - call revokeRoleFromRole(hsOfficeRelationAgent(oldDebitorRel), hsOfficeBankAccountAdmin(oldRefundBankAccount)); - call grantRoleToRole(hsOfficeRelationAgent(newDebitorRel), hsOfficeBankAccountAdmin(newRefundBankAccount)); - - call revokeRoleFromRole(hsOfficeBankAccountReferrer(oldRefundBankAccount), hsOfficeRelationAgent(oldDebitorRel)); - call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationAgent(newDebitorRel)); + if NEW.refundBankAccountUuid is distinct from OLD.refundBankAccountUuid then + delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid; + call buildRbacSystemForHsOfficeDebitor(NEW); end if; - - call leaveTriggerForObjectUuid(NEW.uuid); end; $$; /* diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 5934c9a4..6047befa 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -11,6 +11,8 @@ databaseChangeLog: file: db/changelog/005-uuid-ossp-extension.sql - include: file: db/changelog/006-numeric-hash-functions.sql + - include: + file: db/changelog/007-table-columns.sql - include: file: db/changelog/009-check-environment.sql - include: -- 2.39.5 From 823269d8aa7ade7d3ccf01ef6110b43fda05ac42 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sun, 24 Mar 2024 11:03:26 +0100 Subject: [PATCH 12/17] cleanup unused method --- .../java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index cf4e3d18..e6967658 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -839,10 +839,6 @@ public class RbacView { }; } - public boolean hasFetchSql() { - return fetchSql != null; - } - private String withoutEntitySuffix(final String simpleEntityName) { return simpleEntityName.substring(0, simpleEntityName.length() - "Entity".length()); } -- 2.39.5 From fd7630aaa80964d25e5278fa0ca24578370d5ad7 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 25 Mar 2024 05:57:58 +0100 Subject: [PATCH 13/17] fix indrirect permission by indirect foreign key --- .../rbac/rbacdef/InsertTriggerGenerator.java | 55 ++++++++++++++++++- .../hsadminng/rbac/rbacdef/RbacView.java | 34 ++++++------ src/test/resources/application.yml | 3 +- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index d625c0d7..e79f8acf 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -114,7 +114,16 @@ public class InsertTriggerGenerator { } } } else { - generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g); + final var superRoleEntityAlias = g.getSuperRoleDef().getEntityAlias(); + + // TODO: Maybe this should depend on the indirection degree of the fetchSql? + // Maybe we need a separate fetchedBy method for all the simple, direct cases? + if (superRoleEntityAlias.fetchSql().sql.contains("JOIN ")) { + generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey(plPgSql, g); + } else { + generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g); + + } } }, () -> { @@ -149,6 +158,50 @@ public class InsertTriggerGenerator { with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName())); } + private void generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey( + final StringWriter plPgSql, + final RbacView.RbacGrantDefinition g) { + plPgSql.writeLn(""" + /** + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, + where the check is performed by an indirect role. + + An indirect role is a role FIXME. + */ + create or replace function ${rawSubTable}_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ + begin + if ( not hasInsertPermission( + ( SELECT ${varName}.uuid FROM + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), + with("varName", g.getSuperRoleDef().getEntityAlias().aliasName())); + plPgSql.indented(3, () -> { + plPgSql.writeLn( + "(" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ") AS ${varName}", + with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()), + with("ref", NEW.name())); + }); + plPgSql.writeLn(""" + + ), 'INSERT', '${rawSubTable}') ) then + raise exception + '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end if; + return NEW; + end; $$; + + create trigger ${rawSubTable}_insert_permission_check_tg + before insert on ${rawSubTable} + for each row + execute procedure ${rawSubTable}_insert_permission_missing_tf(); + + """, + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); + } + private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) { plPgSql.writeLn(""" /** diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index e6967658..ee1c9fdf 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -1033,6 +1033,23 @@ public class RbacView { } } + private static void generateRbacView(final Class c) { + final Method mainMethod = stream(c.getMethods()).filter( + m -> isStatic(m.getModifiers()) && m.getName().equals("main") + ) + .findFirst() + .orElse(null); + if (mainMethod != null) { + try { + mainMethod.invoke(null, new Object[] { null }); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } else { + System.err.println("WARNING: no main method in: " + c.getName() + " => no RBAC rules generated"); + } + } + /** * This main method generates the RbacViews (PostgreSQL+diagram) for all given entity classes. */ @@ -1052,21 +1069,6 @@ public class RbacView { HsOfficeSepaMandateEntity.class, HsOfficeCoopSharesTransactionEntity.class, HsOfficeMembershipEntity.class - ).forEach(c -> { - final Method mainMethod = stream(c.getMethods()).filter( - m -> isStatic(m.getModifiers()) && m.getName().equals("main") - ) - .findFirst() - .orElse(null); - if (mainMethod != null) { - try { - mainMethod.invoke(null, new Object[] { null }); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } else { - System.err.println("WARNING: no main method in: " + c.getName() + " => no RBAC rules generated"); - } - }); + ).forEach(RbacView::generateRbacView); } } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index a4f570f9..40ae85bb 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -4,8 +4,9 @@ spring: platform: postgres datasource: - url: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers + url-tc: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers url-local: jdbc:postgresql://localhost:5432/postgres + url: ${spring.datasource.url-tc} username: postgres password: password -- 2.39.5 From 670ba8e9c910b2256a6d9727337f7b941f332a51 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 25 Mar 2024 06:45:59 +0100 Subject: [PATCH 14/17] generate indirect permission by indirect foreign key depending on directlyFetchedByDependsOnColumn vs. fetchedBySql --- .../office/debitor/HsOfficeDebitorEntity.java | 22 ++++---------- .../office/partner/HsOfficePartnerEntity.java | 6 ++-- .../relation/HsOfficeRelationEntity.java | 8 ++--- .../rbac/rbacdef/InsertTriggerGenerator.java | 10 ++----- .../hsadminng/rbac/rbacdef/RbacView.java | 10 +++++-- .../hsadminng/test/dom/TestDomainEntity.java | 7 ++--- .../hsadminng/test/pac/TestPackageEntity.java | 5 +--- .../db/changelog/123-test-package-rbac.sql | 30 ++++++++++--------- .../db/changelog/133-test-domain-rbac.sql | 22 +++++++++----- .../223-hs-office-relation-rbac-generated.sql | 20 ++++++------- .../233-hs-office-partner-rbac-generated.sql | 12 ++++---- ...3-hs-office-sepamandate-rbac-generated.sql | 8 ++--- .../273-hs-office-debitor-rbac-generated.sql | 15 ++-------- 13 files changed, 79 insertions(+), 96 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java index 3c9f0481..4fb08538 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java @@ -22,7 +22,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnCo import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @@ -135,32 +135,22 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { .createPermission(INSERT).grantedTo("global", ADMIN) .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, - fetchedBySql(""" - SELECT * - FROM hs_office_relation AS r - WHERE r.type = 'DEBITOR' AND r.holderUuid = ${REF}.debitorRelUuid - """), + directlyFetchedByDependsOnColumn(), dependsOnColumn("debitorRelUuid")) .createPermission(DELETE).grantedTo("debitorRel", OWNER) .createPermission(UPDATE).grantedTo("debitorRel", ADMIN) .createPermission(SELECT).grantedTo("debitorRel", TENANT) .importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class, - dependsOnColumn("refundBankAccountUuid"), fetchedBySql(""" - SELECT * - FROM hs_office_relation AS r - WHERE r.type = 'DEBITOR' AND r.holderUuid = ${REF}.debitorRelUuid - """), + dependsOnColumn("refundBankAccountUuid"), + directlyFetchedByDependsOnColumn(), NULLABLE) .toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT) .toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER) .importEntityAlias("partnerRel", HsOfficeRelationEntity.class, - dependsOnColumn("partnerRelUuid"), fetchedBySql(""" - SELECT * - FROM hs_office_relation AS partnerRel - WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid - """), + dependsOnColumn("partnerRelUuid"), + directlyFetchedByDependsOnColumn(), NULLABLE) .toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN) .toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java index 5aaa5318..b16dcc76 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java @@ -22,7 +22,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnCo import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @@ -93,14 +93,14 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid { .createPermission(INSERT).grantedTo("global", ADMIN) .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, - fetchedBySql("SELECT * FROM hs_office_relation AS r WHERE r.uuid = ${ref}.partnerRelUuid"), + directlyFetchedByDependsOnColumn(), dependsOnColumn("partnerRelUuid")) .createPermission(DELETE).grantedTo("partnerRel", ADMIN) .createPermission(UPDATE).grantedTo("partnerRel", AGENT) .createPermission(SELECT).grantedTo("partnerRel", TENANT) .importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class, - fetchedBySql("SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = ${ref}.detailsUuid"), + directlyFetchedByDependsOnColumn(), dependsOnColumn("detailsUuid")) .createPermission("partnerDetails", DELETE).grantedTo("partnerRel", ADMIN) .createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java index 0452c0df..364368af 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java @@ -20,7 +20,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; 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.fetchedBySql; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @@ -91,15 +91,15 @@ public class HsOfficeRelationEntity implements HasUuid, Stringifyable { .withUpdatableColumns("contactUuid") .importEntityAlias("anchorPerson", HsOfficePersonEntity.class, dependsOnColumn("anchorUuid"), - fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.anchorUuid"), + directlyFetchedByDependsOnColumn(), NULLABLE) .importEntityAlias("holderPerson", HsOfficePersonEntity.class, dependsOnColumn("holderUuid"), - fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.holderUuid"), + directlyFetchedByDependsOnColumn(), NULLABLE) .importEntityAlias("contact", HsOfficeContactEntity.class, dependsOnColumn("contactUuid"), - fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid"), + directlyFetchedByDependsOnColumn(), NULLABLE) .createRole(OWNER, (with) -> { with.owningUser(CREATOR); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index e79f8acf..000988fa 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -115,14 +115,10 @@ public class InsertTriggerGenerator { } } else { final var superRoleEntityAlias = g.getSuperRoleDef().getEntityAlias(); - - // TODO: Maybe this should depend on the indirection degree of the fetchSql? - // Maybe we need a separate fetchedBy method for all the simple, direct cases? - if (superRoleEntityAlias.fetchSql().sql.contains("JOIN ")) { - generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey(plPgSql, g); - } else { + if (superRoleEntityAlias.fetchSql().part == RbacView.SQL.Part.AUTO_FETCH) { generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g); - + } else { + generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey(plPgSql, g); } } }, diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index ee1c9fdf..99acade9 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -34,7 +34,7 @@ import static java.util.Arrays.stream; import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.autoFetched; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static org.apache.commons.lang3.StringUtils.uncapitalize; @Getter @@ -343,7 +343,7 @@ public class RbacView { public RbacView importEntityAlias( final String aliasName, final Class entityClass, final Column dependsOnColum) { - importEntityAliasImpl(aliasName, entityClass, autoFetched(), dependsOnColum, false, null); + importEntityAliasImpl(aliasName, entityClass, directlyFetchedByDependsOnColumn(), dependsOnColum, false, null); return this; } @@ -839,6 +839,10 @@ public class RbacView { }; } + public boolean hasFetchSql() { + return fetchSql != null; + } + private String withoutEntitySuffix(final String simpleEntityName) { return simpleEntityName.substring(0, simpleEntityName.length() - "Entity".length()); } @@ -924,7 +928,7 @@ public class RbacView { * * @return the wrapped SQL definition object */ - public static SQL autoFetched() { + public static SQL directlyFetchedByDependsOnColumn() { return new SQL(null, Part.AUTO_FETCH); } diff --git a/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java b/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java index 778c71ec..7db03c7e 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java @@ -17,7 +17,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnCo import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; @Entity @@ -50,10 +50,7 @@ public class TestDomainEntity implements HasUuid { .importEntityAlias("package", TestPackageEntity.class, dependsOnColumn("packageUuid"), - fetchedBySql(""" - SELECT * FROM test_package p - WHERE p.uuid= ${ref}.packageUuid - """), + directlyFetchedByDependsOnColumn(), NULLABLE) .toRole("package", ADMIN).grantPermission(INSERT) diff --git a/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java b/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java index 27546cf2..8f72fc4c 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java @@ -51,10 +51,7 @@ public class TestPackageEntity implements HasUuid { .importEntityAlias("customer", TestCustomerEntity.class, dependsOnColumn("customerUuid"), - fetchedBySql(""" - SELECT * FROM test_customer c - WHERE c.uuid= ${ref}.customerUuid - """), + directlyFetchedByDependsOnColumn(), NOT_NULL) .toRole("customer", ADMIN).grantPermission(INSERT) diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index 912430bb..dc4d042f 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -35,9 +35,7 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); - SELECT * FROM test_customer c - WHERE c.uuid= NEW.customerUuid - INTO newCustomer; + SELECT * FROM test_customer WHERE uuid = NEW.customerUuid INTO newCustomer; assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid); @@ -103,14 +101,10 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); - SELECT * FROM test_customer c - WHERE c.uuid= OLD.customerUuid - INTO oldCustomer; + SELECT * FROM test_customer WHERE uuid = OLD.customerUuid INTO oldCustomer; assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s', OLD.customerUuid); - SELECT * FROM test_customer c - WHERE c.uuid= NEW.customerUuid - INTO newCustomer; + SELECT * FROM test_customer WHERE uuid = NEW.customerUuid INTO newCustomer; assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid); @@ -195,22 +189,30 @@ execute procedure test_package_test_customer_insert_tf(); /** Checks if the user or assumed roles are allowed to insert a row to test_package, - where the check is performed by a direct role. + where the check is performed by an indirect role. - A direct role is a role depending on a foreign key directly available in the NEW row. + An indirect role is a role FIXME. */ create or replace function test_package_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - raise exception '[403] insert into test_package not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); + if ( not hasInsertPermission( + ( SELECT customer.uuid FROM + + (SELECT * FROM test_customer WHERE uuid = NEW.customerUuid) AS customer + + ), 'INSERT', 'test_package') ) then + raise exception + '[403] insert into test_package not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end if; + return NEW; end; $$; create trigger test_package_insert_permission_check_tg before insert on test_package for each row - when ( not hasInsertPermission(NEW.customerUuid, 'INSERT', 'test_package') ) execute procedure test_package_insert_permission_missing_tf(); --// diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index e20f21c4..3b41dcbf 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -35,9 +35,7 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); - SELECT * FROM test_package p - WHERE p.uuid= NEW.packageUuid - INTO newPackage; + SELECT * FROM test_package WHERE uuid = NEW.packageUuid INTO newPackage; perform createRoleWithGrants( testDomainOwner(NEW), @@ -163,22 +161,30 @@ execute procedure test_domain_test_package_insert_tf(); /** Checks if the user or assumed roles are allowed to insert a row to test_domain, - where the check is performed by a direct role. + where the check is performed by an indirect role. - A direct role is a role depending on a foreign key directly available in the NEW row. + An indirect role is a role FIXME. */ create or replace function test_domain_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - raise exception '[403] insert into test_domain not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); + if ( not hasInsertPermission( + ( SELECT package.uuid FROM + + (SELECT * FROM test_package WHERE uuid = NEW.packageUuid) AS package + + ), 'INSERT', 'test_domain') ) then + raise exception + '[403] insert into test_domain not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); + end if; + return NEW; end; $$; create trigger test_domain_insert_permission_check_tg before insert on test_domain for each row - when ( not hasInsertPermission(NEW.packageUuid, 'INSERT', 'test_domain') ) execute procedure test_domain_insert_permission_missing_tf(); --// diff --git a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql index 65645895..0b918ccc 100644 --- a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql +++ b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql @@ -37,11 +37,11 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); - select * from hs_office_person as p where p.uuid = NEW.holderUuid INTO newHolderPerson; + SELECT * FROM hs_office_person WHERE uuid = NEW.holderUuid INTO newHolderPerson; - select * from hs_office_person as p where p.uuid = NEW.anchorUuid INTO newAnchorPerson; + SELECT * FROM hs_office_person WHERE uuid = NEW.anchorUuid INTO newAnchorPerson; - select * from hs_office_contact as c where c.uuid = NEW.contactUuid INTO newContact; + SELECT * FROM hs_office_contact WHERE uuid = NEW.contactUuid INTO newContact; perform createRoleWithGrants( hsOfficeRelationOwner(NEW), @@ -54,28 +54,28 @@ begin hsOfficeRelationAdmin(NEW), permissions => array['UPDATE'], incomingSuperRoles => array[ - hsOfficePersonAdmin(newAnchorPerson), - hsOfficeRelationOwner(NEW)] + hsOfficeRelationOwner(NEW), + hsOfficePersonAdmin(newAnchorPerson)] ); perform createRoleWithGrants( hsOfficeRelationAgent(NEW), incomingSuperRoles => array[ - hsOfficePersonAdmin(newHolderPerson), - hsOfficeRelationAdmin(NEW)] + hsOfficeRelationAdmin(NEW), + hsOfficePersonAdmin(newHolderPerson)] ); perform createRoleWithGrants( hsOfficeRelationTenant(NEW), permissions => array['SELECT'], incomingSuperRoles => array[ - hsOfficeContactAdmin(newContact), hsOfficeRelationAgent(NEW), + hsOfficeContactAdmin(newContact), hsOfficePersonAdmin(newHolderPerson)], outgoingSubRoles => array[ - hsOfficeContactReferrer(newContact), hsOfficePersonReferrer(newAnchorPerson), - hsOfficePersonReferrer(newHolderPerson)] + hsOfficePersonReferrer(newHolderPerson), + hsOfficeContactReferrer(newContact)] ); call leaveTriggerForObjectUuid(NEW.uuid); diff --git a/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql b/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql index 683a8892..1db004d0 100644 --- a/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql +++ b/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql @@ -36,10 +36,10 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); - SELECT * FROM hs_office_relation AS r WHERE r.uuid = NEW.partnerRelUuid INTO newPartnerRel; + SELECT * FROM hs_office_relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel; assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid); - SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = NEW.detailsUuid INTO newPartnerDetails; + SELECT * FROM hs_office_partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails; assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid); call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationAdmin(newPartnerRel)); @@ -95,16 +95,16 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); - SELECT * FROM hs_office_relation AS r WHERE r.uuid = OLD.partnerRelUuid INTO oldPartnerRel; + SELECT * FROM hs_office_relation WHERE uuid = OLD.partnerRelUuid INTO oldPartnerRel; assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRelUuid = %s', OLD.partnerRelUuid); - SELECT * FROM hs_office_relation AS r WHERE r.uuid = NEW.partnerRelUuid INTO newPartnerRel; + SELECT * FROM hs_office_relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel; assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid); - SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = OLD.detailsUuid INTO oldPartnerDetails; + SELECT * FROM hs_office_partner_details WHERE uuid = OLD.detailsUuid INTO oldPartnerDetails; assert oldPartnerDetails.uuid is not null, format('oldPartnerDetails must not be null for OLD.detailsUuid = %s', OLD.detailsUuid); - SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = NEW.detailsUuid INTO newPartnerDetails; + SELECT * FROM hs_office_partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails; assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid); diff --git a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql index ac4e9fee..b1a3fbd0 100644 --- a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql +++ b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql @@ -57,17 +57,17 @@ begin hsOfficeSepaMandateAgent(NEW), incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)], outgoingSubRoles => array[ - hsOfficeRelationAgent(newDebitorRel), - hsOfficeBankAccountReferrer(newBankAccount)] + hsOfficeBankAccountReferrer(newBankAccount), + hsOfficeRelationAgent(newDebitorRel)] ); perform createRoleWithGrants( hsOfficeSepaMandateReferrer(NEW), permissions => array['SELECT'], incomingSuperRoles => array[ - hsOfficeSepaMandateAgent(NEW), + hsOfficeBankAccountAdmin(newBankAccount), hsOfficeRelationAgent(newDebitorRel), - hsOfficeBankAccountAdmin(newBankAccount)], + hsOfficeSepaMandateAgent(NEW)], outgoingSubRoles => array[hsOfficeRelationTenant(newDebitorRel)] ); diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql b/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql index 7f6afc13..084bdf85 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql @@ -37,21 +37,12 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); - SELECT * - FROM hs_office_relation AS partnerRel - WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid - INTO newPartnerRel; + SELECT * FROM hs_office_relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel; - SELECT * - FROM hs_office_relation AS r - WHERE r.type = 'DEBITOR' AND r.holderUuid = NEW.debitorRelUuid - INTO newDebitorRel; + SELECT * FROM hs_office_relation WHERE uuid = NEW.debitorRelUuid INTO newDebitorRel; assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid); - SELECT * - FROM hs_office_relation AS r - WHERE r.type = 'DEBITOR' AND r.holderUuid = NEW.debitorRelUuid - INTO newRefundBankAccount; + SELECT * FROM hs_office_bankaccount WHERE uuid = NEW.refundBankAccountUuid INTO newRefundBankAccount; call grantRoleToRole(hsOfficeBankAccountReferrer(newRefundBankAccount), hsOfficeRelationAgent(newDebitorRel)); call grantRoleToRole(hsOfficeRelationAdmin(newDebitorRel), hsOfficeRelationAdmin(newPartnerRel)); -- 2.39.5 From 66af0def5b49ca798f44c41913fe7634b59c324c Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 25 Mar 2024 09:13:37 +0100 Subject: [PATCH 15/17] minor fixes --- .../hsadminng/test/dom/TestDomainEntity.java | 4 +-- .../db/changelog/133-test-domain-rbac.sql | 35 ++++++++++++++++--- .../223-hs-office-relation-rbac-generated.sql | 8 ++--- ...3-hs-office-sepamandate-rbac-generated.sql | 2 +- .../TestCustomerControllerAcceptanceTest.java | 2 +- 5 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java b/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java index 7db03c7e..70626f89 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/dom/TestDomainEntity.java @@ -14,7 +14,7 @@ import java.io.IOException; import java.util.UUID; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; @@ -51,7 +51,7 @@ public class TestDomainEntity implements HasUuid { .importEntityAlias("package", TestPackageEntity.class, dependsOnColumn("packageUuid"), directlyFetchedByDependsOnColumn(), - NULLABLE) + NOT_NULL) .toRole("package", ADMIN).grantPermission(INSERT) .createRole(OWNER, (with) -> { diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index 3b41dcbf..e87adb9c 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -36,6 +36,8 @@ begin call enterTriggerForObjectUuid(NEW.uuid); SELECT * FROM test_package WHERE uuid = NEW.packageUuid INTO newPackage; + assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid); + perform createRoleWithGrants( testDomainOwner(NEW), @@ -87,12 +89,37 @@ create or replace procedure updateRbacRulesForTestDomain( NEW test_domain ) language plpgsql as $$ -begin - if NEW.packageUuid is distinct from OLD.packageUuid then - delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid; - call buildRbacSystemForTestDomain(NEW); +declare + oldPackage test_package; + newPackage test_package; + +begin + call enterTriggerForObjectUuid(NEW.uuid); + + SELECT * FROM test_package WHERE uuid = OLD.packageUuid INTO oldPackage; + assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s', OLD.packageUuid); + + SELECT * FROM test_package WHERE uuid = NEW.packageUuid INTO newPackage; + assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid); + + + if NEW.packageUuid <> OLD.packageUuid then + + call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage)); + + call revokeRoleFromRole(testDomainOwner(OLD), testPackageAdmin(oldPackage)); + call grantRoleToRole(testDomainOwner(NEW), testPackageAdmin(newPackage)); + + call revokeRoleFromRole(testPackageTenant(oldPackage), testDomainOwner(OLD)); + call grantRoleToRole(testPackageTenant(newPackage), testDomainOwner(NEW)); + + call revokeRoleFromRole(testPackageTenant(oldPackage), testDomainAdmin(OLD)); + call grantRoleToRole(testPackageTenant(newPackage), testDomainAdmin(NEW)); + end if; + + call leaveTriggerForObjectUuid(NEW.uuid); end; $$; /* diff --git a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql index 0b918ccc..1b72bc0a 100644 --- a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql +++ b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql @@ -54,8 +54,8 @@ begin hsOfficeRelationAdmin(NEW), permissions => array['UPDATE'], incomingSuperRoles => array[ - hsOfficeRelationOwner(NEW), - hsOfficePersonAdmin(newAnchorPerson)] + hsOfficePersonAdmin(newAnchorPerson), + hsOfficeRelationOwner(NEW)] ); perform createRoleWithGrants( @@ -74,8 +74,8 @@ begin hsOfficePersonAdmin(newHolderPerson)], outgoingSubRoles => array[ hsOfficePersonReferrer(newAnchorPerson), - hsOfficePersonReferrer(newHolderPerson), - hsOfficeContactReferrer(newContact)] + hsOfficeContactReferrer(newContact), + hsOfficePersonReferrer(newHolderPerson)] ); call leaveTriggerForObjectUuid(NEW.uuid); diff --git a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql index b1a3fbd0..5edf9454 100644 --- a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql +++ b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql @@ -65,8 +65,8 @@ begin hsOfficeSepaMandateReferrer(NEW), permissions => array['SELECT'], incomingSuperRoles => array[ - hsOfficeBankAccountAdmin(newBankAccount), hsOfficeRelationAgent(newDebitorRel), + hsOfficeBankAccountAdmin(newBankAccount), hsOfficeSepaMandateAgent(NEW)], outgoingSubRoles => array[hsOfficeRelationTenant(newDebitorRel)] ); diff --git a/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerControllerAcceptanceTest.java index 942351c0..e9e1d47c 100644 --- a/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerControllerAcceptanceTest.java @@ -204,7 +204,7 @@ class TestCustomerControllerAcceptanceTest { .statusCode(403) .contentType(ContentType.JSON) .statusCode(403) - .body("message", containsString("insert into test_customer not allowed for current subjects {customer-admin@yyy.example.com}")); + .body("message", containsString("ERROR: [403] insert into test_customer not allowed for current subjects {customer-admin@yyy.example.com}")); // @formatter:on // finally, the new customer was not created -- 2.39.5 From 9fdeb047ee1774cae6fea0acba2117cb723f14ef Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 25 Mar 2024 11:46:39 +0100 Subject: [PATCH 16/17] avoid nested subselect for insert permission check --- .../partner/HsOfficePartnerDetailsEntity.java | 2 +- .../rbac/rbacdef/InsertTriggerGenerator.java | 24 +++++++++---------- .../hsadminng/rbac/rbacdef/RbacView.java | 24 ++++++++++++++----- .../db/changelog/123-test-package-rbac.sql | 18 ++++---------- .../db/changelog/133-test-domain-rbac.sql | 18 ++++---------- .../223-hs-office-relation-rbac-generated.sql | 12 +++++----- ...-office-partner-details-rbac-generated.sql | 2 +- ...3-hs-office-sepamandate-rbac-generated.sql | 8 +++---- 8 files changed, 51 insertions(+), 57 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java index 31ff56ab..acf39249 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java @@ -88,7 +88,7 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable { .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, fetchedBySql(""" - SELECT partnerRel.* + SELECT ${columns} FROM hs_office_relation AS partnerRel JOIN hs_office_partner AS partner ON partner.detailsUuid = ${ref}.uuid diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index 000988fa..85810d1a 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -114,8 +114,7 @@ public class InsertTriggerGenerator { } } } else { - final var superRoleEntityAlias = g.getSuperRoleDef().getEntityAlias(); - if (superRoleEntityAlias.fetchSql().part == RbacView.SQL.Part.AUTO_FETCH) { + if (g.getSuperRoleDef().getEntityAlias().isFetchedByDirectForeignKey()) { generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g); } else { generateInsertPermissionTriggerAllowByRoleOfIndirectForeignKey(plPgSql, g); @@ -164,24 +163,23 @@ public class InsertTriggerGenerator { An indirect role is a role FIXME. */ - create or replace function ${rawSubTable}_insert_permission_missing_tf() + create or replace function ${rawSubTable}_insert_permission_check_tf() returns trigger language plpgsql as $$ + declare + superRoleObjectUuid uuid; begin - if ( not hasInsertPermission( - ( SELECT ${varName}.uuid FROM + """, - with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()), - with("varName", g.getSuperRoleDef().getEntityAlias().aliasName())); - plPgSql.indented(3, () -> { + with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); + plPgSql.indented(2, () -> { plPgSql.writeLn( - "(" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ") AS ${varName}", - with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()), + "superRoleObjectUuid := (" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ");", + with("columns", g.getSuperRoleDef().getEntityAlias().aliasName() + ".uuid"), with("ref", NEW.name())); }); plPgSql.writeLn(""" - - ), 'INSERT', '${rawSubTable}') ) then + if ( not hasInsertPermission(superRoleObjectUuid, 'INSERT', '${rawSubTable}') ) then raise exception '[403] insert into ${rawSubTable} not allowed for current subjects % (%)', currentSubjects(), currentSubjectsUuids(); @@ -192,7 +190,7 @@ public class InsertTriggerGenerator { create trigger ${rawSubTable}_insert_permission_check_tg before insert on ${rawSubTable} for each row - execute procedure ${rawSubTable}_insert_permission_missing_tf(); + execute procedure ${rawSubTable}_insert_permission_check_tf(); """, with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index 99acade9..d6fe2ab3 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -34,6 +34,7 @@ import static java.util.Arrays.stream; import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.Part.AUTO_FETCH; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static org.apache.commons.lang3.StringUtils.uncapitalize; @@ -839,8 +840,8 @@ public class RbacView { }; } - public boolean hasFetchSql() { - return fetchSql != null; + boolean isFetchedByDirectForeignKey() { + return fetchSql != null && fetchSql.part == AUTO_FETCH; } private String withoutEntitySuffix(final String simpleEntityName) { @@ -909,14 +910,25 @@ public class RbacView { /** * 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`. + * using the reference `${ref}` of the root entity and `${columns}` for the projection. + * + *

The query must define the entity alias name of the fetched table + * as its alias for, so it can be used in the generated projection (the columns between + * `SELECT` and `FROM`.

+ * + *

`${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`.

+ * + *

`${columns}` is going to be replaced by the columns which are needed for the query, + * e.g. `*` or `uuid`.

* * @param sql an SQL SELECT expression (not ending with ';) * @return the wrapped SQL expression */ public static SQL fetchedBySql(final String sql) { + if ( !sql.startsWith("SELECT ${columns}") ) { + throw new IllegalArgumentException("SQL SELECT expression must start with 'SELECT ${columns}', but is: " + sql); + } validateExpression(sql); return new SQL(sql, Part.SQL_QUERY); } @@ -929,7 +941,7 @@ public class RbacView { * @return the wrapped SQL definition object */ public static SQL directlyFetchedByDependsOnColumn() { - return new SQL(null, Part.AUTO_FETCH); + return new SQL(null, AUTO_FETCH); } /** diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index dc4d042f..dc0cb457 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -189,30 +189,22 @@ execute procedure test_package_test_customer_insert_tf(); /** Checks if the user or assumed roles are allowed to insert a row to test_package, - where the check is performed by an indirect role. + where the check is performed by a direct role. - An indirect role is a role FIXME. + A direct role is a role depending on a foreign key directly available in the NEW row. */ create or replace function test_package_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - if ( not hasInsertPermission( - ( SELECT customer.uuid FROM - - (SELECT * FROM test_customer WHERE uuid = NEW.customerUuid) AS customer - - ), 'INSERT', 'test_package') ) then - raise exception - '[403] insert into test_package not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); - end if; - return NEW; + raise exception '[403] insert into test_package not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); end; $$; create trigger test_package_insert_permission_check_tg before insert on test_package for each row + when ( not hasInsertPermission(NEW.customerUuid, 'INSERT', 'test_package') ) execute procedure test_package_insert_permission_missing_tf(); --// diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index e87adb9c..bb8c515d 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -188,30 +188,22 @@ execute procedure test_domain_test_package_insert_tf(); /** Checks if the user or assumed roles are allowed to insert a row to test_domain, - where the check is performed by an indirect role. + where the check is performed by a direct role. - An indirect role is a role FIXME. + A direct role is a role depending on a foreign key directly available in the NEW row. */ create or replace function test_domain_insert_permission_missing_tf() returns trigger language plpgsql as $$ begin - if ( not hasInsertPermission( - ( SELECT package.uuid FROM - - (SELECT * FROM test_package WHERE uuid = NEW.packageUuid) AS package - - ), 'INSERT', 'test_domain') ) then - raise exception - '[403] insert into test_domain not allowed for current subjects % (%)', - currentSubjects(), currentSubjectsUuids(); - end if; - return NEW; + raise exception '[403] insert into test_domain not allowed for current subjects % (%)', + currentSubjects(), currentSubjectsUuids(); end; $$; create trigger test_domain_insert_permission_check_tg before insert on test_domain for each row + when ( not hasInsertPermission(NEW.packageUuid, 'INSERT', 'test_domain') ) execute procedure test_domain_insert_permission_missing_tf(); --// diff --git a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql index 1b72bc0a..766722be 100644 --- a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql +++ b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql @@ -54,8 +54,8 @@ begin hsOfficeRelationAdmin(NEW), permissions => array['UPDATE'], incomingSuperRoles => array[ - hsOfficePersonAdmin(newAnchorPerson), - hsOfficeRelationOwner(NEW)] + hsOfficeRelationOwner(NEW), + hsOfficePersonAdmin(newAnchorPerson)] ); perform createRoleWithGrants( @@ -69,13 +69,13 @@ begin hsOfficeRelationTenant(NEW), permissions => array['SELECT'], incomingSuperRoles => array[ - hsOfficeRelationAgent(NEW), hsOfficeContactAdmin(newContact), - hsOfficePersonAdmin(newHolderPerson)], + hsOfficePersonAdmin(newHolderPerson), + hsOfficeRelationAgent(NEW)], outgoingSubRoles => array[ - hsOfficePersonReferrer(newAnchorPerson), + hsOfficePersonReferrer(newHolderPerson), hsOfficeContactReferrer(newContact), - hsOfficePersonReferrer(newHolderPerson)] + hsOfficePersonReferrer(newAnchorPerson)] ); call leaveTriggerForObjectUuid(NEW.uuid); diff --git a/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql b/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql index 04fc7be7..e3846a95 100644 --- a/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql +++ b/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql @@ -35,7 +35,7 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); - SELECT partnerRel.* + SELECT ${columns} FROM hs_office_relation AS partnerRel JOIN hs_office_partner AS partner ON partner.detailsUuid = NEW.uuid diff --git a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql index 5edf9454..ac4e9fee 100644 --- a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql +++ b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql @@ -57,17 +57,17 @@ begin hsOfficeSepaMandateAgent(NEW), incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)], outgoingSubRoles => array[ - hsOfficeBankAccountReferrer(newBankAccount), - hsOfficeRelationAgent(newDebitorRel)] + hsOfficeRelationAgent(newDebitorRel), + hsOfficeBankAccountReferrer(newBankAccount)] ); perform createRoleWithGrants( hsOfficeSepaMandateReferrer(NEW), permissions => array['SELECT'], incomingSuperRoles => array[ + hsOfficeSepaMandateAgent(NEW), hsOfficeRelationAgent(newDebitorRel), - hsOfficeBankAccountAdmin(newBankAccount), - hsOfficeSepaMandateAgent(NEW)], + hsOfficeBankAccountAdmin(newBankAccount)], outgoingSubRoles => array[hsOfficeRelationTenant(newDebitorRel)] ); -- 2.39.5 From 260de22fd676834f0330a5b75471bf0430e3c39b Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 26 Mar 2024 10:01:25 +0100 Subject: [PATCH 17/17] fixes regarding code-review --- .../rbac/rbacdef/InsertTriggerGenerator.java | 11 ++++++--- .../rbacdef/RbacIdentityViewGenerator.java | 12 ++++++---- .../rbacdef/RbacRestrictedViewGenerator.java | 6 ++--- .../RolesGrantsAndPermissionsGenerator.java | 16 ++++++++----- .../hsadminng/rbac/rbacdef/StringWriter.java | 4 ++-- .../rbacgrant/RbacGrantsDiagramService.java | 4 ---- .../resources/db/changelog/050-rbac-base.sql | 2 +- .../db/changelog/057-rbac-role-builder.sql | 2 +- .../db/changelog/113-test-customer-rbac.sql | 5 ++-- .../db/changelog/123-test-package-rbac.sql | 7 +++--- .../db/changelog/133-test-domain-rbac.sql | 7 +++--- .../203-hs-office-contact-rbac-generated.sql | 5 ++-- .../213-hs-office-person-rbac-generated.sql | 5 ++-- .../223-hs-office-relation-rbac-generated.sql | 22 ++++++++--------- .../233-hs-office-partner-rbac-generated.sql | 12 +++++----- ...-office-partner-details-rbac-generated.sql | 14 +++++------ ...3-hs-office-bankaccount-rbac-generated.sql | 5 ++-- ...3-hs-office-sepamandate-rbac-generated.sql | 13 +++++----- .../273-hs-office-debitor-rbac-generated.sql | 24 +++++++++---------- ...iceMembershipControllerAcceptanceTest.java | 2 +- 20 files changed, 94 insertions(+), 84 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index 85810d1a..2e0a4a2f 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -161,23 +161,28 @@ public class InsertTriggerGenerator { Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}, where the check is performed by an indirect role. - An indirect role is a role FIXME. + An indirect role is a role which depends on an object uuid which is not a direct foreign key + of the source entity, but needs to be fetched via joined tables. */ create or replace function ${rawSubTable}_insert_permission_check_tf() returns trigger language plpgsql as $$ + declare superRoleObjectUuid uuid; + begin - """, with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName())); + plPgSql.chopEmptyLines(); plPgSql.indented(2, () -> { plPgSql.writeLn( - "superRoleObjectUuid := (" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ");", + "superRoleObjectUuid := (" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ");\n" + + "assert superRoleObjectUuid is not null, 'superRoleObjectUuid must not be null';", with("columns", g.getSuperRoleDef().getEntityAlias().aliasName() + ".uuid"), with("ref", NEW.name())); }); + plPgSql.writeLn(); plPgSql.writeLn(""" if ( not hasInsertPermission(superRoleObjectUuid, 'INSERT', '${rawSubTable}') ) then raise exception diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java index 7e3c6a3b..066acba2 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacIdentityViewGenerator.java @@ -26,18 +26,20 @@ public class RbacIdentityViewGenerator { plPgSql.writeLn( switch (rbacDef.getIdentityViewSqlQuery().part) { case SQL_PROJECTION -> """ - call generateRbacIdentityViewFromProjection('${rawTableName}', $idName$ - ${identityViewSqlPart} + call generateRbacIdentityViewFromProjection('${rawTableName}', + $idName$ + ${identityViewSqlPart} $idName$); """; case SQL_QUERY -> """ - call generateRbacIdentityViewFromQuery('${rawTableName}', $idName$ - ${identityViewSqlPart} + call generateRbacIdentityViewFromQuery('${rawTableName}', + $idName$ + ${identityViewSqlPart} $idName$); """; default -> throw new IllegalStateException("illegal SQL part given"); }, - with("identityViewSqlPart", rbacDef.getIdentityViewSqlQuery().sql), + with("identityViewSqlPart", StringWriter.indented(2, rbacDef.getIdentityViewSqlQuery().sql)), with("rawTableName", rawTableName)); plPgSql.writeLn("--//"); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java index f0bd50a0..b5757865 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java @@ -32,10 +32,10 @@ public class RbacRestrictedViewGenerator { """, with("liquibaseTagPrefix", liquibaseTagPrefix), - with("orderBy", indented(rbacDef.getOrderBySqlExpression().sql, 2)), - with("updates", indented(rbacDef.getUpdatableColumns().stream() + with("orderBy", indented(2, rbacDef.getOrderBySqlExpression().sql)), + with("updates", indented(2, rbacDef.getUpdatableColumns().stream() .map(c -> c + " = new." + c) - .collect(joining(",\n")), 2)), + .collect(joining(",\n")))), with("rawTableName", rawTableName)); } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java index 87a730a3..719c8ab4 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; import java.util.stream.Stream; +import static java.util.Optional.ofNullable; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toSet; import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW; @@ -233,6 +234,7 @@ class RolesGrantsAndPermissionsGenerator { final PostgresTriggerReference old) { plPgSql.writeLn( ea.fetchSql().sql + " INTO " + entityRefVar(old, ea) + ";", + with("columns", ea.aliasName() + ".*"), with("ref", old.name())); if (ea.nullable() == RbacView.Nullable.NOT_NULL) { plPgSql.writeLn( @@ -244,14 +246,11 @@ class RolesGrantsAndPermissionsGenerator { } } - private boolean isUpdatable(final RbacView.Column c) { - return rbacDef.getUpdatableColumns().contains(c); - } - private void updateGrantsDependingOn(final StringWriter plPgSql, final String columnName) { rbacDef.getGrantDefs().stream() .filter(RbacView.RbacGrantDefinition::isToCreate) .filter(g -> g.dependsOnColumn(columnName)) + .filter(g -> !isInsertPermissionGrant(g)) .forEach(g -> { plPgSql.ensureSingleEmptyLine(); plPgSql.writeLn(generateRevoke(g)); @@ -260,6 +259,11 @@ class RolesGrantsAndPermissionsGenerator { }); } + private static Boolean isInsertPermissionGrant(final RbacView.RbacGrantDefinition g) { + final var isInsertPermissionGrant = ofNullable(g.getPermDef()).map(RbacPermissionDefinition::getPermission).map(p -> p == INSERT).orElse(false); + return isInsertPermissionGrant; + } + private void generateGrants(final StringWriter plPgSql, final RbacView.RbacGrantDefinition.GrantType grantType) { plPgSql.ensureSingleEmptyLine(); rbacGrants.stream() @@ -406,7 +410,7 @@ class RolesGrantsAndPermissionsGenerator { if (!incomingGrants.isEmpty()) { final var arrayElements = incomingGrants.stream() .map(g -> toPlPgSqlReference(NEW, g.getSuperRoleDef(), g.isAssumed())) - .toList(); + .sorted().toList(); plPgSql.indented(() -> plPgSql.writeLn("incomingSuperRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n")); rbacGrants.removeAll(incomingGrants); @@ -418,7 +422,7 @@ class RolesGrantsAndPermissionsGenerator { if (!outgoingGrants.isEmpty()) { final var arrayElements = outgoingGrants.stream() .map(g -> toPlPgSqlReference(NEW, g.getSubRoleDef(), g.isAssumed())) - .toList(); + .sorted().toList(); plPgSql.indented(() -> plPgSql.writeLn("outgoingSubRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n")); rbacGrants.removeAll(outgoingGrants); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java index 0376236c..fe4b0548 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java @@ -82,7 +82,7 @@ public class StringWriter { return string.toString(); } - public static String indented(final String text, final int indentLevel) { + public static String indented(final int indentLevel, final String text) { final var indentation = StringUtils.repeat(" ", indentLevel); final var indented = stream(text.split("\n")) .map(line -> line.trim().isBlank() ? "" : indentation + line) @@ -94,7 +94,7 @@ public class StringWriter { if ( indentLevel == 0) { return text; } - return indented(text, indentLevel); + return indented(indentLevel, text); } record VarDef(String name, String value){} diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java index e643fe8d..cf05496a 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java @@ -71,10 +71,6 @@ public class RbacGrantsDiagramService { private void traverseGrantsTo(final Set graph, final UUID refUuid, final EnumSet includes) { final var grants = rawGrantRepo.findByAscendingUuid(refUuid); grants.forEach(g -> { - if ( g.getDescendantIdName() == null ) { - // FIXME: what's that? - return; - } if (!includes.contains(PERMISSIONS) && g.getDescendantIdName().startsWith("perm ")) { return; } diff --git a/src/main/resources/db/changelog/050-rbac-base.sql b/src/main/resources/db/changelog/050-rbac-base.sql index 735f1932..ca560bf9 100644 --- a/src/main/resources/db/changelog/050-rbac-base.sql +++ b/src/main/resources/db/changelog/050-rbac-base.sql @@ -691,7 +691,7 @@ declare superRoleId uuid; subRoleId uuid; begin - -- FIXME: maybe separate method grantRoleToRoleIfNotNull(...)? + -- TODO: maybe separate method grantRoleToRoleIfNotNull(...) for NULLABLE references if superRole.objectUuid is null or subRole.objectuuid is null then return; end if; diff --git a/src/main/resources/db/changelog/057-rbac-role-builder.sql b/src/main/resources/db/changelog/057-rbac-role-builder.sql index b3ddbecd..57a97a2f 100644 --- a/src/main/resources/db/changelog/057-rbac-role-builder.sql +++ b/src/main/resources/db/changelog/057-rbac-role-builder.sql @@ -60,7 +60,7 @@ begin if cardinality(userUuids) > 0 then -- direct grants to users need a grantedByRole which can revoke the grant if grantedByRole is null then - userGrantsByRoleUuid := roleUuid; -- FIXME: or do we want to require an explicit userGrantsByRoleUuid? + userGrantsByRoleUuid := roleUuid; -- TODO: or do we want to require an explicit userGrantsByRoleUuid? else userGrantsByRoleUuid := getRoleId(grantedByRole); end if; diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index b98fc949..874cbc9a 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -143,8 +143,9 @@ create trigger test_customer_insert_permission_check_tg --changeset test-customer-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacIdentityViewFromProjection('test_customer', $idName$ - prefix +call generateRbacIdentityViewFromProjection('test_customer', + $idName$ + prefix $idName$); --// diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index dc0cb457..070d3fcc 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -110,8 +110,6 @@ begin if NEW.customerUuid <> OLD.customerUuid then - call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testCustomerAdmin(oldCustomer)); - call revokeRoleFromRole(testPackageOwner(OLD), testCustomerAdmin(oldCustomer)); call grantRoleToRole(testPackageOwner(NEW), testCustomerAdmin(newCustomer)); @@ -212,8 +210,9 @@ create trigger test_package_insert_permission_check_tg --changeset test-package-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacIdentityViewFromProjection('test_package', $idName$ - name +call generateRbacIdentityViewFromProjection('test_package', + $idName$ + name $idName$); --// diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index bb8c515d..bef72697 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -106,8 +106,6 @@ begin if NEW.packageUuid <> OLD.packageUuid then - call revokePermissionFromRole(getPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage)); - call revokeRoleFromRole(testDomainOwner(OLD), testPackageAdmin(oldPackage)); call grantRoleToRole(testDomainOwner(NEW), testPackageAdmin(newPackage)); @@ -211,8 +209,9 @@ create trigger test_domain_insert_permission_check_tg --changeset test-domain-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacIdentityViewFromProjection('test_domain', $idName$ - name +call generateRbacIdentityViewFromProjection('test_domain', + $idName$ + name $idName$); --// diff --git a/src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.sql b/src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.sql index ac0b5114..136dad87 100644 --- a/src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.sql +++ b/src/main/resources/db/changelog/203-hs-office-contact-rbac-generated.sql @@ -103,8 +103,9 @@ create trigger hs_office_contact_insert_permission_check_tg --changeset hs-office-contact-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacIdentityViewFromProjection('hs_office_contact', $idName$ - label +call generateRbacIdentityViewFromProjection('hs_office_contact', + $idName$ + label $idName$); --// diff --git a/src/main/resources/db/changelog/213-hs-office-person-rbac-generated.sql b/src/main/resources/db/changelog/213-hs-office-person-rbac-generated.sql index afd7ed66..f99c2a46 100644 --- a/src/main/resources/db/changelog/213-hs-office-person-rbac-generated.sql +++ b/src/main/resources/db/changelog/213-hs-office-person-rbac-generated.sql @@ -103,8 +103,9 @@ create trigger hs_office_person_insert_permission_check_tg --changeset hs-office-person-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacIdentityViewFromProjection('hs_office_person', $idName$ - concat(tradeName, familyName, givenName) +call generateRbacIdentityViewFromProjection('hs_office_person', + $idName$ + concat(tradeName, familyName, givenName) $idName$); --// diff --git a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql index 766722be..5301dc56 100644 --- a/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql +++ b/src/main/resources/db/changelog/223-hs-office-relation-rbac-generated.sql @@ -54,15 +54,15 @@ begin hsOfficeRelationAdmin(NEW), permissions => array['UPDATE'], incomingSuperRoles => array[ - hsOfficeRelationOwner(NEW), - hsOfficePersonAdmin(newAnchorPerson)] + hsOfficePersonAdmin(newAnchorPerson), + hsOfficeRelationOwner(NEW)] ); perform createRoleWithGrants( hsOfficeRelationAgent(NEW), incomingSuperRoles => array[ - hsOfficeRelationAdmin(NEW), - hsOfficePersonAdmin(newHolderPerson)] + hsOfficePersonAdmin(newHolderPerson), + hsOfficeRelationAdmin(NEW)] ); perform createRoleWithGrants( @@ -73,9 +73,9 @@ begin hsOfficePersonAdmin(newHolderPerson), hsOfficeRelationAgent(NEW)], outgoingSubRoles => array[ - hsOfficePersonReferrer(newHolderPerson), hsOfficeContactReferrer(newContact), - hsOfficePersonReferrer(newAnchorPerson)] + hsOfficePersonReferrer(newAnchorPerson), + hsOfficePersonReferrer(newHolderPerson)] ); call leaveTriggerForObjectUuid(NEW.uuid); @@ -169,11 +169,11 @@ create trigger hs_office_relation_insert_permission_check_tg --changeset hs-office-relation-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacIdentityViewFromProjection('hs_office_relation', $idName$ - (select idName from hs_office_person_iv p where p.uuid = anchorUuid) - || '-with-' || target.type || '-' - || (select idName from hs_office_person_iv p where p.uuid = holderUuid) - +call generateRbacIdentityViewFromProjection('hs_office_relation', + $idName$ + (select idName from hs_office_person_iv p where p.uuid = anchorUuid) + || '-with-' || target.type || '-' + || (select idName from hs_office_person_iv p where p.uuid = holderUuid) $idName$); --// diff --git a/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql b/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql index 1db004d0..8b12e95f 100644 --- a/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql +++ b/src/main/resources/db/changelog/233-hs-office-partner-rbac-generated.sql @@ -220,12 +220,12 @@ create trigger hs_office_partner_insert_permission_check_tg --changeset hs-office-partner-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- - call generateRbacIdentityViewFromQuery('hs_office_partner', $idName$ - 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 AS partner - + call generateRbacIdentityViewFromQuery('hs_office_partner', + $idName$ + 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 AS partner $idName$); --// diff --git a/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql b/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql index e3846a95..4fd78a87 100644 --- a/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql +++ b/src/main/resources/db/changelog/234-hs-office-partner-details-rbac-generated.sql @@ -35,7 +35,7 @@ declare begin call enterTriggerForObjectUuid(NEW.uuid); - SELECT ${columns} + SELECT partnerRel.* FROM hs_office_relation AS partnerRel JOIN hs_office_partner AS partner ON partner.detailsUuid = NEW.uuid @@ -133,12 +133,12 @@ create trigger hs_office_partner_details_insert_permission_check_tg --changeset hs-office-partner-details-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- - call generateRbacIdentityViewFromQuery('hs_office_partner_details', $idName$ - SELECT partner_iv.idName || '-details' - FROM hs_office_partner_details AS partnerDetails - JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid - JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid - + call generateRbacIdentityViewFromQuery('hs_office_partner_details', + $idName$ + SELECT partner_iv.idName || '-details' + FROM hs_office_partner_details AS partnerDetails + JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid + JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid $idName$); --// diff --git a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.sql b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.sql index 9d83aad5..6b96fb34 100644 --- a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.sql +++ b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac-generated.sql @@ -103,8 +103,9 @@ create trigger hs_office_bankaccount_insert_permission_check_tg --changeset hs-office-bankaccount-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacIdentityViewFromProjection('hs_office_bankaccount', $idName$ - iban || ':' || holder +call generateRbacIdentityViewFromProjection('hs_office_bankaccount', + $idName$ + iban || ':' || holder $idName$); --// diff --git a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql index ac4e9fee..1e383951 100644 --- a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql +++ b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac-generated.sql @@ -57,17 +57,17 @@ begin hsOfficeSepaMandateAgent(NEW), incomingSuperRoles => array[hsOfficeSepaMandateAdmin(NEW)], outgoingSubRoles => array[ - hsOfficeRelationAgent(newDebitorRel), - hsOfficeBankAccountReferrer(newBankAccount)] + hsOfficeBankAccountReferrer(newBankAccount), + hsOfficeRelationAgent(newDebitorRel)] ); perform createRoleWithGrants( hsOfficeSepaMandateReferrer(NEW), permissions => array['SELECT'], incomingSuperRoles => array[ - hsOfficeSepaMandateAgent(NEW), + hsOfficeBankAccountAdmin(newBankAccount), hsOfficeRelationAgent(newDebitorRel), - hsOfficeBankAccountAdmin(newBankAccount)], + hsOfficeSepaMandateAgent(NEW)], outgoingSubRoles => array[hsOfficeRelationTenant(newDebitorRel)] ); @@ -121,8 +121,9 @@ create trigger hs_office_sepamandate_insert_permission_check_tg --changeset hs-office-sepamandate-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacIdentityViewFromProjection('hs_office_sepamandate', $idName$ - concat(tradeName, familyName, givenName) +call generateRbacIdentityViewFromProjection('hs_office_sepamandate', + $idName$ + concat(tradeName, familyName, givenName) $idName$); --// diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql b/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql index 084bdf85..f827ea67 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac-generated.sql @@ -185,18 +185,18 @@ create trigger hs_office_debitor_insert_permission_check_tg --changeset hs-office-debitor-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- - call generateRbacIdentityViewFromQuery('hs_office_debitor', $idName$ - SELECT debitor.uuid, - 'D-' || (SELECT partner.partnerNumber - FROM hs_office_partner partner - JOIN hs_office_relation partnerRel - ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER' - JOIN hs_office_relation debitorRel - ON debitorRel.anchorUuid = partnerRel.holderUuid AND partnerRel.type = 'DEBITOR' - WHERE debitorRel.uuid = debitor.debitorRelUuid) - || to_char(debitorNumberSuffix, 'fm00') - from hs_office_debitor as debitor - + call generateRbacIdentityViewFromQuery('hs_office_debitor', + $idName$ + SELECT debitor.uuid, + 'D-' || (SELECT partner.partnerNumber + FROM hs_office_partner partner + JOIN hs_office_relation partnerRel + ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER' + JOIN hs_office_relation debitorRel + ON debitorRel.anchorUuid = partnerRel.holderUuid AND partnerRel.type = 'DEBITOR' + WHERE debitorRel.uuid = debitor.debitorRelUuid) + || to_char(debitorNumberSuffix, 'fm00') + from hs_office_debitor as debitor $idName$); --// diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java index 293741b6..7574d8b2 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java @@ -462,7 +462,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") - .header("assumed-roles", "hs_office_partner#FirstGmbH-firstcontact.agent") + .header("assumed-roles", "hs_office_partner#10001:FirstGmbH-firstcontact.admin") .port(port) .when() .delete("http://localhost/api/hs/office/memberships/" + givenMembership.getUuid()) -- 2.39.5