introduce-hosting-module #46
@ -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) {
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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) -> {
|
||||||
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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
call grantPermissionToRole(
|
if NEW.type = 'DEBITOR' then
|
||||||
|
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; $$;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,11 +128,11 @@ 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));
|
||||||
end if;
|
end if;
|
||||||
return NEW;
|
return NEW;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
|
@ -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 }",
|
||||||
|
@ -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 }",
|
||||||
|
Loading…
Reference in New Issue
Block a user