rekursive Entity-Imports and render complex Mermad-Flowcharts (example: Debitor with parterRel+personRel and holderPerspn+anchorPerson each)

This commit is contained in:
Michael Hoennig 2024-02-23 16:09:10 +01:00
parent 54cff5ece9
commit 3e2fa5a6f6
7 changed files with 231 additions and 125 deletions

View File

@ -3,8 +3,6 @@ package net.hostsharing.hsadminng.hs.office.bankaccount;
import lombok.*; import lombok.*;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView; import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
@ -17,10 +15,8 @@ import jakarta.persistence.Table;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
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.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@ -59,7 +55,7 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
return holder; return holder;
} }
public static RbacView hsOfficeBankAccount() { public static RbacView rbac() {
// @formatter:off // @formatter:off
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class) return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
.withIdentityView(SQL.query("target.iban || ':' || target.holder")) .withIdentityView(SQL.query("target.iban || ':' || target.holder"))
@ -75,69 +71,4 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
.pop(); .pop();
// @formatter:on // @formatter:on
} }
public static RbacView hsOfficeDebitor() {
// @formatter:off
return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
.withIdentityView(SQL.query("""
SELECT debitor.uuid,
'D-' || (SELECT partner.partnerNumber
FROM hs_office_partner partner
JOIN hs_office_relationship partnerRel
ON partnerRel.uuid = partner.partnerRoleUUid AND partnerRel.relType = 'PARTNER'
JOIN hs_office_relationship debitorRel
ON debitorRel.relAnchorUuid = partnerRel.relHolderUuid AND partnerRel.relType = 'ACCOUNTING'
WHERE debitorRel.uuid = debitor.debitorRelUuid)
|| to_char(debitorNumberSuffix, 'fm00')
from hs_office_debitor as debitor;
"""))
.withUpdatableColumns(
"debitorRel",
"billable",
"billingContactUuid",
"refundBankAccountUuid",
"vatId",
"vatCountryCode",
"vatBusiness",
"vatreversecharge",
"defaultPrefix" /* TODO: do we want that updatable? */ )
.createPermission(custom("new-debitor")).grantedTo("global", ADMIN).pop()
.defineProxyEntityAlias("debitorRel", HsOfficeRelationshipEntity.class, fetchedBySql("""
SELECT *
FROM hs_office_relationship AS r
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid;
"""),
dependsOnColumn("debitorRelUuid"))
.createPermission(ALL).grantedTo("debitorRel", OWNER).pop()
.createPermission(EDIT).grantedTo("debitorRel", ADMIN).pop()
.createPermission(VIEW).grantedTo("debitorRel", TENANT).pop()
.defineEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class, fetchedBySql("""
SELECT *
FROM hs_office_relationship AS r
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid;
"""),
dependsOnColumn("bankAccountUuid"))
.importRbacViewAs("refundBankAccount", HsOfficeBankAccountEntity.hsOfficeBankAccount())
.toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
.defineEntityAlias("partnerRel", HsOfficeRelationshipEntity.class, fetchedBySql("""
SELECT *
FROM hs_office_relationship AS partnerRel
WHERE ${debitorRel}.relAnchorUuid = partnerRel.relHolderUuid;
"""),
dependsOnColumn("debitorRelUuid"))
.toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN)
.toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT)
.declareEntityAliases("partnerPerson", "operationalPerson")
.forExampleRole("partnerPerson", ADMIN).wouldBeGrantedTo("partnerRel", ADMIN)
.forExampleRole("operationalPerson", ADMIN).wouldBeGrantedTo("partnerRel", ADMIN)
.forExampleRole("partnerRel", TENANT).wouldBeGrantedTo("partnerPerson", REFERRER);
// @formatter:on
}
} }

View File

@ -4,8 +4,10 @@ import lombok.*;
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.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
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;
@ -14,6 +16,13 @@ import jakarta.persistence.*;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.VIEW;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.REFERRER;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
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
@ -97,4 +106,69 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
public String toShortString() { public String toShortString() {
return DEBITOR_NUMBER_TAG + getDebitorNumberString(); return DEBITOR_NUMBER_TAG + getDebitorNumberString();
} }
public static RbacView rbac() {
// @formatter:off
return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
.withIdentityView(RbacView.SQL.query("""
SELECT debitor.uuid,
'D-' || (SELECT partner.partnerNumber
FROM hs_office_partner partner
JOIN hs_office_relationship partnerRel
ON partnerRel.uuid = partner.partnerRoleUUid AND partnerRel.relType = 'PARTNER'
JOIN hs_office_relationship debitorRel
ON debitorRel.relAnchorUuid = partnerRel.relHolderUuid AND partnerRel.relType = 'ACCOUNTING'
WHERE debitorRel.uuid = debitor.debitorRelUuid)
|| to_char(debitorNumberSuffix, 'fm00')
from hs_office_debitor as debitor;
"""))
.withUpdatableColumns(
"debitorRel",
"billable",
"billingContactUuid",
"refundBankAccountUuid",
"vatId",
"vatCountryCode",
"vatBusiness",
"vatReverseCharge",
"defaultPrefix" /* TODO: do we want that updatable? */ )
.createPermission(custom("new-debitor")).grantedTo("global", ADMIN).pop()
.importProxyEntity("debitorRel", HsOfficeRelationshipEntity.class,
fetchedBySql("""
SELECT *
FROM hs_office_relationship AS r
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid;
"""),
dependsOnColumn("debitorRelUuid"))
.createPermission(ALL).grantedTo("debitorRel", OWNER).pop()
.createPermission(EDIT).grantedTo("debitorRel", ADMIN).pop()
.createPermission(VIEW).grantedTo("debitorRel", TENANT).pop()
.importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class,
fetchedBySql("""
SELECT *
FROM hs_office_relationship AS r
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid;
"""),
dependsOnColumn("bankAccountUuid"))
.toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
.importEntityAlias("partnerRel", HsOfficeRelationshipEntity.class,
fetchedBySql("""
SELECT *
FROM hs_office_relationship AS partnerRel
WHERE ${debitorRel}.relAnchorUuid = partnerRel.relHolderUuid;
"""),
dependsOnColumn("debitorRelUuid"))
.toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN)
.toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT)
.declarePlaceholderEntityAliases("partnerPerson", "operationalPerson")
.forExampleRole("partnerPerson", ADMIN).wouldBeGrantedTo("partnerRel", ADMIN)
.forExampleRole("operationalPerson", ADMIN).wouldBeGrantedTo("partnerRel", ADMIN)
.forExampleRole("partnerRel", TENANT).wouldBeGrantedTo("partnerPerson", REFERRER);
// @formatter:on
}
} }

View File

@ -4,6 +4,7 @@ import lombok.*;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
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.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -11,6 +12,11 @@ import org.apache.commons.lang3.StringUtils;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.query;
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
@ -56,4 +62,21 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable {
return personType + " " + return personType + " " +
(!StringUtils.isEmpty(tradeName) ? tradeName : (familyName + ", " + givenName)); (!StringUtils.isEmpty(tradeName) ? tradeName : (familyName + ", " + givenName));
} }
public static RbacView rbac() {
// @formatter:off
return rbacViewFor("person", HsOfficePersonEntity.class)
.withIdentityView(query("concat(target.tradeName, target.familyName, target.givenName)"))
.withUpdatableColumns("personType", "tradeName", "givenName", "familyName")
.createRole(OWNER)
.withPermission(ALL)
.withCurrentUserAsOwner()
.withIncomingSuperRole(GLOBAL, ADMIN)
.createSubRole(ADMIN)
.withPermission(EDIT)
.createSubRole(REFERRER)
.withPermission(VIEW)
.pop();
// @formatter:on
}
} }

View File

@ -5,12 +5,20 @@ import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
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 jakarta.persistence.*; import jakarta.persistence.*;
import java.util.UUID; import java.util.UUID;
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.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
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
@ -67,4 +75,34 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
public String toShortString() { public String toShortString() {
return toShortString.apply(this); return toShortString.apply(this);
} }
public static RbacView rbac() {
// @formatter:off
return rbacViewFor("relationship", HsOfficeRelationshipEntity.class)
.withIdentityView(SQL.query("""
(select idName from hs_office_person_iv p where p.uuid = target.relAnchorUuid)
|| '-with-' || target.relType || '-'
|| (select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)
"""))
.withUpdatableColumns("contactUuid")
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relAnchorUuid"),
dependsOnColumn("relAnchorUuid"))
.importEntityAlias("holderPerson", HsOfficePersonEntity.class,
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relHolderUuid"),
dependsOnColumn("relHolderUuid"))
.createRole(OWNER)
.withCurrentUserAsOwner()
.withPermission(ALL)
.withIncomingSuperRole(GLOBAL, ADMIN)
.withIncomingSuperRole("anchorPerson", ADMIN)
.createSubRole(ADMIN)
.withPermission(EDIT)
.createSubRole(AGENT)
.withIncomingSuperRole("holderPerson", ADMIN)
.createSubRole(TENANT)
.withPermission(VIEW)
.pop();
// @formatter:on
}
} }

View File

@ -6,6 +6,7 @@ import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEnti
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.lang.reflect.InvocationTargetException;
import java.util.*; import java.util.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
@ -36,11 +37,11 @@ public class RbacView {
private SQL identityViewSqlQuery; private SQL identityViewSqlQuery;
private EntityAlias entityAliasProxy; private EntityAlias entityAliasProxy;
public static <E extends HasUuid> RbacView rbacViewFor(final String alias, final Class entityClass) { public static <E extends HasUuid> RbacView rbacViewFor(final String alias, final Class<? extends HasUuid> entityClass) {
return new RbacView(alias, entityClass); return new RbacView(alias, entityClass);
} }
RbacView(final String alias, final Class entityClass) { RbacView(final String alias, final Class<? extends HasUuid> entityClass) {
entityAlias = new EntityAlias(alias, entityClass); entityAlias = new EntityAlias(alias, entityClass);
entityAliases.put(alias, entityAlias); entityAliases.put(alias, entityAlias);
new RbacUserReference(CREATOR); new RbacUserReference(CREATOR);
@ -71,30 +72,52 @@ public class RbacView {
return permDef; return permDef;
} }
public <EC extends HasUuid> RbacView declareEntityAliases(final String... aliasNames) { public <EC extends HasUuid> RbacView declarePlaceholderEntityAliases(final String... aliasNames) {
for ( String alias: aliasNames ) { for ( String alias: aliasNames ) {
entityAliases.put(alias, new EntityAlias(alias)); entityAliases.put(alias, new EntityAlias(alias));
} }
return this; return this;
} }
public <EC extends HasUuid> RbacView defineProxyEntityAlias( public <EC extends HasUuid> RbacView importProxyEntity(
final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) { final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) {
entityAliasProxy = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum); if ( entityAliasProxy != null ) {
entityAliases.put(aliasName, entityAliasProxy); throw new IllegalStateException("there is already an entityAliasProxy: " + entityAliasProxy);
}
entityAliasProxy = importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum);
return this; return this;
} }
public <EC extends HasUuid> RbacView defineEntityAlias( public RbacView importEntityAlias(
final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) { final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) {
entityAliases.put(aliasName, new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum)); importEntityAliasImpl(aliasName, entityClass, fetchSql, dependsOnColum);
return this; return this;
} }
public RbacView importRbacViewAs(final String aliasName, final RbacView importedRbacView) { private EntityAlias importEntityAliasImpl(final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) {
final var entityAlias = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum);
entityAliases.put(aliasName, entityAlias);
try {
importAsAlias(aliasName, rbacDefinition(entityClass));
} catch ( final Exception exc) {
new RuntimeException("cannot import entity: " + entityClass, exc);
}
return entityAlias;
}
private static RbacView rbacDefinition(final Class<? extends HasUuid> entityClass)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
return (RbacView) entityClass.getMethod("rbac").invoke(null);
}
private RbacView importAsAlias(final String aliasName, final RbacView importedRbacView) {
final var mapper = new AliasNameMapper(importedRbacView, aliasName); final var mapper = new AliasNameMapper(importedRbacView, aliasName);
importedRbacView.getEntityAliases().values().forEach(entityAlias -> { importedRbacView.getEntityAliases().values().stream()
new EntityAlias( mapper.map(entityAlias.aliasName), entityAlias.entityClass); .filter(entityAlias -> !importedRbacView.isMainEntityAlias(entityAlias))
.filter(entityAlias -> !entityAlias.isGlobal())
.forEach(entityAlias -> {
final String mappedAliasName = mapper.map(entityAlias.aliasName);
entityAliases.put(mappedAliasName, new EntityAlias(mappedAliasName, entityAlias.entityClass));
}); });
importedRbacView.getRoleDefs().forEach(roleDef -> { importedRbacView.getRoleDefs().forEach(roleDef -> {
new RbacRoleDefinition( findEntityAlias(mapper.map(roleDef.entityAlias.aliasName)), roleDef.role); new RbacRoleDefinition( findEntityAlias(mapper.map(roleDef.entityAlias.aliasName)), roleDef.role);
@ -120,15 +143,15 @@ public class RbacView {
private RbacGrantDefinition grantRoleToCurrentUser(final RbacRoleDefinition roleDefinition) { private RbacGrantDefinition grantRoleToCurrentUser(final RbacRoleDefinition roleDefinition) {
return new RbacGrantDefinition(roleDefinition, currentUser()); return new RbacGrantDefinition(roleDefinition, currentUser()).toCreate();
} }
private RbacGrantDefinition grantPermissionToRole(final RbacPermissionDefinition permDef , final RbacRoleDefinition roleDef) { private RbacGrantDefinition grantPermissionToRole(final RbacPermissionDefinition permDef , final RbacRoleDefinition roleDef) {
return new RbacGrantDefinition(permDef, roleDef); return new RbacGrantDefinition(permDef, roleDef).toCreate();
} }
private RbacGrantDefinition grantSubRoleToSuperRole(final RbacRoleDefinition subRoleDefinition, final RbacRoleDefinition superRoleDefinition) { private RbacGrantDefinition grantSubRoleToSuperRole(final RbacRoleDefinition subRoleDefinition, final RbacRoleDefinition superRoleDefinition) {
return new RbacGrantDefinition(subRoleDefinition, superRoleDefinition); return new RbacGrantDefinition(subRoleDefinition, superRoleDefinition).toCreate();
} }
boolean isMainEntityAlias(final EntityAlias entityAlias) { boolean isMainEntityAlias(final EntityAlias entityAlias) {
@ -148,7 +171,7 @@ public class RbacView {
} }
public RbacView grantRole(final String entityAlias, final Role role) { public RbacView grantRole(final String entityAlias, final Role role) {
new RbacGrantDefinition(findRbacRole(entityAlias, role), superRoleDef); new RbacGrantDefinition(findRbacRole(entityAlias, role), superRoleDef).toCreate();
return RbacView.this; return RbacView.this;
} }
@ -161,6 +184,7 @@ public class RbacView {
private final RbacRoleDefinition superRoleDef; private final RbacRoleDefinition superRoleDef;
private final RbacRoleDefinition subRoleDef; private final RbacRoleDefinition subRoleDef;
private final RbacPermissionDefinition permDef; private final RbacPermissionDefinition permDef;
private boolean toCreate;
@Override @Override
public String toString() { public String toString() {
@ -203,7 +227,16 @@ public class RbacView {
boolean isAssumed() { boolean isAssumed() {
// TODO: not implemented yet // TODO: not implemented yet
return false; return true;
}
boolean isToCreate() {
return toCreate;
}
RbacGrantDefinition toCreate() {
toCreate = true;
return this;
} }
public enum GrantType { public enum GrantType {
@ -262,7 +295,7 @@ public class RbacView {
} }
public RbacPermissionDefinition grantedTo(final String entityAlias, final Role role) { public RbacPermissionDefinition grantedTo(final String entityAlias, final Role role) {
new RbacGrantDefinition(this, findRbacRole(entityAlias, role) ); new RbacGrantDefinition(this, findRbacRole(entityAlias, role) ).toCreate();
return this; return this;
} }
@ -309,7 +342,7 @@ public class RbacView {
public RbacRoleDefinition createSubRole(final Role role) { public RbacRoleDefinition createSubRole(final Role role) {
final var roleDef = findRbacRole(entityAlias, role).toCreate(); final var roleDef = findRbacRole(entityAlias, role).toCreate();
new RbacGrantDefinition(roleDef, this); new RbacGrantDefinition(roleDef, this).toCreate();
return roleDef; return roleDef;
} }
@ -346,7 +379,7 @@ public class RbacView {
} }
} }
private EntityAlias findEntityAlias(final String aliasName) { EntityAlias findEntityAlias(final String aliasName) {
final var found = entityAliases.get(aliasName); final var found = entityAliases.get(aliasName);
if ( found == null ) { if ( found == null ) {
throw new IllegalArgumentException("entityAlias not found: " + aliasName); throw new IllegalArgumentException("entityAlias not found: " + aliasName);
@ -354,7 +387,7 @@ public class RbacView {
return found; return found;
} }
public RbacRoleDefinition findRbacRole(final EntityAlias entityAlias, final Role role) { RbacRoleDefinition findRbacRole(final EntityAlias entityAlias, final Role role) {
return roleDefs.stream() return roleDefs.stream()
.filter(r -> r.getEntityAlias() == entityAlias && r.getRole().equals(role)) .filter(r -> r.getEntityAlias() == entityAlias && r.getRole().equals(role))
.findFirst() .findFirst()
@ -378,6 +411,10 @@ public class RbacView {
boolean isGlobal() { boolean isGlobal() {
return aliasName().equals("global"); return aliasName().equals("global");
} }
boolean isPlaceholder() {
return entityClass == null;
}
} }
public record Role(String roleName) { public record Role(String roleName) {
@ -389,7 +426,7 @@ public class RbacView {
@Override @Override
public String toString() { public String toString() {
return "." + roleName; return ":" + roleName;
} }
@Override @Override
@ -409,7 +446,7 @@ public class RbacView {
@Override @Override
public String toString() { public String toString() {
return "." + permission; return ":" + permission;
} }
} }
@ -457,7 +494,10 @@ public class RbacView {
if (originalAliasName.equals(importedRbacView.entityAlias.aliasName) ) { if (originalAliasName.equals(importedRbacView.entityAlias.aliasName) ) {
return outerAliasName; return outerAliasName;
} }
if (originalAliasName.equals("global") ) {
return originalAliasName; return originalAliasName;
} }
return outerAliasName + "." + originalAliasName;
}
} }
} }

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.rbac.rbacdef; package net.hostsharing.hsadminng.rbac.rbacdef;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.io.IOException; import java.io.IOException;
@ -11,7 +11,8 @@ import static java.util.stream.Collectors.joining;
public class RbacViewMermaidFlowchart { public class RbacViewMermaidFlowchart {
public static final String HOSTSHARING_ORANGE = "#dd4901";
public static final String HOSTSHARING_LIGHTBLUE = "#99bcdb";
private final RbacView rbacDef; private final RbacView rbacDef;
private final StringBuilder flowchart = new StringBuilder(); private final StringBuilder flowchart = new StringBuilder();
@ -21,38 +22,28 @@ public class RbacViewMermaidFlowchart {
### rbac %{entityAlias} %{timestamp} ### rbac %{entityAlias} %{timestamp}
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB flowchart TB
""" """
.replace("%{entityAlias}", rbacDef.getEntityAlias().aliasName()) .replace("%{entityAlias}", rbacDef.getEntityAlias().aliasName())
.replace("%{timestamp}", LocalDateTime.now().toString())); .replace("%{timestamp}", LocalDateTime.now().toString()));
renderSubgraphGlobal();
renderEntitySubgraphs(); renderEntitySubgraphs();
renderGrants(); renderGrants();
flowchart.append("```"); flowchart.append("```");
} }
void renderSubgraphGlobal() {
flowchart.append("""
subgraph global
style global fill: lightgray
role:global.admin[global.admin]
end
""");
}
private void renderEntitySubgraphs() { private void renderEntitySubgraphs() {
rbacDef.getEntityAliases().values().stream() rbacDef.getEntityAliases().values().stream()
.filter(entityAlias -> !rbacDef.isEntityAliasProxy(entityAlias)) .filter(entityAlias -> !rbacDef.isEntityAliasProxy(entityAlias))
.filter(entityAlias -> !entityAlias.isPlaceholder())
.forEach(this::renderEntitySubgraph); .forEach(this::renderEntitySubgraph);
} }
private void renderEntitySubgraph(final RbacView.EntityAlias entity) { private void renderEntitySubgraph(final RbacView.EntityAlias entity) {
final var color = rbacDef.isMainEntityAlias(entity) ? "lightgreen" : "lightgray"; final var color = rbacDef.isMainEntityAlias(entity) ? HOSTSHARING_ORANGE : HOSTSHARING_LIGHTBLUE;
flowchart.append(""" flowchart.append("""
subgraph %{aliasName} subgraph %{aliasName}["`**%{aliasName}**`"]
direction TB direction TB
style %{aliasName} fill: %{color} style %{aliasName} fill: %{color}
@ -98,13 +89,16 @@ public class RbacViewMermaidFlowchart {
} }
private String grantDef(final RbacView.RbacGrantDefinition grant) { private String grantDef(final RbacView.RbacGrantDefinition grant) {
final var arrow = grant.isToCreate()
? grant.isAssumed() ? " ==> " : " == // ==> "
: grant.isAssumed() ? " -.-> " : " -.- // -.-> ";
return switch (grant.grantType()) { return switch (grant.grantType()) {
case USER_TO_ROLE -> case USER_TO_ROLE ->
// TODO: other user types not implemented yet // TODO: other user types not implemented yet
"user:creator" + (grant.isAssumed() ? " -.-> " : " --> ") + roleId(grant.getSubRoleDef()); "user:creator" + arrow + roleId(grant.getSubRoleDef());
case ROLE_TO_ROLE -> case ROLE_TO_ROLE ->
roleId(grant.getSuperRoleDef()) + (grant.isAssumed() ? " -.-> " : " --> ") + roleId(grant.getSubRoleDef()); roleId(grant.getSuperRoleDef()) + arrow + roleId(grant.getSubRoleDef());
case ROLE_TO_PERM -> roleId(grant.getSuperRoleDef()) + " --> " + permId(grant.getPermDef()); case ROLE_TO_PERM -> roleId(grant.getSuperRoleDef()) + arrow + permId(grant.getPermDef());
}; };
} }
@ -131,14 +125,20 @@ public class RbacViewMermaidFlowchart {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
Files.writeString( // Files.writeString(
Paths.get("doc", "hsOfficeBankAccount.md"), // Paths.get("doc", "hsOfficeRelationship.md"),
new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.hsOfficeBankAccount()).toString(), // new RbacViewMermaidFlowchart(HsOfficeRelationshipEntity.rbac()).toString(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); // StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
//
//
// Files.writeString(
// Paths.get("doc", "hsOfficeBankAccount.md"),
// new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.rbac()).toString(),
// StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
Files.writeString( Files.writeString(
Paths.get("doc", "hsOfficeDebitor.md"), Paths.get("doc", "hsOfficeDebitor.md"),
new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.hsOfficeDebitor()).toString(), new RbacViewMermaidFlowchart(HsOfficeDebitorEntity.rbac()).toString(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
} }
} }

View File

@ -41,7 +41,7 @@ public static void main(String[] args) throws IOException {
Files.writeString( Files.writeString(
Paths.get("doc", "hsOfficeBankAccount.sql"), Paths.get("doc", "hsOfficeBankAccount.sql"),
new RbacViewPostgresGenerator(HsOfficeBankAccountEntity.hsOfficeBankAccount()).toString(), new RbacViewPostgresGenerator(HsOfficeBankAccountEntity.rbac()).toString(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
} }
} }