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 21257a24..bae8d498 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java @@ -1,9 +1,10 @@ package net.hostsharing.hsadminng.rbac.rbacdef; -import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; +import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.time.LocalDateTime; @@ -32,15 +33,18 @@ public class RbacViewPostgresGenerator { @Override -public String toString() { + public String toString() { return plPgSql.toString(); } -public static void main(String[] args) throws IOException { + public static void main(String[] args) throws IOException { + final var rbac = HsOfficeRelationshipEntity.rbac(); + final Path outputPath = Paths.get("doc", rbac.getRootEntityAlias().simpleName() + ".sql"); + Files.writeString( + outputPath, + new RbacViewPostgresGenerator(rbac).toString(), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - Files.writeString( - Paths.get("doc", "hsOfficeBankAccount.sql"), - new RbacViewPostgresGenerator(HsOfficeBankAccountEntity.rbac()).toString(), - StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + System.out.println(outputPath.toAbsolutePath()); } } 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 27e84a3b..ccb2988f 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java @@ -5,9 +5,8 @@ import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition; import jakarta.persistence.Table; import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; -import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.*; import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NEW; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; @@ -26,13 +25,15 @@ class RolesGrantsAndPermissionsGenerator { RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) { this.rbacDef = rbacDef; - this.rbacGrants.addAll(rbacDef.getGrantDefs()); + this.rbacGrants.addAll(rbacDef.getGrantDefs().stream() + .filter(RbacView.RbacGrantDefinition::isToCreate) + .collect(toSet())); this.liquibaseTagPrefix = liquibaseTagPrefix; entityClass = rbacDef.getRootEntityAlias().entityClass(); simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName(); simpleEntityName = capitalize(simpleEntityVarName); - rawTableName = withoutRvSuffix(entityClass.getAnnotation(Table.class).name()); + rawTableName = getRawTableName(entityClass); } void generateTo(final StringWriter plPgSql) { @@ -45,10 +46,10 @@ class RolesGrantsAndPermissionsGenerator { private void generateHeader(final StringWriter plPgSql) { plPgSql.writeLn(""" -- ============================================================================ - --changeset %{liquibaseTagPrefix}-rbac-CREATE-ROLES-GRANTS-PERMISSIONS:1 endDelimiter:--// + --changeset ${liquibaseTagPrefix}-rbac-CREATE-ROLES-GRANTS-PERMISSIONS:1 endDelimiter:--// -- ---------------------------------------------------------------------------- """ - .replace("%{liquibaseTagPrefix}", liquibaseTagPrefix)); + .replace("${liquibaseTagPrefix}", liquibaseTagPrefix)); } private void generateTriggerFunction(final StringWriter plPgSql) { @@ -57,28 +58,53 @@ class RolesGrantsAndPermissionsGenerator { Creates the roles, grants and permission for the AFTER INSERT TRIGGER. */ - create or replace function createRbacRolesFor%{simpleEntityName}() + create or replace function createRbacRolesFor${simpleEntityName}() returns trigger language plpgsql strict as $$ + declare + """ + .replace("${simpleEntityName}", simpleEntityName)); + + plPgSql.indented(() -> { + rbacDef.getEntityAliases().values().stream() + .filter((ea) -> !rbacDef.isRootEntityAlias(ea)) + .filter((ea) -> ea.fetchSql() != null) + .forEach((ea) -> { + plPgSql.writeLn( entityRefVar(NEW, ea) + " " + getRawTableName(ea.entityClass()) + ";"); + }); + }); + + plPgSql.writeLn(""" begin if TG_OP <> 'INSERT' then raise exception 'invalid usage of TRIGGER AFTER INSERT function'; end if; - """ - .replace("%{simpleEntityName}", simpleEntityName)); + """); plPgSql.indented(() -> { + + plPgSql.writeLn(); + rbacDef.getEntityAliases().values().stream() + .filter((ea) -> !rbacDef.isRootEntityAlias(ea)) + .filter((ea) -> ea.fetchSql() != null) + .forEach((ea) -> { + plPgSql.writeLn( ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";"); + }); + createRolesWithGrantsSql(plPgSql, OWNER); createRolesWithGrantsSql(plPgSql, ADMIN); createRolesWithGrantsSql(plPgSql, AGENT); createRolesWithGrantsSql(plPgSql, TENANT); createRolesWithGrantsSql(plPgSql, REFERRER); - if (!rbacGrants.isEmpty()) { - throw new IllegalStateException("unprocessed grants: " + rbacGrants); - // rbacGrants.forEach(g -> plPgSql.writeLn("-- unprocessed grant: " + g)); - } + plPgSql.writeLn(); + rbacGrants + .forEach(g -> plPgSql.writeLn( + "call grantRoleToRole(${subRoleRef}, ${superRoleRef});" + .replace("${subRoleRef}", roleRef(NEW, g.getSubRoleDef()) ) + .replace("${superRoleRef}", roleRef(NEW, g.getSuperRoleDef()) )) + ); plPgSql.writeLn("return NEW;"); }); @@ -87,6 +113,24 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.writeLn(); } + private String getRawTableName(final Class entityClass) { + return withoutRvSuffix(entityClass.getAnnotation(Table.class).name()); + } + + private String roleRef(final PostgresTriggerReference rootRefVar, final RbacView.RbacRoleDefinition roleDef) { + if ( roleDef.getEntityAlias().isGlobal()) { + return "globalAdmin()"; + } + final String entityRefVar = entityRefVar(rootRefVar, roleDef.getEntityAlias()); + return roleDef.getEntityAlias().simpleName() + capitalize(roleDef.getRole().roleName()) + + "(" + entityRefVar + ")"; + } + + private static String entityRefVar( + final PostgresTriggerReference rootRefVar, + final RbacView.EntityAlias entityAlias) { + return rootRefVar.name().toLowerCase() + capitalize(entityAlias.aliasName()); + } private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacView.Role role) { @@ -100,9 +144,9 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.writeLn(); plPgSql.writeLn("perform createRoleWithGrants("); plPgSql.indented( () -> { - plPgSql.writeLn("%{simpleVarName)%{roleSuffix}(NEW)," - .replace("%{simpleVarName)", simpleEntityVarName) - .replace("%{roleSuffix}", capitalize(role.roleName()))); + plPgSql.writeLn("${simpleVarName)${roleSuffix}(NEW)," + .replace("${simpleVarName)", simpleEntityVarName) + .replace("${roleSuffix}", capitalize(role.roleName()))); final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role); if (!permissionGrantsForRole.isEmpty()) { @@ -161,21 +205,21 @@ class RolesGrantsAndPermissionsGenerator { final var roleDef = rbacDef.findRbacRole(entityAlias, role); return rbacGrants.stream() .filter(g -> g.grantType() == ROLE_TO_PERM && g.getSuperRoleDef()==roleDef ) - .collect(Collectors.toSet()); + .collect(toSet()); } private Set findGrantsToUserForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) { final var roleDef = rbacDef.findRbacRole(entityAlias, role); return rbacGrants.stream() .filter(g -> g.grantType() == USER_TO_ROLE && g.getSubRoleDef() == roleDef ) - .collect(Collectors.toSet()); + .collect(toSet()); } private Set findIncomingSuperRolesForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) { final var roleDef = rbacDef.findRbacRole(entityAlias, role); return rbacGrants.stream() .filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSubRoleDef()==roleDef ) - .collect(Collectors.toSet()); + .collect(toSet()); } private Set findOutgoingSuperRolesForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) { @@ -183,24 +227,24 @@ class RolesGrantsAndPermissionsGenerator { return rbacGrants.stream() .filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSuperRoleDef()==roleDef ) .filter(g -> g.getSubRoleDef().getEntityAlias() != entityAlias) - .collect(Collectors.toSet()); + .collect(toSet()); } private void generageInsertTrigger(final StringWriter plPgSql) { plPgSql.writeLn(""" /* - An AFTER INSERT TRIGGER which creates the role structure for a new %{simpleEntityName} + An AFTER INSERT TRIGGER which creates the role structure for a new ${simpleEntityName} */ - create trigger createRbacRolesFor%{simpleEntityName}_Trigger + create trigger createRbacRolesFor${simpleEntityName}_Trigger after insert - on %{rawTableName} + on ${rawTableName} for each row - execute procedure createRbacRolesFor%{simpleEntityName}(); + execute procedure createRbacRolesFor${simpleEntityName}(); --// """ - .replace("%{simpleEntityName}", simpleEntityName) - .replace("%{rawTableName}", rawTableName) + .replace("${simpleEntityName}", simpleEntityName) + .replace("${rawTableName}", rawTableName) ); }