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
4 changed files with 103 additions and 77 deletions
Showing only changes of commit b187c705b1 - Show all commits

View File

@ -503,7 +503,7 @@ public class RbacView {
} }
return switch (fetchSql.part) { return switch (fetchSql.part) {
case SQL_QUERY -> fetchSql; case SQL_QUERY -> fetchSql;
case AUTO_FETCH -> SQL.query("SELECT * FROM " + getRawTableName(entityClass) + " WHERE uuid = ${ref}." + dependsOnColum.column); case AUTO_FETCH -> SQL.query("SELECT * FROM " + getRawTableName() + " WHERE uuid = ${ref}." + dependsOnColum.column);
default -> throw new IllegalStateException("unexpected SQL definition: " + fetchSql); default -> throw new IllegalStateException("unexpected SQL definition: " + fetchSql);
}; };
} }
@ -521,10 +521,10 @@ public class RbacView {
? aliasName ? aliasName
: uncapitalize(withoutEntitySuffix(entityClass.getSimpleName())); : uncapitalize(withoutEntitySuffix(entityClass.getSimpleName()));
} }
}
public static String getRawTableName(final Class<?> entityClass) { String getRawTableName() {
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name()); return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
}
} }
public static String withoutRvSuffix(final String tableName) { public static String withoutRvSuffix(final String tableName) {
return tableName.substring(0, tableName.length()-"_rv".length()); return tableName.substring(0, tableName.length()-"_rv".length());

View File

@ -20,7 +20,7 @@ public class RbacViewPostgresGenerator {
public RbacViewPostgresGenerator(final RbacView forRbacDef) { public RbacViewPostgresGenerator(final RbacView forRbacDef) {
rbacDef = forRbacDef; rbacDef = forRbacDef;
liqibaseTagPrefix = rbacDef.getRootEntityAlias().entityClass().getSimpleName(); liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableName().replace("_", "-");
plPgSql.writeLn(""" plPgSql.writeLn("""
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by ${generator} at ${timestamp}. -- This code generated was by ${generator} at ${timestamp}.

View File

@ -13,7 +13,6 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.PostgresTriggerReference.NE
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.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.getRawTableName;
import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with; import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
import static org.apache.commons.lang3.StringUtils.capitalize; import static org.apache.commons.lang3.StringUtils.capitalize;
import static org.apache.commons.lang3.StringUtils.uncapitalize; import static org.apache.commons.lang3.StringUtils.uncapitalize;
@ -38,36 +37,65 @@ class RolesGrantsAndPermissionsGenerator {
entityClass = rbacDef.getRootEntityAlias().entityClass(); entityClass = rbacDef.getRootEntityAlias().entityClass();
simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName(); simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
simpleEntityName = capitalize(simpleEntityVarName); simpleEntityName = capitalize(simpleEntityVarName);
rawTableName = getRawTableName(entityClass); rawTableName = rbacDef.getRootEntityAlias().getRawTableName();
} }
void generateTo(final StringWriter plPgSql) { void generateTo(final StringWriter plPgSql) {
generateHeader(plPgSql);
generateTriggerFunction(plPgSql);
generateInsertTrigger(plPgSql); generateInsertTrigger(plPgSql);
if (hasAnyUpdatableEntityAliases()) { if (hasAnyUpdatableEntityAliases()) {
generateUpdateTrigger(plPgSql); generateUpdateTrigger(plPgSql);
} }
generateFooter(plPgSql);
} }
private void generateHeader(final StringWriter plPgSql) { private void generateHeader(final StringWriter plPgSql, final String triggerType) {
plPgSql.writeLn(""" plPgSql.writeLn("""
-- ============================================================================ -- ============================================================================
--changeset ${liquibaseTagPrefix}-rbac-CREATE-ROLES-GRANTS-PERMISSIONS:1 endDelimiter:--// --changeset ${liquibaseTagPrefix}-rbac-${triggerType}-trigger:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
""" """,
.replace("${liquibaseTagPrefix}", liquibaseTagPrefix)); with("liquibaseTagPrefix", liquibaseTagPrefix),
with("triggerType", triggerType));
} }
private void generateTriggerFunction(final StringWriter plPgSql) { private void generateInsertTriggerFunction(final StringWriter plPgSql) {
plPgSql.writeLn(""" plPgSql.writeLn("""
/* /*
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 buildRbacSystemFor${simpleEntityName}( create or replace procedure buildRbacSystemFor${simpleEntityName}(
TG_OP text, NEW ${rawTableName}
)
language plpgsql as $$
declare
"""
.replace("${simpleEntityName}", simpleEntityName)
.replace("${rawTableName}", rawTableName));
plPgSql.chopEmptyLines();
plPgSql.indented(() -> {
referencedEntityAliases()
.forEach((ea) -> plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";"));
});
plPgSql.writeLn();
plPgSql.writeLn("begin");
plPgSql.indented(() -> {
generateCreateRolesAndGrantsAfterInsert(plPgSql);
plPgSql.ensureSingleEmptyLine();
});
plPgSql.writeLn("end; $$;");
plPgSql.writeLn();
}
private void generateUpdateTriggerFunction(final StringWriter plPgSql) {
plPgSql.writeLn("""
/*
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
*/
create or replace procedure updateRbacGrantsFor${simpleEntityName}(
OLD ${rawTableName}, OLD ${rawTableName},
NEW ${rawTableName} NEW ${rawTableName}
) )
@ -80,27 +108,17 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.chopEmptyLines(); plPgSql.chopEmptyLines();
plPgSql.indented(() -> { plPgSql.indented(() -> {
referencedEntityAliases()
.forEach((ea) -> plPgSql.writeLn(entityRefVar(NEW, ea) + " " + getRawTableName(ea.entityClass()) + ";"));
updatableEntityAliases() updatableEntityAliases()
.forEach((ea) -> plPgSql.writeLn(entityRefVar(OLD, ea) + " " + getRawTableName(ea.entityClass()) + ";")); .forEach((ea) -> {
plPgSql.writeLn(entityRefVar(OLD, ea) + " " + ea.getRawTableName() + ";");
plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";");
});
}); });
plPgSql.writeLn(); plPgSql.writeLn();
plPgSql.writeLn("begin"); plPgSql.writeLn("begin");
plPgSql.indented(() -> { plPgSql.indented(() -> {
generateUpdateRolesAndGrantsAfterUpdate(plPgSql);
generateCreateRolesAndGrantsAfterInsert(plPgSql);
if (hasAnyUpdatableEntityAliases()) {
generateUpdateRolesAndGrantsAfterUpdate(plPgSql);
}
plPgSql.writeLn("""
else
raise exception 'invalid usage of TRIGGER';
end if;
""");
plPgSql.ensureSingleEmptyLine(); plPgSql.ensureSingleEmptyLine();
}); });
plPgSql.writeLn("end; $$;"); plPgSql.writeLn("end; $$;");
@ -117,23 +135,15 @@ class RolesGrantsAndPermissionsGenerator {
ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";", ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";",
with("ref", NEW.name()))); with("ref", NEW.name())));
plPgSql.ensureSingleEmptyLine(); createRolesWithGrantsSql(plPgSql, OWNER);
plPgSql.writeLn("if TG_OP = 'INSERT' then"); createRolesWithGrantsSql(plPgSql, ADMIN);
createRolesWithGrantsSql(plPgSql, AGENT);
createRolesWithGrantsSql(plPgSql, TENANT);
createRolesWithGrantsSql(plPgSql, REFERRER);
plPgSql.indented(() -> { generateGrants(plPgSql, ROLE_TO_USER);
generateGrants(plPgSql, ROLE_TO_ROLE);
plPgSql.chopEmptyLines(); generateGrants(plPgSql, PERM_TO_ROLE);
createRolesWithGrantsSql(plPgSql, OWNER);
createRolesWithGrantsSql(plPgSql, ADMIN);
createRolesWithGrantsSql(plPgSql, AGENT);
createRolesWithGrantsSql(plPgSql, TENANT);
createRolesWithGrantsSql(plPgSql, REFERRER);
generateGrants(plPgSql, ROLE_TO_USER);
generateGrants(plPgSql, ROLE_TO_ROLE);
generateGrants(plPgSql, PERM_TO_ROLE);
plPgSql.ensureSingleEmptyLine();
});
} }
private Stream<RbacView.EntityAlias> referencedEntityAliases() { private Stream<RbacView.EntityAlias> referencedEntityAliases() {
@ -151,29 +161,30 @@ class RolesGrantsAndPermissionsGenerator {
private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) { private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) {
plPgSql.ensureSingleEmptyLine(); plPgSql.ensureSingleEmptyLine();
plPgSql.writeLn("elsif TG_OP = 'UPDATE' then");
plPgSql.indented(() -> { updatableEntityAliases()
.forEach((ea) -> {
updatableEntityAliases() plPgSql.writeLn(
.forEach((ea) -> plPgSql.writeLn(
ea.fetchSql().sql + " into " + entityRefVar(OLD, ea) + ";", ea.fetchSql().sql + " into " + entityRefVar(OLD, ea) + ";",
with("ref", OLD.name()))); with("ref", OLD.name()));
plPgSql.writeLn(
ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";",
with("ref", NEW.name()));
});
updatableEntityAliases() updatableEntityAliases()
.map(RbacView.EntityAlias::dependsOnColum) .map(RbacView.EntityAlias::dependsOnColum)
.map(c -> c.column) .map(c -> c.column)
.sorted() .sorted()
.distinct() .distinct()
.forEach(columnName -> { .forEach(columnName -> {
plPgSql.writeLn(); plPgSql.writeLn();
plPgSql.writeLn("if NEW." + columnName + " <> OLD." + columnName + " then"); plPgSql.writeLn("if NEW." + columnName + " <> OLD." + columnName + " then");
plPgSql.indented(() -> { plPgSql.indented(() -> {
updateGrantsDependingOn(plPgSql, columnName); updateGrantsDependingOn(plPgSql, columnName);
});
plPgSql.writeLn("end if;");
}); });
}); plPgSql.writeLn("end if;");
});
} }
private boolean isUpdatable(final RbacView.Column c) { private boolean isUpdatable(final RbacView.Column c) {
@ -386,6 +397,10 @@ class RolesGrantsAndPermissionsGenerator {
} }
private void generateInsertTrigger(final StringWriter plPgSql) { private void generateInsertTrigger(final StringWriter plPgSql) {
generateHeader(plPgSql, "insert");
generateInsertTriggerFunction(plPgSql);
plPgSql.writeLn(""" plPgSql.writeLn("""
/* /*
AFTER INSERT TRIGGER to create the role+grant structure for a new ${rawTableName} row. AFTER INSERT TRIGGER to create the role+grant structure for a new ${rawTableName} row.
@ -405,14 +420,19 @@ class RolesGrantsAndPermissionsGenerator {
on ${rawTableName} on ${rawTableName}
for each row for each row
execute procedure insertTriggerFor${simpleEntityName}_tf(); execute procedure insertTriggerFor${simpleEntityName}_tf();
--//
""" """
.replace("${simpleEntityName}", simpleEntityName) .replace("${simpleEntityName}", simpleEntityName)
.replace("${rawTableName}", rawTableName) .replace("${rawTableName}", rawTableName)
); );
generateFooter(plPgSql);
} }
private void generateUpdateTrigger(final StringWriter plPgSql) { private void generateUpdateTrigger(final StringWriter plPgSql) {
generateHeader(plPgSql, "update");
generateUpdateTriggerFunction(plPgSql);
plPgSql.writeLn(""" plPgSql.writeLn("""
/* /*
AFTER INSERT TRIGGER to re-wire the grant structure for a new ${rawTableName} row. AFTER INSERT TRIGGER to re-wire the grant structure for a new ${rawTableName} row.
@ -423,7 +443,7 @@ class RolesGrantsAndPermissionsGenerator {
language plpgsql language plpgsql
strict as $$ strict as $$
begin begin
call buildRbacSystemFor${simpleEntityName}(TG_OP, OLD, NEW); call buildRbacSystemFor${simpleEntityName}(NEW);
return NEW; return NEW;
end; $$; end; $$;
@ -437,10 +457,12 @@ class RolesGrantsAndPermissionsGenerator {
.replace("${simpleEntityName}", simpleEntityName) .replace("${simpleEntityName}", simpleEntityName)
.replace("${rawTableName}", rawTableName) .replace("${rawTableName}", rawTableName)
); );
generateFooter(plPgSql);
} }
private static void generateFooter(final StringWriter plPgSql) { private static void generateFooter(final StringWriter plPgSql) {
plPgSql.writeLn(); plPgSql.writeLn("--//");
plPgSql.writeLn(); plPgSql.writeLn();
} }

View File

@ -90,14 +90,18 @@ public class StringWriter {
this.varDefs = varDefs; this.varDefs = varDefs;
} }
String apply(final String text) { String apply(final String textToAppend) {
this.text = text; try {
stream(varDefs).forEach(varDef -> { text = textToAppend;
final var pattern = Pattern.compile("\\$\\{" + varDef.name() + "}", Pattern.CASE_INSENSITIVE); stream(varDefs).forEach(varDef -> {
final var matcher = pattern.matcher(text); final var pattern = Pattern.compile("\\$\\{" + varDef.name() + "}", Pattern.CASE_INSENSITIVE);
this.text = matcher.replaceAll(varDef.value()); final var matcher = pattern.matcher(text);
}); text = matcher.replaceAll(varDef.value());
return this.text; });
return text;
} catch (Exception exc) {
throw exc;
}
} }
} }
} }