RBAC generator with conditional grants used for REPRESENTATIVE-Relation (#33)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #33
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-04-08 11:16:06 +02:00
parent 896968e110
commit 44ff30c54a
29 changed files with 567 additions and 375 deletions

View File

@ -10,7 +10,6 @@ import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
@ -25,8 +24,6 @@ import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.io.IOException; import java.io.IOException;
import java.io.IOException;
import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Optional; import java.util.Optional;

View File

@ -1,6 +1,10 @@
package net.hostsharing.hsadminng.hs.office.debitor; package net.hostsharing.hsadminng.hs.office.debitor;
import lombok.*; import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
@ -15,7 +19,13 @@ import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import jakarta.persistence.*; import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
@ -26,6 +36,7 @@ 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.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NULLABLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
@ -157,6 +168,8 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
.toRole("global", ADMIN).grantPermission(INSERT) .toRole("global", ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, .importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class,
// 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)

View File

@ -29,8 +29,6 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.ADMIN;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.AGENT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.AGENT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.OWNER; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.OWNER;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.TENANT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;

View File

@ -29,6 +29,7 @@ import java.util.UUID;
import static jakarta.persistence.CascadeType.*; import static jakarta.persistence.CascadeType.*;
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.usingDefaultCase;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
@ -98,18 +99,19 @@ public class HsOfficePartnerEntity implements Stringifyable, RbacObject {
.toRole("global", ADMIN).grantPermission(INSERT) .toRole("global", ADMIN).grantPermission(INSERT)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class, .importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
usingDefaultCase(),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
dependsOnColumn("partnerRelUuid")) dependsOnColumn("partnerRelUuid"))
.createPermission(DELETE).grantedTo("partnerRel", ADMIN) .createPermission(DELETE).grantedTo("partnerRel", OWNER)
.createPermission(UPDATE).grantedTo("partnerRel", AGENT) .createPermission(UPDATE).grantedTo("partnerRel", ADMIN)
.createPermission(SELECT).grantedTo("partnerRel", TENANT) .createPermission(SELECT).grantedTo("partnerRel", TENANT)
.importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class, .importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class,
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
dependsOnColumn("detailsUuid")) dependsOnColumn("detailsUuid"))
.createPermission("partnerDetails", DELETE).grantedTo("partnerRel", ADMIN) .createPermission("partnerDetails", DELETE).grantedTo("partnerRel", OWNER)
.createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT) .createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT)
.createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); .createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); // not TENANT!
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {

View File

@ -11,17 +11,19 @@ import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.persistence.Column;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inCaseOf;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inOtherCases;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@ -101,31 +103,55 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
dependsOnColumn("contactUuid"), dependsOnColumn("contactUuid"),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NOT_NULL) NOT_NULL)
.createRole(OWNER, (with) -> { .switchOnColumn("type",
with.owningUser(CREATOR); inCaseOf("REPRESENTATIVE", then -> {
with.incomingSuperRole(GLOBAL, ADMIN); then.createRole(OWNER, (with) -> {
// TODO: if type=REPRESENTATIIVE with.owningUser(CREATOR);
// with.incomingSuperRole("holderPerson", ADMIN); with.incomingSuperRole(GLOBAL, ADMIN);
with.permission(DELETE); with.incomingSuperRole("holderPerson", ADMIN);
}) with.permission(DELETE);
.createSubRole(ADMIN, (with) -> { })
with.incomingSuperRole("anchorPerson", ADMIN); .createSubRole(ADMIN, (with) -> {
// TODO: if type=REPRESENTATIIVE with.outgoingSubRole("anchorPerson", OWNER);
// with.outgoingSuperRole("anchorPerson", OWNER); with.permission(UPDATE);
with.permission(UPDATE); })
}) .createSubRole(AGENT, (with) -> {
.createSubRole(AGENT, (with) -> { with.incomingSuperRole("anchorPerson", ADMIN);
with.incomingSuperRole("holderPerson", ADMIN); })
}) .createSubRole(TENANT, (with) -> {
.createSubRole(TENANT, (with) -> { with.incomingSuperRole("contact", ADMIN);
with.incomingSuperRole("holderPerson", ADMIN); with.outgoingSubRole("anchorPerson", REFERRER);
with.incomingSuperRole("contact", ADMIN); with.outgoingSubRole("holderPerson", REFERRER);
with.outgoingSubRole("anchorPerson", REFERRER); with.outgoingSubRole("contact", REFERRER);
with.outgoingSubRole("holderPerson", REFERRER); with.permission(SELECT);
with.outgoingSubRole("contact", REFERRER); });
with.permission(SELECT); }),
}) // inCaseOf("DEBITOR", then -> {}), TODO.spec: needs to be defined
inOtherCases(then -> {
then.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
with.incomingSuperRole("anchorPerson", ADMIN);
with.permission(DELETE);
})
.createSubRole(ADMIN, (with) -> {
with.permission(UPDATE);
})
.createSubRole(AGENT, (with) -> {
// TODO.spec: we need relation:PROXY, to allow changing the relation contact.
// the alternative would be to move this to the relation:ADMIN role,
// but then the partner holder person could update the partner relation itself,
// see partner entity.
with.incomingSuperRole("holderPerson", ADMIN);
})
.createSubRole(TENANT, (with) -> {
with.incomingSuperRole("contact", ADMIN);
with.outgoingSubRole("anchorPerson", REFERRER);
with.outgoingSubRole("holderPerson", REFERRER);
with.outgoingSubRole("contact", REFERRER);
with.permission(SELECT);
});
}))
.toRole("anchorPerson", ADMIN).grantPermission(INSERT); .toRole("anchorPerson", ADMIN).grantPermission(INSERT);
} }

View File

@ -14,7 +14,6 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity; import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.test.cust.TestCustomerEntity; import net.hostsharing.hsadminng.test.cust.TestCustomerEntity;
import net.hostsharing.hsadminng.test.dom.TestDomainEntity; import net.hostsharing.hsadminng.test.dom.TestDomainEntity;
import net.hostsharing.hsadminng.test.pac.TestPackageEntity; import net.hostsharing.hsadminng.test.pac.TestPackageEntity;
@ -27,18 +26,22 @@ import java.lang.reflect.Method;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static java.lang.reflect.Modifier.isStatic; import static java.lang.reflect.Modifier.isStatic;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
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.Nullable.NOT_NULL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.Part.AUTO_FETCH; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.Part.AUTO_FETCH;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.directlyFetchedByDependsOnColumn; import static org.apache.commons.collections4.SetUtils.hashSet;
import static org.apache.commons.lang3.StringUtils.uncapitalize; import static org.apache.commons.lang3.StringUtils.uncapitalize;
@Getter @Getter
// TODO.refa: rename to RbacDSL
public class RbacView { public class RbacView {
public static final String GLOBAL = "global"; public static final String GLOBAL = "global";
@ -61,11 +64,23 @@ public class RbacView {
}; };
private final Set<String> updatableColumns = new LinkedHashSet<>(); private final Set<String> updatableColumns = new LinkedHashSet<>();
private final Set<RbacGrantDefinition> grantDefs = new LinkedHashSet<>(); private final Set<RbacGrantDefinition> grantDefs = new LinkedHashSet<>();
private final Set<CaseDef> allCases = new LinkedHashSet<>();
private String discriminatorColumName;
private CaseDef processingCase;
private SQL identityViewSqlQuery; private SQL identityViewSqlQuery;
private SQL orderBySqlExpression; private SQL orderBySqlExpression;
private EntityAlias rootEntityAliasProxy; private EntityAlias rootEntityAliasProxy;
private RbacRoleDefinition previousRoleDef; private RbacRoleDefinition previousRoleDef;
private final Map<String, CaseDef> cases = new LinkedHashMap<>() {
@Override
public CaseDef put(final String key, final CaseDef value) {
if (containsKey(key)) {
throw new IllegalArgumentException("duplicate case: " + key);
}
return super.put(key, value);
}
};
/** Crates an RBAC definition template for the given entity class and defining the given alias. /** Crates an RBAC definition template for the given entity class and defining the given alias.
* *
@ -239,7 +254,11 @@ public class RbacView {
} }
private RbacPermissionDefinition createPermission(final EntityAlias entityAlias, final Permission permission) { private RbacPermissionDefinition createPermission(final EntityAlias entityAlias, final Permission permission) {
return new RbacPermissionDefinition(entityAlias, permission, null, true); return permDefs.stream()
.filter(p -> p.permission == permission && p.entityAlias == entityAlias)
.findFirst()
// .map(g -> g.forCase(processingCase)) TODO.impl: not implemented case dependent
.orElseGet(() -> new RbacPermissionDefinition(entityAlias, permission, null, true));
} }
public <EC extends RbacObject> RbacView declarePlaceholderEntityAliases(final String... aliasNames) { public <EC extends RbacObject> RbacView declarePlaceholderEntityAliases(final String... aliasNames) {
@ -278,12 +297,13 @@ public class RbacView {
public <EC extends RbacObject> RbacView importRootEntityAliasProxy( public <EC extends RbacObject> RbacView importRootEntityAliasProxy(
final String aliasName, final String aliasName,
final Class<? extends RbacObject> entityClass, final Class<? extends RbacObject> entityClass,
final ColumnValue forCase,
final SQL fetchSql, final SQL fetchSql,
final Column dependsOnColum) { final Column dependsOnColum) {
if (rootEntityAliasProxy != null) { if (rootEntityAliasProxy != null) {
throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy); throw new IllegalStateException("there is already an entityAliasProxy: " + rootEntityAliasProxy);
} }
rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, NOT_NULL); rootEntityAliasProxy = importEntityAliasImpl(aliasName, entityClass, forCase, fetchSql, dependsOnColum, false, NOT_NULL);
return this; return this;
} }
@ -302,7 +322,7 @@ public class RbacView {
public RbacView importSubEntityAlias( public RbacView importSubEntityAlias(
final String aliasName, final Class<? extends RbacObject> entityClass, final String aliasName, final Class<? extends RbacObject> entityClass,
final SQL fetchSql, final Column dependsOnColum) { final SQL fetchSql, final Column dependsOnColum) {
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, true, NOT_NULL); importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, true, NOT_NULL);
return this; return this;
} }
@ -336,25 +356,17 @@ public class RbacView {
public RbacView importEntityAlias( public RbacView importEntityAlias(
final String aliasName, final Class<? extends RbacObject> entityClass, final String aliasName, final Class<? extends RbacObject> entityClass,
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) { final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum, false, nullable); importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, false, nullable);
return this;
}
// TODO: remove once it's not used in HsOffice...Entity anymore
public RbacView importEntityAlias(
final String aliasName, final Class<? extends RbacObject> entityClass,
final Column dependsOnColum) {
importEntityAliasImpl(aliasName, entityClass, directlyFetchedByDependsOnColumn(), dependsOnColum, false, null);
return this; return this;
} }
private EntityAlias importEntityAliasImpl( private EntityAlias importEntityAliasImpl(
final String aliasName, final Class<? extends RbacObject> entityClass, final String aliasName, final Class<? extends RbacObject> entityClass, final ColumnValue forCase,
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); final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum, asSubEntity, nullable);
entityAliases.put(aliasName, entityAlias); entityAliases.put(aliasName, entityAlias);
try { try {
importAsAlias(aliasName, rbacDefinition(entityClass), asSubEntity); importAsAlias(aliasName, rbacDefinition(entityClass), forCase, 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);
} }
@ -366,7 +378,7 @@ public class RbacView {
return (RbacView) entityClass.getMethod("rbac").invoke(null); return (RbacView) entityClass.getMethod("rbac").invoke(null);
} }
private RbacView importAsAlias(final String aliasName, final RbacView importedRbacView, final boolean asSubEntity) { private RbacView importAsAlias(final String aliasName, final RbacView importedRbacView, final ColumnValue forCase, final boolean asSubEntity) {
final var mapper = new AliasNameMapper(importedRbacView, aliasName, final var mapper = new AliasNameMapper(importedRbacView, aliasName,
asSubEntity ? entityAliases.keySet() : null); asSubEntity ? entityAliases.keySet() : null);
importedRbacView.getEntityAliases().values().stream() importedRbacView.getEntityAliases().values().stream()
@ -381,7 +393,8 @@ public class RbacView {
new RbacRoleDefinition(findEntityAlias(mapper.map(roleDef.entityAlias.aliasName)), roleDef.role); new RbacRoleDefinition(findEntityAlias(mapper.map(roleDef.entityAlias.aliasName)), roleDef.role);
}); });
importedRbacView.getGrantDefs().forEach(grantDef -> { importedRbacView.getGrantDefs().forEach(grantDef -> {
if (grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE) { if ( grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE &&
(grantDef.forCases == null || grantDef.matchesCase(forCase)) ) {
final var importedGrantDef = findOrCreateGrantDef( final var importedGrantDef = findOrCreateGrantDef(
findRbacRole( findRbacRole(
mapper.map(grantDef.getSubRoleDef().entityAlias.aliasName), mapper.map(grantDef.getSubRoleDef().entityAlias.aliasName),
@ -398,6 +411,18 @@ public class RbacView {
return this; return this;
} }
public RbacView switchOnColumn(final String discriminatorColumName, final CaseDef... caseDefs) {
this.discriminatorColumName = discriminatorColumName;
allCases.addAll(stream(caseDefs).toList());
stream(caseDefs).forEach(caseDef -> {
this.processingCase = caseDef;
caseDef.def.accept(this);
this.processingCase = null;
});
return this;
}
private void verifyVersionColumnExists() { 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)) {
@ -456,7 +481,15 @@ public class RbacView {
} }
public void generateWithBaseFileName(final String baseFileName) { public void generateWithBaseFileName(final String baseFileName) {
new RbacViewMermaidFlowchartGenerator(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + ".md")); if (allCases.size() > 1) {
allCases.forEach(caseDef -> {
final var fileName = baseFileName + (caseDef.isDefaultCase() ? "" : "-" + caseDef.value) + ".md";
new RbacViewMermaidFlowchartGenerator(this, caseDef)
.generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, fileName));
});
} else {
new RbacViewMermaidFlowchartGenerator(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + ".md"));
}
new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + ".sql")); new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + ".sql"));
} }
@ -496,22 +529,28 @@ public class RbacView {
private final RbacPermissionDefinition permDef; private final RbacPermissionDefinition permDef;
private boolean assumed = true; private boolean assumed = true;
private boolean toCreate = false; private boolean toCreate = false;
private Set<CaseDef> forCases = new HashSet<>();
@Override @Override
public String toString() { public String toString() {
final var arrow = isAssumed() ? " --> " : " -- // --> "; final var arrow = isAssumed() ? " --> " : " -- // --> ";
return switch (grantType()) { final var grant = switch (grantType()) {
case ROLE_TO_USER -> userDef.toString() + arrow + subRoleDef.toString(); case ROLE_TO_USER -> userDef.toString() + arrow + subRoleDef.toString();
case ROLE_TO_ROLE -> superRoleDef + arrow + subRoleDef; case ROLE_TO_ROLE -> superRoleDef + arrow + subRoleDef;
case PERM_TO_ROLE -> superRoleDef + arrow + permDef; case PERM_TO_ROLE -> superRoleDef + arrow + permDef;
}; };
final var condition = isConditional()
? (" (" +forCases.stream().map(CaseDef::toString).collect(Collectors.joining("||")) + ")")
: "";
return grant + condition;
} }
RbacGrantDefinition(final RbacRoleDefinition subRoleDef, final RbacRoleDefinition superRoleDef) { RbacGrantDefinition(final RbacRoleDefinition subRoleDef, final RbacRoleDefinition superRoleDef, final CaseDef forCase) {
this.userDef = null; this.userDef = null;
this.subRoleDef = subRoleDef; this.subRoleDef = subRoleDef;
this.superRoleDef = superRoleDef; this.superRoleDef = superRoleDef;
this.permDef = null; this.permDef = null;
this.forCases = forCase != null ? hashSet(forCase) : null;
register(this); register(this);
} }
@ -537,7 +576,7 @@ public class RbacView {
@NotNull @NotNull
GrantType grantType() { GrantType grantType() {
return permDef != null ? GrantType.PERM_TO_ROLE return permDef != null ? PERM_TO_ROLE
: userDef != null ? GrantType.ROLE_TO_USER : userDef != null ? GrantType.ROLE_TO_USER
: GrantType.ROLE_TO_ROLE; : GrantType.ROLE_TO_ROLE;
} }
@ -546,6 +585,23 @@ public class RbacView {
return assumed; return assumed;
} }
RbacGrantDefinition forCase(final CaseDef processingCase) {
forCases.add(processingCase);
return this;
}
boolean isConditional() {
return forCases != null && !forCases.isEmpty() && forCases.size()<allCases.size();
}
boolean matchesCase(final ColumnValue requestedCase) {
final var noCasesDefined = forCases.isEmpty();
final var generateForAllCases = requestedCase == null;
final boolean isGrantedForRequestedCase = forCases.stream().anyMatch(c -> c.isCase(requestedCase));
return noCasesDefined || generateForAllCases || isGrantedForRequestedCase;
}
boolean isToCreate() { boolean isToCreate() {
return toCreate; return toCreate;
} }
@ -567,8 +623,9 @@ public class RbacView {
.orElse(false); .orElse(false);
} }
public void unassumed() { public RbacGrantDefinition unassumed() {
this.assumed = false; this.assumed = false;
return this;
} }
public enum GrantType { public enum GrantType {
@ -794,7 +851,7 @@ public class RbacView {
private RbacGrantDefinition findOrCreateGrantDef(final RbacPermissionDefinition permDef, final RbacRoleDefinition roleDef) { private RbacGrantDefinition findOrCreateGrantDef(final RbacPermissionDefinition permDef, final RbacRoleDefinition roleDef) {
return grantDefs.stream() return grantDefs.stream()
.filter(g -> g.permDef == permDef && g.subRoleDef == roleDef) .filter(g -> g.permDef == permDef && g.superRoleDef == roleDef)
.findFirst() .findFirst()
.orElseGet(() -> new RbacGrantDefinition(permDef, roleDef)); .orElseGet(() -> new RbacGrantDefinition(permDef, roleDef));
} }
@ -802,10 +859,12 @@ public class RbacView {
private RbacGrantDefinition findOrCreateGrantDef( private RbacGrantDefinition findOrCreateGrantDef(
final RbacRoleDefinition subRoleDefinition, final RbacRoleDefinition subRoleDefinition,
final RbacRoleDefinition superRoleDefinition) { final RbacRoleDefinition superRoleDefinition) {
return grantDefs.stream() final var distinctGrantDef = grantDefs.stream()
.filter(g -> g.subRoleDef == subRoleDefinition && g.superRoleDef == superRoleDefinition) .filter(g -> g.subRoleDef == subRoleDefinition && g.superRoleDef == superRoleDefinition)
.findFirst() .findFirst()
.orElseGet(() -> new RbacGrantDefinition(subRoleDefinition, superRoleDefinition)); .map(g -> g.forCase(processingCase))
.orElseGet(() -> new RbacGrantDefinition(subRoleDefinition, superRoleDefinition, processingCase));
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, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) {
@ -1022,6 +1081,23 @@ public class RbacView {
} }
} }
public static class ColumnValue {
public static ColumnValue usingDefaultCase() {
return new ColumnValue(null);
}
public static ColumnValue usingCase(final String value) {
return new ColumnValue(value);
}
public final String value;
private ColumnValue(final String value) {
this.value = value;
}
}
private static class AliasNameMapper { private static class AliasNameMapper {
private final RbacView importedRbacView; private final RbacView importedRbacView;
@ -1046,6 +1122,55 @@ public class RbacView {
} }
} }
public static class CaseDef extends ColumnValue {
final Consumer<RbacView> def;
private CaseDef(final String discriminatorColumnValue, final Consumer<RbacView> def) {
super(discriminatorColumnValue);
this.def = def;
}
public static CaseDef inCaseOf(final String discriminatorColumnValue, final Consumer<RbacView> def) {
return new CaseDef(discriminatorColumnValue, def);
}
public static CaseDef inOtherCases(final Consumer<RbacView> def) {
return new CaseDef(null, def);
}
@Override
public int hashCode() {
return ofNullable(value).map(String::hashCode).orElse(0);
}
@Override
public boolean equals(final Object other) {
if (this == other)
return true;
if (other == null || getClass() != other.getClass())
return false;
final CaseDef caseDef = (CaseDef) other;
return Objects.equals(value, caseDef.value);
}
boolean isDefaultCase() {
return value == null;
}
@Override
public String toString() {
return isDefaultCase()
? "inOtherCases"
: "inCaseOf:" + value;
}
public boolean isCase(final ColumnValue requestedCase) {
return Objects.equals(requestedCase.value, this.value);
}
}
private static void generateRbacView(final Class<? extends RbacObject> c) { private static void generateRbacView(final Class<? extends RbacObject> c) {
final Method mainMethod = stream(c.getMethods()).filter( final Method mainMethod = stream(c.getMethods()).filter(
m -> isStatic(m.getModifiers()) && m.getName().equals("main") m -> isStatic(m.getModifiers()) && m.getName().equals("main")

View File

@ -1,6 +1,7 @@
package net.hostsharing.hsadminng.rbac.rbacdef; package net.hostsharing.hsadminng.rbac.rbacdef;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.nio.file.*; import java.nio.file.*;
@ -15,10 +16,13 @@ public class RbacViewMermaidFlowchartGenerator {
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";
private final RbacView rbacDef; private final RbacView rbacDef;
private final CaseDef forCase;
private final StringWriter flowchart = new StringWriter(); private final StringWriter flowchart = new StringWriter();
public RbacViewMermaidFlowchartGenerator(final RbacView rbacDef) { public RbacViewMermaidFlowchartGenerator(final RbacView rbacDef, final CaseDef forCase) {
this.rbacDef = rbacDef; this.rbacDef = rbacDef;
this.forCase = forCase;
flowchart.writeLn(""" flowchart.writeLn("""
%%{init:{'flowchart':{'htmlLabels':false}}}%% %%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
@ -26,6 +30,10 @@ public class RbacViewMermaidFlowchartGenerator {
renderEntitySubgraphs(); renderEntitySubgraphs();
renderGrants(); renderGrants();
} }
public RbacViewMermaidFlowchartGenerator(final RbacView rbacDef) {
this(rbacDef, null);
}
private void renderEntitySubgraphs() { private void renderEntitySubgraphs() {
rbacDef.getEntityAliases().values().stream() rbacDef.getEntityAliases().values().stream()
.filter(entityAlias -> !rbacDef.isEntityAliasProxy(entityAlias)) .filter(entityAlias -> !rbacDef.isEntityAliasProxy(entityAlias))
@ -99,6 +107,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.grantType() == grantType) .filter(g -> g.grantType() == grantType)
.filter(this::isToBeRenderedInThisGraph)
.toList(); .toList();
if ( !grantsOfRequestedType.isEmpty()) { if ( !grantsOfRequestedType.isEmpty()) {
flowchart.ensureSingleEmptyLine(); flowchart.ensureSingleEmptyLine();
@ -107,10 +116,19 @@ public class RbacViewMermaidFlowchartGenerator {
} }
} }
private boolean isToBeRenderedInThisGraph(final RbacView.RbacGrantDefinition g) {
if ( g.grantType() != ROLE_TO_ROLE )
return true;
if ( forCase == null && !g.isConditional() )
return true;
final var isToBeRenderedInThisGraph = g.getForCases() == null || g.getForCases().contains(forCase);
return isToBeRenderedInThisGraph;
}
private String grantDef(final RbacView.RbacGrantDefinition grant) { private String grantDef(final RbacView.RbacGrantDefinition grant) {
final var arrow = (grant.isToCreate() ? " ==>" : " -.->") final var arrow = (grant.isToCreate() ? " ==>" : " -.->")
+ (grant.isAssumed() ? " " : "|XX| "); + (grant.isAssumed() ? " " : "|XX| ");
return switch (grant.grantType()) { final var grantDef = switch (grant.grantType()) {
case ROLE_TO_USER -> case ROLE_TO_USER ->
// TODO: other user types not implemented yet // TODO: other user types not implemented yet
"user:creator" + arrow + roleId(grant.getSubRoleDef()); "user:creator" + arrow + roleId(grant.getSubRoleDef());
@ -118,6 +136,7 @@ public class RbacViewMermaidFlowchartGenerator {
roleId(grant.getSuperRoleDef()) + arrow + roleId(grant.getSubRoleDef()); roleId(grant.getSuperRoleDef()) + arrow + roleId(grant.getSubRoleDef());
case PERM_TO_ROLE -> roleId(grant.getSuperRoleDef()) + arrow + permId(grant.getPermDef()); case PERM_TO_ROLE -> roleId(grant.getSuperRoleDef()) + arrow + permId(grant.getPermDef());
}; };
return grantDef;
} }
private String permDef(final RbacView.RbacPermissionDefinition perm) { private String permDef(final RbacView.RbacPermissionDefinition perm) {
@ -146,16 +165,17 @@ public class RbacViewMermaidFlowchartGenerator {
Files.writeString( Files.writeString(
path, path,
""" """
### rbac %{entityAlias} ### rbac %{entityAlias}%{case}
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid ```mermaid
%{flowchart} %{flowchart}
``` ```
""" """
.replace("%{entityAlias}", rbacDef.getRootEntityAlias().aliasName()) .replace("%{entityAlias}", rbacDef.getRootEntityAlias().aliasName())
.replace("%{flowchart}", flowchart.toString()), .replace("%{flowchart}", flowchart.toString())
.replace("%{case}", forCase == null ? "" : " " + forCase),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
System.out.println("Markdown-File: " + path.toAbsolutePath()); System.out.println("Markdown-File: " + path.toAbsolutePath());
} }

View File

@ -1,10 +1,13 @@
package net.hostsharing.hsadminng.rbac.rbacdef; package net.hostsharing.hsadminng.rbac.rbacdef;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream; import java.util.stream.Stream;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
@ -22,7 +25,7 @@ import static org.apache.commons.lang3.StringUtils.uncapitalize;
class RolesGrantsAndPermissionsGenerator { class RolesGrantsAndPermissionsGenerator {
private final RbacView rbacDef; private final RbacView rbacDef;
private final Set<RbacView.RbacGrantDefinition> rbacGrants = new HashSet<>(); private final Set<RbacGrantDefinition> rbacGrants = new HashSet<>();
private final String liquibaseTagPrefix; private final String liquibaseTagPrefix;
private final String simpleEntityName; private final String simpleEntityName;
private final String simpleEntityVarName; private final String simpleEntityVarName;
@ -31,7 +34,7 @@ class RolesGrantsAndPermissionsGenerator {
RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) { RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
this.rbacDef = rbacDef; this.rbacDef = rbacDef;
this.rbacGrants.addAll(rbacDef.getGrantDefs().stream() this.rbacGrants.addAll(rbacDef.getGrantDefs().stream()
.filter(RbacView.RbacGrantDefinition::isToCreate) .filter(RbacGrantDefinition::isToCreate)
.collect(toSet())); .collect(toSet()));
this.liquibaseTagPrefix = liquibaseTagPrefix; this.liquibaseTagPrefix = liquibaseTagPrefix;
@ -67,13 +70,11 @@ class RolesGrantsAndPermissionsGenerator {
NEW ${rawTableName} NEW ${rawTableName}
) )
language plpgsql as $$ language plpgsql as $$
declare
""" """
.replace("${simpleEntityName}", simpleEntityName) .replace("${simpleEntityName}", simpleEntityName)
.replace("${rawTableName}", rawTableName)); .replace("${rawTableName}", rawTableName));
plPgSql.chopEmptyLines(); plPgSql.writeLn("declare");
plPgSql.indented(() -> { plPgSql.indented(() -> {
referencedEntityAliases() referencedEntityAliases()
.forEach((ea) -> plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";")); .forEach((ea) -> plPgSql.writeLn(entityRefVar(NEW, ea) + " " + ea.getRawTableName() + ";"));
@ -172,6 +173,10 @@ class RolesGrantsAndPermissionsGenerator {
.anyMatch(e -> true); .anyMatch(e -> true);
} }
private boolean hasAnyConditionalGrants() {
return rbacDef.getGrantDefs().stream().anyMatch(RbacGrantDefinition::isConditional);
}
private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) { private void generateCreateRolesAndGrantsAfterInsert(final StringWriter plPgSql) {
referencedEntityAliases() referencedEntityAliases()
.forEach((ea) -> { .forEach((ea) -> {
@ -186,7 +191,25 @@ class RolesGrantsAndPermissionsGenerator {
createRolesWithGrantsSql(plPgSql, REFERRER); createRolesWithGrantsSql(plPgSql, REFERRER);
generateGrants(plPgSql, ROLE_TO_USER); generateGrants(plPgSql, ROLE_TO_USER);
generateGrants(plPgSql, ROLE_TO_ROLE); generateGrants(plPgSql, ROLE_TO_ROLE);
if (!rbacDef.getAllCases().isEmpty()) {
plPgSql.writeLn();
final var ifOrElsIf = new AtomicReference<>("IF ");
rbacDef.getAllCases().forEach(caseDef -> {
if (caseDef.value != null) {
plPgSql.writeLn(ifOrElsIf + "NEW." + rbacDef.getDiscriminatorColumName() + " = '" + caseDef.value + "' THEN");
} else {
plPgSql.writeLn("ELSE");
}
plPgSql.indented(() -> {
generateGrants(plPgSql, ROLE_TO_ROLE, caseDef);
});
ifOrElsIf.set("ELSIF ");
});
plPgSql.writeLn("END IF;");
}
generateGrants(plPgSql, PERM_TO_ROLE); generateGrants(plPgSql, PERM_TO_ROLE);
} }
@ -248,7 +271,7 @@ class RolesGrantsAndPermissionsGenerator {
private void updateGrantsDependingOn(final StringWriter plPgSql, final String columnName) { private void updateGrantsDependingOn(final StringWriter plPgSql, final String columnName) {
rbacDef.getGrantDefs().stream() rbacDef.getGrantDefs().stream()
.filter(RbacView.RbacGrantDefinition::isToCreate) .filter(RbacGrantDefinition::isToCreate)
.filter(g -> g.dependsOnColumn(columnName)) .filter(g -> g.dependsOnColumn(columnName))
.filter(g -> !isInsertPermissionGrant(g)) .filter(g -> !isInsertPermissionGrant(g))
.forEach(g -> { .forEach(g -> {
@ -259,21 +282,31 @@ class RolesGrantsAndPermissionsGenerator {
}); });
} }
private static Boolean isInsertPermissionGrant(final RbacView.RbacGrantDefinition g) { private static Boolean isInsertPermissionGrant(final RbacGrantDefinition g) {
final var isInsertPermissionGrant = ofNullable(g.getPermDef()).map(RbacPermissionDefinition::getPermission).map(p -> p == INSERT).orElse(false); final var isInsertPermissionGrant = ofNullable(g.getPermDef()).map(RbacPermissionDefinition::getPermission).map(p -> p == INSERT).orElse(false);
return isInsertPermissionGrant; return isInsertPermissionGrant;
} }
private void generateGrants(final StringWriter plPgSql, final RbacView.RbacGrantDefinition.GrantType grantType) { private void generateGrants(final StringWriter plPgSql, final RbacGrantDefinition.GrantType grantType, final CaseDef caseDef) {
plPgSql.ensureSingleEmptyLine();
rbacGrants.stream() rbacGrants.stream()
.filter(g -> g.matchesCase(caseDef))
.filter(g -> g.grantType() == grantType) .filter(g -> g.grantType() == grantType)
.map(this::generateGrant) .map(this::generateGrant)
.sorted() .sorted()
.forEach(text -> plPgSql.writeLn(text)); .forEach(text -> plPgSql.writeLn(text, with("ref", NEW.name())));
} }
private String generateRevoke(RbacView.RbacGrantDefinition grantDef) { private void generateGrants(final StringWriter plPgSql, final RbacGrantDefinition.GrantType grantType) {
plPgSql.ensureSingleEmptyLine();
rbacGrants.stream()
.filter(g -> !g.isConditional())
.filter(g -> g.grantType() == grantType)
.map(this::generateGrant)
.sorted()
.forEach(text -> plPgSql.writeLn(text, with("ref", NEW.name())));
}
private String generateRevoke(RbacGrantDefinition grantDef) {
return switch (grantDef.grantType()) { return switch (grantDef.grantType()) {
case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant"); case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant");
case ROLE_TO_ROLE -> "call revokeRoleFromRole(${subRoleRef}, ${superRoleRef});" case ROLE_TO_ROLE -> "call revokeRoleFromRole(${subRoleRef}, ${superRoleRef});"
@ -285,8 +318,8 @@ class RolesGrantsAndPermissionsGenerator {
}; };
} }
private String generateGrant(RbacView.RbacGrantDefinition grantDef) { private String generateGrant(RbacGrantDefinition grantDef) {
return switch (grantDef.grantType()) { final var grantSql = switch (grantDef.grantType()) {
case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant"); case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant");
case ROLE_TO_ROLE -> "call grantRoleToRole(${subRoleRef}, ${superRoleRef}${assumed});" case ROLE_TO_ROLE -> "call grantRoleToRole(${subRoleRef}, ${superRoleRef}${assumed});"
.replace("${assumed}", grantDef.isAssumed() ? "" : ", unassumed()") .replace("${assumed}", grantDef.isAssumed() ? "" : ", unassumed()")
@ -298,6 +331,7 @@ class RolesGrantsAndPermissionsGenerator {
.replace("${permRef}", createPerm(NEW, grantDef.getPermDef())) .replace("${permRef}", createPerm(NEW, grantDef.getPermDef()))
.replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef())); .replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef()));
}; };
return grantSql;
} }
private String findPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { private String findPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) {
@ -362,11 +396,8 @@ class RolesGrantsAndPermissionsGenerator {
.replace("${roleSuffix}", capitalize(role.name()))); .replace("${roleSuffix}", capitalize(role.name())));
generatePermissionsForRole(plPgSql, role); generatePermissionsForRole(plPgSql, role);
generateIncomingSuperRolesForRole(plPgSql, role); generateIncomingSuperRolesForRole(plPgSql, role);
generateOutgoingSubRolesForRole(plPgSql, role); generateOutgoingSubRolesForRole(plPgSql, role);
generateUserGrantsForRole(plPgSql, role); generateUserGrantsForRole(plPgSql, role);
plPgSql.chopTail(",\n"); plPgSql.chopTail(",\n");
@ -380,7 +411,7 @@ class RolesGrantsAndPermissionsGenerator {
final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role); final var grantsToUsers = findGrantsToUserForRole(rbacDef.getRootEntityAlias(), role);
if (!grantsToUsers.isEmpty()) { if (!grantsToUsers.isEmpty()) {
final var arrayElements = grantsToUsers.stream() final var arrayElements = grantsToUsers.stream()
.map(RbacView.RbacGrantDefinition::getUserDef) .map(RbacGrantDefinition::getUserDef)
.map(this::toPlPgSqlReference) .map(this::toPlPgSqlReference)
.toList(); .toList();
plPgSql.indented(() -> plPgSql.indented(() ->
@ -393,7 +424,7 @@ class RolesGrantsAndPermissionsGenerator {
final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role); final var permissionGrantsForRole = findPermissionsGrantsForRole(rbacDef.getRootEntityAlias(), role);
if (!permissionGrantsForRole.isEmpty()) { if (!permissionGrantsForRole.isEmpty()) {
final var arrayElements = permissionGrantsForRole.stream() final var arrayElements = permissionGrantsForRole.stream()
.map(RbacView.RbacGrantDefinition::getPermDef) .map(RbacGrantDefinition::getPermDef)
.map(RbacPermissionDefinition::getPermission) .map(RbacPermissionDefinition::getPermission)
.map(RbacView.Permission::name) .map(RbacView.Permission::name)
.map(p -> "'" + p + "'") .map(p -> "'" + p + "'")
@ -406,26 +437,30 @@ class RolesGrantsAndPermissionsGenerator {
} }
private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) { private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role); final var unconditionalIncomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream()
if (!incomingGrants.isEmpty()) { .filter(g -> !g.isConditional())
final var arrayElements = incomingGrants.stream() .toList();
if (!unconditionalIncomingGrants.isEmpty()) {
final var arrayElements = unconditionalIncomingGrants.stream()
.map(g -> toPlPgSqlReference(NEW, g.getSuperRoleDef(), g.isAssumed())) .map(g -> toPlPgSqlReference(NEW, g.getSuperRoleDef(), g.isAssumed()))
.sorted().toList(); .sorted().toList();
plPgSql.indented(() -> plPgSql.indented(() ->
plPgSql.writeLn("incomingSuperRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n")); plPgSql.writeLn("incomingSuperRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n"));
rbacGrants.removeAll(incomingGrants); rbacGrants.removeAll(unconditionalIncomingGrants);
} }
} }
private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacView.Role role) { private void generateOutgoingSubRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role); final var unconditionalOutgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role).stream()
if (!outgoingGrants.isEmpty()) { .filter(g -> !g.isConditional())
final var arrayElements = outgoingGrants.stream() .toList();
if (!unconditionalOutgoingGrants.isEmpty()) {
final var arrayElements = unconditionalOutgoingGrants.stream()
.map(g -> toPlPgSqlReference(NEW, g.getSubRoleDef(), g.isAssumed())) .map(g -> toPlPgSqlReference(NEW, g.getSubRoleDef(), g.isAssumed()))
.sorted().toList(); .sorted().toList();
plPgSql.indented(() -> plPgSql.indented(() ->
plPgSql.writeLn("outgoingSubRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n")); plPgSql.writeLn("outgoingSubRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n"));
rbacGrants.removeAll(outgoingGrants); rbacGrants.removeAll(unconditionalOutgoingGrants);
} }
} }
@ -435,7 +470,7 @@ class RolesGrantsAndPermissionsGenerator {
: arrayElements.stream().collect(joining(",\n\t", "\n\t", "")); : arrayElements.stream().collect(joining(",\n\t", "\n\t", ""));
} }
private Set<RbacView.RbacGrantDefinition> findPermissionsGrantsForRole( private Set<RbacGrantDefinition> findPermissionsGrantsForRole(
final RbacView.EntityAlias entityAlias, final RbacView.EntityAlias entityAlias,
final RbacView.Role role) { final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
@ -444,7 +479,7 @@ class RolesGrantsAndPermissionsGenerator {
.collect(toSet()); .collect(toSet());
} }
private Set<RbacView.RbacGrantDefinition> findGrantsToUserForRole( private Set<RbacGrantDefinition> findGrantsToUserForRole(
final RbacView.EntityAlias entityAlias, final RbacView.EntityAlias entityAlias,
final RbacView.Role role) { final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
@ -453,7 +488,7 @@ class RolesGrantsAndPermissionsGenerator {
.collect(toSet()); .collect(toSet());
} }
private Set<RbacView.RbacGrantDefinition> findIncomingSuperRolesForRole( private Set<RbacGrantDefinition> findIncomingSuperRolesForRole(
final RbacView.EntityAlias entityAlias, final RbacView.EntityAlias entityAlias,
final RbacView.Role role) { final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
@ -462,7 +497,7 @@ class RolesGrantsAndPermissionsGenerator {
.collect(toSet()); .collect(toSet());
} }
private Set<RbacView.RbacGrantDefinition> findOutgoingSuperRolesForRole( private Set<RbacGrantDefinition> findOutgoingSuperRolesForRole(
final RbacView.EntityAlias entityAlias, final RbacView.EntityAlias entityAlias,
final RbacView.Role role) { final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role); final var roleDef = rbacDef.findRbacRole(entityAlias, role);
@ -506,7 +541,7 @@ class RolesGrantsAndPermissionsGenerator {
private void generateUpdateTrigger(final StringWriter plPgSql) { private void generateUpdateTrigger(final StringWriter plPgSql) {
generateHeader(plPgSql, "update"); generateHeader(plPgSql, "update");
if ( hasAnyUpdatableAndNullableEntityAliases() ) { if ( hasAnyUpdatableAndNullableEntityAliases() || hasAnyConditionalGrants() ) {
generateSimplifiedUpdateTriggerFunction(plPgSql); generateSimplifiedUpdateTriggerFunction(plPgSql);
} else { } else {
generateUpdateTriggerFunction(plPgSql); generateUpdateTriggerFunction(plPgSql);

View File

@ -167,7 +167,7 @@ public class RbacGrantsDiagramService {
return "users"; return "users";
} }
if (refType.equals("perm")) { if (refType.equals("perm")) {
return node.idName().split(" ", 4)[3]; return node.idName().split(":", 3)[1];
} }
if (refType.equals("role")) { if (refType.equals("role")) {
final var withoutRolePrefix = node.idName().substring("role:".length()); final var withoutRolePrefix = node.idName().substring("role:".length());
@ -209,7 +209,7 @@ public class RbacGrantsDiagramService {
} }
class LimitedHashSet<T> extends HashSet<T> { static class LimitedHashSet<T> extends HashSet<T> {
@Override @Override
public boolean add(final T t) { public boolean add(final T t) {

View File

@ -248,7 +248,7 @@ declare
objectUuidOfRole uuid; objectUuidOfRole uuid;
roleUuid uuid; roleUuid uuid;
begin begin
-- TODO.refact: extract function toRbacRoleDescriptor(roleIdName varchar) + find other occurrences -- TODO.refa: extract function toRbacRoleDescriptor(roleIdName varchar) + find other occurrences
roleParts = overlay(roleIdName placing '#' from length(roleIdName) + 1 - strpos(reverse(roleIdName), ':')); roleParts = overlay(roleIdName placing '#' from length(roleIdName) + 1 - strpos(reverse(roleIdName), ':'));
objectTableFromRoleIdName = split_part(roleParts, '#', 1); objectTableFromRoleIdName = split_part(roleParts, '#', 1);
objectNameFromRoleIdName = split_part(roleParts, '#', 2); objectNameFromRoleIdName = split_part(roleParts, '#', 2);
@ -356,16 +356,13 @@ create trigger deleteRbacRolesOfRbacObject_Trigger
/* /*
*/ */
create domain RbacOp as varchar(67) -- TODO: shorten to 8, once the deprecated values are gone create domain RbacOp as varchar(6)
check ( check (
VALUE = 'DELETE' VALUE = 'DELETE'
or VALUE = 'UPDATE' or VALUE = 'UPDATE'
or VALUE = 'SELECT' or VALUE = 'SELECT'
or VALUE = 'INSERT' or VALUE = 'INSERT'
or VALUE = 'ASSUME' or VALUE = 'ASSUME'
-- TODO: all values below are deprecated, use insert with table
or VALUE ~ '^add-[a-z]+$'
or VALUE ~ '^new-[a-z-]+$'
); );
create table RbacPermission create table RbacPermission
@ -417,37 +414,6 @@ begin
return permissionUuid; return permissionUuid;
end; $$; end; $$;
-- TODO: deprecated, remove and amend all usages to createPermission
create or replace function createPermissions(forObjectUuid uuid, permitOps RbacOp[])
returns uuid[]
language plpgsql as $$
declare
refId uuid;
permissionIds uuid[] = array []::uuid[];
begin
if (forObjectUuid is null) then
raise exception 'forObjectUuid must not be null';
end if;
for i in array_lower(permitOps, 1)..array_upper(permitOps, 1)
loop
refId = (select uuid from RbacPermission where objectUuid = forObjectUuid and op = permitOps[i]);
if (refId is null) then
insert
into RbacReference ("type")
values ('RbacPermission')
returning uuid into refId;
insert
into RbacPermission (uuid, objectUuid, op)
values (refId, forObjectUuid, permitOps[i]);
end if;
permissionIds = permissionIds || refId;
end loop;
return permissionIds;
end;
$$;
create or replace function findEffectivePermissionId(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null) create or replace function findEffectivePermissionId(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null)
returns uuid returns uuid
returns null on null input returns null on null input
@ -649,25 +615,6 @@ begin
end; end;
$$; $$;
-- TODO: deprecated, remove and use grantPermissionToRole(...)
create or replace procedure grantPermissionsToRole(roleUuid uuid, permissionIds uuid[])
language plpgsql as $$
begin
if cardinality(permissionIds) = 0 then return; end if;
for i in array_lower(permissionIds, 1)..array_upper(permissionIds, 1)
loop
perform assertReferenceType('roleId (ascendant)', roleUuid, 'RbacRole');
perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission');
insert
into RbacGrants (grantedByTriggerOf, ascendantUuid, descendantUuid, assumed)
values (currentTriggerObjectUuid(), roleUuid, permissionIds[i], true)
on conflict do nothing; -- allow granting multiple times
end loop;
end;
$$;
create or replace procedure grantRoleToRole(subRoleId uuid, superRoleId uuid, doAssume bool = true) create or replace procedure grantRoleToRole(subRoleId uuid, superRoleId uuid, doAssume bool = true)
language plpgsql as $$ language plpgsql as $$
begin begin
@ -691,7 +638,7 @@ declare
superRoleId uuid; superRoleId uuid;
subRoleId uuid; subRoleId uuid;
begin begin
-- TODO: maybe separate method grantRoleToRoleIfNotNull(...) for NULLABLE references -- TODO.refa: maybe separate method grantRoleToRoleIfNotNull(...) for NULLABLE references
if superRole.objectUuid is null or subRole.objectuuid is null then if superRole.objectUuid is null or subRole.objectuuid is null then
return; return;
end if; end if;
@ -712,30 +659,6 @@ begin
on conflict do nothing; -- allow granting multiple times on conflict do nothing; -- allow granting multiple times
end; $$; end; $$;
create or replace procedure grantRoleToRoleIfNotNull(subRole RbacRoleDescriptor, superRole RbacRoleDescriptor, doAssume bool = true)
language plpgsql as $$
declare
superRoleId uuid;
subRoleId uuid;
begin
if ( superRoleId is null ) then return; end if;
superRoleId := findRoleId(superRole);
if ( subRoleId is null ) then return; end if;
subRoleId := findRoleId(subRole);
perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole');
perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole');
if isGranted(subRoleId, superRoleId) then
call raiseDuplicateRoleGrantException(subRoleId, superRoleId);
end if;
insert
into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed)
values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume)
on conflict do nothing; -- allow granting multiple times
end; $$;
create or replace procedure revokeRoleFromRole(subRole RbacRoleDescriptor, superRole RbacRoleDescriptor) create or replace procedure revokeRoleFromRole(subRole RbacRoleDescriptor, superRole RbacRoleDescriptor)
language plpgsql as $$ language plpgsql as $$
declare declare

View File

@ -20,19 +20,18 @@ begin
return currentSubjectsUuids[1]; return currentSubjectsUuids[1];
end; $$; end; $$;
create or replace procedure grantRoleToUserUnchecked(grantedByRoleUuid uuid, roleUuid uuid, userUuid uuid, doAssume boolean = true) create or replace procedure grantRoleToUserUnchecked(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid, doAssume boolean = true)
language plpgsql as $$ language plpgsql as $$
begin begin
perform assertReferenceType('grantingRoleUuid', grantedByRoleUuid, 'RbacRole'); perform assertReferenceType('grantingRoleUuid', grantedByRoleUuid, 'RbacRole');
perform assertReferenceType('roleId (descendant)', roleUuid, 'RbacRole'); perform assertReferenceType('roleId (descendant)', grantedRoleUuid, 'RbacRole');
perform assertReferenceType('userId (ascendant)', userUuid, 'RbacUser'); perform assertReferenceType('userId (ascendant)', userUuid, 'RbacUser');
insert insert
into RbacGrants (grantedByRoleUuid, ascendantUuid, descendantUuid, assumed) into RbacGrants (grantedByRoleUuid, ascendantUuid, descendantUuid, assumed)
values (grantedByRoleUuid, userUuid, roleUuid, doAssume); values (grantedByRoleUuid, userUuid, grantedRoleUuid, doAssume)
-- TODO.spec: What should happen on multiple grants? What if options (doAssume) are not the same? -- TODO: check if grantedByRoleUuid+doAssume are the same, otherwise raise exception?
-- Most powerful or latest grant wins? What about managed? on conflict do nothing; -- allow granting multiple times
-- on conflict do nothing; -- allow granting multiple times
end; $$; end; $$;
create or replace procedure grantRoleToUser(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid, doAssume boolean = true) create or replace procedure grantRoleToUser(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid, doAssume boolean = true)

View File

@ -6,6 +6,7 @@
--changeset rbac-role-builder-create-role:1 endDelimiter:--// --changeset rbac-role-builder-create-role:1 endDelimiter:--//
-- ----------------------------------------------------------------- -- -----------------------------------------------------------------
-- TODO: rename to defineRoleWithGrants because it does not complain if the role already exists
create or replace function createRoleWithGrants( create or replace function createRoleWithGrants(
roleDescriptor RbacRoleDescriptor, roleDescriptor RbacRoleDescriptor,
permissions RbacOp[] = array[]::RbacOp[], permissions RbacOp[] = array[]::RbacOp[],
@ -28,7 +29,7 @@ declare
userUuid uuid; userUuid uuid;
userGrantsByRoleUuid uuid; userGrantsByRoleUuid uuid;
begin begin
roleUuid := createRole(roleDescriptor); roleUuid := coalesce(findRoleId(roleDescriptor), createRole(roleDescriptor));
foreach permission in array permissions foreach permission in array permissions
loop loop

View File

@ -0,0 +1,102 @@
### rbac relation inCaseOf:REPRESENTATIVE
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph holderPerson["`**holderPerson**`"]
direction TB
style holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph holderPerson:roles[ ]
style holderPerson:roles fill:#99bcdb,stroke:white
role:holderPerson:OWNER[[holderPerson:OWNER]]
role:holderPerson:ADMIN[[holderPerson:ADMIN]]
role:holderPerson:REFERRER[[holderPerson:REFERRER]]
end
end
subgraph anchorPerson["`**anchorPerson**`"]
direction TB
style anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph anchorPerson:roles[ ]
style anchorPerson:roles fill:#99bcdb,stroke:white
role:anchorPerson:OWNER[[anchorPerson:OWNER]]
role:anchorPerson:ADMIN[[anchorPerson:ADMIN]]
role:anchorPerson:REFERRER[[anchorPerson:REFERRER]]
end
end
subgraph contact["`**contact**`"]
direction TB
style contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph contact:roles[ ]
style contact:roles fill:#99bcdb,stroke:white
role:contact:OWNER[[contact:OWNER]]
role:contact:ADMIN[[contact:ADMIN]]
role:contact:REFERRER[[contact:REFERRER]]
end
end
subgraph relation["`**relation**`"]
direction TB
style relation fill:#dd4901,stroke:#274d6e,stroke-width:8px
subgraph relation:roles[ ]
style relation:roles fill:#dd4901,stroke:white
role:relation:OWNER[[relation:OWNER]]
role:relation:ADMIN[[relation:ADMIN]]
role:relation:AGENT[[relation:AGENT]]
role:relation:TENANT[[relation:TENANT]]
end
subgraph relation:permissions[ ]
style relation:permissions fill:#dd4901,stroke:white
perm:relation:DELETE{{relation:DELETE}}
perm:relation:UPDATE{{relation:UPDATE}}
perm:relation:SELECT{{relation:SELECT}}
perm:relation:INSERT{{relation:INSERT}}
end
end
%% granting roles to users
user:creator ==> role:relation:OWNER
%% granting roles to roles
role:global:ADMIN -.-> role:anchorPerson:OWNER
role:anchorPerson:OWNER -.-> role:anchorPerson:ADMIN
role:anchorPerson:ADMIN -.-> role:anchorPerson:REFERRER
role:global:ADMIN -.-> role:holderPerson:OWNER
role:holderPerson:OWNER -.-> role:holderPerson:ADMIN
role:holderPerson:ADMIN -.-> role:holderPerson:REFERRER
role:global:ADMIN -.-> role:contact:OWNER
role:contact:OWNER -.-> role:contact:ADMIN
role:contact:ADMIN -.-> role:contact:REFERRER
role:global:ADMIN ==> role:relation:OWNER
role:holderPerson:ADMIN ==> role:relation:OWNER
role:relation:OWNER ==> role:relation:ADMIN
role:relation:ADMIN ==> role:anchorPerson:OWNER
role:relation:ADMIN ==> role:relation:AGENT
role:anchorPerson:ADMIN ==> role:relation:AGENT
role:relation:AGENT ==> role:relation:TENANT
role:contact:ADMIN ==> role:relation:TENANT
role:relation:TENANT ==> role:anchorPerson:REFERRER
role:relation:TENANT ==> role:holderPerson:REFERRER
role:relation:TENANT ==> role:contact:REFERRER
%% granting permissions to roles
role:relation:OWNER ==> perm:relation:DELETE
role:relation:ADMIN ==> perm:relation:UPDATE
role:relation:TENANT ==> perm:relation:SELECT
role:anchorPerson:ADMIN ==> perm:relation:INSERT
```

View File

@ -1,4 +1,4 @@
### rbac relation ### rbac relation inOtherCases
This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually. This code generated was by RbacViewMermaidFlowchartGenerator, do not amend manually.
@ -83,15 +83,14 @@ role:contact:OWNER -.-> role:contact:ADMIN
role:contact:ADMIN -.-> role:contact:REFERRER role:contact:ADMIN -.-> role:contact:REFERRER
role:global:ADMIN ==> role:relation:OWNER role:global:ADMIN ==> role:relation:OWNER
role:relation:OWNER ==> role:relation:ADMIN role:relation:OWNER ==> role:relation:ADMIN
role:anchorPerson:ADMIN ==> role:relation:ADMIN
role:relation:ADMIN ==> role:relation:AGENT role:relation:ADMIN ==> role:relation:AGENT
role:holderPerson:ADMIN ==> role:relation:AGENT
role:relation:AGENT ==> role:relation:TENANT role:relation:AGENT ==> role:relation:TENANT
role:holderPerson:ADMIN ==> role:relation:TENANT
role:contact:ADMIN ==> role:relation:TENANT role:contact:ADMIN ==> role:relation:TENANT
role:relation:TENANT ==> role:anchorPerson:REFERRER role:relation:TENANT ==> role:anchorPerson:REFERRER
role:relation:TENANT ==> role:holderPerson:REFERRER role:relation:TENANT ==> role:holderPerson:REFERRER
role:relation:TENANT ==> role:contact:REFERRER role:relation:TENANT ==> role:contact:REFERRER
role:anchorPerson:ADMIN ==> role:relation:OWNER
role:holderPerson:ADMIN ==> role:relation:AGENT
%% granting permissions to roles %% granting permissions to roles
role:relation:OWNER ==> perm:relation:DELETE role:relation:OWNER ==> perm:relation:DELETE

View File

@ -57,16 +57,12 @@ begin
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeRelationADMIN(NEW), hsOfficeRelationADMIN(NEW),
permissions => array['UPDATE'], permissions => array['UPDATE'],
incomingSuperRoles => array[ incomingSuperRoles => array[hsOfficeRelationOWNER(NEW)]
hsOfficePersonADMIN(newAnchorPerson),
hsOfficeRelationOWNER(NEW)]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficeRelationAGENT(NEW), hsOfficeRelationAGENT(NEW),
incomingSuperRoles => array[ incomingSuperRoles => array[hsOfficeRelationADMIN(NEW)]
hsOfficePersonADMIN(newHolderPerson),
hsOfficeRelationADMIN(NEW)]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
@ -74,7 +70,6 @@ begin
permissions => array['SELECT'], permissions => array['SELECT'],
incomingSuperRoles => array[ incomingSuperRoles => array[
hsOfficeContactADMIN(newContact), hsOfficeContactADMIN(newContact),
hsOfficePersonADMIN(newHolderPerson),
hsOfficeRelationAGENT(NEW)], hsOfficeRelationAGENT(NEW)],
outgoingSubRoles => array[ outgoingSubRoles => array[
hsOfficeContactREFERRER(newContact), hsOfficeContactREFERRER(newContact),
@ -82,6 +77,15 @@ begin
hsOfficePersonREFERRER(newHolderPerson)] hsOfficePersonREFERRER(newHolderPerson)]
); );
IF NEW.type = 'REPRESENTATIVE' THEN
call grantRoleToRole(hsOfficePersonOWNER(newAnchorPerson), hsOfficeRelationADMIN(NEW));
call grantRoleToRole(hsOfficeRelationAGENT(NEW), hsOfficePersonADMIN(newAnchorPerson));
call grantRoleToRole(hsOfficeRelationOWNER(NEW), hsOfficePersonADMIN(newHolderPerson));
ELSE
call grantRoleToRole(hsOfficeRelationAGENT(NEW), hsOfficePersonADMIN(newHolderPerson));
call grantRoleToRole(hsOfficeRelationOWNER(NEW), hsOfficePersonADMIN(newAnchorPerson));
END IF;
call leaveTriggerForObjectUuid(NEW.uuid); call leaveTriggerForObjectUuid(NEW.uuid);
end; $$; end; $$;
@ -118,48 +122,12 @@ create or replace procedure updateRbacRulesForHsOfficeRelation(
NEW hs_office_relation NEW hs_office_relation
) )
language plpgsql as $$ language plpgsql as $$
declare
oldHolderPerson hs_office_person;
newHolderPerson hs_office_person;
oldAnchorPerson hs_office_person;
newAnchorPerson hs_office_person;
oldContact hs_office_contact;
newContact hs_office_contact;
begin begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_person WHERE uuid = OLD.holderUuid INTO oldHolderPerson;
assert oldHolderPerson.uuid is not null, format('oldHolderPerson must not be null for OLD.holderUuid = %s', OLD.holderUuid);
SELECT * FROM hs_office_person WHERE uuid = NEW.holderUuid INTO newHolderPerson;
assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s', NEW.holderUuid);
SELECT * FROM hs_office_person WHERE uuid = OLD.anchorUuid INTO oldAnchorPerson;
assert oldAnchorPerson.uuid is not null, format('oldAnchorPerson must not be null for OLD.anchorUuid = %s', OLD.anchorUuid);
SELECT * FROM hs_office_person WHERE uuid = NEW.anchorUuid INTO newAnchorPerson;
assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s', NEW.anchorUuid);
SELECT * FROM hs_office_contact WHERE uuid = OLD.contactUuid INTO oldContact;
assert oldContact.uuid is not null, format('oldContact must not be null for OLD.contactUuid = %s', OLD.contactUuid);
SELECT * FROM hs_office_contact WHERE uuid = NEW.contactUuid INTO newContact;
assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s', NEW.contactUuid);
if NEW.contactUuid <> OLD.contactUuid then
call revokeRoleFromRole(hsOfficeRelationTENANT(OLD), hsOfficeContactADMIN(oldContact));
call grantRoleToRole(hsOfficeRelationTENANT(NEW), hsOfficeContactADMIN(newContact));
call revokeRoleFromRole(hsOfficeContactREFERRER(oldContact), hsOfficeRelationTENANT(OLD));
call grantRoleToRole(hsOfficeContactREFERRER(newContact), hsOfficeRelationTENANT(NEW));
if NEW.contactUuid is distinct from OLD.contactUuid then
delete from rbacgrants g where g.grantedbytriggerof = OLD.uuid;
call buildRbacSystemForHsOfficeRelation(NEW);
end if; end if;
call leaveTriggerForObjectUuid(NEW.uuid);
end; $$; end; $$;
/* /*

View File

@ -98,22 +98,21 @@ role:partnerRel.contact:OWNER -.-> role:partnerRel.contact:ADMIN
role:partnerRel.contact:ADMIN -.-> role:partnerRel.contact:REFERRER role:partnerRel.contact:ADMIN -.-> role:partnerRel.contact:REFERRER
role:global:ADMIN -.-> role:partnerRel:OWNER role:global:ADMIN -.-> role:partnerRel:OWNER
role:partnerRel:OWNER -.-> role:partnerRel:ADMIN role:partnerRel:OWNER -.-> role:partnerRel:ADMIN
role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:ADMIN
role:partnerRel:ADMIN -.-> role:partnerRel:AGENT role:partnerRel:ADMIN -.-> role:partnerRel:AGENT
role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT
role:partnerRel:AGENT -.-> role:partnerRel:TENANT role:partnerRel:AGENT -.-> role:partnerRel:TENANT
role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:TENANT
role:partnerRel.contact:ADMIN -.-> role:partnerRel:TENANT role:partnerRel.contact:ADMIN -.-> role:partnerRel:TENANT
role:partnerRel:TENANT -.-> role:partnerRel.anchorPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.anchorPerson:REFERRER
role:partnerRel:TENANT -.-> role:partnerRel.holderPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.holderPerson:REFERRER
role:partnerRel:TENANT -.-> role:partnerRel.contact:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.contact:REFERRER
role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:OWNER
role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT
%% granting permissions to roles %% granting permissions to roles
role:global:ADMIN ==> perm:partner:INSERT role:global:ADMIN ==> perm:partner:INSERT
role:partnerRel:ADMIN ==> perm:partner:DELETE role:partnerRel:OWNER ==> perm:partner:DELETE
role:partnerRel:AGENT ==> perm:partner:UPDATE role:partnerRel:ADMIN ==> perm:partner:UPDATE
role:partnerRel:TENANT ==> perm:partner:SELECT role:partnerRel:TENANT ==> perm:partner:SELECT
role:partnerRel:ADMIN ==> perm:partnerDetails:DELETE role:partnerRel:OWNER ==> perm:partnerDetails:DELETE
role:partnerRel:AGENT ==> perm:partnerDetails:UPDATE role:partnerRel:AGENT ==> perm:partnerDetails:UPDATE
role:partnerRel:AGENT ==> perm:partnerDetails:SELECT role:partnerRel:AGENT ==> perm:partnerDetails:SELECT

View File

@ -42,10 +42,10 @@ begin
SELECT * FROM hs_office_partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails; SELECT * FROM hs_office_partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails;
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid); assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationADMIN(newPartnerRel)); call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationOWNER(newPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTENANT(newPartnerRel)); call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTENANT(newPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAGENT(newPartnerRel)); call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationADMIN(newPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationADMIN(newPartnerRel)); call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationOWNER(newPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAGENT(newPartnerRel)); call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'SELECT'), hsOfficeRelationAGENT(newPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAGENT(newPartnerRel)); call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAGENT(newPartnerRel));
@ -110,17 +110,17 @@ begin
if NEW.partnerRelUuid <> OLD.partnerRelUuid then if NEW.partnerRelUuid <> OLD.partnerRelUuid then
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'DELETE'), hsOfficeRelationADMIN(oldPartnerRel)); call revokePermissionFromRole(getPermissionId(OLD.uuid, 'DELETE'), hsOfficeRelationOWNER(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationADMIN(newPartnerRel)); call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationOWNER(newPartnerRel));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'UPDATE'), hsOfficeRelationAGENT(oldPartnerRel)); call revokePermissionFromRole(getPermissionId(OLD.uuid, 'UPDATE'), hsOfficeRelationADMIN(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationAGENT(newPartnerRel)); call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationADMIN(newPartnerRel));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'SELECT'), hsOfficeRelationTENANT(oldPartnerRel)); call revokePermissionFromRole(getPermissionId(OLD.uuid, 'SELECT'), hsOfficeRelationTENANT(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTENANT(newPartnerRel)); call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationTENANT(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'DELETE'), hsOfficeRelationADMIN(oldPartnerRel)); call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'DELETE'), hsOfficeRelationOWNER(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationADMIN(newPartnerRel)); call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationOWNER(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAGENT(oldPartnerRel)); call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAGENT(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAGENT(newPartnerRel)); call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationAGENT(newPartnerRel));

View File

@ -151,15 +151,14 @@ 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:global:ADMIN -.-> role:debitorRel:OWNER
role:debitorRel:OWNER -.-> role:debitorRel:ADMIN role:debitorRel:OWNER -.-> role:debitorRel:ADMIN
role:debitorRel.anchorPerson:ADMIN -.-> role:debitorRel:ADMIN
role:debitorRel:ADMIN -.-> role:debitorRel:AGENT role:debitorRel:ADMIN -.-> role:debitorRel:AGENT
role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel:AGENT
role:debitorRel:AGENT -.-> role:debitorRel:TENANT role:debitorRel:AGENT -.-> role:debitorRel:TENANT
role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel:TENANT
role:debitorRel.contact:ADMIN -.-> role:debitorRel:TENANT role:debitorRel.contact:ADMIN -.-> role:debitorRel:TENANT
role:debitorRel:TENANT -.-> role:debitorRel.anchorPerson:REFERRER role:debitorRel:TENANT -.-> role:debitorRel.anchorPerson:REFERRER
role:debitorRel:TENANT -.-> role:debitorRel.holderPerson:REFERRER role:debitorRel:TENANT -.-> role:debitorRel.holderPerson:REFERRER
role:debitorRel:TENANT -.-> role:debitorRel.contact: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
@ -176,15 +175,14 @@ role:partnerRel.contact:OWNER -.-> role:partnerRel.contact:ADMIN
role:partnerRel.contact:ADMIN -.-> role:partnerRel.contact:REFERRER role:partnerRel.contact:ADMIN -.-> role:partnerRel.contact:REFERRER
role:global:ADMIN -.-> role:partnerRel:OWNER role:global:ADMIN -.-> role:partnerRel:OWNER
role:partnerRel:OWNER -.-> role:partnerRel:ADMIN role:partnerRel:OWNER -.-> role:partnerRel:ADMIN
role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:ADMIN
role:partnerRel:ADMIN -.-> role:partnerRel:AGENT role:partnerRel:ADMIN -.-> role:partnerRel:AGENT
role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT
role:partnerRel:AGENT -.-> role:partnerRel:TENANT role:partnerRel:AGENT -.-> role:partnerRel:TENANT
role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:TENANT
role:partnerRel.contact:ADMIN -.-> role:partnerRel:TENANT role:partnerRel.contact:ADMIN -.-> role:partnerRel:TENANT
role:partnerRel:TENANT -.-> role:partnerRel.anchorPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.anchorPerson:REFERRER
role:partnerRel:TENANT -.-> role:partnerRel.holderPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.holderPerson:REFERRER
role:partnerRel:TENANT -.-> role:partnerRel.contact:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.contact:REFERRER
role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:OWNER
role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT
role:partnerRel:ADMIN ==> role:debitorRel:ADMIN role:partnerRel:ADMIN ==> role:debitorRel:ADMIN
role:partnerRel:AGENT ==> role:debitorRel:AGENT role:partnerRel:AGENT ==> role:debitorRel:AGENT
role:debitorRel:AGENT ==> role:partnerRel:TENANT role:debitorRel:AGENT ==> role:partnerRel:TENANT

View File

@ -110,15 +110,14 @@ 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:global:ADMIN -.-> role:debitorRel:OWNER
role:debitorRel:OWNER -.-> role:debitorRel:ADMIN role:debitorRel:OWNER -.-> role:debitorRel:ADMIN
role:debitorRel.anchorPerson:ADMIN -.-> role:debitorRel:ADMIN
role:debitorRel:ADMIN -.-> role:debitorRel:AGENT role:debitorRel:ADMIN -.-> role:debitorRel:AGENT
role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel:AGENT
role:debitorRel:AGENT -.-> role:debitorRel:TENANT role:debitorRel:AGENT -.-> role:debitorRel:TENANT
role:debitorRel.holderPerson:ADMIN -.-> role:debitorRel:TENANT
role:debitorRel.contact:ADMIN -.-> role:debitorRel:TENANT role:debitorRel.contact:ADMIN -.-> role:debitorRel:TENANT
role:debitorRel:TENANT -.-> role:debitorRel.anchorPerson:REFERRER role:debitorRel:TENANT -.-> role:debitorRel.anchorPerson:REFERRER
role:debitorRel:TENANT -.-> role:debitorRel.holderPerson:REFERRER role:debitorRel:TENANT -.-> role:debitorRel.holderPerson:REFERRER
role:debitorRel:TENANT -.-> role:debitorRel.contact: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

@ -96,15 +96,14 @@ role:partnerRel.contact:OWNER -.-> role:partnerRel.contact:ADMIN
role:partnerRel.contact:ADMIN -.-> role:partnerRel.contact:REFERRER role:partnerRel.contact:ADMIN -.-> role:partnerRel.contact:REFERRER
role:global:ADMIN -.-> role:partnerRel:OWNER role:global:ADMIN -.-> role:partnerRel:OWNER
role:partnerRel:OWNER -.-> role:partnerRel:ADMIN role:partnerRel:OWNER -.-> role:partnerRel:ADMIN
role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:ADMIN
role:partnerRel:ADMIN -.-> role:partnerRel:AGENT role:partnerRel:ADMIN -.-> role:partnerRel:AGENT
role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT
role:partnerRel:AGENT -.-> role:partnerRel:TENANT role:partnerRel:AGENT -.-> role:partnerRel:TENANT
role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:TENANT
role:partnerRel.contact:ADMIN -.-> role:partnerRel:TENANT role:partnerRel.contact:ADMIN -.-> role:partnerRel:TENANT
role:partnerRel:TENANT -.-> role:partnerRel.anchorPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.anchorPerson:REFERRER
role:partnerRel:TENANT -.-> role:partnerRel.holderPerson:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.holderPerson:REFERRER
role:partnerRel:TENANT -.-> role:partnerRel.contact:REFERRER role:partnerRel:TENANT -.-> role:partnerRel.contact:REFERRER
role:partnerRel.anchorPerson:ADMIN -.-> role:partnerRel:OWNER
role:partnerRel.holderPerson:ADMIN -.-> role:partnerRel:AGENT
role:membership:OWNER ==> role:membership:ADMIN role:membership:OWNER ==> role:membership:ADMIN
role:partnerRel:ADMIN ==> role:membership:ADMIN role:partnerRel:ADMIN ==> role:membership:ADMIN
role:membership:ADMIN ==> role:membership:AGENT role:membership:ADMIN ==> role:membership:AGENT

View File

@ -97,15 +97,14 @@ role:membership.partnerRel.contact:OWNER -.-> role:membership.partnerRel.contact
role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel.contact:REFERRER role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel.contact:REFERRER
role:global:ADMIN -.-> role:membership.partnerRel:OWNER role:global:ADMIN -.-> role:membership.partnerRel:OWNER
role:membership.partnerRel:OWNER -.-> role:membership.partnerRel:ADMIN role:membership.partnerRel:OWNER -.-> role:membership.partnerRel:ADMIN
role:membership.partnerRel.anchorPerson:ADMIN -.-> role:membership.partnerRel:ADMIN
role:membership.partnerRel:ADMIN -.-> role:membership.partnerRel:AGENT role:membership.partnerRel:ADMIN -.-> role:membership.partnerRel:AGENT
role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:AGENT
role:membership.partnerRel:AGENT -.-> role:membership.partnerRel:TENANT role:membership.partnerRel:AGENT -.-> role:membership.partnerRel:TENANT
role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:TENANT
role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel:TENANT role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel:TENANT
role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.anchorPerson:REFERRER role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.anchorPerson:REFERRER
role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.holderPerson:REFERRER role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.holderPerson:REFERRER
role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.contact:REFERRER role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.contact:REFERRER
role:membership.partnerRel.anchorPerson:ADMIN -.-> role:membership.partnerRel:OWNER
role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:AGENT
role:membership:OWNER -.-> role:membership:ADMIN role:membership:OWNER -.-> role:membership:ADMIN
role:membership.partnerRel:ADMIN -.-> role:membership:ADMIN role:membership.partnerRel:ADMIN -.-> role:membership:ADMIN
role:membership:ADMIN -.-> role:membership:AGENT role:membership:ADMIN -.-> role:membership:AGENT

View File

@ -97,15 +97,14 @@ role:membership.partnerRel.contact:OWNER -.-> role:membership.partnerRel.contact
role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel.contact:REFERRER role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel.contact:REFERRER
role:global:ADMIN -.-> role:membership.partnerRel:OWNER role:global:ADMIN -.-> role:membership.partnerRel:OWNER
role:membership.partnerRel:OWNER -.-> role:membership.partnerRel:ADMIN role:membership.partnerRel:OWNER -.-> role:membership.partnerRel:ADMIN
role:membership.partnerRel.anchorPerson:ADMIN -.-> role:membership.partnerRel:ADMIN
role:membership.partnerRel:ADMIN -.-> role:membership.partnerRel:AGENT role:membership.partnerRel:ADMIN -.-> role:membership.partnerRel:AGENT
role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:AGENT
role:membership.partnerRel:AGENT -.-> role:membership.partnerRel:TENANT role:membership.partnerRel:AGENT -.-> role:membership.partnerRel:TENANT
role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:TENANT
role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel:TENANT role:membership.partnerRel.contact:ADMIN -.-> role:membership.partnerRel:TENANT
role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.anchorPerson:REFERRER role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.anchorPerson:REFERRER
role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.holderPerson:REFERRER role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.holderPerson:REFERRER
role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.contact:REFERRER role:membership.partnerRel:TENANT -.-> role:membership.partnerRel.contact:REFERRER
role:membership.partnerRel.anchorPerson:ADMIN -.-> role:membership.partnerRel:OWNER
role:membership.partnerRel.holderPerson:ADMIN -.-> role:membership.partnerRel:AGENT
role:membership:OWNER -.-> role:membership:ADMIN role:membership:OWNER -.-> role:membership:ADMIN
role:membership.partnerRel:ADMIN -.-> role:membership:ADMIN role:membership.partnerRel:ADMIN -.-> role:membership:ADMIN
role:membership:ADMIN -.-> role:membership:AGENT role:membership:ADMIN -.-> role:membership:AGENT

View File

@ -186,13 +186,13 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
"{ grant perm:debitor#D-1000122:DELETE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }", "{ grant perm:debitor#D-1000122:DELETE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }",
"{ grant perm:relation#FirstGmbH-with-DEBITOR-FourtheG:DELETE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }", "{ grant perm:relation#FirstGmbH-with-DEBITOR-FourtheG:DELETE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }",
"{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER to role:global#global:ADMIN by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER to role:global#global:ADMIN by system and assume }",
"{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER to role:person#FirstGmbH:ADMIN by system and assume }",
"{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER to user:superuser-alex@hostsharing.net by relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER to user:superuser-alex@hostsharing.net by relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER and assume }",
// admin // admin
"{ grant perm:debitor#D-1000122:UPDATE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN by system and assume }", "{ grant perm:debitor#D-1000122:UPDATE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN by system and assume }",
"{ grant perm:relation#FirstGmbH-with-DEBITOR-FourtheG:UPDATE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN by system and assume }", "{ grant perm:relation#FirstGmbH-with-DEBITOR-FourtheG:UPDATE to role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN by system and assume }",
"{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN to role:relation#FirstGmbH-with-DEBITOR-FourtheG:OWNER by system and assume }",
"{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN to role:person#FirstGmbH:ADMIN by system and assume }",
"{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN to role:relation#HostsharingeG-with-PARTNER-FirstGmbH:ADMIN by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:ADMIN to role:relation#HostsharingeG-with-PARTNER-FirstGmbH:ADMIN by system and assume }",
// agent // agent
@ -208,7 +208,6 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
"{ grant role:person#FirstGmbH:REFERRER to role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT by system and assume }", "{ grant role:person#FirstGmbH:REFERRER to role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT by system and assume }",
"{ grant role:person#FourtheG:REFERRER to role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT by system and assume }", "{ grant role:person#FourtheG:REFERRER to role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT by system and assume }",
"{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT to role:contact#fourthcontact:ADMIN by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT to role:contact#fourthcontact:ADMIN by system and assume }",
"{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT to role:person#FourtheG:ADMIN by system and assume }",
"{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT to role:relation#FirstGmbH-with-DEBITOR-FourtheG:AGENT by system and assume }", "{ grant role:relation#FirstGmbH-with-DEBITOR-FourtheG:TENANT to role:relation#FirstGmbH-with-DEBITOR-FourtheG:AGENT by system and assume }",
null)); null));

View File

@ -134,6 +134,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
"{ grant perm:membership#M-1000117:SELECT to role:membership#M-1000117:AGENT by system and assume }", "{ grant perm:membership#M-1000117:SELECT to role:membership#M-1000117:AGENT by system and assume }",
"{ grant role:membership#M-1000117:AGENT to role:membership#M-1000117:ADMIN by system and assume }", "{ grant role:membership#M-1000117:AGENT to role:membership#M-1000117:ADMIN by system and assume }",
// referrer
"{ grant role:membership#M-1000117:AGENT to role:relation#HostsharingeG-with-PARTNER-FirstGmbH:AGENT by system and assume }", "{ grant role:membership#M-1000117:AGENT to role:relation#HostsharingeG-with-PARTNER-FirstGmbH:AGENT by system and assume }",
"{ grant role:relation#HostsharingeG-with-PARTNER-FirstGmbH:TENANT to role:membership#M-1000117:AGENT by system and assume }", "{ grant role:relation#HostsharingeG-with-PARTNER-FirstGmbH:TENANT to role:membership#M-1000117:AGENT by system and assume }",

View File

@ -218,23 +218,6 @@ public class ImportOfficeData extends ContextBasedTest {
} }
} }
@Test
@Order(1021)
void buildDebitorRelations() {
debitors.forEach( (id, debitor) -> {
final var debitorRel = HsOfficeRelationEntity.builder()
.type(HsOfficeRelationType.DEBITOR)
.anchor(debitor.getPartner().getPartnerRel().getHolder())
.holder(debitor.getPartner().getPartnerRel().getHolder()) // just 1 debitor/partner in legacy hsadmin
// FIXME .contact()
.build();
if (debitorRel.getAnchor() != null && debitorRel.getHolder() != null &&
debitorRel.getContact() != null ) {
relations.put(relationId++, debitorRel);
}
});
}
@Test @Test
@Order(1029) @Order(1029)
void verifyContacts() { void verifyContacts() {
@ -292,29 +275,25 @@ public class ImportOfficeData extends ContextBasedTest {
{ {
2000000=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000000=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000001=rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000001=rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000002=rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000002=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000003=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000003=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'),
2000004=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), 2000004=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000005=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), 2000005=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000006=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000006=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'),
2000007=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000007=rel(anchor='null null, null', type='DEBITOR'),
2000008=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000008=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000009=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'), 2000009=rel(anchor='NP Mellies, Michael', type='REPRESENTATIVE', holder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000010=rel(anchor='null null, null', type='DEBITOR'), 2000010=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'),
2000011=rel(anchor='null null, null', type='DEBITOR'), 2000011=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000012=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000012=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000013=rel(anchor='NP Mellies, Michael', type='REPRESENTATIVE', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000013=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000014=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'), 2000014=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000015=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000015=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000016=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000016=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000017=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000017=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'),
2000018=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000018=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000019=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000019=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000020=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000020=rel(anchor='NP Mellies, Michael', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga ')
2000021=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'),
2000022=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000023=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000024=rel(anchor='NP Mellies, Michael', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga ')
} }
"""); """);
} }
@ -425,14 +404,33 @@ public class ImportOfficeData extends ContextBasedTest {
} }
@Test @Test
@Order(2009) @Order(3001)
void removeSelfRepresentativeRelations() {
assumeThatWeAreImportingControlledTestData();
// this happens if a natural person is marked as 'contractual' for itself
final var idsToRemove = new HashSet<Integer>();
relations.forEach( (id, r) -> {
if (r.getHolder() == r.getAnchor() ) {
idsToRemove.add(id);
}
});
// remove self-representatives
idsToRemove.forEach(id -> {
System.out.println("removing self representative relation: " + relations.get(id).toString());
relations.remove(id);
});
}
@Test
@Order(3002)
void removeEmptyRelations() { void removeEmptyRelations() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
// avoid a error when persisting the deliberately invalid partner entry #99 // avoid a error when persisting the deliberately invalid partner entry #99
final var idsToRemove = new HashSet<Integer>(); final var idsToRemove = new HashSet<Integer>();
relations.forEach( (id, r) -> { relations.forEach( (id, r) -> {
// such a record
if (r.getContact() == null || r.getContact().getLabel() == null || if (r.getContact() == null || r.getContact().getLabel() == null ||
r.getHolder() == null || r.getHolder().getPersonType() == null ) { r.getHolder() == null || r.getHolder().getPersonType() == null ) {
idsToRemove.add(id); idsToRemove.add(id);
@ -447,7 +445,7 @@ public class ImportOfficeData extends ContextBasedTest {
} }
@Test @Test
@Order(2002) @Order(3003)
void removeEmptyPartners() { void removeEmptyPartners() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
@ -471,7 +469,7 @@ public class ImportOfficeData extends ContextBasedTest {
} }
@Test @Test
@Order(2003) @Order(3004)
void removeEmptyDebitors() { void removeEmptyDebitors() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
@ -490,7 +488,7 @@ public class ImportOfficeData extends ContextBasedTest {
} }
@Test @Test
@Order(3000) @Order(9000)
@Commit @Commit
void persistEntities() { void persistEntities() {
@ -516,6 +514,7 @@ public class ImportOfficeData extends ContextBasedTest {
relations.forEach(this::persist); relations.forEach(this::persist);
}).assertSuccessful(); }).assertSuccessful();
System.out.println("persisting " + partners.size() + " partners");
jpaAttempt.transacted(() -> { jpaAttempt.transacted(() -> {
context(rbacSuperuser); context(rbacSuperuser);
partners.forEach((id, partner) -> { partners.forEach((id, partner) -> {
@ -533,7 +532,7 @@ public class ImportOfficeData extends ContextBasedTest {
context(rbacSuperuser); context(rbacSuperuser);
debitors.forEach((id, debitor) -> { debitors.forEach((id, debitor) -> {
debitor.setDebitorRel(em.merge(debitor.getDebitorRel())); debitor.setDebitorRel(em.merge(debitor.getDebitorRel()));
em.persist(debitor); persist(id, debitor);
}); });
}).assertSuccessful(); }).assertSuccessful();
@ -721,7 +720,6 @@ public class ImportOfficeData extends ContextBasedTest {
null, // will be set in contacts import null, // will be set in contacts import
null // will beset in contacts import null // will beset in contacts import
); );
relations.put(relationId++, debitorRel);
final var debitor = HsOfficeDebitorEntity.builder() final var debitor = HsOfficeDebitorEntity.builder()
.debitorNumberSuffix("00") .debitorNumberSuffix("00")

View File

@ -10,7 +10,6 @@ import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.test.Array;
import net.hostsharing.test.JpaAttempt; import net.hostsharing.test.JpaAttempt;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
@ -25,13 +24,13 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectEntity.objectDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacObjectEntity.objectDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.test.Array.fromFormatted; import static net.hostsharing.test.Array.from;
import static net.hostsharing.test.JpaAttempt.attempt; import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -130,7 +129,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
}).assertSuccessful(); }).assertSuccessful();
// then // then
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(from(
initialRoleNames, initialRoleNames,
"hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler:OWNER", "hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler:OWNER",
"hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler:ADMIN", "hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler:ADMIN",
@ -140,44 +139,43 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
.map(s -> s.replace("ErbenBesslerMelBessler", "EBess")) .map(s -> s.replace("ErbenBesslerMelBessler", "EBess"))
.map(s -> s.replace("fourthcontact", "4th")) .map(s -> s.replace("fourthcontact", "4th"))
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(distinct(fromFormatted( .containsExactlyInAnyOrder(distinct(from(
initialGrantNames, initialGrantNames,
"{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:INSERT>sepamandate to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:INSERT>sepamandate to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }",
// permissions on partner // permissions on partner
"{ grant perm:partner#P-20032:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", "{ grant perm:partner#P-20032:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }",
"{ grant perm:partner#P-20032:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }", "{ grant perm:partner#P-20032:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }",
"{ grant perm:partner#P-20032:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", "{ grant perm:partner#P-20032:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }",
// permissions on partner-details // permissions on partner-details
"{ grant perm:partner_details#P-20032:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", "{ grant perm:partner_details#P-20032:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }",
"{ grant perm:partner_details#P-20032:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }", "{ grant perm:partner_details#P-20032:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }",
"{ grant perm:partner_details#P-20032:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }", "{ grant perm:partner_details#P-20032:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }",
// permissions on partner-relation // permissions on partner-relation
"{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }", "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:DELETE to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }",
"{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:UPDATE to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }",
"{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", "{ grant perm:relation#HostsharingeG-with-PARTNER-EBess:SELECT to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }",
// relation owner // relation owner
"{ grant role:relation#HostsharingeG-with-PARTNER-EBess:OWNER to role:global#global:ADMIN by system and assume }", "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:OWNER to role:global#global:ADMIN by system and assume }",
"{ grant role:relation#HostsharingeG-with-PARTNER-EBess:OWNER to user:superuser-alex@hostsharing.net by relation#HostsharingeG-with-PARTNER-EBess:OWNER and assume }", "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:OWNER to user:superuser-alex@hostsharing.net by relation#HostsharingeG-with-PARTNER-EBess:OWNER and assume }",
// relation admin // relation admin
"{ grant role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }", "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN to role:relation#HostsharingeG-with-PARTNER-EBess:OWNER by system and assume }",
"{ grant role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN to role:person#HostsharingeG:ADMIN by system and assume }", "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:OWNER to role:person#HostsharingeG:ADMIN by system and assume }",
// relation agent // relation agent
"{ grant role:relation#HostsharingeG-with-PARTNER-EBess:AGENT to role:person#EBess:ADMIN by system and assume }", "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:AGENT to role:person#EBess:ADMIN by system and assume }",
"{ grant role:relation#HostsharingeG-with-PARTNER-EBess:AGENT to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }", "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:AGENT to role:relation#HostsharingeG-with-PARTNER-EBess:ADMIN by system and assume }",
// relation tenant // relation tenant
"{ grant role:contact#4th:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", "{ grant role:contact#4th:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }",
"{ grant role:person#EBess:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", "{ grant role:person#EBess:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }",
"{ grant role:person#HostsharingeG:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }", "{ grant role:person#HostsharingeG:REFERRER to role:relation#HostsharingeG-with-PARTNER-EBess:TENANT by system and assume }",
"{ grant role:relation#HostsharingeG-with-PARTNER-EBess:TENANT to role:contact#4th:ADMIN by system and assume }", "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:TENANT to role:contact#4th:ADMIN by system and assume }",
"{ grant role:relation#HostsharingeG-with-PARTNER-EBess:TENANT to role:person#EBess:ADMIN by system and assume }", "{ grant role:relation#HostsharingeG-with-PARTNER-EBess:TENANT to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }",
"{ grant role:relation#HostsharingeG-with-PARTNER-EBess:TENANT to role:relation#HostsharingeG-with-PARTNER-EBess:AGENT by system and assume }",
null))); null)));
} }
@ -411,9 +409,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
public void deletingAPartnerAlsoDeletesRelatedRolesAndGrants() { public void deletingAPartnerAlsoDeletesRelatedRolesAndGrants() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var initialObjects = Array.from(objectDisplaysOf(rawObjectRepo.findAll())); final var initialObjects = from(objectDisplaysOf(rawObjectRepo.findAll()));
final var initialRoleNames = Array.from(distinctRoleNamesOf(rawRoleRepo.findAll())); final var initialRoleNames = from(distinctRoleNamesOf(rawRoleRepo.findAll()));
final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll())); final var initialGrantNames = from(distinctGrantDisplaysOf(rawGrantRepo.findAll()));
final var givenPartner = givenSomeTemporaryHostsharingPartner(20034, "Erben Bessler", "twelfth"); final var givenPartner = givenSomeTemporaryHostsharingPartner(20034, "Erben Bessler", "twelfth");
// when // when
@ -499,8 +497,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
private String[] distinct(final String[] strings) { private String[] distinct(final String[] strings) {
// TODO: alternatively cleanup all rbac objects in @AfterEach? // TODO: alternatively cleanup all rbac objects in @AfterEach?
final var set = new HashSet<String>(); return Arrays.stream(strings).filter(Objects::nonNull).distinct().toList().toArray(new String[0]);
set.addAll(List.of(strings));
return set.toArray(new String[0]);
} }
} }

View File

@ -362,7 +362,7 @@ class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithClean
assertThat(givenRelation.getContact().getLabel()).isEqualTo("seventh contact"); assertThat(givenRelation.getContact().getLabel()).isEqualTo("seventh contact");
final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0);
final var location = RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.contentType(ContentType.JSON) .contentType(ContentType.JSON)

View File

@ -140,9 +140,10 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
"{ grant perm:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:UPDATE to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN by system and assume }", "{ grant perm:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:UPDATE to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN by system and assume }",
"{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:OWNER by system and assume }", "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:OWNER by system and assume }",
"{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN to role:hs_office_person#ErbenBesslerMelBessler:ADMIN by system and assume }", "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:OWNER to role:hs_office_person#BesslerBert:ADMIN by system and assume }",
"{ grant role:hs_office_person#ErbenBesslerMelBessler:OWNER to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN by system and assume }",
"{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:AGENT to role:hs_office_person#BesslerBert:ADMIN by system and assume }", "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:AGENT to role:hs_office_person#ErbenBesslerMelBessler:ADMIN by system and assume }",
"{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:AGENT to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN by system and assume }", "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:AGENT to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:ADMIN by system and assume }",
"{ grant perm:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:SELECT to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT by system and assume }", "{ grant perm:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:SELECT to role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT by system and assume }",
@ -153,8 +154,6 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
// REPRESENTATIVE holder person -> (represented) anchor person // REPRESENTATIVE holder person -> (represented) anchor person
"{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT to role:hs_office_contact#fourthcontact:ADMIN by system and assume }", "{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT to role:hs_office_contact#fourthcontact:ADMIN by system and assume }",
"{ grant role:hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerBert:TENANT to role:hs_office_person#BesslerBert:ADMIN by system and assume }",
null) null)
); );
} }
@ -217,10 +216,10 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenRelation = givenSomeTemporaryRelationBessler( final var givenRelation = givenSomeTemporaryRelationBessler(
"Bert", "fifth contact"); "Bert", "fifth contact");
assertThatRelationActuallyInDatabase(givenRelation);
assertThatRelationIsVisibleForUserWithRole( assertThatRelationIsVisibleForUserWithRole(
givenRelation, givenRelation,
"hs_office_person#ErbenBesslerMelBessler:ADMIN"); "hs_office_person#ErbenBesslerMelBessler:ADMIN");
assertThatRelationActuallyInDatabase(givenRelation);
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenContact = contactRepo.findContactByOptionalLabelLike("sixth contact").stream().findFirst().orElseThrow(); final var givenContact = contactRepo.findContactByOptionalLabelLike("sixth contact").stream().findFirst().orElseThrow();
@ -249,19 +248,19 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
} }
@Test @Test
public void holderAdmin_canNotUpdateRelatedRelation() { public void relationAgent_canSelectButNotUpdateRelatedRelation() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenRelation = givenSomeTemporaryRelationBessler( final var givenRelation = givenSomeTemporaryRelationBessler(
"Anita", "eighth"); "Anita", "eighth");
assertThatRelationIsVisibleForUserWithRole( assertThatRelationIsVisibleForUserWithRole(
givenRelation, givenRelation,
"hs_office_person#BesslerAnita:ADMIN"); "hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerAnita:AGENT");
assertThatRelationActuallyInDatabase(givenRelation); assertThatRelationActuallyInDatabase(givenRelation);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", "hs_office_person#BesslerAnita:ADMIN"); context("superuser-alex@hostsharing.net", "hs_office_relation#ErbenBesslerMelBessler-with-REPRESENTATIVE-BesslerAnita:AGENT");
givenRelation.setContact(null); givenRelation.setContact(null);
return relationRepo.save(givenRelation); return relationRepo.save(givenRelation);
}); });

View File

@ -17,7 +17,6 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository; import org.springframework.data.repository.Repository;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.lang.reflect.Method;
import java.util.*; import java.util.*;
import static java.lang.System.out; import static java.lang.System.out;
@ -272,12 +271,11 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
/** /**
* Generates a diagram of the RBAC-Grants to the current subjects (user or assumed roles). * Generates a diagram of the RBAC-Grants to the current subjects (user or assumed roles).
*/ */
protected void generateRbacDiagramForCurrentSubjects(final EnumSet<RbacGrantsDiagramService.Include> include) { protected void generateRbacDiagramForCurrentSubjects(final EnumSet<RbacGrantsDiagramService.Include> include, final String name) {
final var title = testInfo.getTestMethod().map(Method::getName).orElseThrow();
RbacGrantsDiagramService.writeToFile( RbacGrantsDiagramService.writeToFile(
title, name,
diagramService.allGrantsToCurrentUser(include), diagramService.allGrantsToCurrentUser(include),
"doc/" + title + ".md" "doc/temp/" + name + ".md"
); );
} }