From b187c705b1cf7b5e84395399896555bc3050a546 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 1 Mar 2024 18:46:41 +0100 Subject: [PATCH] finally working version for UPDATE-trigger-function with separated INSERT+UPDATE trigger functions --- .../hsadminng/rbac/rbacdef/RbacView.java | 8 +- .../rbacdef/RbacViewPostgresGenerator.java | 2 +- .../RolesGrantsAndPermissionsGenerator.java | 150 ++++++++++-------- .../hsadminng/rbac/rbacdef/StringWriter.java | 20 ++- 4 files changed, 103 insertions(+), 77 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index 00e74c95..f193903d 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -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,10 +521,10 @@ public class RbacView { ? aliasName : uncapitalize(withoutEntitySuffix(entityClass.getSimpleName())); } - } - public static String getRawTableName(final Class entityClass) { - return withoutRvSuffix(entityClass.getAnnotation(Table.class).name()); + String getRawTableName() { + return withoutRvSuffix(entityClass.getAnnotation(Table.class).name()); + } } public static String withoutRvSuffix(final String tableName) { return tableName.substring(0, tableName.length()-"_rv".length()); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java index 75333987..5a4fdd48 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewPostgresGenerator.java @@ -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}. diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java index 887328e8..199cef9d 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java @@ -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; - """); + generateUpdateRolesAndGrantsAfterUpdate(plPgSql); plPgSql.ensureSingleEmptyLine(); }); plPgSql.writeLn("end; $$;"); @@ -117,23 +135,15 @@ class RolesGrantsAndPermissionsGenerator { ea.fetchSql().sql + " into " + entityRefVar(NEW, ea) + ";", with("ref", NEW.name()))); - plPgSql.ensureSingleEmptyLine(); - plPgSql.writeLn("if TG_OP = 'INSERT' then"); + createRolesWithGrantsSql(plPgSql, OWNER); + createRolesWithGrantsSql(plPgSql, ADMIN); + createRolesWithGrantsSql(plPgSql, AGENT); + createRolesWithGrantsSql(plPgSql, TENANT); + createRolesWithGrantsSql(plPgSql, REFERRER); - plPgSql.indented(() -> { - - plPgSql.chopEmptyLines(); - 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(); - }); + generateGrants(plPgSql, ROLE_TO_USER); + generateGrants(plPgSql, ROLE_TO_ROLE); + generateGrants(plPgSql, PERM_TO_ROLE); } private Stream referencedEntityAliases() { @@ -151,29 +161,30 @@ class RolesGrantsAndPermissionsGenerator { private void generateUpdateRolesAndGrantsAfterUpdate(final StringWriter plPgSql) { plPgSql.ensureSingleEmptyLine(); - plPgSql.writeLn("elsif TG_OP = 'UPDATE' then"); - plPgSql.indented(() -> { - - updatableEntityAliases() - .forEach((ea) -> plPgSql.writeLn( + updatableEntityAliases() + .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) - .map(c -> c.column) - .sorted() - .distinct() - .forEach(columnName -> { - plPgSql.writeLn(); - plPgSql.writeLn("if NEW." + columnName + " <> OLD." + columnName + " then"); - plPgSql.indented(() -> { - updateGrantsDependingOn(plPgSql, columnName); - }); - plPgSql.writeLn("end if;"); + updatableEntityAliases() + .map(RbacView.EntityAlias::dependsOnColum) + .map(c -> c.column) + .sorted() + .distinct() + .forEach(columnName -> { + plPgSql.writeLn(); + plPgSql.writeLn("if NEW." + columnName + " <> OLD." + columnName + " then"); + plPgSql.indented(() -> { + updateGrantsDependingOn(plPgSql, columnName); }); - }); + 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(); } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java index 876527dc..00f684e2 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java @@ -90,14 +90,18 @@ public class StringWriter { this.varDefs = varDefs; } - String apply(final String text) { - this.text = text; - 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()); - }); - return this.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); + text = matcher.replaceAll(varDef.value()); + }); + return text; + } catch (Exception exc) { + throw exc; + } } } }