From 9806bcd78fa23e7d1b3d0e536983dd965329fafd Mon Sep 17 00:00:00 2001
From: Michael Hoennig <michael.hoennig@hostsharing.net>
Date: Tue, 23 Apr 2024 10:42:24 +0200
Subject: [PATCH] conditional insert permission grant (so far just exactly 1
 unique for each table) (#48)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/48
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
---
 build.gradle                                  |  2 +-
 .../hs/booking/item/HsBookingItemEntity.java  |  7 +-
 .../HsOfficeCoopAssetsTransactionEntity.java  |  3 +-
 .../HsOfficeCoopSharesTransactionEntity.java  |  3 +-
 .../office/debitor/HsOfficeDebitorEntity.java | 10 +--
 .../membership/HsOfficeMembershipEntity.java  |  3 +-
 .../relation/HsOfficeRelationEntity.java      |  8 ++-
 .../HsOfficeSepaMandateEntity.java            |  7 +-
 .../rbac/rbacdef/InsertTriggerGenerator.java  | 25 ++++++--
 .../hsadminng/rbac/rbacdef/RbacView.java      | 64 ++++++++++++++-----
 .../RbacViewMermaidFlowchartGenerator.java    |  5 ++
 .../rbac/test/dom/TestDomainEntity.java       |  3 +-
 .../rbac/test/pac/TestPackageEntity.java      |  3 +-
 .../5063-hs-office-debitor-rbac.md            | 10 ---
 .../5073-hs-office-sepamandate-rbac.md        | 10 ---
 .../5073-hs-office-sepamandate-rbac.sql       |  5 +-
 .../6013-hs-booking-item-rbac.md              | 20 ------
 .../6013-hs-booking-item-rbac.sql             |  8 +--
 ...fficePartnerRepositoryIntegrationTest.java |  2 -
 ...ficeRelationRepositoryIntegrationTest.java |  2 -
 20 files changed, 111 insertions(+), 89 deletions(-)

diff --git a/build.gradle b/build.gradle
index 45b75734..bd48e3ac 100644
--- a/build.gradle
+++ b/build.gradle
@@ -174,7 +174,7 @@ project.tasks.processResources.dependsOn processSpring
 project.tasks.compileJava.dependsOn processSpring
 
 // Rename javax to jakarta in OpenApi generated java files because
-// io.openapiprocessor.openapi-processor 2022.2 does not yet support the openapiprocessor useSpringBoot3 config option.
+// io.openapiprocessor.openapi-processor 2022.5 does not yet support the openapiprocessor useSpringBoot3 config option.
 // TODO.impl: Upgrade to io.openapiprocessor.openapi-processor >= 2024.2
 //  and use either `bean-validation: true` in api-mapping.yaml or `useSpringBoot3 true` (not sure where exactly).
 task openApiGenerate(type: Copy) {
diff --git a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java
index aad7d836..3d948ef2 100644
--- a/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java
+++ b/src/main/java/net/hostsharing/hsadminng/hs/booking/item/HsBookingItemEntity.java
@@ -35,10 +35,13 @@ import java.util.Map;
 import java.util.UUID;
 
 import static java.util.Optional.ofNullable;
+import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
 import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
 import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
 import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange;
 import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
+import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingCase;
+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.Permission.DELETE;
 import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT;
@@ -148,12 +151,12 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
                 .withRestrictedViewOrderBy(SQL.expression("validity"))
                 .withUpdatableColumns("version", "caption", "validity", "resources")
 
-                .importEntityAlias("debitor", HsOfficeDebitorEntity.class,
+                .importEntityAlias("debitor", HsOfficeDebitorEntity.class, usingDefaultCase(),
                         dependsOnColumn("debitorUuid"),
                         directlyFetchedByDependsOnColumn(),
                         NOT_NULL)
 
-                .importEntityAlias("debitorRel", HsOfficeRelationEntity.class,
+                .importEntityAlias("debitorRel", HsOfficeRelationEntity.class, usingCase(DEBITOR),
                         dependsOnColumn("debitorUuid"),
                         fetchedBySql("""
                                 SELECT ${columns}
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 4ec6685d..2cf4f089 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
@@ -22,6 +22,7 @@ import java.util.UUID;
 
 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.Permission.INSERT;
 import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT;
@@ -125,7 +126,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacO
         return rbacViewFor("coopAssetsTransaction", HsOfficeCoopAssetsTransactionEntity.class)
                 .withIdentityView(RbacView.SQL.projection("reference"))
                 .withUpdatableColumns("comment")
-                .importEntityAlias("membership", HsOfficeMembershipEntity.class,
+                .importEntityAlias("membership", HsOfficeMembershipEntity.class, usingDefaultCase(),
                         dependsOnColumn("membershipUuid"),
                         directlyFetchedByDependsOnColumn(),
                         NOT_NULL)
diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java
index 8604ec16..c886170e 100644
--- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java
+++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java
@@ -20,6 +20,7 @@ import java.util.UUID;
 
 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.Permission.INSERT;
 import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT;
@@ -119,7 +120,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacO
         return rbacViewFor("coopSharesTransaction", HsOfficeCoopSharesTransactionEntity.class)
                 .withIdentityView(SQL.projection("reference"))
                 .withUpdatableColumns("comment")
-                .importEntityAlias("membership", HsOfficeMembershipEntity.class,
+                .importEntityAlias("membership", HsOfficeMembershipEntity.class, usingDefaultCase(),
                         dependsOnColumn("membershipUuid"),
                         directlyFetchedByDependsOnColumn(),
                         NOT_NULL)
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 51df906f..33e6f2e8 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
@@ -36,7 +36,9 @@ import static jakarta.persistence.CascadeType.MERGE;
 import static jakarta.persistence.CascadeType.PERSIST;
 import static jakarta.persistence.CascadeType.REFRESH;
 import static java.util.Optional.ofNullable;
+import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
 import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
+import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingCase;
 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;
@@ -171,23 +173,21 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
                         "defaultPrefix" /* TODO.spec: do we want that updatable? */)
                 .toRole("global", ADMIN).grantPermission(INSERT)
 
-                .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class,
-                        // TODO.spec: do we need a distinct case for DEBITOR-Relation?
-                        usingDefaultCase(),
+                .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, usingCase(DEBITOR),
                         directlyFetchedByDependsOnColumn(),
                         dependsOnColumn("debitorRelUuid"))
                 .createPermission(DELETE).grantedTo("debitorRel", OWNER)
                 .createPermission(UPDATE).grantedTo("debitorRel", ADMIN)
                 .createPermission(SELECT).grantedTo("debitorRel", TENANT)
 
-                .importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class,
+                .importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class, usingDefaultCase(),
                         dependsOnColumn("refundBankAccountUuid"),
                         directlyFetchedByDependsOnColumn(),
                         NULLABLE)
                 .toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
                 .toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
 
-                .importEntityAlias("partnerRel", HsOfficeRelationEntity.class,
+                .importEntityAlias("partnerRel", HsOfficeRelationEntity.class, usingDefaultCase(),
                         dependsOnColumn("debitorRelUuid"),
                         fetchedBySql("""
                                 SELECT ${columns}
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 d031389d..67050ccc 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
@@ -38,6 +38,7 @@ import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveF
 import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
 import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange;
 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.Permission.DELETE;
 import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT;
@@ -156,7 +157,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
                 .withRestrictedViewOrderBy(SQL.projection("validity"))
                 .withUpdatableColumns("validity", "membershipFeeBillable", "status")
 
-                .importEntityAlias("partnerRel", HsOfficeRelationEntity.class,
+                .importEntityAlias("partnerRel", HsOfficeRelationEntity.class, usingDefaultCase(),
                         dependsOnColumn("partnerUuid"),
                         fetchedBySql("""
                                 SELECT ${columns}
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 2bc9c452..e8e90702 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
@@ -19,6 +19,8 @@ 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.ColumnValue.usingCase;
+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.Permission.*;
 import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
@@ -94,15 +96,15 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
                 .withRestrictedViewOrderBy(SQL.expression(
                         "(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)"))
                 .withUpdatableColumns("contactUuid")
-                .importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
+                .importEntityAlias("anchorPerson", HsOfficePersonEntity.class, usingDefaultCase(),
                         dependsOnColumn("anchorUuid"),
                         directlyFetchedByDependsOnColumn(),
                         NOT_NULL)
-                .importEntityAlias("holderPerson", HsOfficePersonEntity.class,
+                .importEntityAlias("holderPerson", HsOfficePersonEntity.class, usingDefaultCase(),
                         dependsOnColumn("holderUuid"),
                         directlyFetchedByDependsOnColumn(),
                         NOT_NULL)
-                .importEntityAlias("contact", HsOfficeContactEntity.class,
+                .importEntityAlias("contact", HsOfficeContactEntity.class, usingDefaultCase(),
                         dependsOnColumn("contactUuid"),
                         directlyFetchedByDependsOnColumn(),
                         NOT_NULL)
diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java
index a4344abe..ad3bf25a 100644
--- a/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java
+++ b/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java
@@ -18,8 +18,11 @@ import java.io.IOException;
 import java.time.LocalDate;
 import java.util.UUID;
 
+import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
 import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
 import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
+import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingCase;
+import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase;
 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.*;
@@ -107,7 +110,7 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, RbacObject {
                 .withRestrictedViewOrderBy(expression("validity"))
                 .withUpdatableColumns("reference", "agreement", "validity")
 
-                .importEntityAlias("debitorRel", HsOfficeRelationEntity.class,
+                .importEntityAlias("debitorRel", HsOfficeRelationEntity.class, usingCase(DEBITOR),
                         dependsOnColumn("debitorUuid"),
                         fetchedBySql("""
                                 SELECT ${columns}
@@ -116,7 +119,7 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, RbacObject {
                                     WHERE debitor.uuid = ${REF}.debitorUuid
                                 """),
                         NOT_NULL)
-                .importEntityAlias("bankAccount", HsOfficeBankAccountEntity.class,
+                .importEntityAlias("bankAccount", HsOfficeBankAccountEntity.class, usingDefaultCase(),
                         dependsOnColumn("bankAccountUuid"),
                         directlyFetchedByDependsOnColumn(),
                         NOT_NULL)
diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java
index 7ef34252..66ef1481 100644
--- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java
+++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java
@@ -50,7 +50,7 @@ public class InsertTriggerGenerator {
                     begin
                         call defineContext('create INSERT INTO ${rawSubTableName} permissions for the related ${rawSuperTableName} rows');
                     
-                        FOR row IN SELECT * FROM ${rawSuperTableName}
+                        FOR row IN SELECT * FROM ${rawSuperTableName}${typeCondition}
                             LOOP
                                 call grantPermissionToRole(
                                     createPermission(row.uuid, 'INSERT', '${rawSubTableName}'),
@@ -61,7 +61,10 @@ public class InsertTriggerGenerator {
                 """,
                 with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
                 with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()),
-                with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, "row"))
+                with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, "row")),
+                with("typeCondition", superRoleDef.getEntityAlias().isCaseDependent()
+                        ? "\n\t\t\tWHERE type = '${case}'".replace("${case}", superRoleDef.getEntityAlias().usingCase().value)
+                        : "")
                 );
         });
     }
@@ -77,9 +80,9 @@ public class InsertTriggerGenerator {
                     language plpgsql
                     strict as $$
                 begin
-                    call grantPermissionToRole(
+                    ${typeConditionIf}call grantPermissionToRole(
                             createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}'),
-                            ${rawSuperRoleDescriptor});
+                            ${rawSuperRoleDescriptor});${typeConditionEndIf}
                     return NEW;
                 end; $$;
                                 
@@ -91,7 +94,14 @@ public class InsertTriggerGenerator {
                 """,
                 with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
                 with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()),
-                with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, NEW.name()))
+                with("rawSuperRoleDescriptor", toRoleDescriptor(superRoleDef, NEW.name())),
+                with("typeConditionIf",
+                        superRoleDef.getEntityAlias().isCaseDependent()
+                            ? "if NEW.type = '${case}' then\n\t\t".replace("${case}", superRoleDef.getEntityAlias().usingCase().value)
+                            : ""),
+                with("typeConditionEndIf", superRoleDef.getEntityAlias().isCaseDependent()
+                        ? "\n\tend if;"
+                        : "")
             );
         });
     }
@@ -241,7 +251,10 @@ public class InsertTriggerGenerator {
 
     private static <T> BinaryOperator<T> singleton() {
         return (x, y) -> {
-            throw new IllegalStateException("only a single INSERT permission grant allowed");
+            if ( !x.equals(y) ) {
+                throw new IllegalStateException("only a single INSERT permission grant allowed");
+            }
+            return x;
         };
     }
 
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 4be78f1f..b9b556a9 100644
--- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java
+++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java
@@ -18,7 +18,9 @@ import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import static java.lang.reflect.Modifier.isStatic;
+import static java.util.Arrays.asList;
 import static java.util.Arrays.stream;
+import static java.util.Collections.max;
 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;
@@ -325,6 +327,9 @@ public class RbacView {
      *  A JPA entity class extending RbacObject which also implements an `rbac` method returning
      *  its RBAC specification.
      *
+     * @param usingCase
+     *  Only use this case value for a switch within the rbac rules.
+     *
      * @param fetchSql
      *  An SQL SELECT statement which fetches the referenced row. Use `${REF}` to speficiy the
      *  newly created or updated row (will be replaced by NEW/OLD from the trigger method).
@@ -342,19 +347,29 @@ public class RbacView {
      *     a JPA entity class extending RbacObject
      */
     public RbacView importEntityAlias(
-            final String aliasName, final Class<? extends RbacObject> entityClass,
+            final String aliasName, final Class<? extends RbacObject> entityClass, final ColumnValue usingCase,
             final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
-        importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, false, nullable);
+        importEntityAliasImpl(aliasName, entityClass, usingCase, fetchSql, dependsOnColum, false, nullable);
         return this;
     }
 
     private EntityAlias importEntityAliasImpl(
-            final String aliasName, final Class<? extends RbacObject> entityClass, final ColumnValue forCase,
+            final String aliasName, final Class<? extends RbacObject> entityClass, final ColumnValue usingCase,
             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);
+
+        final var entityAlias = ofNullable(entityAliases.get(aliasName))
+                .orElseGet(() -> {
+                    final var ea = new EntityAlias(aliasName, entityClass, usingCase, fetchSql, dependsOnColum, asSubEntity, nullable);
+                    entityAliases.put(aliasName, ea);
+                    return ea;
+                });
+
         try {
-            importAsAlias(aliasName, rbacDefinition(entityClass), forCase, asSubEntity);
+            // TODO.rbac: this only works for directly recursive RBAC definitions, not for indirect recursion
+            final var rbacDef = entityClass == rootEntityAlias.entityClass
+                ? this
+                : rbacDefinition(entityClass);
+            importAsAlias(aliasName, rbacDef, usingCase, asSubEntity);
         } catch (final ReflectiveOperationException exc) {
             throw new RuntimeException("cannot import entity: " + entityClass, exc);
         }
@@ -369,7 +384,7 @@ public class RbacView {
     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()
+        copyOf(importedRbacView.getEntityAliases().values()).stream()
                 .filter(entityAlias -> !importedRbacView.isRootEntityAlias(entityAlias))
                 .filter(entityAlias -> !entityAlias.isGlobal())
                 .filter(entityAlias -> !asSubEntity || !entityAliases.containsKey(entityAlias.aliasName))
@@ -377,10 +392,10 @@ public class RbacView {
                     final String mappedAliasName = mapper.map(entityAlias.aliasName);
                     entityAliases.put(mappedAliasName, new EntityAlias(mappedAliasName, entityAlias.entityClass));
                 });
-        importedRbacView.getRoleDefs().forEach(roleDef -> {
+        copyOf(importedRbacView.getRoleDefs()).forEach(roleDef -> {
             new RbacRoleDefinition(findEntityAlias(mapper.map(roleDef.entityAlias.aliasName)), roleDef.role);
         });
-        importedRbacView.getGrantDefs().forEach(grantDef -> {
+        copyOf(importedRbacView.getGrantDefs()).forEach(grantDef -> {
             if ( grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE &&
                     (grantDef.forCases == null || grantDef.matchesCase(forCase)) ) {
                 final var importedGrantDef = findOrCreateGrantDef(
@@ -411,6 +426,10 @@ public class RbacView {
         return this;
     }
 
+    private static <T> List<T> copyOf(final Collection<T> eas) {
+        return eas.stream().toList();
+    }
+
     private void verifyVersionColumnExists() {
         if (stream(rootEntityAlias.entityClass.getDeclaredFields())
                 .noneMatch(f -> f.getAnnotation(Version.class) != null)) {
@@ -615,6 +634,13 @@ public class RbacView {
             return this;
         }
 
+        public long level() {
+            return max(asList(
+                    superRoleDef != null ? superRoleDef.entityAlias.level() : 0,
+                    subRoleDef != null ? subRoleDef.entityAlias.level() : 0,
+                    permDef != null ? permDef.entityAlias.level() : 0));
+        }
+
         public enum GrantType {
             ROLE_TO_USER,
             ROLE_TO_ROLE,
@@ -854,14 +880,14 @@ public class RbacView {
         return distinctGrantDef;
     }
 
-    record EntityAlias(String aliasName, Class<? extends RbacObject> entityClass, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) {
+    record EntityAlias(String aliasName, Class<? extends RbacObject> entityClass, ColumnValue usingCase, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) {
 
         public EntityAlias(final String aliasName) {
-            this(aliasName, null, null, null, false, null);
+            this(aliasName, null, null, null, null, false, null);
         }
 
         public EntityAlias(final String aliasName, final Class<? extends RbacObject> entityClass) {
-            this(aliasName, entityClass, null, null, false, null);
+            this(aliasName, entityClass, null, null, null, false, null);
         }
 
         boolean isGlobal() {
@@ -873,7 +899,6 @@ public class RbacView {
         }
 
         @NotNull
-        @Override
         public SQL fetchSql() {
             if (fetchSql == null) {
                 return SQL.noop();
@@ -914,6 +939,14 @@ public class RbacView {
             }
             return dependsOnColum.column;
         }
+
+        long level() {
+            return aliasName.chars().filter(ch -> ch == '.').count() + 1;
+        }
+
+        boolean isCaseDependent() {
+            return usingCase != null && usingCase.value != null;
+        }
     }
 
     public static String withoutRvSuffix(final String tableName) {
@@ -1074,10 +1107,9 @@ public class RbacView {
             return new ColumnValue(null);
         }
 
-        public static ColumnValue usingCase(final String value) {
-            return new ColumnValue(value);
+        public static <E extends Enum<E>> ColumnValue usingCase(final E value) {
+            return new ColumnValue(value.name());
         }
-
         public final String value;
 
         private ColumnValue(final String value) {
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 96a956e5..3522a629 100644
--- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java
+++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchartGenerator.java
@@ -15,6 +15,9 @@ public class RbacViewMermaidFlowchartGenerator {
     public static final String HOSTSHARING_LIGHT_ORANGE = "#feb28c";
     public static final String HOSTSHARING_DARK_BLUE = "#274d6e";
     public static final String HOSTSHARING_LIGHT_BLUE = "#99bcdb";
+
+    // TODO.rbac: implement level limit for all renderable items and remove items which not part of a grant
+    private static final long MAX_LEVEL_TO_RENDER = 3;
     private final RbacView rbacDef;
 
     private final CaseDef forCase;
@@ -56,6 +59,7 @@ public class RbacViewMermaidFlowchartGenerator {
 
         flowchart.indented( () -> {
             rbacDef.getEntityAliases().values().stream()
+                    .filter(e -> e.level() <= MAX_LEVEL_TO_RENDER)
                     .filter(e -> e.aliasName().startsWith(entity.aliasName() + ":"))
                     .forEach(this::renderEntitySubgraph);
 
@@ -106,6 +110,7 @@ public class RbacViewMermaidFlowchartGenerator {
 
     private void renderGrants(final RbacView.RbacGrantDefinition.GrantType grantType, final String comment) {
         final var grantsOfRequestedType = rbacDef.getGrantDefs().stream()
+                .filter(g -> g.level() <= MAX_LEVEL_TO_RENDER)
                 .filter(g -> g.grantType() == grantType)
                 .filter(this::isToBeRenderedInThisGraph)
                 .toList();
diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/test/dom/TestDomainEntity.java b/src/main/java/net/hostsharing/hsadminng/rbac/test/dom/TestDomainEntity.java
index 38610de3..167618ad 100644
--- a/src/main/java/net/hostsharing/hsadminng/rbac/test/dom/TestDomainEntity.java
+++ b/src/main/java/net/hostsharing/hsadminng/rbac/test/dom/TestDomainEntity.java
@@ -14,6 +14,7 @@ import java.io.IOException;
 import java.util.UUID;
 
 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.Permission.*;
 import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@@ -48,7 +49,7 @@ public class TestDomainEntity implements RbacObject {
                 .withIdentityView(SQL.projection("name"))
                 .withUpdatableColumns("version", "packageUuid", "description")
 
-                .importEntityAlias("package", TestPackageEntity.class,
+                .importEntityAlias("package", TestPackageEntity.class, usingDefaultCase(),
                         dependsOnColumn("packageUuid"),
                         directlyFetchedByDependsOnColumn(),
                         NOT_NULL)
diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/test/pac/TestPackageEntity.java b/src/main/java/net/hostsharing/hsadminng/rbac/test/pac/TestPackageEntity.java
index c338e38e..c7161064 100644
--- a/src/main/java/net/hostsharing/hsadminng/rbac/test/pac/TestPackageEntity.java
+++ b/src/main/java/net/hostsharing/hsadminng/rbac/test/pac/TestPackageEntity.java
@@ -14,6 +14,7 @@ import java.io.IOException;
 import java.util.UUID;
 
 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.Permission.*;
 import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@@ -49,7 +50,7 @@ public class TestPackageEntity implements RbacObject {
                 .withIdentityView(SQL.projection("name"))
                 .withUpdatableColumns("version", "customerUuid", "description")
 
-                .importEntityAlias("customer", TestCustomerEntity.class,
+                .importEntityAlias("customer", TestCustomerEntity.class, usingDefaultCase(),
                         dependsOnColumn("customerUuid"),
                         directlyFetchedByDependsOnColumn(),
                         NOT_NULL)
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 57ce3e73..d6e546cf 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
@@ -149,16 +149,6 @@ role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel.holderPerson:REFERRER
 role:global:ADMIN -.-> role:debitorRel.contact:OWNER
 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:ADMIN -.-> role:debitorRel:AGENT
-role:debitorRel:AGENT -.-> 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
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 e3528f7f..7791348c 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
@@ -108,16 +108,6 @@ role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel.holderPerson:REFERRER
 role:global:ADMIN -.-> role:debitorRel.contact:OWNER
 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:ADMIN -.-> role:debitorRel:AGENT
-role:debitorRel:AGENT -.-> 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/507-sepamandate/5073-hs-office-sepamandate-rbac.sql b/src/main/resources/db/changelog/5-hs-office/507-sepamandate/5073-hs-office-sepamandate-rbac.sql
index 9f126a22..839c29f6 100644
--- a/src/main/resources/db/changelog/5-hs-office/507-sepamandate/5073-hs-office-sepamandate-rbac.sql
+++ b/src/main/resources/db/changelog/5-hs-office/507-sepamandate/5073-hs-office-sepamandate-rbac.sql
@@ -115,6 +115,7 @@ do language plpgsql $$
         call defineContext('create INSERT INTO hs_office_sepamandate permissions for the related hs_office_relation rows');
 
         FOR row IN SELECT * FROM hs_office_relation
+			WHERE type = 'DEBITOR'
             LOOP
                 call grantPermissionToRole(
                     createPermission(row.uuid, 'INSERT', 'hs_office_sepamandate'),
@@ -131,9 +132,11 @@ create or replace function hs_office_sepamandate_hs_office_relation_insert_tf()
     language plpgsql
     strict as $$
 begin
-    call grantPermissionToRole(
+    if NEW.type = 'DEBITOR' then
+		call grantPermissionToRole(
             createPermission(NEW.uuid, 'INSERT', 'hs_office_sepamandate'),
             hsOfficeRelationADMIN(NEW));
+	end if;
     return NEW;
 end; $$;
 
diff --git a/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6013-hs-booking-item-rbac.md b/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6013-hs-booking-item-rbac.md
index 5cc8616f..9f94aaa5 100644
--- a/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6013-hs-booking-item-rbac.md
+++ b/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6013-hs-booking-item-rbac.md
@@ -216,16 +216,6 @@ role:debitor.debitorRel.holderPerson:ADMIN -.-> role:debitor.debitorRel.holderPe
 role:global:ADMIN -.-> role:debitor.debitorRel.contact:OWNER
 role:debitor.debitorRel.contact:OWNER -.-> role:debitor.debitorRel.contact:ADMIN
 role:debitor.debitorRel.contact:ADMIN -.-> role:debitor.debitorRel.contact:REFERRER
-role:global:ADMIN -.-> role:debitor.debitorRel:OWNER
-role:debitor.debitorRel:OWNER -.-> role:debitor.debitorRel:ADMIN
-role:debitor.debitorRel:ADMIN -.-> role:debitor.debitorRel:AGENT
-role:debitor.debitorRel:AGENT -.-> role:debitor.debitorRel:TENANT
-role:debitor.debitorRel.contact:ADMIN -.-> role:debitor.debitorRel:TENANT
-role:debitor.debitorRel:TENANT -.-> role:debitor.debitorRel.anchorPerson:REFERRER
-role:debitor.debitorRel:TENANT -.-> role:debitor.debitorRel.holderPerson:REFERRER
-role:debitor.debitorRel:TENANT -.-> role:debitor.debitorRel.contact:REFERRER
-role:debitor.debitorRel.anchorPerson:ADMIN -.-> role:debitor.debitorRel:OWNER
-role:debitor.debitorRel.holderPerson:ADMIN -.-> role:debitor.debitorRel:AGENT
 role:global:ADMIN -.-> role:debitor.refundBankAccount:OWNER
 role:debitor.refundBankAccount:OWNER -.-> role:debitor.refundBankAccount:ADMIN
 role:debitor.refundBankAccount:ADMIN -.-> role:debitor.refundBankAccount:REFERRER
@@ -262,16 +252,6 @@ role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel.holderPerson:REFERRER
 role:global:ADMIN -.-> role:debitorRel.contact:OWNER
 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:ADMIN -.-> role:debitorRel:AGENT
-role:debitorRel:AGENT -.-> 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:debitorRel:AGENT ==> role:bookingItem:OWNER
 role:bookingItem:OWNER ==> role:bookingItem:ADMIN
 role:debitorRel:AGENT ==> role:bookingItem:ADMIN
diff --git a/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6013-hs-booking-item-rbac.sql b/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6013-hs-booking-item-rbac.sql
index b2add620..5b40e779 100644
--- a/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6013-hs-booking-item-rbac.sql
+++ b/src/main/resources/db/changelog/6-hs-booking/601-booking-item/6013-hs-booking-item-rbac.sql
@@ -111,7 +111,7 @@ do language plpgsql $$
         call defineContext('create INSERT INTO hs_booking_item permissions for the related hs_office_relation rows');
 
         FOR row IN SELECT * FROM hs_office_relation
-                WHERE type in ('DEBITOR') -- TODO.rbac: currently manually patched, needs to be generated
+			WHERE type = 'DEBITOR'
             LOOP
                 call grantPermissionToRole(
                     createPermission(row.uuid, 'INSERT', 'hs_booking_item'),
@@ -128,11 +128,11 @@ create or replace function hs_booking_item_hs_office_relation_insert_tf()
     language plpgsql
     strict as $$
 begin
-    if NEW.type = 'DEBITOR' then -- TODO.rbac: currently manually patched, needs to be generated
-        call grantPermissionToRole(
+    if NEW.type = 'DEBITOR' then
+		call grantPermissionToRole(
             createPermission(NEW.uuid, 'INSERT', 'hs_booking_item'),
             hsOfficeRelationADMIN(NEW));
-    end if;
+	end if;
     return NEW;
 end; $$;
 
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 7e09519c..a26eda11 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
@@ -141,8 +141,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
                     .map(s -> s.replace("hs_office_", ""))
                     .containsExactlyInAnyOrder(distinct(from(
                             initialGrantNames,
-                            // TODO.rbac: this grant should only be created for DEBITOR-Relationships, thus the RBAC DSL needs to support conditional grants
-                            "{ 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:OWNER by system and assume }",
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 8e632c21..9c251466 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
@@ -131,8 +131,6 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
                     "hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT"));
             assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted(
                     initialGrantNames,
-                    // TODO.rbac: this grant should only be created for DEBITOR-Relationships, thus the RBAC DSL needs to support conditional grants
-                    "{ grant perm:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:INSERT>hs_office_sepamandate to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN by system and assume }",
 
                     "{ grant perm:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:DELETE to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:OWNER by system and assume }",
                     "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:OWNER to role:global#global:ADMIN by system and assume }",