RBAC Diagram+PostgreSQL Generator #21

Merged
hsh-michaelhoennig merged 54 commits from experimental-rbacview-generator into master 2024-03-11 12:30:44 +01:00
2 changed files with 81 additions and 33 deletions
Showing only changes of commit faf6710ef1 - Show all commits

View File

@ -1,9 +1,10 @@
package net.hostsharing.hsadminng.rbac.rbacdef; 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.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -32,15 +33,18 @@ public class RbacViewPostgresGenerator {
@Override @Override
public String toString() { public String toString() {
return plPgSql.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( Files.writeString(
Paths.get("doc", "hsOfficeBankAccount.sql"), outputPath,
new RbacViewPostgresGenerator(HsOfficeBankAccountEntity.rbac()).toString(), new RbacViewPostgresGenerator(rbac).toString(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
System.out.println(outputPath.toAbsolutePath());
} }
} }

View File

@ -5,9 +5,8 @@ import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; 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.PostgresTriggerReference.NEW;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -26,13 +25,15 @@ class RolesGrantsAndPermissionsGenerator {
RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) { RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
this.rbacDef = rbacDef; this.rbacDef = rbacDef;
this.rbacGrants.addAll(rbacDef.getGrantDefs()); this.rbacGrants.addAll(rbacDef.getGrantDefs().stream()
.filter(RbacView.RbacGrantDefinition::isToCreate)
.collect(toSet()));
this.liquibaseTagPrefix = liquibaseTagPrefix; this.liquibaseTagPrefix = liquibaseTagPrefix;
entityClass = rbacDef.getRootEntityAlias().entityClass(); entityClass = rbacDef.getRootEntityAlias().entityClass();
simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName(); simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
simpleEntityName = capitalize(simpleEntityVarName); simpleEntityName = capitalize(simpleEntityVarName);
rawTableName = withoutRvSuffix(entityClass.getAnnotation(Table.class).name()); rawTableName = getRawTableName(entityClass);
} }
void generateTo(final StringWriter plPgSql) { void generateTo(final StringWriter plPgSql) {
@ -45,10 +46,10 @@ class RolesGrantsAndPermissionsGenerator {
private void generateHeader(final StringWriter plPgSql) { private void generateHeader(final StringWriter plPgSql) {
plPgSql.writeLn(""" 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) { private void generateTriggerFunction(final StringWriter plPgSql) {
@ -57,28 +58,53 @@ class RolesGrantsAndPermissionsGenerator {
Creates the roles, grants and permission for the AFTER INSERT TRIGGER. 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 returns trigger
language plpgsql language plpgsql
strict as $$ 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 begin
if TG_OP <> 'INSERT' then if TG_OP <> 'INSERT' then
raise exception 'invalid usage of TRIGGER AFTER INSERT function'; raise exception 'invalid usage of TRIGGER AFTER INSERT function';
end if; end if;
""" """);
.replace("%{simpleEntityName}", simpleEntityName));
plPgSql.indented(() -> { 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, OWNER);
createRolesWithGrantsSql(plPgSql, ADMIN); createRolesWithGrantsSql(plPgSql, ADMIN);
createRolesWithGrantsSql(plPgSql, AGENT); createRolesWithGrantsSql(plPgSql, AGENT);
createRolesWithGrantsSql(plPgSql, TENANT); createRolesWithGrantsSql(plPgSql, TENANT);
createRolesWithGrantsSql(plPgSql, REFERRER); createRolesWithGrantsSql(plPgSql, REFERRER);
if (!rbacGrants.isEmpty()) { plPgSql.writeLn();
throw new IllegalStateException("unprocessed grants: " + rbacGrants); rbacGrants
// rbacGrants.forEach(g -> plPgSql.writeLn("-- unprocessed grant: " + g)); .forEach(g -> plPgSql.writeLn(
} "call grantRoleToRole(${subRoleRef}, ${superRoleRef});"
.replace("${subRoleRef}", roleRef(NEW, g.getSubRoleDef()) )
.replace("${superRoleRef}", roleRef(NEW, g.getSuperRoleDef()) ))
);
plPgSql.writeLn("return NEW;"); plPgSql.writeLn("return NEW;");
}); });
@ -87,6 +113,24 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.writeLn(); 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) { private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacView.Role role) {
@ -100,9 +144,9 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.writeLn(); plPgSql.writeLn();
plPgSql.writeLn("perform createRoleWithGrants("); plPgSql.writeLn("perform createRoleWithGrants(");
plPgSql.indented( () -> { plPgSql.indented( () -> {
plPgSql.writeLn("%{simpleVarName)%{roleSuffix}(NEW)," plPgSql.writeLn("${simpleVarName)${roleSuffix}(NEW),"
.replace("%{simpleVarName)", simpleEntityVarName) .replace("${simpleVarName)", simpleEntityVarName)
.replace("%{roleSuffix}", capitalize(role.roleName()))); .replace("${roleSuffix}", capitalize(role.roleName())));
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role); final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
if (!permissionGrantsForRole.isEmpty()) { if (!permissionGrantsForRole.isEmpty()) {
@ -161,21 +205,21 @@ class RolesGrantsAndPermissionsGenerator {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
return rbacGrants.stream() return rbacGrants.stream()
.filter(g -> g.grantType() == ROLE_TO_PERM && g.getSuperRoleDef()==roleDef ) .filter(g -> g.grantType() == ROLE_TO_PERM && g.getSuperRoleDef()==roleDef )
.collect(Collectors.toSet()); .collect(toSet());
} }
private Set<RbacView.RbacGrantDefinition> findGrantsToUserForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) { private Set<RbacView.RbacGrantDefinition> findGrantsToUserForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
return rbacGrants.stream() return rbacGrants.stream()
.filter(g -> g.grantType() == USER_TO_ROLE && g.getSubRoleDef() == roleDef ) .filter(g -> g.grantType() == USER_TO_ROLE && g.getSubRoleDef() == roleDef )
.collect(Collectors.toSet()); .collect(toSet());
} }
private Set<RbacView.RbacGrantDefinition> findIncomingSuperRolesForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) { private Set<RbacView.RbacGrantDefinition> findIncomingSuperRolesForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
return rbacGrants.stream() return rbacGrants.stream()
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSubRoleDef()==roleDef ) .filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSubRoleDef()==roleDef )
.collect(Collectors.toSet()); .collect(toSet());
} }
private Set<RbacView.RbacGrantDefinition> findOutgoingSuperRolesForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) { private Set<RbacView.RbacGrantDefinition> findOutgoingSuperRolesForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
@ -183,24 +227,24 @@ class RolesGrantsAndPermissionsGenerator {
return rbacGrants.stream() return rbacGrants.stream()
.filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSuperRoleDef()==roleDef ) .filter(g -> g.grantType() == ROLE_TO_ROLE && g.getSuperRoleDef()==roleDef )
.filter(g -> g.getSubRoleDef().getEntityAlias() != entityAlias) .filter(g -> g.getSubRoleDef().getEntityAlias() != entityAlias)
.collect(Collectors.toSet()); .collect(toSet());
} }
private void generageInsertTrigger(final StringWriter plPgSql) { private void generageInsertTrigger(final StringWriter plPgSql) {
plPgSql.writeLn(""" 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 after insert
on %{rawTableName} on ${rawTableName}
for each row for each row
execute procedure createRbacRolesFor%{simpleEntityName}(); execute procedure createRbacRolesFor${simpleEntityName}();
--// --//
""" """
.replace("%{simpleEntityName}", simpleEntityName) .replace("${simpleEntityName}", simpleEntityName)
.replace("%{rawTableName}", rawTableName) .replace("${rawTableName}", rawTableName)
); );
} }