From 44ff30c54af190e9eb101c27524964e999e656d2 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Mon, 8 Apr 2024 11:16:06 +0200 Subject: [PATCH] RBAC generator with conditional grants used for REPRESENTATIVE-Relation (#33) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/33 Reviewed-by: Marc Sandlus --- .../HsOfficeCoopAssetsTransactionEntity.java | 3 - .../office/debitor/HsOfficeDebitorEntity.java | 17 +- .../membership/HsOfficeMembershipEntity.java | 2 - .../office/partner/HsOfficePartnerEntity.java | 10 +- .../relation/HsOfficeRelationEntity.java | 80 +++++--- .../hsadminng/rbac/rbacdef/RbacView.java | 177 +++++++++++++++--- .../RbacViewMermaidFlowchartGenerator.java | 44 +++-- .../RolesGrantsAndPermissionsGenerator.java | 97 +++++++--- .../rbacgrant/RbacGrantsDiagramService.java | 4 +- .../db/changelog/1-rbac/1050-rbac-base.sql | 83 +------- .../changelog/1-rbac/1051-rbac-user-grant.sql | 11 +- .../1-rbac/1057-rbac-role-builder.sql | 3 +- ...-hs-office-relation-rbac-REPRESENTATIVE.md | 102 ++++++++++ .../5033-hs-office-relation-rbac.md | 7 +- .../5033-hs-office-relation-rbac.sql | 60 ++---- .../5043-hs-office-partner-rbac.md | 11 +- .../5043-hs-office-partner-rbac.sql | 18 +- .../5063-hs-office-debitor-rbac.md | 10 +- .../5073-hs-office-sepamandate-rbac.md | 5 +- .../5103-hs-office-membership-rbac.md | 5 +- .../5113-hs-office-coopshares-rbac.md | 5 +- .../5123-hs-office-coopassets-rbac.md | 5 +- ...fficeDebitorRepositoryIntegrationTest.java | 3 +- ...ceMembershipRepositoryIntegrationTest.java | 1 + .../hs/office/migration/ImportOfficeData.java | 92 +++++---- ...fficePartnerRepositoryIntegrationTest.java | 62 +++--- ...fficeRelationControllerAcceptanceTest.java | 2 +- ...ficeRelationRepositoryIntegrationTest.java | 15 +- .../test/ContextBasedTestWithCleanup.java | 8 +- 29 files changed, 567 insertions(+), 375 deletions(-) create mode 100644 src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac-REPRESENTATIVE.md diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java index 47fd03a6..49de8f08 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java @@ -10,7 +10,6 @@ import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; -import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; import org.hibernate.annotations.GenericGenerator; @@ -25,8 +24,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import java.io.IOException; -import java.io.IOException; -import java.io.IOException; import java.math.BigDecimal; import java.time.LocalDate; import java.util.Optional; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java index 08c70f66..509cb165 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java @@ -1,6 +1,10 @@ package net.hostsharing.hsadminng.hs.office.debitor; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; @@ -15,7 +19,13 @@ import org.hibernate.annotations.JoinFormula; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import jakarta.validation.constraints.Pattern; import java.io.IOException; import java.util.UUID; @@ -26,6 +36,7 @@ import static jakarta.persistence.CascadeType.PERSIST; import static jakarta.persistence.CascadeType.REFRESH; import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; @@ -157,6 +168,8 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable { .toRole("global", ADMIN).grantPermission(INSERT) .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, + // TODO.spec: do we need a distinct case for DEBITOR-Relation? + usingDefaultCase(), directlyFetchedByDependsOnColumn(), dependsOnColumn("debitorRelUuid")) .createPermission(DELETE).grantedTo("debitorRel", OWNER) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java index c486dc92..26c5706a 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java @@ -29,8 +29,6 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.ADMIN; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.AGENT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.OWNER; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java index 43b78fca..6b019f62 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java @@ -29,6 +29,7 @@ import java.util.UUID; import static jakarta.persistence.CascadeType.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; @@ -98,18 +99,19 @@ public class HsOfficePartnerEntity implements Stringifyable, RbacObject { .toRole("global", ADMIN).grantPermission(INSERT) .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, + usingDefaultCase(), directlyFetchedByDependsOnColumn(), dependsOnColumn("partnerRelUuid")) - .createPermission(DELETE).grantedTo("partnerRel", ADMIN) - .createPermission(UPDATE).grantedTo("partnerRel", AGENT) + .createPermission(DELETE).grantedTo("partnerRel", OWNER) + .createPermission(UPDATE).grantedTo("partnerRel", ADMIN) .createPermission(SELECT).grantedTo("partnerRel", TENANT) .importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class, directlyFetchedByDependsOnColumn(), dependsOnColumn("detailsUuid")) - .createPermission("partnerDetails", DELETE).grantedTo("partnerRel", ADMIN) + .createPermission("partnerDetails", DELETE).grantedTo("partnerRel", OWNER) .createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT) - .createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); + .createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); // not TENANT! } public static void main(String[] args) throws IOException { 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 8d6c6fe8..1dbed5cc 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 @@ -11,17 +11,19 @@ import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; import jakarta.persistence.*; +import jakarta.persistence.Column; import java.io.IOException; import java.util.UUID; +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.GLOBAL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @Entity @@ -101,31 +103,55 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable { dependsOnColumn("contactUuid"), directlyFetchedByDependsOnColumn(), NOT_NULL) - .createRole(OWNER, (with) -> { - with.owningUser(CREATOR); - with.incomingSuperRole(GLOBAL, ADMIN); - // TODO: if type=REPRESENTATIIVE - // with.incomingSuperRole("holderPerson", ADMIN); - with.permission(DELETE); - }) - .createSubRole(ADMIN, (with) -> { - with.incomingSuperRole("anchorPerson", ADMIN); - // TODO: if type=REPRESENTATIIVE - // with.outgoingSuperRole("anchorPerson", OWNER); - with.permission(UPDATE); - }) - .createSubRole(AGENT, (with) -> { - with.incomingSuperRole("holderPerson", ADMIN); - }) - .createSubRole(TENANT, (with) -> { - with.incomingSuperRole("holderPerson", ADMIN); - with.incomingSuperRole("contact", ADMIN); - with.outgoingSubRole("anchorPerson", REFERRER); - with.outgoingSubRole("holderPerson", REFERRER); - with.outgoingSubRole("contact", REFERRER); - with.permission(SELECT); - }) - + .switchOnColumn("type", + inCaseOf("REPRESENTATIVE", then -> { + then.createRole(OWNER, (with) -> { + with.owningUser(CREATOR); + with.incomingSuperRole(GLOBAL, ADMIN); + with.incomingSuperRole("holderPerson", ADMIN); + with.permission(DELETE); + }) + .createSubRole(ADMIN, (with) -> { + with.outgoingSubRole("anchorPerson", OWNER); + with.permission(UPDATE); + }) + .createSubRole(AGENT, (with) -> { + with.incomingSuperRole("anchorPerson", ADMIN); + }) + .createSubRole(TENANT, (with) -> { + with.incomingSuperRole("contact", ADMIN); + with.outgoingSubRole("anchorPerson", REFERRER); + with.outgoingSubRole("holderPerson", REFERRER); + with.outgoingSubRole("contact", REFERRER); + with.permission(SELECT); + }); + }), + // inCaseOf("DEBITOR", then -> {}), TODO.spec: needs to be defined + inOtherCases(then -> { + then.createRole(OWNER, (with) -> { + with.owningUser(CREATOR); + with.incomingSuperRole(GLOBAL, ADMIN); + with.incomingSuperRole("anchorPerson", ADMIN); + with.permission(DELETE); + }) + .createSubRole(ADMIN, (with) -> { + with.permission(UPDATE); + }) + .createSubRole(AGENT, (with) -> { + // TODO.spec: we need relation:PROXY, to allow changing the relation contact. + // the alternative would be to move this to the relation:ADMIN role, + // but then the partner holder person could update the partner relation itself, + // see partner entity. + with.incomingSuperRole("holderPerson", ADMIN); + }) + .createSubRole(TENANT, (with) -> { + with.incomingSuperRole("contact", ADMIN); + with.outgoingSubRole("anchorPerson", REFERRER); + with.outgoingSubRole("holderPerson", REFERRER); + with.outgoingSubRole("contact", REFERRER); + with.permission(SELECT); + }); + })) .toRole("anchorPerson", ADMIN).grantPermission(INSERT); } 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 cb048455..d052b958 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -14,7 +14,6 @@ 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.rbacobject.RbacObject; -import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.hsadminng.test.cust.TestCustomerEntity; import net.hostsharing.hsadminng.test.dom.TestDomainEntity; import net.hostsharing.hsadminng.test.pac.TestPackageEntity; @@ -27,18 +26,22 @@ 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; import static java.util.Arrays.stream; import static java.util.Optional.ofNullable; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.Part.AUTO_FETCH; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; +import static org.apache.commons.collections4.SetUtils.hashSet; import static org.apache.commons.lang3.StringUtils.uncapitalize; @Getter +// TODO.refa: rename to RbacDSL public class RbacView { public static final String GLOBAL = "global"; @@ -61,11 +64,23 @@ public class RbacView { }; private final Set updatableColumns = new LinkedHashSet<>(); private final Set grantDefs = new LinkedHashSet<>(); + private final Set allCases = new LinkedHashSet<>(); + private String discriminatorColumName; + private CaseDef processingCase; private SQL identityViewSqlQuery; private SQL orderBySqlExpression; private EntityAlias rootEntityAliasProxy; private RbacRoleDefinition previousRoleDef; + private final Map cases = new LinkedHashMap<>() { + @Override + public CaseDef put(final String key, final CaseDef value) { + if (containsKey(key)) { + throw new IllegalArgumentException("duplicate case: " + key); + } + return super.put(key, value); + } + }; /** Crates an RBAC definition template for the given entity class and defining the given alias. * @@ -239,7 +254,11 @@ public class RbacView { } private RbacPermissionDefinition createPermission(final EntityAlias entityAlias, final Permission permission) { - return new RbacPermissionDefinition(entityAlias, permission, null, true); + return permDefs.stream() + .filter(p -> p.permission == permission && p.entityAlias == entityAlias) + .findFirst() + // .map(g -> g.forCase(processingCase)) TODO.impl: not implemented case dependent + .orElseGet(() -> new RbacPermissionDefinition(entityAlias, permission, null, true)); } public RbacView declarePlaceholderEntityAliases(final String... aliasNames) { @@ -278,12 +297,13 @@ public class RbacView { public RbacView importRootEntityAliasProxy( final String aliasName, final Class entityClass, + final ColumnValue forCase, final SQL fetchSql, final Column dependsOnColum) { if (rootEntityAliasProxy != null) { throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy); } - rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL); + rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, forCase, fetchSql, dependsOnColum, false, NOT_NULL); return this; } @@ -302,7 +322,7 @@ public class RbacView { public RbacView importSubEntityAlias( final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) { - importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, true, NOT_NULL); + importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, true, NOT_NULL); return this; } @@ -336,25 +356,17 @@ public class RbacView { public RbacView importEntityAlias( final String aliasName, final Class entityClass, final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) { - importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, nullable); - return this; - } - - // TODO: remove once it's not used in HsOffice...Entity anymore - public RbacView importEntityAlias( - final String aliasName, final Class entityClass, - final Column dependsOnColum) { - importEntityAliasImpl(aliasName, entityClass, directlyFetchedByDependsOnColumn(), dependsOnColum, false, null); + importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, false, nullable); return this; } private EntityAlias importEntityAliasImpl( - final String aliasName, final Class entityClass, + final String aliasName, final Class entityClass, final ColumnValue forCase, final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity, final Nullable nullable) { final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum, asSubEntity, nullable); entityAliases.put(aliasName, entityAlias); try { - importAsAlias(aliasName, rbacDefinition(entityClass), asSubEntity); + importAsAlias(aliasName, rbacDefinition(entityClass), forCase, asSubEntity); } catch (final ReflectiveOperationException exc) { throw new RuntimeException("cannot import entity: " + entityClass, exc); } @@ -366,7 +378,7 @@ public class RbacView { return (RbacView) entityClass.getMethod("rbac").invoke(null); } - private RbacView importAsAlias(final String aliasName, final RbacView importedRbacView, final boolean asSubEntity) { + private RbacView importAsAlias(final String aliasName, final RbacView importedRbacView, final ColumnValue forCase, final boolean asSubEntity) { final var mapper = new AliasNameMapper(importedRbacView, aliasName, asSubEntity ? entityAliases.keySet() : null); importedRbacView.getEntityAliases().values().stream() @@ -381,7 +393,8 @@ public class RbacView { new RbacRoleDefinition(findEntityAlias(mapper.map(roleDef.entityAlias.aliasName)), roleDef.role); }); importedRbacView.getGrantDefs().forEach(grantDef -> { - if (grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE) { + if ( grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE && + (grantDef.forCases == null || grantDef.matchesCase(forCase)) ) { final var importedGrantDef = findOrCreateGrantDef( findRbacRole( mapper.map(grantDef.getSubRoleDef().entityAlias.aliasName), @@ -398,6 +411,18 @@ public class RbacView { return this; } + public RbacView switchOnColumn(final String discriminatorColumName, final CaseDef... caseDefs) { + this.discriminatorColumName = discriminatorColumName; + allCases.addAll(stream(caseDefs).toList()); + + stream(caseDefs).forEach(caseDef -> { + this.processingCase = caseDef; + caseDef.def.accept(this); + this.processingCase = null; + }); + return this; + } + private void verifyVersionColumnExists() { if (stream(rootEntityAlias.entityClass.getDeclaredFields()) .noneMatch(f -> f.getAnnotation(Version.class) != null)) { @@ -456,7 +481,15 @@ public class RbacView { } public void generateWithBaseFileName(final String baseFileName) { - new RbacViewMermaidFlowchartGenerator(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + ".md")); + if (allCases.size() > 1) { + allCases.forEach(caseDef -> { + final var fileName = baseFileName + (caseDef.isDefaultCase() ? "" : "-" + caseDef.value) + ".md"; + new RbacViewMermaidFlowchartGenerator(this, caseDef) + .generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, fileName)); + }); + } else { + new RbacViewMermaidFlowchartGenerator(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + ".md")); + } new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + ".sql")); } @@ -496,22 +529,28 @@ public class RbacView { private final RbacPermissionDefinition permDef; private boolean assumed = true; private boolean toCreate = false; + private Set forCases = new HashSet<>(); @Override public String toString() { final var arrow = isAssumed() ? " --> " : " -- // --> "; - return switch (grantType()) { + 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) { + RbacGrantDefinition(final RbacRoleDefinition subRoleDef, final RbacRoleDefinition superRoleDef, final CaseDef forCase) { this.userDef = null; this.subRoleDef = subRoleDef; this.superRoleDef = superRoleDef; this.permDef = null; + this.forCases = forCase != null ? hashSet(forCase) : null; register(this); } @@ -537,7 +576,7 @@ public class RbacView { @NotNull GrantType grantType() { - return permDef != null ? GrantType.PERM_TO_ROLE + return permDef != null ? PERM_TO_ROLE : userDef != null ? GrantType.ROLE_TO_USER : GrantType.ROLE_TO_ROLE; } @@ -546,6 +585,23 @@ public class RbacView { return assumed; } + + RbacGrantDefinition forCase(final CaseDef processingCase) { + forCases.add(processingCase); + return this; + } + + boolean isConditional() { + return forCases != null && !forCases.isEmpty() && forCases.size() c.isCase(requestedCase)); + return noCasesDefined || generateForAllCases || isGrantedForRequestedCase; + } + boolean isToCreate() { return toCreate; } @@ -567,8 +623,9 @@ public class RbacView { .orElse(false); } - public void unassumed() { + public RbacGrantDefinition unassumed() { this.assumed = false; + return this; } public enum GrantType { @@ -794,7 +851,7 @@ public class RbacView { private RbacGrantDefinition findOrCreateGrantDef(final RbacPermissionDefinition permDef, final RbacRoleDefinition roleDef) { return grantDefs.stream() - .filter(g -> g.permDef == permDef && g.subRoleDef == roleDef) + .filter(g -> g.permDef == permDef && g.superRoleDef == roleDef) .findFirst() .orElseGet(() -> new RbacGrantDefinition(permDef, roleDef)); } @@ -802,10 +859,12 @@ public class RbacView { private RbacGrantDefinition findOrCreateGrantDef( final RbacRoleDefinition subRoleDefinition, final RbacRoleDefinition superRoleDefinition) { - return grantDefs.stream() + final var distinctGrantDef = grantDefs.stream() .filter(g -> g.subRoleDef == subRoleDefinition && g.superRoleDef == superRoleDefinition) .findFirst() - .orElseGet(() -> new RbacGrantDefinition(subRoleDefinition, superRoleDefinition)); + .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) { @@ -1022,6 +1081,23 @@ public class RbacView { } } + public static class ColumnValue { + + public static ColumnValue usingDefaultCase() { + return new ColumnValue(null); + } + + public static ColumnValue usingCase(final String value) { + return new ColumnValue(value); + } + + public final String value; + + private ColumnValue(final String value) { + this.value = value; + } + } + private static class AliasNameMapper { private final RbacView importedRbacView; @@ -1046,6 +1122,55 @@ public class RbacView { } } + public static class CaseDef extends ColumnValue { + + final Consumer def; + + private CaseDef(final String discriminatorColumnValue, final Consumer def) { + super(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(value).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(value, caseDef.value); + } + + boolean isDefaultCase() { + return value == null; + } + + @Override + public String toString() { + return isDefaultCase() + ? "inOtherCases" + : "inCaseOf:" + value; + } + + public boolean isCase(final ColumnValue requestedCase) { + return Objects.equals(requestedCase.value, this.value); + } + } + 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/RbacViewMermaidFlowchartGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java index c6e775c9..96a956e5 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java @@ -1,6 +1,7 @@ package net.hostsharing.hsadminng.rbac.rbacdef; import lombok.SneakyThrows; +import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef; import org.apache.commons.lang3.StringUtils; import java.nio.file.*; @@ -15,10 +16,13 @@ public class RbacViewMermaidFlowchartGenerator { public static final String HOSTSHARING_DARK_BLUE = "#274d6e"; public static final String HOSTSHARING_LIGHT_BLUE = "#99bcdb"; private final RbacView rbacDef; + + private final CaseDef forCase; private final StringWriter flowchart = new StringWriter(); - public RbacViewMermaidFlowchartGenerator(final RbacView rbacDef) { + public RbacViewMermaidFlowchartGenerator(final RbacView rbacDef, final CaseDef forCase) { this.rbacDef = rbacDef; + this.forCase = forCase; flowchart.writeLn(""" %%{init:{'flowchart':{'htmlLabels':false}}}%% flowchart TB @@ -26,6 +30,10 @@ public class RbacViewMermaidFlowchartGenerator { renderEntitySubgraphs(); renderGrants(); } + + public RbacViewMermaidFlowchartGenerator(final RbacView rbacDef) { + this(rbacDef, null); + } private void renderEntitySubgraphs() { rbacDef.getEntityAliases().values().stream() .filter(entityAlias -> !rbacDef.isEntityAliasProxy(entityAlias)) @@ -99,6 +107,7 @@ public class RbacViewMermaidFlowchartGenerator { private void renderGrants(final RbacView.RbacGrantDefinition.GrantType grantType, final String comment) { final var grantsOfRequestedType = rbacDef.getGrantDefs().stream() .filter(g -> g.grantType() == grantType) + .filter(this::isToBeRenderedInThisGraph) .toList(); if ( !grantsOfRequestedType.isEmpty()) { flowchart.ensureSingleEmptyLine(); @@ -107,10 +116,19 @@ public class RbacViewMermaidFlowchartGenerator { } } + private boolean isToBeRenderedInThisGraph(final RbacView.RbacGrantDefinition g) { + if ( g.grantType() != ROLE_TO_ROLE ) + return true; + if ( forCase == null && !g.isConditional() ) + return true; + final var isToBeRenderedInThisGraph = g.getForCases() == null || g.getForCases().contains(forCase); + return isToBeRenderedInThisGraph; + } + private String grantDef(final RbacView.RbacGrantDefinition grant) { final var arrow = (grant.isToCreate() ? " ==>" : " -.->") + (grant.isAssumed() ? " " : "|XX| "); - return switch (grant.grantType()) { + final var grantDef = switch (grant.grantType()) { case ROLE_TO_USER -> // TODO: other user types not implemented yet "user:creator" + arrow + roleId(grant.getSubRoleDef()); @@ -118,6 +136,7 @@ public class RbacViewMermaidFlowchartGenerator { roleId(grant.getSuperRoleDef()) + arrow + roleId(grant.getSubRoleDef()); case PERM_TO_ROLE -> roleId(grant.getSuperRoleDef()) + arrow + permId(grant.getPermDef()); }; + return grantDef; } private String permDef(final RbacView.RbacPermissionDefinition perm) { @@ -146,16 +165,17 @@ public class RbacViewMermaidFlowchartGenerator { Files.writeString( path, """ - ### rbac %{entityAlias} - - This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. - - ```mermaid - %{flowchart} - ``` - """ - .replace("%{entityAlias}", rbacDef.getRootEntityAlias().aliasName()) - .replace("%{flowchart}", flowchart.toString()), + ### rbac %{entityAlias}%{case} + + This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + + ```mermaid + %{flowchart} + ``` + """ + .replace("%{entityAlias}", rbacDef.getRootEntityAlias().aliasName()) + .replace("%{flowchart}", flowchart.toString()) + .replace("%{case}", forCase == null ? "" : " " + forCase), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); System.out.println("Markdown-File: " + path.toAbsolutePath()); } 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 484415f2..2089d4d9 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java @@ -1,10 +1,13 @@ package net.hostsharing.hsadminng.rbac.rbacdef; +import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef; +import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import static java.util.Optional.ofNullable; @@ -22,7 +25,7 @@ import static org.apache.commons.lang3.StringUtils.uncapitalize; class RolesGrantsAndPermissionsGenerator { private final RbacView rbacDef; - private final Set rbacGrants = new HashSet<>(); + private final Set rbacGrants = new HashSet<>(); private final String liquibaseTagPrefix; private final String simpleEntityName; private final String simpleEntityVarName; @@ -31,7 +34,7 @@ class RolesGrantsAndPermissionsGenerator { RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) { this.rbacDef = rbacDef; this.rbacGrants.addAll(rbacDef.getGrantDefs().stream() - .filter(RbacView.RbacGrantDefinition::isToCreate) + .filter(RbacGrantDefinition::isToCreate) .collect(toSet())); this.liquibaseTagPrefix = liquibaseTagPrefix; @@ -67,13 +70,11 @@ class RolesGrantsAndPermissionsGenerator { NEW ${rawTableName} ) language plpgsql as $$ - - declare """ .replace("${simpleEntityName}", simpleEntityName) .replace("${rawTableName}", rawTableName)); - plPgSql.chopEmptyLines(); + plPgSql.writeLn("declare"); plPgSql.indented(() -> { referencedEntityAliases() .forEach((ea) -> plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";")); @@ -172,6 +173,10 @@ class RolesGrantsAndPermissionsGenerator { .anyMatch(e -> true); } + private boolean hasAnyConditionalGrants() { + return rbacDef.getGrantDefs().stream().anyMatch(RbacGrantDefinition::isConditional); + } + private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) { referencedEntityAliases() .forEach((ea) -> { @@ -186,7 +191,25 @@ class RolesGrantsAndPermissionsGenerator { createRolesWithGrantsSql(plPgSql, REFERRER); generateGrants(plPgSql, ROLE_TO_USER); + generateGrants(plPgSql, ROLE_TO_ROLE); + if (!rbacDef.getAllCases().isEmpty()) { + plPgSql.writeLn(); + final var ifOrElsIf = new AtomicReference<>("IF "); + rbacDef.getAllCases().forEach(caseDef -> { + if (caseDef.value != null) { + plPgSql.writeLn(ifOrElsIf + "NEW." + rbacDef.getDiscriminatorColumName() + " = '" + caseDef.value + "' THEN"); + } else { + plPgSql.writeLn("ELSE"); + } + plPgSql.indented(() -> { + generateGrants(plPgSql, ROLE_TO_ROLE, caseDef); + }); + ifOrElsIf.set("ELSIF "); + }); + plPgSql.writeLn("END IF;"); + } + generateGrants(plPgSql, PERM_TO_ROLE); } @@ -248,7 +271,7 @@ class RolesGrantsAndPermissionsGenerator { private void updateGrantsDependingOn(final StringWriter plPgSql, final String columnName) { rbacDef.getGrantDefs().stream() - .filter(RbacView.RbacGrantDefinition::isToCreate) + .filter(RbacGrantDefinition::isToCreate) .filter(g -> g.dependsOnColumn(columnName)) .filter(g -> !isInsertPermissionGrant(g)) .forEach(g -> { @@ -259,21 +282,31 @@ class RolesGrantsAndPermissionsGenerator { }); } - private static Boolean isInsertPermissionGrant(final RbacView.RbacGrantDefinition g) { + private static Boolean isInsertPermissionGrant(final RbacGrantDefinition g) { final var isInsertPermissionGrant = ofNullable(g.getPermDef()).map(RbacPermissionDefinition::getPermission).map(p -> p == INSERT).orElse(false); return isInsertPermissionGrant; } - private void generateGrants(final StringWriter plPgSql, final RbacView.RbacGrantDefinition.GrantType grantType) { - plPgSql.ensureSingleEmptyLine(); + private void generateGrants(final StringWriter plPgSql, final RbacGrantDefinition.GrantType grantType, final CaseDef caseDef) { rbacGrants.stream() + .filter(g -> g.matchesCase(caseDef)) .filter(g -> g.grantType() == grantType) .map(this::generateGrant) .sorted() - .forEach(text -> plPgSql.writeLn(text)); + .forEach(text -> plPgSql.writeLn(text, with("ref", NEW.name()))); } - private String generateRevoke(RbacView.RbacGrantDefinition grantDef) { + private void generateGrants(final StringWriter plPgSql, final RbacGrantDefinition.GrantType grantType) { + plPgSql.ensureSingleEmptyLine(); + rbacGrants.stream() + .filter(g -> !g.isConditional()) + .filter(g -> g.grantType() == grantType) + .map(this::generateGrant) + .sorted() + .forEach(text -> plPgSql.writeLn(text, with("ref", NEW.name()))); + } + + private String generateRevoke(RbacGrantDefinition grantDef) { return switch (grantDef.grantType()) { case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant"); case ROLE_TO_ROLE -> "call revokeRoleFromRole(${subRoleRef}, ${superRoleRef});" @@ -285,8 +318,8 @@ class RolesGrantsAndPermissionsGenerator { }; } - private String generateGrant(RbacView.RbacGrantDefinition grantDef) { - return switch (grantDef.grantType()) { + private String generateGrant(RbacGrantDefinition grantDef) { + final var grantSql = switch (grantDef.grantType()) { case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant"); case ROLE_TO_ROLE -> "call grantRoleToRole(${subRoleRef}, ${superRoleRef}${assumed});" .replace("${assumed}", grantDef.isAssumed() ? "" : ", unassumed()") @@ -298,6 +331,7 @@ class RolesGrantsAndPermissionsGenerator { .replace("${permRef}", createPerm(NEW, grantDef.getPermDef())) .replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef())); }; + return grantSql; } private String findPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { @@ -362,11 +396,8 @@ class RolesGrantsAndPermissionsGenerator { .replace("${roleSuffix}", capitalize(role.name()))); generatePermissionsForRole(plPgSql, role); - generateIncomingSuperRolesForRole(plPgSql, role); - generateOutgoingSubRolesForRole(plPgSql, role); - generateUserGrantsForRole(plPgSql, role); plPgSql.chopTail(",\n"); @@ -380,7 +411,7 @@ class RolesGrantsAndPermissionsGenerator { final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role); if (!grantsToUsers.isEmpty()) { final var arrayElements = grantsToUsers.stream() - .map(RbacView.RbacGrantDefinition::getUserDef) + .map(RbacGrantDefinition::getUserDef) .map(this::toPlPgSqlReference) .toList(); plPgSql.indented(() -> @@ -393,7 +424,7 @@ class RolesGrantsAndPermissionsGenerator { final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role); if (!permissionGrantsForRole.isEmpty()) { final var arrayElements = permissionGrantsForRole.stream() - .map(RbacView.RbacGrantDefinition::getPermDef) + .map(RbacGrantDefinition::getPermDef) .map(RbacPermissionDefinition::getPermission) .map(RbacView.Permission::name) .map(p -> "'" + p + "'") @@ -406,26 +437,30 @@ class RolesGrantsAndPermissionsGenerator { } private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) { - final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role); - if (!incomingGrants.isEmpty()) { - final var arrayElements = incomingGrants.stream() + final var unconditionalIncomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream() + .filter(g -> !g.isConditional()) + .toList(); + if (!unconditionalIncomingGrants.isEmpty()) { + final var arrayElements = unconditionalIncomingGrants.stream() .map(g -> toPlPgSqlReference(NEW, g.getSuperRoleDef(), g.isAssumed())) .sorted().toList(); plPgSql.indented(() -> plPgSql.writeLn("incomingSuperRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n")); - rbacGrants.removeAll(incomingGrants); + rbacGrants.removeAll(unconditionalIncomingGrants); } } private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacView.Role role) { - final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role); - if (!outgoingGrants.isEmpty()) { - final var arrayElements = outgoingGrants.stream() + final var unconditionalOutgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream() + .filter(g -> !g.isConditional()) + .toList(); + if (!unconditionalOutgoingGrants.isEmpty()) { + final var arrayElements = unconditionalOutgoingGrants.stream() .map(g -> toPlPgSqlReference(NEW, g.getSubRoleDef(), g.isAssumed())) .sorted().toList(); plPgSql.indented(() -> plPgSql.writeLn("outgoingSubRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n")); - rbacGrants.removeAll(outgoingGrants); + rbacGrants.removeAll(unconditionalOutgoingGrants); } } @@ -435,7 +470,7 @@ class RolesGrantsAndPermissionsGenerator { : arrayElements.stream().collect(joining(",\n\t", "\n\t", "")); } - private Set findPermissionsGrantsForRole( + private Set findPermissionsGrantsForRole( final RbacView.EntityAlias entityAlias, final RbacView.Role role) { final var roleDef = rbacDef.findRbacRole(entityAlias, role); @@ -444,7 +479,7 @@ class RolesGrantsAndPermissionsGenerator { .collect(toSet()); } - private Set findGrantsToUserForRole( + private Set findGrantsToUserForRole( final RbacView.EntityAlias entityAlias, final RbacView.Role role) { final var roleDef = rbacDef.findRbacRole(entityAlias, role); @@ -453,7 +488,7 @@ class RolesGrantsAndPermissionsGenerator { .collect(toSet()); } - private Set findIncomingSuperRolesForRole( + private Set findIncomingSuperRolesForRole( final RbacView.EntityAlias entityAlias, final RbacView.Role role) { final var roleDef = rbacDef.findRbacRole(entityAlias, role); @@ -462,7 +497,7 @@ class RolesGrantsAndPermissionsGenerator { .collect(toSet()); } - private Set findOutgoingSuperRolesForRole( + private Set findOutgoingSuperRolesForRole( final RbacView.EntityAlias entityAlias, final RbacView.Role role) { final var roleDef = rbacDef.findRbacRole(entityAlias, role); @@ -506,7 +541,7 @@ class RolesGrantsAndPermissionsGenerator { private void generateUpdateTrigger(final StringWriter plPgSql) { generateHeader(plPgSql, "update"); - if ( hasAnyUpdatableAndNullableEntityAliases() ) { + if ( hasAnyUpdatableAndNullableEntityAliases() || hasAnyConditionalGrants() ) { generateSimplifiedUpdateTriggerFunction(plPgSql); } else { generateUpdateTriggerFunction(plPgSql); diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java index f8746eb5..f4dc2167 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacgrant/RbacGrantsDiagramService.java @@ -167,7 +167,7 @@ public class RbacGrantsDiagramService { return "users"; } if (refType.equals("perm")) { - return node.idName().split(" ", 4)[3]; + return node.idName().split(":", 3)[1]; } if (refType.equals("role")) { final var withoutRolePrefix = node.idName().substring("role:".length()); @@ -209,7 +209,7 @@ public class RbacGrantsDiagramService { } - class LimitedHashSet extends HashSet { + static class LimitedHashSet extends HashSet { @Override public boolean add(final T t) { diff --git a/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql b/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql index 6a3387fb..2b3147c9 100644 --- a/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql +++ b/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql @@ -248,7 +248,7 @@ declare objectUuidOfRole uuid; roleUuid uuid; begin - -- TODO.refact: extract function toRbacRoleDescriptor(roleIdName varchar) + find other occurrences + -- TODO.refa: extract function toRbacRoleDescriptor(roleIdName varchar) + find other occurrences roleParts = overlay(roleIdName placing '#' from length(roleIdName) + 1 - strpos(reverse(roleIdName), ':')); objectTableFromRoleIdName = split_part(roleParts, '#', 1); objectNameFromRoleIdName = split_part(roleParts, '#', 2); @@ -356,16 +356,13 @@ create trigger deleteRbacRolesOfRbacObject_Trigger /* */ -create domain RbacOp as varchar(67) -- TODO: shorten to 8, once the deprecated values are gone +create domain RbacOp as varchar(6) check ( VALUE = 'DELETE' or VALUE = 'UPDATE' or VALUE = 'SELECT' or VALUE = 'INSERT' or VALUE = 'ASSUME' - -- TODO: all values below are deprecated, use insert with table - or VALUE ~ '^add-[a-z]+$' - or VALUE ~ '^new-[a-z-]+$' ); create table RbacPermission @@ -417,37 +414,6 @@ begin return permissionUuid; end; $$; --- TODO: deprecated, remove and amend all usages to createPermission -create or replace function createPermissions(forObjectUuid uuid, permitOps RbacOp[]) - returns uuid[] - language plpgsql as $$ -declare - refId uuid; - permissionIds uuid[] = array []::uuid[]; -begin - if (forObjectUuid is null) then - raise exception 'forObjectUuid must not be null'; - end if; - - for i in array_lower(permitOps, 1)..array_upper(permitOps, 1) - loop - refId = (select uuid from RbacPermission where objectUuid = forObjectUuid and op = permitOps[i]); - if (refId is null) then - insert - into RbacReference ("type") - values ('RbacPermission') - returning uuid into refId; - insert - into RbacPermission (uuid, objectUuid, op) - values (refId, forObjectUuid, permitOps[i]); - end if; - permissionIds = permissionIds || refId; - end loop; - - return permissionIds; -end; -$$; - create or replace function findEffectivePermissionId(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null) returns uuid returns null on null input @@ -649,25 +615,6 @@ begin end; $$; --- TODO: deprecated, remove and use grantPermissionToRole(...) -create or replace procedure grantPermissionsToRole(roleUuid uuid, permissionIds uuid[]) - language plpgsql as $$ -begin - if cardinality(permissionIds) = 0 then return; end if; - - for i in array_lower(permissionIds, 1)..array_upper(permissionIds, 1) - loop - perform assertReferenceType('roleId (ascendant)', roleUuid, 'RbacRole'); - perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission'); - - insert - into RbacGrants (grantedByTriggerOf, ascendantUuid, descendantUuid, assumed) - values (currentTriggerObjectUuid(), roleUuid, permissionIds[i], true) - on conflict do nothing; -- allow granting multiple times - end loop; -end; -$$; - create or replace procedure grantRoleToRole(subRoleId uuid, superRoleId uuid, doAssume bool = true) language plpgsql as $$ begin @@ -691,7 +638,7 @@ declare superRoleId uuid; subRoleId uuid; begin - -- TODO: maybe separate method grantRoleToRoleIfNotNull(...) for NULLABLE references + -- TODO.refa: maybe separate method grantRoleToRoleIfNotNull(...) for NULLABLE references if superRole.objectUuid is null or subRole.objectuuid is null then return; end if; @@ -712,30 +659,6 @@ begin on conflict do nothing; -- allow granting multiple times end; $$; -create or replace procedure grantRoleToRoleIfNotNull(subRole RbacRoleDescriptor, superRole RbacRoleDescriptor, doAssume bool = true) - language plpgsql as $$ -declare - superRoleId uuid; - subRoleId uuid; -begin - if ( superRoleId is null ) then return; end if; - superRoleId := findRoleId(superRole); - if ( subRoleId is null ) then return; end if; - subRoleId := findRoleId(subRole); - - perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole'); - perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole'); - - if isGranted(subRoleId, superRoleId) then - call raiseDuplicateRoleGrantException(subRoleId, superRoleId); - end if; - - insert - into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed) - values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume) - on conflict do nothing; -- allow granting multiple times -end; $$; - create or replace procedure revokeRoleFromRole(subRole RbacRoleDescriptor, superRole RbacRoleDescriptor) language plpgsql as $$ declare diff --git a/src/main/resources/db/changelog/1-rbac/1051-rbac-user-grant.sql b/src/main/resources/db/changelog/1-rbac/1051-rbac-user-grant.sql index a82865c8..17645ca3 100644 --- a/src/main/resources/db/changelog/1-rbac/1051-rbac-user-grant.sql +++ b/src/main/resources/db/changelog/1-rbac/1051-rbac-user-grant.sql @@ -20,19 +20,18 @@ begin return currentSubjectsUuids[1]; end; $$; -create or replace procedure grantRoleToUserUnchecked(grantedByRoleUuid uuid, roleUuid uuid, userUuid uuid, doAssume boolean = true) +create or replace procedure grantRoleToUserUnchecked(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid, doAssume boolean = true) language plpgsql as $$ begin perform assertReferenceType('grantingRoleUuid', grantedByRoleUuid, 'RbacRole'); - perform assertReferenceType('roleId (descendant)', roleUuid, 'RbacRole'); + perform assertReferenceType('roleId (descendant)', grantedRoleUuid, 'RbacRole'); perform assertReferenceType('userId (ascendant)', userUuid, 'RbacUser'); insert into RbacGrants (grantedByRoleUuid, ascendantUuid, descendantUuid, assumed) - values (grantedByRoleUuid, userUuid, roleUuid, doAssume); - -- TODO.spec: What should happen on multiple grants? What if options (doAssume) are not the same? - -- Most powerful or latest grant wins? What about managed? - -- on conflict do nothing; -- allow granting multiple times + values (grantedByRoleUuid, userUuid, grantedRoleUuid, doAssume) + -- TODO: check if grantedByRoleUuid+doAssume are the same, otherwise raise exception? + on conflict do nothing; -- allow granting multiple times end; $$; create or replace procedure grantRoleToUser(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid, doAssume boolean = true) diff --git a/src/main/resources/db/changelog/1-rbac/1057-rbac-role-builder.sql b/src/main/resources/db/changelog/1-rbac/1057-rbac-role-builder.sql index 57ba3cb7..1e9bd2bc 100644 --- a/src/main/resources/db/changelog/1-rbac/1057-rbac-role-builder.sql +++ b/src/main/resources/db/changelog/1-rbac/1057-rbac-role-builder.sql @@ -6,6 +6,7 @@ --changeset rbac-role-builder-create-role:1 endDelimiter:--// -- ----------------------------------------------------------------- +-- TODO: rename to defineRoleWithGrants because it does not complain if the role already exists create or replace function createRoleWithGrants( roleDescriptor RbacRoleDescriptor, permissions RbacOp[] = array[]::RbacOp[], @@ -28,7 +29,7 @@ declare userUuid uuid; userGrantsByRoleUuid uuid; begin - roleUuid := createRole(roleDescriptor); + roleUuid := coalesce(findRoleId(roleDescriptor), createRole(roleDescriptor)); foreach permission in array permissions loop diff --git a/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac-REPRESENTATIVE.md b/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac-REPRESENTATIVE.md new file mode 100644 index 00000000..e5f608e8 --- /dev/null +++ b/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac-REPRESENTATIVE.md @@ -0,0 +1,102 @@ +### rbac relation inCaseOf:REPRESENTATIVE + +This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. + +```mermaid +%%{init:{'flowchart':{'htmlLabels':false}}}%% +flowchart TB + +subgraph holderPerson["`**holderPerson**`"] + direction TB + style holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph holderPerson:roles[ ] + style holderPerson:roles fill:#99bcdb,stroke:white + + role:holderPerson:OWNER[[holderPerson:OWNER]] + role:holderPerson:ADMIN[[holderPerson:ADMIN]] + role:holderPerson:REFERRER[[holderPerson:REFERRER]] + end +end + +subgraph anchorPerson["`**anchorPerson**`"] + direction TB + style anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph anchorPerson:roles[ ] + style anchorPerson:roles fill:#99bcdb,stroke:white + + role:anchorPerson:OWNER[[anchorPerson:OWNER]] + role:anchorPerson:ADMIN[[anchorPerson:ADMIN]] + role:anchorPerson:REFERRER[[anchorPerson:REFERRER]] + end +end + +subgraph contact["`**contact**`"] + direction TB + style contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px + + subgraph contact:roles[ ] + style contact:roles fill:#99bcdb,stroke:white + + role:contact:OWNER[[contact:OWNER]] + role:contact:ADMIN[[contact:ADMIN]] + role:contact:REFERRER[[contact:REFERRER]] + end +end + +subgraph relation["`**relation**`"] + direction TB + style relation fill:#dd4901,stroke:#274d6e,stroke-width:8px + + subgraph relation:roles[ ] + style relation:roles fill:#dd4901,stroke:white + + role:relation:OWNER[[relation:OWNER]] + role:relation:ADMIN[[relation:ADMIN]] + role:relation:AGENT[[relation:AGENT]] + role:relation:TENANT[[relation:TENANT]] + end + + subgraph relation:permissions[ ] + style relation:permissions fill:#dd4901,stroke:white + + perm:relation:DELETE{{relation:DELETE}} + perm:relation:UPDATE{{relation:UPDATE}} + perm:relation:SELECT{{relation:SELECT}} + perm:relation:INSERT{{relation:INSERT}} + end +end + +%% granting roles to users +user:creator ==> role:relation:OWNER + +%% granting roles to roles +role:global:ADMIN -.-> role:anchorPerson:OWNER +role:anchorPerson:OWNER -.-> role:anchorPerson:ADMIN +role:anchorPerson:ADMIN -.-> role:anchorPerson:REFERRER +role:global:ADMIN -.-> role:holderPerson:OWNER +role:holderPerson:OWNER -.-> role:holderPerson:ADMIN +role:holderPerson:ADMIN -.-> role:holderPerson:REFERRER +role:global:ADMIN -.-> role:contact:OWNER +role:contact:OWNER -.-> role:contact:ADMIN +role:contact:ADMIN -.-> role:contact:REFERRER +role:global:ADMIN ==> role:relation:OWNER +role:holderPerson:ADMIN ==> role:relation:OWNER +role:relation:OWNER ==> role:relation:ADMIN +role:relation:ADMIN ==> role:anchorPerson:OWNER +role:relation:ADMIN ==> role:relation:AGENT +role:anchorPerson:ADMIN ==> role:relation:AGENT +role:relation:AGENT ==> role:relation:TENANT +role:contact:ADMIN ==> role:relation:TENANT +role:relation:TENANT ==> role:anchorPerson:REFERRER +role:relation:TENANT ==> role:holderPerson:REFERRER +role:relation:TENANT ==> role:contact:REFERRER + +%% granting permissions to roles +role:relation:OWNER ==> perm:relation:DELETE +role:relation:ADMIN ==> perm:relation:UPDATE +role:relation:TENANT ==> perm:relation:SELECT +role:anchorPerson:ADMIN ==> perm:relation:INSERT + +``` diff --git a/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac.md b/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac.md index 8014cdaf..4ff19e79 100644 --- a/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac.md +++ b/src/main/resources/db/changelog/5-hs-office/503-relation/5033-hs-office-relation-rbac.md @@ -1,4 +1,4 @@ -### rbac relation +### rbac relation inOtherCases This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. @@ -83,15 +83,14 @@ role:contact:OWNER -.-> role:contact:ADMIN role:contact:ADMIN -.-> role:contact:REFERRER role:global:ADMIN ==> role:relation:OWNER role:relation:OWNER ==> role:relation:ADMIN -role:anchorPerson:ADMIN ==> role:relation:ADMIN role:relation:ADMIN ==> role:relation:AGENT -role:holderPerson:ADMIN ==> role:relation:AGENT role:relation:AGENT ==> role:relation:TENANT -role:holderPerson:ADMIN ==> role:relation:TENANT role:contact:ADMIN ==> role:relation:TENANT role:relation:TENANT ==> role:anchorPerson:REFERRER role:relation:TENANT ==> role:holderPerson:REFERRER role:relation:TENANT ==> role:contact:REFERRER +role:anchorPerson:ADMIN ==> role:relation:OWNER +role:holderPerson:ADMIN ==> role:relation:AGENT %% granting permissions to roles role:relation:OWNER ==> perm:relation:DELETE 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 ff890a59..15114d03 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 @@ -57,16 +57,12 @@ begin perform createRoleWithGrants( hsOfficeRelationADMIN(NEW), permissions => array['UPDATE'], - incomingSuperRoles => array[ - hsOfficePersonADMIN(newAnchorPerson), - hsOfficeRelationOWNER(NEW)] + incomingSuperRoles => array[hsOfficeRelationOWNER(NEW)] ); perform createRoleWithGrants( hsOfficeRelationAGENT(NEW), - incomingSuperRoles => array[ - hsOfficePersonADMIN(newHolderPerson), - hsOfficeRelationADMIN(NEW)] + incomingSuperRoles => array[hsOfficeRelationADMIN(NEW)] ); perform createRoleWithGrants( @@ -74,7 +70,6 @@ begin permissions => array['SELECT'], incomingSuperRoles => array[ hsOfficeContactADMIN(newContact), - hsOfficePersonADMIN(newHolderPerson), hsOfficeRelationAGENT(NEW)], outgoingSubRoles => array[ hsOfficeContactREFERRER(newContact), @@ -82,6 +77,15 @@ begin hsOfficePersonREFERRER(newHolderPerson)] ); + IF NEW.type = 'REPRESENTATIVE' THEN + call grantRoleToRole(hsOfficePersonOWNER(newAnchorPerson), hsOfficeRelationADMIN(NEW)); + call grantRoleToRole(hsOfficeRelationAGENT(NEW), hsOfficePersonADMIN(newAnchorPerson)); + call grantRoleToRole(hsOfficeRelationOWNER(NEW), hsOfficePersonADMIN(newHolderPerson)); + ELSE + call grantRoleToRole(hsOfficeRelationAGENT(NEW), hsOfficePersonADMIN(newHolderPerson)); + call grantRoleToRole(hsOfficeRelationOWNER(NEW), hsOfficePersonADMIN(newAnchorPerson)); + END IF; + call leaveTriggerForObjectUuid(NEW.uuid); end; $$; @@ -118,48 +122,12 @@ create or replace procedure updateRbacRulesForHsOfficeRelation( NEW hs_office_relation ) language plpgsql as $$ - -declare - oldHolderPerson hs_office_person; - newHolderPerson hs_office_person; - oldAnchorPerson hs_office_person; - newAnchorPerson hs_office_person; - oldContact hs_office_contact; - newContact hs_office_contact; - begin - call enterTriggerForObjectUuid(NEW.uuid); - - SELECT * FROM hs_office_person WHERE uuid = OLD.holderUuid INTO oldHolderPerson; - assert oldHolderPerson.uuid is not null, format('oldHolderPerson must not be null for OLD.holderUuid = %s', OLD.holderUuid); - - SELECT * FROM hs_office_person WHERE uuid = NEW.holderUuid INTO newHolderPerson; - assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s', NEW.holderUuid); - - SELECT * FROM hs_office_person WHERE uuid = OLD.anchorUuid INTO oldAnchorPerson; - assert oldAnchorPerson.uuid is not null, format('oldAnchorPerson must not be null for OLD.anchorUuid = %s', OLD.anchorUuid); - - SELECT * FROM hs_office_person WHERE uuid = NEW.anchorUuid INTO newAnchorPerson; - assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s', NEW.anchorUuid); - - SELECT * FROM hs_office_contact WHERE uuid = OLD.contactUuid INTO oldContact; - assert oldContact.uuid is not null, format('oldContact must not be null for OLD.contactUuid = %s', OLD.contactUuid); - - SELECT * FROM hs_office_contact WHERE uuid = NEW.contactUuid INTO newContact; - assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s', NEW.contactUuid); - - - if NEW.contactUuid <> OLD.contactUuid then - - call revokeRoleFromRole(hsOfficeRelationTENANT(OLD), hsOfficeContactADMIN(oldContact)); - call grantRoleToRole(hsOfficeRelationTENANT(NEW), hsOfficeContactADMIN(newContact)); - - call revokeRoleFromRole(hsOfficeContactREFERRER(oldContact), hsOfficeRelationTENANT(OLD)); - call grantRoleToRole(hsOfficeContactREFERRER(newContact), hsOfficeRelationTENANT(NEW)); + if NEW.contactUuid is distinct from OLD.contactUuid then + delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid; + call buildRbacSystemForHsOfficeRelation(NEW); end if; - - call leaveTriggerForObjectUuid(NEW.uuid); end; $$; /* diff --git a/src/main/resources/db/changelog/5-hs-office/504-partner/5043-hs-office-partner-rbac.md b/src/main/resources/db/changelog/5-hs-office/504-partner/5043-hs-office-partner-rbac.md index a0caa074..3522b5a3 100644 --- a/src/main/resources/db/changelog/5-hs-office/504-partner/5043-hs-office-partner-rbac.md +++ b/src/main/resources/db/changelog/5-hs-office/504-partner/5043-hs-office-partner-rbac.md @@ -98,22 +98,21 @@ role:partnerRel.contact:OWNER -.-> role:partnerRel.contact:ADMIN role:partnerRel.contact:ADMIN -.-> role:partnerRel.contact:REFERRER role:global:ADMIN -.-> role:partnerRel:OWNER role:partnerRel:OWNER -.-> role:partnerRel:ADMIN -role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:ADMIN role:partnerRel:ADMIN -.-> role:partnerRel:AGENT -role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT role:partnerRel:AGENT -.-> role:partnerRel:TENANT -role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:TENANT role:partnerRel.contact:ADMIN -.-> role:partnerRel:TENANT role:partnerRel:TENANT -.-> role:partnerRel.anchorPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.holderPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.contact:REFERRER +role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:OWNER +role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT %% granting permissions to roles role:global:ADMIN ==> perm:partner:INSERT -role:partnerRel:ADMIN ==> perm:partner:DELETE -role:partnerRel:AGENT ==> perm:partner:UPDATE +role:partnerRel:OWNER ==> perm:partner:DELETE +role:partnerRel:ADMIN ==> perm:partner:UPDATE role:partnerRel:TENANT ==> perm:partner:SELECT -role:partnerRel:ADMIN ==> perm:partnerDetails:DELETE +role:partnerRel:OWNER ==> perm:partnerDetails:DELETE role:partnerRel:AGENT ==> perm:partnerDetails:UPDATE role:partnerRel:AGENT ==> perm:partnerDetails:SELECT diff --git a/src/main/resources/db/changelog/5-hs-office/504-partner/5043-hs-office-partner-rbac.sql b/src/main/resources/db/changelog/5-hs-office/504-partner/5043-hs-office-partner-rbac.sql index b5510d8c..7d263dbd 100644 --- a/src/main/resources/db/changelog/5-hs-office/504-partner/5043-hs-office-partner-rbac.sql +++ b/src/main/resources/db/changelog/5-hs-office/504-partner/5043-hs-office-partner-rbac.sql @@ -42,10 +42,10 @@ begin SELECT * FROM hs_office_partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails; assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid); - call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationADMIN(newPartnerRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationOWNER(newPartnerRel)); call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTENANT(newPartnerRel)); - call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAGENT(newPartnerRel)); - call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationADMIN(newPartnerRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationADMIN(newPartnerRel)); + call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationOWNER(newPartnerRel)); call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAGENT(newPartnerRel)); call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAGENT(newPartnerRel)); @@ -110,17 +110,17 @@ begin if NEW.partnerRelUuid <> OLD.partnerRelUuid then - call revokePermissionFromRole(getPermissionId(OLD.uuid, 'DELETE'), hsOfficeRelationADMIN(oldPartnerRel)); - call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationADMIN(newPartnerRel)); + call revokePermissionFromRole(getPermissionId(OLD.uuid, 'DELETE'), hsOfficeRelationOWNER(oldPartnerRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationOWNER(newPartnerRel)); - call revokePermissionFromRole(getPermissionId(OLD.uuid, 'UPDATE'), hsOfficeRelationAGENT(oldPartnerRel)); - call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAGENT(newPartnerRel)); + call revokePermissionFromRole(getPermissionId(OLD.uuid, 'UPDATE'), hsOfficeRelationADMIN(oldPartnerRel)); + call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationADMIN(newPartnerRel)); call revokePermissionFromRole(getPermissionId(OLD.uuid, 'SELECT'), hsOfficeRelationTENANT(oldPartnerRel)); call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTENANT(newPartnerRel)); - call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'DELETE'), hsOfficeRelationADMIN(oldPartnerRel)); - call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationADMIN(newPartnerRel)); + call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'DELETE'), hsOfficeRelationOWNER(oldPartnerRel)); + call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationOWNER(newPartnerRel)); call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAGENT(oldPartnerRel)); call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAGENT(newPartnerRel)); diff --git a/src/main/resources/db/changelog/5-hs-office/506-debitor/5063-hs-office-debitor-rbac.md b/src/main/resources/db/changelog/5-hs-office/506-debitor/5063-hs-office-debitor-rbac.md index 5c43e03d..57ce3e73 100644 --- a/src/main/resources/db/changelog/5-hs-office/506-debitor/5063-hs-office-debitor-rbac.md +++ b/src/main/resources/db/changelog/5-hs-office/506-debitor/5063-hs-office-debitor-rbac.md @@ -151,15 +151,14 @@ role:debitorRel.contact:OWNER -.-> role:debitorRel.contact:ADMIN role:debitorRel.contact:ADMIN -.-> role:debitorRel.contact:REFERRER role:global:ADMIN -.-> role:debitorRel:OWNER role:debitorRel:OWNER -.-> role:debitorRel:ADMIN -role:debitorRel.anchorPerson:ADMIN -.-> role:debitorRel:ADMIN role:debitorRel:ADMIN -.-> role:debitorRel:AGENT -role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel:AGENT role:debitorRel:AGENT -.-> role:debitorRel:TENANT -role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel:TENANT role:debitorRel.contact:ADMIN -.-> role:debitorRel:TENANT role:debitorRel:TENANT -.-> role:debitorRel.anchorPerson:REFERRER role:debitorRel:TENANT -.-> role:debitorRel.holderPerson:REFERRER role:debitorRel:TENANT -.-> role:debitorRel.contact:REFERRER +role:debitorRel.anchorPerson:ADMIN -.-> role:debitorRel:OWNER +role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel:AGENT role:global:ADMIN -.-> role:refundBankAccount:OWNER role:refundBankAccount:OWNER -.-> role:refundBankAccount:ADMIN role:refundBankAccount:ADMIN -.-> role:refundBankAccount:REFERRER @@ -176,15 +175,14 @@ role:partnerRel.contact:OWNER -.-> role:partnerRel.contact:ADMIN role:partnerRel.contact:ADMIN -.-> role:partnerRel.contact:REFERRER role:global:ADMIN -.-> role:partnerRel:OWNER role:partnerRel:OWNER -.-> role:partnerRel:ADMIN -role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:ADMIN role:partnerRel:ADMIN -.-> role:partnerRel:AGENT -role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT role:partnerRel:AGENT -.-> role:partnerRel:TENANT -role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:TENANT role:partnerRel.contact:ADMIN -.-> role:partnerRel:TENANT role:partnerRel:TENANT -.-> role:partnerRel.anchorPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.holderPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.contact:REFERRER +role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:OWNER +role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT role:partnerRel:ADMIN ==> role:debitorRel:ADMIN role:partnerRel:AGENT ==> role:debitorRel:AGENT role:debitorRel:AGENT ==> role:partnerRel:TENANT diff --git a/src/main/resources/db/changelog/5-hs-office/507-sepamandate/5073-hs-office-sepamandate-rbac.md b/src/main/resources/db/changelog/5-hs-office/507-sepamandate/5073-hs-office-sepamandate-rbac.md index aa3059f9..e3528f7f 100644 --- a/src/main/resources/db/changelog/5-hs-office/507-sepamandate/5073-hs-office-sepamandate-rbac.md +++ b/src/main/resources/db/changelog/5-hs-office/507-sepamandate/5073-hs-office-sepamandate-rbac.md @@ -110,15 +110,14 @@ role:debitorRel.contact:OWNER -.-> role:debitorRel.contact:ADMIN role:debitorRel.contact:ADMIN -.-> role:debitorRel.contact:REFERRER role:global:ADMIN -.-> role:debitorRel:OWNER role:debitorRel:OWNER -.-> role:debitorRel:ADMIN -role:debitorRel.anchorPerson:ADMIN -.-> role:debitorRel:ADMIN role:debitorRel:ADMIN -.-> role:debitorRel:AGENT -role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel:AGENT role:debitorRel:AGENT -.-> role:debitorRel:TENANT -role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel:TENANT role:debitorRel.contact:ADMIN -.-> role:debitorRel:TENANT role:debitorRel:TENANT -.-> role:debitorRel.anchorPerson:REFERRER role:debitorRel:TENANT -.-> role:debitorRel.holderPerson:REFERRER role:debitorRel:TENANT -.-> role:debitorRel.contact:REFERRER +role:debitorRel.anchorPerson:ADMIN -.-> role:debitorRel:OWNER +role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel:AGENT role:global:ADMIN -.-> role:bankAccount:OWNER role:bankAccount:OWNER -.-> role:bankAccount:ADMIN role:bankAccount:ADMIN -.-> role:bankAccount:REFERRER diff --git a/src/main/resources/db/changelog/5-hs-office/510-membership/5103-hs-office-membership-rbac.md b/src/main/resources/db/changelog/5-hs-office/510-membership/5103-hs-office-membership-rbac.md index 3681b8e6..9e5752b8 100644 --- a/src/main/resources/db/changelog/5-hs-office/510-membership/5103-hs-office-membership-rbac.md +++ b/src/main/resources/db/changelog/5-hs-office/510-membership/5103-hs-office-membership-rbac.md @@ -96,15 +96,14 @@ role:partnerRel.contact:OWNER -.-> role:partnerRel.contact:ADMIN role:partnerRel.contact:ADMIN -.-> role:partnerRel.contact:REFERRER role:global:ADMIN -.-> role:partnerRel:OWNER role:partnerRel:OWNER -.-> role:partnerRel:ADMIN -role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:ADMIN role:partnerRel:ADMIN -.-> role:partnerRel:AGENT -role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT role:partnerRel:AGENT -.-> role:partnerRel:TENANT -role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:TENANT role:partnerRel.contact:ADMIN -.-> role:partnerRel:TENANT role:partnerRel:TENANT -.-> role:partnerRel.anchorPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.holderPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.contact:REFERRER +role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:OWNER +role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT role:membership:OWNER ==> role:membership:ADMIN role:partnerRel:ADMIN ==> role:membership:ADMIN role:membership:ADMIN ==> role:membership:AGENT diff --git a/src/main/resources/db/changelog/5-hs-office/511-coopshares/5113-hs-office-coopshares-rbac.md b/src/main/resources/db/changelog/5-hs-office/511-coopshares/5113-hs-office-coopshares-rbac.md index 26ff3d5c..b38ad4a0 100644 --- a/src/main/resources/db/changelog/5-hs-office/511-coopshares/5113-hs-office-coopshares-rbac.md +++ b/src/main/resources/db/changelog/5-hs-office/511-coopshares/5113-hs-office-coopshares-rbac.md @@ -97,15 +97,14 @@ role:membership.partnerRel.contact:OWNER -.-> role:membership.partnerRel.contact role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel.contact:REFERRER role:global:ADMIN -.-> role:membership.partnerRel:OWNER role:membership.partnerRel:OWNER -.-> role:membership.partnerRel:ADMIN -role:membership.partnerRel.anchorPerson:ADMIN -.-> role:membership.partnerRel:ADMIN role:membership.partnerRel:ADMIN -.-> role:membership.partnerRel:AGENT -role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:AGENT role:membership.partnerRel:AGENT -.-> role:membership.partnerRel:TENANT -role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:TENANT role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel:TENANT role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.anchorPerson:REFERRER role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.holderPerson:REFERRER role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.contact:REFERRER +role:membership.partnerRel.anchorPerson:ADMIN -.-> role:membership.partnerRel:OWNER +role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:AGENT role:membership:OWNER -.-> role:membership:ADMIN role:membership.partnerRel:ADMIN -.-> role:membership:ADMIN role:membership:ADMIN -.-> role:membership:AGENT diff --git a/src/main/resources/db/changelog/5-hs-office/512-coopassets/5123-hs-office-coopassets-rbac.md b/src/main/resources/db/changelog/5-hs-office/512-coopassets/5123-hs-office-coopassets-rbac.md index d220a38c..77de3dc2 100644 --- a/src/main/resources/db/changelog/5-hs-office/512-coopassets/5123-hs-office-coopassets-rbac.md +++ b/src/main/resources/db/changelog/5-hs-office/512-coopassets/5123-hs-office-coopassets-rbac.md @@ -97,15 +97,14 @@ role:membership.partnerRel.contact:OWNER -.-> role:membership.partnerRel.contact role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel.contact:REFERRER role:global:ADMIN -.-> role:membership.partnerRel:OWNER role:membership.partnerRel:OWNER -.-> role:membership.partnerRel:ADMIN -role:membership.partnerRel.anchorPerson:ADMIN -.-> role:membership.partnerRel:ADMIN role:membership.partnerRel:ADMIN -.-> role:membership.partnerRel:AGENT -role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:AGENT role:membership.partnerRel:AGENT -.-> role:membership.partnerRel:TENANT -role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:TENANT role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel:TENANT role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.anchorPerson:REFERRER role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.holderPerson:REFERRER role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.contact:REFERRER +role:membership.partnerRel.anchorPerson:ADMIN -.-> role:membership.partnerRel:OWNER +role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:AGENT role:membership:OWNER -.-> role:membership:ADMIN role:membership.partnerRel:ADMIN -.-> role:membership:ADMIN role:membership:ADMIN -.-> role:membership:AGENT diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index 32f441af..3a4d4e16 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -186,13 +186,13 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean "{ grant perm:debitor#D-1000122:DELETE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }", "{ grant perm:relation#FirstGmbH-with-DEBITOR-FourtheG:DELETE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER to role:global#global:ADMIN by system and assume }", + "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER to role:person#FirstGmbH:ADMIN by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER to user:superuser-alex@hostsharing.net by relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER and assume }", // admin "{ grant perm:debitor#D-1000122:UPDATE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN by system and assume }", "{ grant perm:relation#FirstGmbH-with-DEBITOR-FourtheG:UPDATE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }", - "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN to role:person#FirstGmbH:ADMIN by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN to role:relation#HostsharingeG-with-PARTNER-FirstGmbH:ADMIN by system and assume }", // agent @@ -208,7 +208,6 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean "{ grant role:person#FirstGmbH:REFERRER to role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT by system and assume }", "{ grant role:person#FourtheG:REFERRER to role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT to role:contact#fourthcontact:ADMIN by system and assume }", - "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT to role:person#FourtheG:ADMIN by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT to role:relation#FirstGmbH-with-DEBITOR-FourtheG:AGENT by system and assume }", null)); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java index 633278a0..a6da48c9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java @@ -134,6 +134,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl "{ grant perm:membership#M-1000117:SELECT to role:membership#M-1000117:AGENT by system and assume }", "{ grant role:membership#M-1000117:AGENT to role:membership#M-1000117:ADMIN by system and assume }", + // referrer "{ grant role:membership#M-1000117:AGENT to role:relation#HostsharingeG-with-PARTNER-FirstGmbH:AGENT by system and assume }", "{ grant role:relation#HostsharingeG-with-PARTNER-FirstGmbH:TENANT to role:membership#M-1000117:AGENT by system and assume }", diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java index 8da1f12f..a763804a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java @@ -218,23 +218,6 @@ public class ImportOfficeData extends ContextBasedTest { } } - @Test - @Order(1021) - void buildDebitorRelations() { - debitors.forEach( (id, debitor) -> { - final var debitorRel = HsOfficeRelationEntity.builder() - .type(HsOfficeRelationType.DEBITOR) - .anchor(debitor.getPartner().getPartnerRel().getHolder()) - .holder(debitor.getPartner().getPartnerRel().getHolder()) // just 1 debitor/partner in legacy hsadmin - // FIXME .contact() - .build(); - if (debitorRel.getAnchor() != null && debitorRel.getHolder() != null && - debitorRel.getContact() != null ) { - relations.put(relationId++, debitorRel); - } - }); - } - @Test @Order(1029) void verifyContacts() { @@ -292,29 +275,25 @@ public class ImportOfficeData extends ContextBasedTest { { 2000000=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000001=rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000002=rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000003=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000004=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), - 2000005=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), - 2000006=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000007=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000008=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000009=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'), - 2000010=rel(anchor='null null, null', type='DEBITOR'), - 2000011=rel(anchor='null null, null', type='DEBITOR'), - 2000012=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000013=rel(anchor='NP Mellies, Michael', type='REPRESENTATIVE', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000014=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'), - 2000015=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000016=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000017=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000018=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000019=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000020=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000021=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), - 2000022=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000023=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'), -2000024=rel(anchor='NP Mellies, Michael', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga ') + 2000002=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000003=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), + 2000004=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000005=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000006=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'), + 2000007=rel(anchor='null null, null', type='DEBITOR'), + 2000008=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), + 2000009=rel(anchor='NP Mellies, Michael', type='REPRESENTATIVE', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), + 2000010=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'), + 2000011=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000012=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000013=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000014=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000015=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000016=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000017=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), + 2000018=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000019=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000020=rel(anchor='NP Mellies, Michael', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga ') } """); } @@ -425,14 +404,33 @@ public class ImportOfficeData extends ContextBasedTest { } @Test - @Order(2009) + @Order(3001) + void removeSelfRepresentativeRelations() { + assumeThatWeAreImportingControlledTestData(); + + // this happens if a natural person is marked as 'contractual' for itself + final var idsToRemove = new HashSet(); + relations.forEach( (id, r) -> { + if (r.getHolder() == r.getAnchor() ) { + idsToRemove.add(id); + } + }); + + // remove self-representatives + idsToRemove.forEach(id -> { + System.out.println("removing self representative relation: " + relations.get(id).toString()); + relations.remove(id); + }); + } + + @Test + @Order(3002) void removeEmptyRelations() { assumeThatWeAreImportingControlledTestData(); // avoid a error when persisting the deliberately invalid partner entry #99 final var idsToRemove = new HashSet(); relations.forEach( (id, r) -> { - // such a record if (r.getContact() == null || r.getContact().getLabel() == null || r.getHolder() == null || r.getHolder().getPersonType() == null ) { idsToRemove.add(id); @@ -447,7 +445,7 @@ public class ImportOfficeData extends ContextBasedTest { } @Test - @Order(2002) + @Order(3003) void removeEmptyPartners() { assumeThatWeAreImportingControlledTestData(); @@ -471,7 +469,7 @@ public class ImportOfficeData extends ContextBasedTest { } @Test - @Order(2003) + @Order(3004) void removeEmptyDebitors() { assumeThatWeAreImportingControlledTestData(); @@ -490,7 +488,7 @@ public class ImportOfficeData extends ContextBasedTest { } @Test - @Order(3000) + @Order(9000) @Commit void persistEntities() { @@ -516,6 +514,7 @@ public class ImportOfficeData extends ContextBasedTest { relations.forEach(this::persist); }).assertSuccessful(); + System.out.println("persisting " + partners.size() + " partners"); jpaAttempt.transacted(() -> { context(rbacSuperuser); partners.forEach((id, partner) -> { @@ -533,7 +532,7 @@ public class ImportOfficeData extends ContextBasedTest { context(rbacSuperuser); debitors.forEach((id, debitor) -> { debitor.setDebitorRel(em.merge(debitor.getDebitorRel())); - em.persist(debitor); + persist(id, debitor); }); }).assertSuccessful(); @@ -721,7 +720,6 @@ public class ImportOfficeData extends ContextBasedTest { null, // will be set in contacts import null // will beset in contacts import ); - relations.put(relationId++, debitorRel); final var debitor = HsOfficeDebitorEntity.builder() .debitorNumberSuffix("00") diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java index 98bff812..c1d3fbc3 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java @@ -10,7 +10,6 @@ import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; -import net.hostsharing.test.Array; import net.hostsharing.test.JpaAttempt; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Nested; @@ -25,13 +24,13 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; -import java.util.HashSet; import java.util.List; +import java.util.Objects; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectEntity.objectDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; -import static net.hostsharing.test.Array.fromFormatted; +import static net.hostsharing.test.Array.from; import static net.hostsharing.test.JpaAttempt.attempt; import static org.assertj.core.api.Assertions.assertThat; @@ -130,7 +129,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean }).assertSuccessful(); // then - assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( + assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(from( initialRoleNames, "hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler:OWNER", "hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler:ADMIN", @@ -140,44 +139,43 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean .map(s -> s.replace("ErbenBesslerMelBessler", "EBess")) .map(s -> s.replace("fourthcontact", "4th")) .map(s -> s.replace("hs_office_", "")) - .containsExactlyInAnyOrder(distinct(fromFormatted( + .containsExactlyInAnyOrder(distinct(from( initialGrantNames, - "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:INSERT>sepamandate to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", + "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:INSERT>sepamandate to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", // permissions on partner - "{ grant perm:partner#P-20032:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", - "{ grant perm:partner#P-20032:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }", - "{ grant perm:partner#P-20032:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", + "{ grant perm:partner#P-20032:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }", + "{ grant perm:partner#P-20032:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", + "{ grant perm:partner#P-20032:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", // permissions on partner-details - "{ grant perm:partner_details#P-20032:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", - "{ grant perm:partner_details#P-20032:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }", - "{ grant perm:partner_details#P-20032:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }", + "{ grant perm:partner_details#P-20032:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }", + "{ grant perm:partner_details#P-20032:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }", + "{ grant perm:partner_details#P-20032:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }", // permissions on partner-relation - "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }", - "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", - "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", + "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }", + "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", + "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", // relation owner - "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:OWNER to role:global#global:ADMIN by system and assume }", - "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:OWNER to user:superuser-alex@hostsharing.net by relation#HostsharingeG-with-PARTNER-EBess:OWNER and assume }", + "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:OWNER to role:global#global:ADMIN by system and assume }", + "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:OWNER to user:superuser-alex@hostsharing.net by relation#HostsharingeG-with-PARTNER-EBess:OWNER and assume }", // relation admin - "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }", - "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN to role:person#HostsharingeG:ADMIN by system and assume }", + "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }", + "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:OWNER to role:person#HostsharingeG:ADMIN by system and assume }", // relation agent - "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:AGENT to role:person#EBess:ADMIN by system and assume }", - "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:AGENT to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", + "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:AGENT to role:person#EBess:ADMIN by system and assume }", + "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:AGENT to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", // relation tenant - "{ grant role:contact#4th:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", - "{ grant role:person#EBess:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", - "{ grant role:person#HostsharingeG:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", - "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:TENANT to role:contact#4th:ADMIN by system and assume }", - "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:TENANT to role:person#EBess:ADMIN by system and assume }", - "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:TENANT to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }", + "{ grant role:contact#4th:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", + "{ grant role:person#EBess:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", + "{ grant role:person#HostsharingeG:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", + "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:TENANT to role:contact#4th:ADMIN by system and assume }", + "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:TENANT to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }", null))); } @@ -411,9 +409,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean public void deletingAPartnerAlsoDeletesRelatedRolesAndGrants() { // given context("superuser-alex@hostsharing.net"); - final var initialObjects = Array.from(objectDisplaysOf(rawObjectRepo.findAll())); - final var initialRoleNames = Array.from(distinctRoleNamesOf(rawRoleRepo.findAll())); - final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll())); + final var initialObjects = from(objectDisplaysOf(rawObjectRepo.findAll())); + final var initialRoleNames = from(distinctRoleNamesOf(rawRoleRepo.findAll())); + final var initialGrantNames = from(distinctGrantDisplaysOf(rawGrantRepo.findAll())); final var givenPartner = givenSomeTemporaryHostsharingPartner(20034, "Erben Bessler", "twelfth"); // when @@ -499,8 +497,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean private String[] distinct(final String[] strings) { // TODO: alternatively cleanup all rbac objects in @AfterEach? - final var set = new HashSet(); - set.addAll(List.of(strings)); - return set.toArray(new String[0]); + return Arrays.stream(strings).filter(Objects::nonNull).distinct().toList().toArray(new String[0]); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java index 78d64e6a..54218b67 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java @@ -362,7 +362,7 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean assertThat(givenRelation.getContact().getLabel()).isEqualTo("seventh contact"); final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0); - final var location = RestAssured // @formatter:off + RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") .contentType(ContentType.JSON) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java index f474de0c..77342f0d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java @@ -140,9 +140,10 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea "{ grant perm:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:UPDATE to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN by system and assume }", "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:OWNER by system and assume }", - "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN to role:hs_office_person#ErbenBesslerMelBessler:ADMIN by system and assume }", + "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:OWNER to role:hs_office_person#BesslerBert:ADMIN by system and assume }", + "{ grant role:hs_office_person#ErbenBesslerMelBessler:OWNER to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN by system and assume }", - "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:AGENT to role:hs_office_person#BesslerBert:ADMIN by system and assume }", + "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:AGENT to role:hs_office_person#ErbenBesslerMelBessler:ADMIN by system and assume }", "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:AGENT to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN by system and assume }", "{ grant perm:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:SELECT to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT by system and assume }", @@ -153,8 +154,6 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea // REPRESENTATIVE holder person -> (represented) anchor person "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT to role:hs_office_contact#fourthcontact:ADMIN by system and assume }", - "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT to role:hs_office_person#BesslerBert:ADMIN by system and assume }", - null) ); } @@ -217,10 +216,10 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea context("superuser-alex@hostsharing.net"); final var givenRelation = givenSomeTemporaryRelationBessler( "Bert", "fifth contact"); + assertThatRelationActuallyInDatabase(givenRelation); assertThatRelationIsVisibleForUserWithRole( givenRelation, "hs_office_person#ErbenBesslerMelBessler:ADMIN"); - assertThatRelationActuallyInDatabase(givenRelation); context("superuser-alex@hostsharing.net"); final var givenContact = contactRepo.findContactByOptionalLabelLike("sixth contact").stream().findFirst().orElseThrow(); @@ -249,19 +248,19 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea } @Test - public void holderAdmin_canNotUpdateRelatedRelation() { + public void relationAgent_canSelectButNotUpdateRelatedRelation() { // given context("superuser-alex@hostsharing.net"); final var givenRelation = givenSomeTemporaryRelationBessler( "Anita", "eighth"); assertThatRelationIsVisibleForUserWithRole( givenRelation, - "hs_office_person#BesslerAnita:ADMIN"); + "hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerAnita:AGENT"); assertThatRelationActuallyInDatabase(givenRelation); // when final var result = jpaAttempt.transacted(() -> { - context("superuser-alex@hostsharing.net", "hs_office_person#BesslerAnita:ADMIN"); + context("superuser-alex@hostsharing.net", "hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerAnita:AGENT"); givenRelation.setContact(null); return relationRepo.save(givenRelation); }); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/test/ContextBasedTestWithCleanup.java b/src/test/java/net/hostsharing/hsadminng/hs/office/test/ContextBasedTestWithCleanup.java index fc0b81c3..64feda26 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/test/ContextBasedTestWithCleanup.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/test/ContextBasedTestWithCleanup.java @@ -17,7 +17,6 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import jakarta.persistence.*; -import java.lang.reflect.Method; import java.util.*; import static java.lang.System.out; @@ -272,12 +271,11 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest { /** * Generates a diagram of the RBAC-Grants to the current subjects (user or assumed roles). */ - protected void generateRbacDiagramForCurrentSubjects(final EnumSet include) { - final var title = testInfo.getTestMethod().map(Method::getName).orElseThrow(); + protected void generateRbacDiagramForCurrentSubjects(final EnumSet include, final String name) { RbacGrantsDiagramService.writeToFile( - title, + name, diagramService.allGrantsToCurrentUser(include), - "doc/" + title + ".md" + "doc/temp/" + name + ".md" ); }