RBAC Diagram+PostgreSQL Generator #21
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user