conditional insert permission grant (so far just exactly 1 unique for each table) #48

Merged
hsh-michaelhoennig merged 4 commits from conditional-insert-permission into master 2024-04-23 10:42:25 +02:00
14 changed files with 103 additions and 80 deletions
Showing only changes of commit 37bd3fefaf - Show all commits

View File

@ -174,7 +174,7 @@ project.tasks.processResources.dependsOn processSpring
project.tasks.compileJava.dependsOn processSpring project.tasks.compileJava.dependsOn processSpring
// Rename javax to jakarta in OpenApi generated java files because // 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 // 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). // and use either `bean-validation: true` in api-mapping.yaml or `useSpringBoot3 true` (not sure where exactly).
task openApiGenerate(type: Copy) { task openApiGenerate(type: Copy) {

View File

@ -35,10 +35,13 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static java.util.Optional.ofNullable; 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.lowerInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange; 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.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.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT;
@ -148,12 +151,12 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
.withRestrictedViewOrderBy(SQL.expression("validity")) .withRestrictedViewOrderBy(SQL.expression("validity"))
.withUpdatableColumns("version", "caption", "validity", "resources") .withUpdatableColumns("version", "caption", "validity", "resources")
.importEntityAlias("debitor", HsOfficeDebitorEntity.class, .importEntityAlias("debitor", HsOfficeDebitorEntity.class, usingDefaultCase(),
dependsOnColumn("debitorUuid"), dependsOnColumn("debitorUuid"),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NOT_NULL) NOT_NULL)
.importEntityAlias("debitorRel", HsOfficeRelationEntity.class, .importEntityAlias("debitorRel", HsOfficeRelationEntity.class, usingCase(DEBITOR),
dependsOnColumn("debitorUuid"), dependsOnColumn("debitorUuid"),
fetchedBySql(""" fetchedBySql("""
SELECT ${columns} SELECT ${columns}

View File

@ -36,7 +36,9 @@ import static jakarta.persistence.CascadeType.MERGE;
import static jakarta.persistence.CascadeType.PERSIST; import static jakarta.persistence.CascadeType.PERSIST;
import static jakarta.persistence.CascadeType.REFRESH; import static jakarta.persistence.CascadeType.REFRESH;
import static java.util.Optional.ofNullable; 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.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.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; 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.Nullable.NULLABLE;
@ -171,23 +173,21 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
"defaultPrefix" /* TODO.spec: do we want that updatable? */) "defaultPrefix" /* TODO.spec: do we want that updatable? */)
.toRole("global", ADMIN).grantPermission(INSERT) .toRole("global", ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, usingCase(DEBITOR),
// TODO.spec: do we need a distinct case for DEBITOR-Relation?
usingDefaultCase(),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
dependsOnColumn("debitorRelUuid")) dependsOnColumn("debitorRelUuid"))
.createPermission(DELETE).grantedTo("debitorRel", OWNER) .createPermission(DELETE).grantedTo("debitorRel", OWNER)
.createPermission(UPDATE).grantedTo("debitorRel", ADMIN) .createPermission(UPDATE).grantedTo("debitorRel", ADMIN)
.createPermission(SELECT).grantedTo("debitorRel", TENANT) .createPermission(SELECT).grantedTo("debitorRel", TENANT)
.importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class, .importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class, usingDefaultCase(),
dependsOnColumn("refundBankAccountUuid"), dependsOnColumn("refundBankAccountUuid"),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NULLABLE) NULLABLE)
.toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT) .toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER) .toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
.importEntityAlias("partnerRel", HsOfficeRelationEntity.class, .importEntityAlias("partnerRel", HsOfficeRelationEntity.class, usingDefaultCase(),
dependsOnColumn("debitorRelUuid"), dependsOnColumn("debitorRelUuid"),
fetchedBySql(""" fetchedBySql("""
SELECT ${columns} SELECT ${columns}

View File

@ -18,8 +18,10 @@ import java.io.IOException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; 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.mapper.PostgresDateRange.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; 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.GLOBAL; 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.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
@ -107,7 +109,7 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, RbacObject {
.withRestrictedViewOrderBy(expression("validity")) .withRestrictedViewOrderBy(expression("validity"))
.withUpdatableColumns("reference", "agreement", "validity") .withUpdatableColumns("reference", "agreement", "validity")
.importEntityAlias("debitorRel", HsOfficeRelationEntity.class, .importEntityAlias("debitorRel", HsOfficeRelationEntity.class, usingCase(DEBITOR),
dependsOnColumn("debitorUuid"), dependsOnColumn("debitorUuid"),
fetchedBySql(""" fetchedBySql("""
SELECT ${columns} SELECT ${columns}

View File

@ -50,7 +50,7 @@ public class InsertTriggerGenerator {
begin begin
call defineContext('create INSERT INTO ${rawSubTableName} permissions for the related ${rawSuperTableName} rows'); 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 LOOP
call grantPermissionToRole( call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', '${rawSubTableName}'), createPermission(row.uuid, 'INSERT', '${rawSubTableName}'),
@ -61,7 +61,10 @@ public class InsertTriggerGenerator {
""", """,
with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
with("rawSuperTableName", superRoleDef.getEntityAlias().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 language plpgsql
strict as $$ strict as $$
begin begin
call grantPermissionToRole( ${typeConditionIf}call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}'), createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}'),
${rawSuperRoleDescriptor}); ${rawSuperRoleDescriptor});${typeConditionEndIf}
return NEW; return NEW;
end; $$; end; $$;
@ -91,7 +94,14 @@ public class InsertTriggerGenerator {
""", """,
with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()),
with("rawSuperTableName", superRoleDef.getEntityAlias().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() { private static <T> BinaryOperator<T> singleton() {
return (x, y) -> { return (x, y) -> {
if ( !x.equals(y) ) {
throw new IllegalStateException("only a single INSERT permission grant allowed"); throw new IllegalStateException("only a single INSERT permission grant allowed");
}
return x;
}; };
} }

View File

@ -18,7 +18,9 @@ import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.lang.reflect.Modifier.isStatic; import static java.lang.reflect.Modifier.isStatic;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Collections.max;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase; 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.NOT_NULL;
@ -314,6 +316,15 @@ public class RbacView {
return this; return this;
} }
// TODO.impl: use importEntityAlias with all parameters
@Deprecated
public RbacView importEntityAlias(
final String aliasName, final Class<? extends RbacObject> entityClass,
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, false, nullable);
return this;
}
/** /**
* Imports the RBAC template from the given entity class and defines an anlias name for it. * Imports the RBAC template from the given entity class and defines an anlias name for it.
* *
@ -325,6 +336,9 @@ public class RbacView {
* A JPA entity class extending RbacObject which also implements an `rbac` method returning * A JPA entity class extending RbacObject which also implements an `rbac` method returning
* its RBAC specification. * its RBAC specification.
* *
* @param usingCase
* Only use this case value for a switch within the rbac rules.
*
* @param fetchSql * @param fetchSql
* An SQL SELECT statement which fetches the referenced row. Use `${REF}` to speficiy the * 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). * newly created or updated row (will be replaced by NEW/OLD from the trigger method).
@ -342,19 +356,29 @@ public class RbacView {
* a JPA entity class extending RbacObject * a JPA entity class extending RbacObject
*/ */
public RbacView importEntityAlias( 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) { 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; return this;
} }
private EntityAlias importEntityAliasImpl( 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 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 { try {
importAsAlias(aliasName, rbacDefinition(entityClass), forCase, asSubEntity); // TODO.impl: 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) { } catch (final ReflectiveOperationException exc) {
throw new RuntimeException("cannot import entity: " + entityClass, exc); throw new RuntimeException("cannot import entity: " + entityClass, exc);
} }
@ -369,7 +393,7 @@ public class RbacView {
private RbacView importAsAlias(final String aliasName, final RbacView importedRbacView, final ColumnValue forCase, 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, final var mapper = new AliasNameMapper(importedRbacView, aliasName,
asSubEntity ? entityAliases.keySet() : null); asSubEntity ? entityAliases.keySet() : null);
importedRbacView.getEntityAliases().values().stream() copyOf(importedRbacView.getEntityAliases().values()).stream()
.filter(entityAlias -> !importedRbacView.isRootEntityAlias(entityAlias)) .filter(entityAlias -> !importedRbacView.isRootEntityAlias(entityAlias))
.filter(entityAlias -> !entityAlias.isGlobal()) .filter(entityAlias -> !entityAlias.isGlobal())
.filter(entityAlias -> !asSubEntity || !entityAliases.containsKey(entityAlias.aliasName)) .filter(entityAlias -> !asSubEntity || !entityAliases.containsKey(entityAlias.aliasName))
@ -377,10 +401,10 @@ public class RbacView {
final String mappedAliasName = mapper.map(entityAlias.aliasName); final String mappedAliasName = mapper.map(entityAlias.aliasName);
entityAliases.put(mappedAliasName, new EntityAlias(mappedAliasName, entityAlias.entityClass)); 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); 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 && if ( grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE &&
(grantDef.forCases == null || grantDef.matchesCase(forCase)) ) { (grantDef.forCases == null || grantDef.matchesCase(forCase)) ) {
final var importedGrantDef = findOrCreateGrantDef( final var importedGrantDef = findOrCreateGrantDef(
@ -411,6 +435,10 @@ public class RbacView {
return this; return this;
} }
private static <T> List<T> copyOf(final Collection<T> eas) {
return eas.stream().toList();
}
private void verifyVersionColumnExists() { private void verifyVersionColumnExists() {
if (stream(rootEntityAlias.entityClass.getDeclaredFields()) if (stream(rootEntityAlias.entityClass.getDeclaredFields())
.noneMatch(f -> f.getAnnotation(Version.class) != null)) { .noneMatch(f -> f.getAnnotation(Version.class) != null)) {
@ -615,6 +643,13 @@ public class RbacView {
return this; 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 { public enum GrantType {
ROLE_TO_USER, ROLE_TO_USER,
ROLE_TO_ROLE, ROLE_TO_ROLE,
@ -854,14 +889,14 @@ public class RbacView {
return distinctGrantDef; 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) { 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) { 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() { boolean isGlobal() {
@ -872,7 +907,6 @@ public class RbacView {
return entityClass == null; return entityClass == null;
} }
@NotNull
@Override @Override
public SQL fetchSql() { public SQL fetchSql() {
if (fetchSql == null) { if (fetchSql == null) {
@ -914,6 +948,14 @@ public class RbacView {
} }
return dependsOnColum.column; 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) { public static String withoutRvSuffix(final String tableName) {
@ -1074,10 +1116,9 @@ public class RbacView {
return new ColumnValue(null); return new ColumnValue(null);
} }
public static ColumnValue usingCase(final String value) { public static <E extends Enum<E>> ColumnValue usingCase(final E value) {
return new ColumnValue(value); return new ColumnValue(value.name());
} }
public final String value; public final String value;
private ColumnValue(final String value) { private ColumnValue(final String value) {

View File

@ -15,6 +15,9 @@ public class RbacViewMermaidFlowchartGenerator {
public static final String HOSTSHARING_LIGHT_ORANGE = "#feb28c"; public static final String HOSTSHARING_LIGHT_ORANGE = "#feb28c";
public static final String HOSTSHARING_DARK_BLUE = "#274d6e"; public static final String HOSTSHARING_DARK_BLUE = "#274d6e";
public static final String HOSTSHARING_LIGHT_BLUE = "#99bcdb"; 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 RbacView rbacDef;
private final CaseDef forCase; private final CaseDef forCase;
@ -56,6 +59,7 @@ public class RbacViewMermaidFlowchartGenerator {
flowchart.indented( () -> { flowchart.indented( () -> {
rbacDef.getEntityAliases().values().stream() rbacDef.getEntityAliases().values().stream()
.filter(e -> e.level() <= MAX_LEVEL_TO_RENDER)
.filter(e -> e.aliasName().startsWith(entity.aliasName() + ":")) .filter(e -> e.aliasName().startsWith(entity.aliasName() + ":"))
.forEach(this::renderEntitySubgraph); .forEach(this::renderEntitySubgraph);
@ -106,6 +110,7 @@ public class RbacViewMermaidFlowchartGenerator {
private void renderGrants(final RbacView.RbacGrantDefinition.GrantType grantType, final String comment) { private void renderGrants(final RbacView.RbacGrantDefinition.GrantType grantType, final String comment) {
final var grantsOfRequestedType = rbacDef.getGrantDefs().stream() final var grantsOfRequestedType = rbacDef.getGrantDefs().stream()
.filter(g -> g.level() <= MAX_LEVEL_TO_RENDER)
.filter(g -> g.grantType() == grantType) .filter(g -> g.grantType() == grantType)
.filter(this::isToBeRenderedInThisGraph) .filter(this::isToBeRenderedInThisGraph)
.toList(); .toList();

View File

@ -149,16 +149,6 @@ role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel.holderPerson:REFERRER
role:global:ADMIN -.-> role:debitorRel.contact:OWNER role:global:ADMIN -.-> role:debitorRel.contact:OWNER
role:debitorRel.contact:OWNER -.-> role:debitorRel.contact:ADMIN role:debitorRel.contact:OWNER -.-> role:debitorRel.contact:ADMIN
role:debitorRel.contact:ADMIN -.-> role:debitorRel.contact:REFERRER 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:global:ADMIN -.-> role:refundBankAccount:OWNER
role:refundBankAccount:OWNER -.-> role:refundBankAccount:ADMIN role:refundBankAccount:OWNER -.-> role:refundBankAccount:ADMIN
role:refundBankAccount:ADMIN -.-> role:refundBankAccount:REFERRER role:refundBankAccount:ADMIN -.-> role:refundBankAccount:REFERRER

View File

@ -108,16 +108,6 @@ role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel.holderPerson:REFERRER
role:global:ADMIN -.-> role:debitorRel.contact:OWNER role:global:ADMIN -.-> role:debitorRel.contact:OWNER
role:debitorRel.contact:OWNER -.-> role:debitorRel.contact:ADMIN role:debitorRel.contact:OWNER -.-> role:debitorRel.contact:ADMIN
role:debitorRel.contact:ADMIN -.-> role:debitorRel.contact:REFERRER 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:global:ADMIN -.-> role:bankAccount:OWNER
role:bankAccount:OWNER -.-> role:bankAccount:ADMIN role:bankAccount:OWNER -.-> role:bankAccount:ADMIN
role:bankAccount:ADMIN -.-> role:bankAccount:REFERRER role:bankAccount:ADMIN -.-> role:bankAccount:REFERRER

View File

@ -115,6 +115,7 @@ do language plpgsql $$
call defineContext('create INSERT INTO hs_office_sepamandate permissions for the related hs_office_relation rows'); call defineContext('create INSERT INTO hs_office_sepamandate permissions for the related hs_office_relation rows');
FOR row IN SELECT * FROM hs_office_relation FOR row IN SELECT * FROM hs_office_relation
WHERE type = 'DEBITOR'
LOOP LOOP
call grantPermissionToRole( call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_office_sepamandate'), 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 language plpgsql
strict as $$ strict as $$
begin begin
if NEW.type = 'DEBITOR' then
call grantPermissionToRole( call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_office_sepamandate'), createPermission(NEW.uuid, 'INSERT', 'hs_office_sepamandate'),
hsOfficeRelationADMIN(NEW)); hsOfficeRelationADMIN(NEW));
end if;
return NEW; return NEW;
end; $$; end; $$;

View File

@ -216,16 +216,6 @@ role:debitor.debitorRel.holderPerson:ADMIN -.-> role:debitor.debitorRel.holderPe
role:global:ADMIN -.-> role:debitor.debitorRel.contact:OWNER role:global:ADMIN -.-> role:debitor.debitorRel.contact:OWNER
role:debitor.debitorRel.contact:OWNER -.-> role:debitor.debitorRel.contact:ADMIN role:debitor.debitorRel.contact:OWNER -.-> role:debitor.debitorRel.contact:ADMIN
role:debitor.debitorRel.contact:ADMIN -.-> role:debitor.debitorRel.contact:REFERRER 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:global:ADMIN -.-> role:debitor.refundBankAccount:OWNER
role:debitor.refundBankAccount:OWNER -.-> role:debitor.refundBankAccount:ADMIN role:debitor.refundBankAccount:OWNER -.-> role:debitor.refundBankAccount:ADMIN
role:debitor.refundBankAccount:ADMIN -.-> role:debitor.refundBankAccount:REFERRER 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:global:ADMIN -.-> role:debitorRel.contact:OWNER
role:debitorRel.contact:OWNER -.-> role:debitorRel.contact:ADMIN role:debitorRel.contact:OWNER -.-> role:debitorRel.contact:ADMIN
role:debitorRel.contact:ADMIN -.-> role:debitorRel.contact:REFERRER 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:debitorRel:AGENT ==> role:bookingItem:OWNER
role:bookingItem:OWNER ==> role:bookingItem:ADMIN role:bookingItem:OWNER ==> role:bookingItem:ADMIN
role:debitorRel:AGENT ==> role:bookingItem:ADMIN role:debitorRel:AGENT ==> role:bookingItem:ADMIN

View File

@ -111,7 +111,7 @@ do language plpgsql $$
call defineContext('create INSERT INTO hs_booking_item permissions for the related hs_office_relation rows'); call defineContext('create INSERT INTO hs_booking_item permissions for the related hs_office_relation rows');
FOR row IN SELECT * FROM hs_office_relation 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 LOOP
call grantPermissionToRole( call grantPermissionToRole(
createPermission(row.uuid, 'INSERT', 'hs_booking_item'), createPermission(row.uuid, 'INSERT', 'hs_booking_item'),
@ -128,7 +128,7 @@ create or replace function hs_booking_item_hs_office_relation_insert_tf()
language plpgsql language plpgsql
strict as $$ strict as $$
begin begin
if NEW.type = 'DEBITOR' then -- TODO.rbac: currently manually patched, needs to be generated if NEW.type = 'DEBITOR' then
call grantPermissionToRole( call grantPermissionToRole(
createPermission(NEW.uuid, 'INSERT', 'hs_booking_item'), createPermission(NEW.uuid, 'INSERT', 'hs_booking_item'),
hsOfficeRelationADMIN(NEW)); hsOfficeRelationADMIN(NEW));

View File

@ -141,8 +141,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(distinct(from( .containsExactlyInAnyOrder(distinct(from(
initialGrantNames, 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 // permissions on partner
"{ grant perm:partner#P-20032:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }", "{ grant perm:partner#P-20032:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }",

View File

@ -131,8 +131,6 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
"hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT")); "hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT"));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted( assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, 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 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 }", "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:OWNER to role:global#global:ADMIN by system and assume }",