From c80dfc2fa8117cf39c2fa7f1455c0439b0273575 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 5 Apr 2024 09:54:39 +0200 Subject: [PATCH] cleaner version with conditional PostgreSQL generation (without DEBITOR-case) --- .../relation/HsOfficeRelationEntity.java | 6 +- .../rbac/rbacdef/ConditionGenerator.java | 75 ---------------- .../hsadminng/rbac/rbacdef/RbacView.java | 87 +++++++++++++++---- .../RolesGrantsAndPermissionsGenerator.java | 7 +- .../5033-hs-office-relation-rbac.sql | 38 ++++---- .../rbacdef/ConditionGeneratorUnitTest.java | 46 ---------- 6 files changed, 88 insertions(+), 171 deletions(-) delete mode 100644 src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/ConditionGenerator.java delete mode 100644 src/test/java/net/hostsharing/hsadminng/rbac/rbacdef/ConditionGeneratorUnitTest.java diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java index e18a8cd4..33a60f31 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java @@ -15,9 +15,9 @@ import jakarta.persistence.Column; import java.io.IOException; import java.util.UUID; -import static net.hostsharing.hsadminng.rbac.rbacdef.ConditionGenerator.CaseDef.inCaseOf; -import static net.hostsharing.hsadminng.rbac.rbacdef.ConditionGenerator.CaseDef.inOtherCases; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.*; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inCaseOf; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inOtherCases; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; @@ -127,7 +127,7 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable { with.permission(SELECT); }); }), - inCaseOf("DEBITOR", then -> {}), + // inCaseOf("DEBITOR", then -> {}), TODO.spec: needs to be defined inOtherCases(then -> { then.createRole(OWNER, (with) -> { with.owningUser(CREATOR); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/ConditionGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/ConditionGenerator.java deleted file mode 100644 index 0ea70b02..00000000 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/ConditionGenerator.java +++ /dev/null @@ -1,75 +0,0 @@ -package net.hostsharing.hsadminng.rbac.rbacdef; - -import java.util.Objects; -import java.util.Set; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import static java.lang.String.join; -import static java.util.Optional.ofNullable; - -public class ConditionGenerator { - - private final String discriminatorColumName; - private final Set allCases; - - public ConditionGenerator(final String discriminatorColumName, final Set allCases) { - this.discriminatorColumName = discriminatorColumName; - this.allCases = allCases; - } - - public String generatePlPgSql(final Set forCases) { - if (forCases.size() == 1) { - if (forCases.iterator().next().isDefaultCase()) { - final var nonDefaultCases = allCases.stream().filter(c -> !c.isDefaultCase()).map(c -> c.val).toList(); - if (nonDefaultCases.size() > 1) { - return discriminatorColumName + " not in ('" + join("', '", nonDefaultCases) + "')"; - } - return discriminatorColumName + " <> '" + nonDefaultCases.getFirst() + "'"; - } - return discriminatorColumName + " = '" + forCases.iterator().next().val + "'"; - } - return discriminatorColumName + " in ('" + forCases.stream().map(c -> c.val).collect(Collectors.joining("', '")) + "')"; - } - - - - public static class CaseDef { - - final String val; - final Consumer def; - - private CaseDef(final String discriminatorColumnValue, final Consumer def) { - this.val = discriminatorColumnValue; - this.def = def; - } - - - public static CaseDef inCaseOf(final String discriminatorColumnValue, final Consumer def) { - return new CaseDef(discriminatorColumnValue, def); - } - - public static CaseDef inOtherCases(final Consumer def) { - return new CaseDef(null, def); - } - - @Override - public int hashCode() { - return ofNullable(val).map(String::hashCode).orElse(0); - } - - @Override - public boolean equals(final Object other) { - if (this == other) - return true; - if (other == null || getClass() != other.getClass()) - return false; - final CaseDef caseDef = (CaseDef) other; - return Objects.equals(val, caseDef.val); - } - - boolean isDefaultCase() { - return val == null; - } - } -} 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 d5a225c5..171a3b64 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -13,7 +13,6 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity; -import net.hostsharing.hsadminng.rbac.rbacdef.ConditionGenerator.CaseDef; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.hsadminng.test.cust.TestCustomerEntity; import net.hostsharing.hsadminng.test.dom.TestDomainEntity; @@ -27,6 +26,7 @@ import java.lang.reflect.Method; import java.nio.file.Path; import java.util.*; import java.util.function.Consumer; +import java.util.stream.Collectors; import java.util.stream.Stream; import static java.lang.reflect.Modifier.isStatic; @@ -483,9 +483,12 @@ public class RbacView { public void generateWithBaseFileName(final String baseFileName) { if (allCases.size() > 1) { - allCases.forEach(c -> { - final var fileName = baseFileName + (c.isDefaultCase() ? "" : "-" + c.val) + ".md"; - new RbacViewMermaidFlowchartGenerator(this, c).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, fileName)); + allCases.forEach(caseDef -> { + if ( caseDef.isDefaultCase() ) { // FIXME remove the condition + final var fileName = baseFileName + (caseDef.isDefaultCase() ? "" : "-" + caseDef.val) + ".md"; + new RbacViewMermaidFlowchartGenerator(this, caseDef) + .generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, fileName)); + } }); } new RbacViewMermaidFlowchartGenerator(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + ".md")); @@ -532,14 +535,14 @@ public class RbacView { @Override public String toString() { - final var arrow = isConditional() - ? (isAssumed() ? " -- ?? --> " : " -- ?//? --> ") - : (isAssumed() ? " --> " : " -- // --> "); - return switch (grantType()) { + final var arrow = isAssumed() ? " --> " : " -- // --> "; + final var grant = switch (grantType()) { case ROLE_TO_USER -> userDef.toString() + arrow + subRoleDef.toString(); case ROLE_TO_ROLE -> superRoleDef + arrow + subRoleDef; case PERM_TO_ROLE -> superRoleDef + arrow + permDef; }; + final var condition = isConditional() ? (" " +forCases.stream().map(CaseDef::toString).collect(Collectors.joining("||"))) : ""; + return grant + condition; } RbacGrantDefinition(final RbacRoleDefinition subRoleDef, final RbacRoleDefinition superRoleDef, final CaseDef forCase) { @@ -547,7 +550,7 @@ public class RbacView { this.subRoleDef = subRoleDef; this.superRoleDef = superRoleDef; this.permDef = null; - this.forCases = hashSet(forCase); + this.forCases = forCase != null ? hashSet(forCase) : null; register(this); } @@ -589,20 +592,19 @@ public class RbacView { } boolean isConditional() { - return !forCases.isEmpty() && forCases.size() g.subRoleDef == subRoleDefinition && g.superRoleDef == superRoleDefinition) .findFirst() .map(g -> g.forCase(processingCase)) .orElseGet(() -> new RbacGrantDefinition(subRoleDefinition, superRoleDefinition, processingCase)); + return distinctGrantDef; } record EntityAlias(String aliasName, Class entityClass, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) { @@ -1124,6 +1127,52 @@ public class RbacView { } } + public static class CaseDef { + + final String val; + final Consumer def; + + private CaseDef(final String discriminatorColumnValue, final Consumer def) { + this.val = discriminatorColumnValue; + this.def = def; + } + + + public static CaseDef inCaseOf(final String discriminatorColumnValue, final Consumer def) { + return new CaseDef(discriminatorColumnValue, def); + } + + public static CaseDef inOtherCases(final Consumer def) { + return new CaseDef(null, def); + } + + @Override + public int hashCode() { + return ofNullable(val).map(String::hashCode).orElse(0); + } + + @Override + public boolean equals(final Object other) { + if (this == other) + return true; + if (other == null || getClass() != other.getClass()) + return false; + final CaseDef caseDef = (CaseDef) other; + return Objects.equals(val, caseDef.val); + } + + boolean isDefaultCase() { + return val == null; + } + + @Override + public String toString() { + return isDefaultCase() + ? "inOtherCases" + : "inCaseOf:" + val; + } + } + private static void generateRbacView(final Class c) { final Method mainMethod = stream(c.getMethods()).filter( m -> isStatic(m.getModifiers()) && m.getName().equals("main") 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 c755d1c5..91a312bf 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java @@ -1,6 +1,6 @@ package net.hostsharing.hsadminng.rbac.rbacdef; -import net.hostsharing.hsadminng.rbac.rbacdef.ConditionGenerator.CaseDef; +import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition; @@ -198,7 +198,7 @@ class RolesGrantsAndPermissionsGenerator { final var ifOrElsIf = new AtomicReference<>("IF "); rbacDef.getAllCases().forEach(caseDef -> { if (caseDef.val != null) { - plPgSql.writeLn(ifOrElsIf + rbacDef.getDiscriminatorColumName() + " = '" + caseDef.val + "' THEN"); + plPgSql.writeLn(ifOrElsIf + "NEW." + rbacDef.getDiscriminatorColumName() + " = '" + caseDef.val + "' THEN"); } else { plPgSql.writeLn("ELSE"); } @@ -401,11 +401,8 @@ class RolesGrantsAndPermissionsGenerator { .replace("${roleSuffix}", capitalize(role.name()))); generatePermissionsForRole(plPgSql, role); - generateIncomingSuperRolesForRole(plPgSql, role); - generateOutgoingSubRolesForRole(plPgSql, role); - generateUserGrantsForRole(plPgSql, role); plPgSql.chopTail(",\n"); diff --git a/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac.sql b/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac.sql index 50cf960e..75adaeb4 100644 --- a/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac.sql +++ b/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac.sql @@ -50,49 +50,41 @@ begin perform createRoleWithGrants( hsOfficeRelationOWNER(NEW), permissions => array['DELETE'], + incomingSuperRoles => array[globalADMIN()], userUuids => array[currentUserUuid()] ); perform createRoleWithGrants( hsOfficeRelationADMIN(NEW), - permissions => array['UPDATE'] + permissions => array['UPDATE'], + incomingSuperRoles => array[hsOfficeRelationOWNER(NEW)] ); perform createRoleWithGrants( - hsOfficeRelationAGENT(NEW) + hsOfficeRelationAGENT(NEW), + incomingSuperRoles => array[hsOfficeRelationADMIN(NEW)] ); perform createRoleWithGrants( hsOfficeRelationTENANT(NEW), - permissions => array['SELECT'] + permissions => array['SELECT'], + incomingSuperRoles => array[ + hsOfficeContactADMIN(newContact), + hsOfficePersonADMIN(newHolderPerson), + hsOfficeRelationAGENT(NEW)], + outgoingSubRoles => array[ + hsOfficeContactREFERRER(newContact), + hsOfficePersonREFERRER(newAnchorPerson), + hsOfficePersonREFERRER(newHolderPerson)] ); - IF type = 'REPRESENTATIVE' THEN - call grantRoleToRole(hsOfficeContactREFERRER(newContact), hsOfficeRelationTENANT(NEW)); + IF NEW.type = 'REPRESENTATIVE' THEN call grantRoleToRole(hsOfficePersonOWNER(newAnchorPerson), hsOfficeRelationADMIN(NEW)); - call grantRoleToRole(hsOfficePersonREFERRER(newAnchorPerson), hsOfficeRelationTENANT(NEW)); - call grantRoleToRole(hsOfficePersonREFERRER(newHolderPerson), hsOfficeRelationTENANT(NEW)); - call grantRoleToRole(hsOfficeRelationADMIN(NEW), hsOfficeRelationOWNER(NEW)); call grantRoleToRole(hsOfficeRelationAGENT(NEW), hsOfficePersonADMIN(newAnchorPerson)); - call grantRoleToRole(hsOfficeRelationAGENT(NEW), hsOfficeRelationADMIN(NEW)); - call grantRoleToRole(hsOfficeRelationOWNER(NEW), globalAdmin()); call grantRoleToRole(hsOfficeRelationOWNER(NEW), hsOfficePersonADMIN(newHolderPerson)); - call grantRoleToRole(hsOfficeRelationTENANT(NEW), hsOfficeContactADMIN(newContact)); - call grantRoleToRole(hsOfficeRelationTENANT(NEW), hsOfficePersonADMIN(newHolderPerson)); - call grantRoleToRole(hsOfficeRelationTENANT(NEW), hsOfficeRelationAGENT(NEW)); - ELSIF type = 'DEBITOR' THEN ELSE - call grantRoleToRole(hsOfficeContactREFERRER(newContact), hsOfficeRelationTENANT(NEW)); - call grantRoleToRole(hsOfficePersonREFERRER(newAnchorPerson), hsOfficeRelationTENANT(NEW)); - call grantRoleToRole(hsOfficePersonREFERRER(newHolderPerson), hsOfficeRelationTENANT(NEW)); call grantRoleToRole(hsOfficeRelationADMIN(NEW), hsOfficePersonADMIN(newAnchorPerson)); - call grantRoleToRole(hsOfficeRelationADMIN(NEW), hsOfficeRelationOWNER(NEW)); call grantRoleToRole(hsOfficeRelationAGENT(NEW), hsOfficePersonADMIN(newHolderPerson)); - call grantRoleToRole(hsOfficeRelationAGENT(NEW), hsOfficeRelationADMIN(NEW)); - call grantRoleToRole(hsOfficeRelationOWNER(NEW), globalAdmin()); - call grantRoleToRole(hsOfficeRelationTENANT(NEW), hsOfficeContactADMIN(newContact)); - call grantRoleToRole(hsOfficeRelationTENANT(NEW), hsOfficePersonADMIN(newHolderPerson)); - call grantRoleToRole(hsOfficeRelationTENANT(NEW), hsOfficeRelationAGENT(NEW)); END IF; call leaveTriggerForObjectUuid(NEW.uuid); diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacdef/ConditionGeneratorUnitTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacdef/ConditionGeneratorUnitTest.java deleted file mode 100644 index 030a496f..00000000 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacdef/ConditionGeneratorUnitTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package net.hostsharing.hsadminng.rbac.rbacdef; - -import net.hostsharing.hsadminng.rbac.rbacdef.ConditionGenerator.CaseDef; -import org.junit.jupiter.api.Test; - -import java.util.function.Consumer; - -import static net.hostsharing.hsadminng.rbac.rbacdef.ConditionGenerator.CaseDef.inCaseOf; -import static net.hostsharing.hsadminng.rbac.rbacdef.ConditionGenerator.CaseDef.inOtherCases; -import static org.apache.commons.collections4.SetUtils.hashSet; -import static org.assertj.core.api.Assertions.assertThat; - -class ConditionGeneratorUnitTest { - - static final Consumer CONSUMER_FAKE = null; - static final CaseDef CASE_A = inCaseOf("A", CONSUMER_FAKE); - static final CaseDef CASE_B = inCaseOf("B", CONSUMER_FAKE); - static final CaseDef DEFAULT_CASE = inOtherCases(CONSUMER_FAKE); - - static final ConditionGenerator GENERATOR_WITH_A_B_AND_DEFAULT = - new ConditionGenerator("someCol", hashSet(CASE_A, CASE_B, DEFAULT_CASE)); - - @Test - void onlyCase_A_from_A_B_and_DEFAULT() { - assertThat(GENERATOR_WITH_A_B_AND_DEFAULT.generatePlPgSql(hashSet(CASE_A))) - .isEqualTo("someCol = 'A'"); - } - - @Test - void case_A_and_B_from_A_B_and_DEFAULT() { - assertThat(GENERATOR_WITH_A_B_AND_DEFAULT.generatePlPgSql(hashSet(CASE_A, CASE_B))) - .isEqualTo("someCol in ('A', 'B')"); - } - - @Test - void case_DEFAULT_from_A_B_and_DEFAULT() { - assertThat(GENERATOR_WITH_A_B_AND_DEFAULT.generatePlPgSql(hashSet(DEFAULT_CASE))) - .isEqualTo("someCol not in ('A', 'B')"); - } - - @Test - void case_A_and_DEFAULT_from_A_B_and_DEFAULT() { - assertThat(GENERATOR_WITH_A_B_AND_DEFAULT.generatePlPgSql(hashSet(CASE_A, DEFAULT_CASE))) - .isEqualTo("someCol <> 'B'"); - } -}