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
Showing only changes of commit bc33f1fd9d - Show all commits

View File

@ -6,8 +6,10 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream;
import static java.util.stream.Collectors.*; import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
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.PostgresTriggerReference.OLD; import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.OLD;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*;
@ -43,6 +45,9 @@ class RolesGrantsAndPermissionsGenerator {
generateHeader(plPgSql); generateHeader(plPgSql);
generateTriggerFunction(plPgSql); generateTriggerFunction(plPgSql);
generateInsertTrigger(plPgSql); generateInsertTrigger(plPgSql);
if (hasAnyUpdatableEntityAliases()) {
generateUpdateTrigger(plPgSql);
}
generateFooter(plPgSql); generateFooter(plPgSql);
} }
@ -61,7 +66,7 @@ class RolesGrantsAndPermissionsGenerator {
A Creates the roles, grants and permission for the AFTER INSERT TRIGGER. A Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/ */
create or replace procedure createRbacRolesFor${simpleEntityName}( create or replace procedure buildRbacSystemFor${simpleEntityName}(
TG_OP text, TG_OP text,
OLD ${rawTableName}, OLD ${rawTableName},
NEW ${rawTableName} NEW ${rawTableName}
@ -73,35 +78,44 @@ class RolesGrantsAndPermissionsGenerator {
.replace("${rawTableName}", rawTableName)); .replace("${rawTableName}", rawTableName));
plPgSql.indented(() -> { plPgSql.indented(() -> {
rbacDef.getEntityAliases().values().stream() updatableEntityAliases()
.filter((ea) -> !rbacDef.isRootEntityAlias(ea))
.filter((ea) -> ea.fetchSql() != null)
.forEach((ea) -> { .forEach((ea) -> {
plPgSql.writeLn( entityRefVar(NEW, ea) + " " + getRawTableName(ea.entityClass()) + ";"); plPgSql.writeLn(entityRefVar(NEW, ea) + " " + getRawTableName(ea.entityClass()) + ";");
}); });
}); });
plPgSql.indented(() -> { plPgSql.indented(() -> {
plPgSql.writeLn("begin"); plPgSql.writeLn("begin");
generateCreateRolesAndGrantsAfterInsert(plPgSql); generateCreateRolesAndGrantsAfterInsert(plPgSql);
if (hasAnyUpdatableEntityAliases()) {
generateUpdateRolesAndGrantsAfterUpdate(plPgSql); generateUpdateRolesAndGrantsAfterUpdate(plPgSql);
}
plPgSql.writeLn("""
else
raise exception 'invalid usage of TRIGGER';
end if;
""");
plPgSql.ensureEmptyLine(); plPgSql.ensureEmptyLine();
plPgSql.writeLn("end; $$;");
}); });
plPgSql.writeLn("end; $$;");
plPgSql.writeLn(); plPgSql.writeLn();
} }
private boolean hasAnyUpdatableEntityAliases() {
return updatableEntityAliases().anyMatch(e -> true);
}
private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) { private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) {
plPgSql.ensureEmptyLine(); plPgSql.ensureEmptyLine();
plPgSql.writeLn("if TG_OP = 'INSERT' then"); plPgSql.writeLn("if TG_OP = 'INSERT' then");
plPgSql.indented(() -> { plPgSql.indented(() -> {
rbacDef.getEntityAliases().values().stream() updatableEntityAliases()
.filter((ea) -> !rbacDef.isRootEntityAlias(ea))
.filter((ea) -> ea.fetchSql() != null)
.forEach((ea) -> { .forEach((ea) -> {
plPgSql.writeLn( ea.fetchSql().sql.replace("${ref}", NEW.name()) + " into " + entityRefVar(NEW, ea) + ";"); plPgSql.writeLn(
ea.fetchSql().sql.replace("${ref}", NEW.name()) + " into " + entityRefVar(NEW, ea) + ";");
}); });
createRolesWithGrantsSql(plPgSql, OWNER); createRolesWithGrantsSql(plPgSql, OWNER);
@ -114,13 +128,22 @@ class RolesGrantsAndPermissionsGenerator {
generateGrants(plPgSql, ROLE_TO_ROLE); generateGrants(plPgSql, ROLE_TO_ROLE);
generateGrants(plPgSql, PERM_TO_ROLE); generateGrants(plPgSql, PERM_TO_ROLE);
}); });
}
plPgSql.writeLn("end if;"); private Stream<RbacView.EntityAlias> referencedEntityAliases() {
} return rbacDef.getEntityAliases().values().stream()
.filter((ea) -> !rbacDef.isRootEntityAlias(ea))
.filter((ea) -> ea.fetchSql() != null);
}
private Stream<RbacView.EntityAlias> updatableEntityAliases() {
return referencedEntityAliases()
.filter(ea -> rbacDef.getUpdatableColumns().contains(ea.dependsOnColum().column) );
}
private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) { private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) {
plPgSql.ensureEmptyLine(); plPgSql.ensureEmptyLine();
plPgSql.writeLn("if TG_OP = 'UPDATE' then"); plPgSql.writeLn("elsif TG_OP = 'UPDATE' then");
plPgSql.indented(() -> { plPgSql.indented(() -> {
@ -128,7 +151,8 @@ class RolesGrantsAndPermissionsGenerator {
.filter(ea -> !rbacDef.isRootEntityAlias(ea)) .filter(ea -> !rbacDef.isRootEntityAlias(ea))
.filter(ea -> ea.fetchSql() != null) .filter(ea -> ea.fetchSql() != null)
.forEach(ea -> { .forEach(ea -> {
plPgSql.writeLn( ea.fetchSql().sql.replace("${ref}", OLD.name()) + " into " + entityRefVar(OLD, ea) + ";"); plPgSql.writeLn(
ea.fetchSql().sql.replace("${ref}", OLD.name()) + " into " + entityRefVar(OLD, ea) + ";");
}); });
rbacDef.getEntityAliases().values().stream() rbacDef.getEntityAliases().values().stream()
@ -147,8 +171,6 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.writeLn("end if;"); plPgSql.writeLn("end if;");
}); });
}); });
plPgSql.writeLn("end if;");
} }
private boolean isUpdatable(final RbacView.Column c) { private boolean isUpdatable(final RbacView.Column c) {
@ -160,7 +182,8 @@ class RolesGrantsAndPermissionsGenerator {
.filter(RbacView.RbacGrantDefinition::isToCreate) .filter(RbacView.RbacGrantDefinition::isToCreate)
.filter(g -> g.dependsOnColumn(columnName)) .filter(g -> g.dependsOnColumn(columnName))
.forEach(g -> { .forEach(g -> {
plPgSql.writeLn("-- TODO: " + g); plPgSql.writeLn("-- TODO: revoke " + g);
plPgSql.writeLn(generateGrant(g));
}); });
} }
@ -177,11 +200,11 @@ class RolesGrantsAndPermissionsGenerator {
return switch (grantDef.grantType()) { return switch (grantDef.grantType()) {
case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant"); case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant");
case ROLE_TO_ROLE -> "call grantRoleToRole(${subRoleRef}, ${superRoleRef}));" case ROLE_TO_ROLE -> "call grantRoleToRole(${subRoleRef}, ${superRoleRef}));"
.replace("${subRoleRef}", roleRef(NEW, grantDef.getSubRoleDef()) ) .replace("${subRoleRef}", roleRef(NEW, grantDef.getSubRoleDef()))
.replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef()) ); .replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef()));
case PERM_TO_ROLE -> "call grantPermissionsToRole(${permRef}, ${superRoleRef}));" case PERM_TO_ROLE -> "call grantPermissionsToRole(${permRef}, ${superRoleRef}));"
.replace("${permRef}", permRef(NEW, grantDef.getPermDef()) ) .replace("${permRef}", permRef(NEW, grantDef.getPermDef()))
.replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef()) ); .replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef()));
}; };
} }
@ -198,10 +221,10 @@ class RolesGrantsAndPermissionsGenerator {
} }
private String roleRef(final PostgresTriggerReference rootRefVar, final RbacView.RbacRoleDefinition roleDef) { private String roleRef(final PostgresTriggerReference rootRefVar, final RbacView.RbacRoleDefinition roleDef) {
if ( roleDef == null ) { if (roleDef == null) {
System.out.println("null"); System.out.println("null");
} }
if ( roleDef.getEntityAlias().isGlobal()) { if (roleDef.getEntityAlias().isGlobal()) {
return "globalAdmin()"; return "globalAdmin()";
} }
final String entityRefVar = entityRefVar(rootRefVar, roleDef.getEntityAlias()); final String entityRefVar = entityRefVar(rootRefVar, roleDef.getEntityAlias());
@ -218,7 +241,7 @@ class RolesGrantsAndPermissionsGenerator {
private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacView.Role role) { private void createRolesWithGrantsSql(final StringWriter plPgSql, final RbacView.Role role) {
final var isToCreate = rbacDef.getRoleDefs().stream() final var isToCreate = rbacDef.getRoleDefs().stream()
.filter(roleDef -> rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) && roleDef.getRole() == role ) .filter(roleDef -> rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) && roleDef.getRole() == role)
.findFirst().map(RbacView.RbacRoleDefinition::isToCreate).orElse(false); .findFirst().map(RbacView.RbacRoleDefinition::isToCreate).orElse(false);
if (!isToCreate) { if (!isToCreate) {
return; return;
@ -226,7 +249,7 @@ 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())));
@ -268,7 +291,7 @@ class RolesGrantsAndPermissionsGenerator {
.map(RbacView.Permission::permission) .map(RbacView.Permission::permission)
.map(p -> "'" + p + "'") .map(p -> "'" + p + "'")
.toList(); .toList();
plPgSql.indented( () -> plPgSql.indented(() ->
plPgSql.writeLn("permissions => array[" + joinArrayElements(arrayElements, 3) + "],\n")); plPgSql.writeLn("permissions => array[" + joinArrayElements(arrayElements, 3) + "],\n"));
rbacGrants.removeAll(permissionGrantsForRole); rbacGrants.removeAll(permissionGrantsForRole);
} }
@ -306,31 +329,39 @@ class RolesGrantsAndPermissionsGenerator {
: arrayElements.stream().collect(joining(",\n\t", "\n\t", "")); : arrayElements.stream().collect(joining(",\n\t", "\n\t", ""));
} }
private Set<RbacView.RbacGrantDefinition> findPermissionsGrantsForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) { private Set<RbacView.RbacGrantDefinition> findPermissionsGrantsForRole(
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() == PERM_TO_ROLE && g.getSuperRoleDef()==roleDef ) .filter(g -> g.grantType() == PERM_TO_ROLE && g.getSuperRoleDef() == roleDef)
.collect(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() == ROLE_TO_USER && g.getSubRoleDef() == roleDef ) .filter(g -> g.grantType() == ROLE_TO_USER && g.getSubRoleDef() == roleDef)
.collect(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(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) {
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.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(toSet()); .collect(toSet());
} }
@ -341,20 +372,47 @@ class RolesGrantsAndPermissionsGenerator {
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 or replace function createRbacRolesFor${simpleEntityName}_tf() create or replace function insertTriggerFor${simpleEntityName}_tf()
returns trigger returns trigger
language plpgsql language plpgsql
strict as $$ strict as $$
begin begin
call createRbacRolesFor${simpleEntityName}(TG_OP, OLD, NEW); call buildRbacSystemFor${simpleEntityName}(TG_OP, OLD, NEW);
return NEW; return NEW;
end; $$; end; $$;
create trigger createRbacRolesFor${simpleEntityName}_tg create trigger insertTriggerFor${simpleEntityName}_tg
after insert after insert
on ${rawTableName} on ${rawTableName}
for each row for each row
execute procedure createRbacRolesFor${simpleEntityName}_tf(); execute procedure insertTriggerFor${simpleEntityName}_tf();
--//
"""
.replace("${simpleEntityName}", simpleEntityName)
.replace("${rawTableName}", rawTableName)
);
}
private void generateUpdateTrigger(final StringWriter plPgSql) {
plPgSql.writeLn("""
/*
An AFTER UPDATE TRIGGER which re-wires the grant structure for an updated ${simpleEntityName}
*/
create or replace function updateTriggerFor${simpleEntityName}_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemFor${simpleEntityName}(TG_OP, OLD, NEW);
return NEW;
end; $$;
create trigger updateTriggerFor${simpleEntityName}_tg
after update
on ${rawTableName}
for each row
execute procedure updateTriggerFor${simpleEntityName}_tf();
--// --//
""" """
.replace("${simpleEntityName}", simpleEntityName) .replace("${simpleEntityName}", simpleEntityName)
@ -385,7 +443,9 @@ class RolesGrantsAndPermissionsGenerator {
return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName()); return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName());
} }
private static String toTriggerReference(final PostgresTriggerReference triggerRef, final RbacView.EntityAlias entityAlias) { private static String toTriggerReference(
final PostgresTriggerReference triggerRef,
final RbacView.EntityAlias entityAlias) {
return triggerRef.name().toLowerCase() + capitalize(entityAlias.aliasName()); return triggerRef.name().toLowerCase() + capitalize(entityAlias.aliasName());
} }
} }