conditional insert permission grant (so far just exactly 1 unique for each table) (#48)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: #48 Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
parent
4eda99b95a
commit
9806bcd78f
@ -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) {
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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) -> {
|
||||
if ( !x.equals(y) ) {
|
||||
throw new IllegalStateException("only a single INSERT permission grant allowed");
|
||||
}
|
||||
return x;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
if NEW.type = 'DEBITOR' then
|
||||
call grantPermissionToRole(
|
||||
createPermission(NEW.uuid, 'INSERT', 'hs_office_sepamandate'),
|
||||
hsOfficeRelationADMIN(NEW));
|
||||
end if;
|
||||
return NEW;
|
||||
end; $$;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,7 +128,7 @@ 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
|
||||
if NEW.type = 'DEBITOR' then
|
||||
call grantPermissionToRole(
|
||||
createPermission(NEW.uuid, 'INSERT', 'hs_booking_item'),
|
||||
hsOfficeRelationADMIN(NEW));
|
||||
|
@ -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 }",
|
||||
|
@ -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 }",
|
||||
|
Loading…
Reference in New Issue
Block a user