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) {
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);
};
}
@ -521,11 +521,11 @@ public class RbacView {
? aliasName
: uncapitalize(withoutEntitySuffix(entityClass.getSimpleName()));
}
}
public static String getRawTableName(final Class<?> entityClass) {
String getRawTableName() {
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
}
}
public static String withoutRvSuffix(final String tableName) {
return tableName.substring(0, tableName.length()-"_rv".length());
}

View File

@ -20,7 +20,7 @@ public class RbacViewPostgresGenerator {
public RbacViewPostgresGenerator(final RbacView forRbacDef) {
rbacDef = forRbacDef;
liqibaseTagPrefix = rbacDef.getRootEntityAlias().entityClass().getSimpleName();
liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableName().replace("_", "-");
plPgSql.writeLn("""
--liquibase formatted sql
-- 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.RbacView.RbacGrantDefinition.GrantType.*;
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 org.apache.commons.lang3.StringUtils.capitalize;
import static org.apache.commons.lang3.StringUtils.uncapitalize;
@ -38,36 +37,65 @@ class RolesGrantsAndPermissionsGenerator {
entityClass = rbacDef.getRootEntityAlias().entityClass();
simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
simpleEntityName = capitalize(simpleEntityVarName);
rawTableName = getRawTableName(entityClass);
rawTableName = rbacDef.getRootEntityAlias().getRawTableName();
}
void generateTo(final StringWriter plPgSql) {
generateHeader(plPgSql);
generateTriggerFunction(plPgSql);
generateInsertTrigger(plPgSql);
if (hasAnyUpdatableEntityAliases()) {
generateUpdateTrigger(plPgSql);
}
generateFooter(plPgSql);
}
private void generateHeader(final StringWriter plPgSql) {
private void generateHeader(final StringWriter plPgSql, final String triggerType) {
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("""
/*
A Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
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},
NEW ${rawTableName}
)
@ -80,27 +108,17 @@ class RolesGrantsAndPermissionsGenerator {
plPgSql.chopEmptyLines();
plPgSql.indented(() -> {
referencedEntityAliases()
.forEach((ea) -> plPgSql.writeLn(entityRefVar(NEW, ea) + " " + getRawTableName(ea.entityClass()) + ";"));
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("begin");
plPgSql.indented(() -> {
generateCreateRolesAndGrantsAfterInsert(plPgSql);
if (hasAnyUpdatableEntityAliases()) {
generateUpdateRolesAndGrantsAfterUpdate(plPgSql);
}
plPgSql.writeLn("""
else
raise exception 'invalid usage of TRIGGER';
end if;
""");
plPgSql.ensureSingleEmptyLine();
});
plPgSql.writeLn("end; $$;");
@ -117,12 +135,6 @@ class RolesGrantsAndPermissionsGenerator {
ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";",
with("ref", NEW.name())));
plPgSql.ensureSingleEmptyLine();
plPgSql.writeLn("if TG_OP = 'INSERT' then");
plPgSql.indented(() -> {
plPgSql.chopEmptyLines();
createRolesWithGrantsSql(plPgSql, OWNER);
createRolesWithGrantsSql(plPgSql, ADMIN);
createRolesWithGrantsSql(plPgSql, AGENT);
@ -132,8 +144,6 @@ class RolesGrantsAndPermissionsGenerator {
generateGrants(plPgSql, ROLE_TO_USER);
generateGrants(plPgSql, ROLE_TO_ROLE);
generateGrants(plPgSql, PERM_TO_ROLE);
plPgSql.ensureSingleEmptyLine();
});
}
private Stream<RbacView.EntityAlias> referencedEntityAliases() {
@ -151,14 +161,16 @@ class RolesGrantsAndPermissionsGenerator {
private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) {
plPgSql.ensureSingleEmptyLine();
plPgSql.writeLn("elsif TG_OP = 'UPDATE' then");
plPgSql.indented(() -> {
updatableEntityAliases()
.forEach((ea) -> plPgSql.writeLn(
.forEach((ea) -> {
plPgSql.writeLn(
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()
.map(RbacView.EntityAlias::dependsOnColum)
@ -173,7 +185,6 @@ class RolesGrantsAndPermissionsGenerator {
});
plPgSql.writeLn("end if;");
});
});
}
private boolean isUpdatable(final RbacView.Column c) {
@ -386,6 +397,10 @@ class RolesGrantsAndPermissionsGenerator {
}
private void generateInsertTrigger(final StringWriter plPgSql) {
generateHeader(plPgSql, "insert");
generateInsertTriggerFunction(plPgSql);
plPgSql.writeLn("""
/*
AFTER INSERT TRIGGER to create the role+grant structure for a new ${rawTableName} row.
@ -405,14 +420,19 @@ class RolesGrantsAndPermissionsGenerator {
on ${rawTableName}
for each row
execute procedure insertTriggerFor${simpleEntityName}_tf();
--//
"""
.replace("${simpleEntityName}", simpleEntityName)
.replace("${rawTableName}", rawTableName)
);
generateFooter(plPgSql);
}
private void generateUpdateTrigger(final StringWriter plPgSql) {
generateHeader(plPgSql, "update");
generateUpdateTriggerFunction(plPgSql);
plPgSql.writeLn("""
/*
AFTER INSERT TRIGGER to re-wire the grant structure for a new ${rawTableName} row.
@ -423,7 +443,7 @@ class RolesGrantsAndPermissionsGenerator {
language plpgsql
strict as $$
begin
call buildRbacSystemFor${simpleEntityName}(TG_OP, OLD, NEW);
call buildRbacSystemFor${simpleEntityName}(NEW);
return NEW;
end; $$;
@ -437,10 +457,12 @@ class RolesGrantsAndPermissionsGenerator {
.replace("${simpleEntityName}", simpleEntityName)
.replace("${rawTableName}", rawTableName)
);
generateFooter(plPgSql);
}
private static void generateFooter(final StringWriter plPgSql) {
plPgSql.writeLn();
plPgSql.writeLn("--//");
plPgSql.writeLn();
}

View File

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