RBAC Diagram+PostgreSQL Generator #21

Merged
hsh-michaelhoennig merged 54 commits from experimental-rbacview-generator into master 2024-03-11 12:30:44 +01:00
4 changed files with 522 additions and 90 deletions
Showing only changes of commit f11edc082d - Show all commits

View File

@ -17,8 +17,10 @@ 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
@ -57,29 +59,27 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
return holder; return holder;
} }
public static RbacView<HsOfficeBankAccountEntity> hsOfficeBankAccount() { public static RbacView hsOfficeBankAccount() {
// @formatter:off // @formatter:off
return rbacViewFor(HsOfficeBankAccountEntity.class) return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
.alias("bankAccount") .withIdentityView(SQL.query("target.iban || ':' || target.holder"))
.withIdentityViewSqlQuery("target.iban || ':' || target.holder")
.withUpdatableColumns("holder", "iban", "bic") .withUpdatableColumns("holder", "iban", "bic")
.createRole(OWNER) .createRole(OWNER)
.withCurrentUserAsOwner() .withCurrentUserAsOwner()
.withPermission(ALL) .withPermission(ALL)
.withIncomingSuperRole(GLOBAL, ADMIN) .withIncomingSuperRole(GLOBAL, ADMIN)
.createSubRole(ADMIN) .createSubRole(ADMIN)
.withPermission(UPDATE) .withPermission(EDIT)
.createSubRole(REFERRER) .createSubRole(REFERRER)
.withPermission(READ) .withPermission(VIEW)
.pop(); .pop();
// @formatter:on // @formatter:on
} }
public static RbacView<HsOfficeDebitorEntity> hsOfficeDebitor() { public static RbacView hsOfficeDebitor() {
// @formatter:off // @formatter:off
return rbacViewFor(HsOfficeDebitorEntity.class) return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
.alias("debitor") .withIdentityView(SQL.query("""
.withIdentityViewSqlQuery("""
SELECT debitor.uuid, SELECT debitor.uuid,
'D-' || (SELECT partner.partnerNumber 'D-' || (SELECT partner.partnerNumber
FROM hs_office_partner partner FROM hs_office_partner partner
@ -90,7 +90,7 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
WHERE debitorRel.uuid = debitor.debitorRelUuid) WHERE debitorRel.uuid = debitor.debitorRelUuid)
|| to_char(debitorNumberSuffix, 'fm00') || to_char(debitorNumberSuffix, 'fm00')
from hs_office_debitor as debitor; from hs_office_debitor as debitor;
""") """))
.withUpdatableColumns( .withUpdatableColumns(
"debitorRel", "debitorRel",
"billable", "billable",
@ -101,32 +101,35 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
"vatBusiness", "vatBusiness",
"vatreversecharge", "vatreversecharge",
"defaultPrefix" /* TODO: do we want that updatable? */ ) "defaultPrefix" /* TODO: do we want that updatable? */ )
.createPermission(extraPermission("new-debitor")).grantedTo("global", ADMIN).pop() .createPermission(custom("new-debitor")).grantedTo("global", ADMIN).pop()
.defineEntityAlias("debitorRel", HsOfficeRelationshipEntity.class, fetchedBySql(""" .defineProxyEntityAlias("debitorRel", HsOfficeRelationshipEntity.class, fetchedBySql("""
SELECT * SELECT *
FROM hs_office_relationship AS r FROM hs_office_relationship AS r
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid; WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid;
"""), dependsOnColumn("debitorRelUuid")) """),
.createPermission(ALL).grantedTo("hsOfficeRelationship:DEBITOR", OWNER).pop() dependsOnColumn("debitorRelUuid"))
.createPermission(UPDATE).grantedTo("hsOfficeRelationship:DEBITOR", ADMIN).pop() .createPermission(ALL).grantedTo("debitorRel", OWNER).pop()
.createPermission(READ).grantedTo("hsOfficeRelationship:DEBITOR", TENANT).pop() .createPermission(EDIT).grantedTo("debitorRel", ADMIN).pop()
.createPermission(VIEW).grantedTo("debitorRel", TENANT).pop()
.defineEntityAlias("bankAccount", HsOfficeBankAccountEntity.class, fetchedBySql(""" .defineEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class, fetchedBySql("""
SELECT * SELECT *
FROM hs_office_relationship AS r FROM hs_office_relationship AS r
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid; WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid;
"""), dependsOnColumn("bankAccountUuid")) """),
.toRole("hsOfficeBankAccount", ADMIN).grantRole("debitorRel", AGENT) dependsOnColumn("bankAccountUuid"))
.toRole("debitorRel", AGENT).grantRole("hsOfficeBankAccount", REFERRER) .importRbacViewAs("refundBankAccount", HsOfficeBankAccountEntity.hsOfficeBankAccount())
.toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
.defineEntityAlias("partnerRel", HsOfficeRelationshipEntity.class, fetchedBySql(""" .defineEntityAlias("partnerRel", HsOfficeRelationshipEntity.class, fetchedBySql("""
SELECT * SELECT *
FROM hs_office_relationship AS partnerRel FROM hs_office_relationship AS partnerRel
WHERE ${debitorRel}.relAnchorUuid = partnerRel.relHolderUuid; WHERE ${debitorRel}.relAnchorUuid = partnerRel.relHolderUuid;
"""), dependsOnColumn("debitorRelUuid")) """),
dependsOnColumn("debitorRelUuid"))
.toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN) .toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN)
.toRole("debitorRel", ADMIN).grantRole("partnerRel", AGENT)
.toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT) .toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT) .toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT)
.declareEntityAliases("partnerPerson", "operationalPerson") .declareEntityAliases("partnerPerson", "operationalPerson")

View File

@ -1,174 +1,459 @@
package net.hostsharing.hsadminng.rbac.rbacdef; package net.hostsharing.hsadminng.rbac.rbacdef;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
public class RbacView<E extends HasUuid> { import jakarta.validation.constraints.NotNull;
import java.util.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserDefinition.UserRole.CREATOR;
@Getter
public class RbacView {
public static final String GLOBAL = "global"; public static final String GLOBAL = "global";
public static SQL fetchedBySql(final String sql) { private final EntityAlias entityAlias;
return new SQL(sql);
private final Set<RbacUserDefinition> userDefs = new HashSet<>();
private final Set<RbacRoleDefinition> roleDefs = new HashSet<>();
private final Set<RbacPermissionDefinition> permDefs = new HashSet<>();
private final Map<String, EntityAlias> entityAliases = new HashMap<>() {
@Override
public EntityAlias put(final String key, final EntityAlias value) {
if ( containsKey(key) ) {
throw new IllegalArgumentException("duplicate entityAlias: " + key);
}
return super.put(key, value);
}
};
private final Set<String> updatableColumns = new TreeSet<>();
private final Set<RbacGrantDefinition> grantDefs = new HashSet<>();
private SQL identityViewSqlQuery;
private EntityAlias entityAliasProxy;
public static <E extends HasUuid> RbacView rbacViewFor(final String alias, final Class entityClass) {
return new RbacView(alias, entityClass);
} }
public static Column dependsOnColumn(final String column) { RbacView(final String alias, final Class entityClass) {
return new Column(column); entityAlias = new EntityAlias(alias, entityClass);
entityAliases.put(alias, entityAlias);
new RbacUserDefinition(CREATOR);
entityAliases.put("global", new EntityAlias("global"));
} }
public static <E extends HasUuid> RbacView<E> rbacViewFor(final Class<E> entityClass) { public RbacView withUpdatableColumns(final String... columnNames) {
return new RbacView<>(entityClass); Collections.addAll(updatableColumns, columnNames);
}
RbacView(final Class<E> entityClass) {
}
public RbacView<E> alias(final String bankAccount) {
return this; return this;
} }
public RbacView<E> withUpdatableColumns(final String... columnNames) { public RbacView withIdentityView(final SQL sqlExpression) {
this.identityViewSqlQuery = sqlExpression;
return this; return this;
} }
public RbacView<E> withIdentityViewSqlQuery(final String sqlExpression) { public RbacRoleDefinition createRole(final Role role) {
return findRbacRole(entityAlias, role);
}
public RbacPermissionDefinition createPermission(final Permission permission) {
return createPermission(entityAlias, permission);
}
private RbacPermissionDefinition createPermission(final EntityAlias entityAlias, final Permission permission) {
final RbacPermissionDefinition permDef = new RbacPermissionDefinition(entityAlias, permission, true);
permDefs.add(permDef);
return permDef;
}
public <EC extends HasUuid> RbacView declareEntityAliases(final String... aliasNames) {
for ( String alias: aliasNames ) {
entityAliases.put(alias, new EntityAlias(alias));
}
return this; return this;
} }
public RbacRoleDefinition<E> createRole(final Role role) { public <EC extends HasUuid> RbacView defineProxyEntityAlias(
return new RbacRoleDefinition<>(role); final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) {
} entityAliasProxy = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum);
entityAliases.put(aliasName, entityAliasProxy);
public RbacPermissionDefinition<E> createPermission(final Permission permission) {
return new RbacPermissionDefinition<>(permission);
}
public <EC extends HasUuid> RbacView<E> declareEntityAliases(final String... aliases) {
return this; return this;
} }
public <EC extends HasUuid> RbacView<E> defineEntityAlias( public <EC extends HasUuid> RbacView defineEntityAlias(
final String alias, final Class<EC> 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));
return this; return this;
} }
public RbacRole toRole(final String hsOfficeBankAccount, final Role role) { public RbacView importRbacViewAs(final String aliasName, final RbacView importedRbacView) {
return new RbacRole(hsOfficeBankAccount, role); final var mapper = new AliasNameMapper(importedRbacView, aliasName);
importedRbacView.getEntityAliases().values().forEach(entityAlias -> {
new EntityAlias( mapper.map(entityAlias.aliasName), entityAlias.entityClass);
});
importedRbacView.getRoleDefs().forEach(roleDef -> {
new RbacRoleDefinition( findEntityAlias(mapper.map(roleDef.entityAlias.aliasName)), roleDef.role);
});
importedRbacView.getGrantDefs().forEach(grantDef -> {
if (grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE) {
new RbacGrantDefinition(
findRbacRole(mapper.map(grantDef.getSubRoleDef().entityAlias.aliasName), grantDef.getSubRoleDef().getRole()),
findRbacRole(mapper.map(grantDef.getSuperRoleDef().entityAlias.aliasName), grantDef.getSuperRoleDef().getRole())
);
}
});
return this;
}
public RbacGrantBuilder toRole(final String entityAlias, final Role role) {
return new RbacGrantBuilder(entityAlias, role);
} }
public RbacExampleRole forExampleRole(final String entityAlias, final Role role) { public RbacExampleRole forExampleRole(final String entityAlias, final Role role) {
return new RbacExampleRole(entityAlias, role); return new RbacExampleRole(entityAlias, role);
} }
public class RbacRole {
public RbacRole(final String entityAlias, final Role role) { private RbacGrantDefinition grantRoleToCurrentUser(final RbacRoleDefinition roleDefinition) {
return new RbacGrantDefinition(roleDefinition, currentUser());
} }
public RbacView<E> grantRole(final String entityAlias, final Role role) { private RbacGrantDefinition grantPermissionToRole(final RbacPermissionDefinition permDef , final RbacRoleDefinition roleDef) {
return new RbacGrantDefinition(permDef, roleDef);
}
private RbacGrantDefinition grantSubRoleToSuperRole(final RbacRoleDefinition subRoleDefinition, final RbacRoleDefinition superRoleDefinition) {
return new RbacGrantDefinition(subRoleDefinition, superRoleDefinition);
}
boolean isMainEntityAlias(final EntityAlias entityAlias) {
return entityAlias == this.entityAlias;
}
public boolean isEntityAliasProxy(final EntityAlias entityAlias) {
return entityAlias == entityAliasProxy;
}
public class RbacGrantBuilder {
private final RbacRoleDefinition superRoleDef;
private RbacGrantBuilder(final String entityAlias, final Role role) {
this.superRoleDef = findRbacRole(entityAlias, role);
}
public RbacView grantRole(final String entityAlias, final Role role) {
new RbacGrantDefinition(findRbacRole(entityAlias, role), superRoleDef);
return RbacView.this; return RbacView.this;
} }
}
@Getter
@EqualsAndHashCode
public class RbacGrantDefinition {
private final RbacUserDefinition userDef;
private final RbacRoleDefinition superRoleDef;
private final RbacRoleDefinition subRoleDef;
private final RbacPermissionDefinition permDef;
@Override
public String toString() {
return switch (grantType()) {
case USER_TO_ROLE -> userDef.toString() + " --> " + subRoleDef.toString();
case ROLE_TO_ROLE -> superRoleDef + " --> " + subRoleDef;
case ROLE_TO_PERM -> superRoleDef + " --> " + permDef;
};
}
public RbacGrantDefinition(final RbacRoleDefinition subRoleDef, final RbacRoleDefinition superRoleDef) {
this.userDef = null;
this.subRoleDef = subRoleDef;
this.superRoleDef = superRoleDef;
this.permDef = null;
grantDefs.add(this);
}
public RbacGrantDefinition(final RbacPermissionDefinition permDef, final RbacRoleDefinition roleDef) {
this.userDef = null;
this.subRoleDef = null;
this.superRoleDef = roleDef;
this.permDef = permDef;
grantDefs.add(this);
}
public RbacGrantDefinition(final RbacRoleDefinition roleDef, final RbacUserDefinition userDef) {
this.userDef = userDef;
this.subRoleDef = roleDef;
this.superRoleDef = null;
this.permDef = null;
}
@NotNull
GrantType grantType() {
return permDef != null ? GrantType.ROLE_TO_PERM
: userDef != null ? GrantType.USER_TO_ROLE
: GrantType.ROLE_TO_ROLE;
}
boolean isAssumed() {
// TODO: not implemented yet
return false;
}
public enum GrantType {
USER_TO_ROLE,
ROLE_TO_ROLE,
ROLE_TO_PERM
}
}
private void addGrant(final RbacGrantDefinition grant) {
grantDefs.add(grant);
} }
public class RbacExampleRole { public class RbacExampleRole {
final EntityAlias subRoleEntity;
final Role subRole;
private EntityAlias superRoleEntity;
Role superRole;
public RbacExampleRole(final String entityAlias, final Role role) { public RbacExampleRole(final String entityAlias, final Role role) {
this.subRoleEntity = findEntityAlias(entityAlias);
this.subRole = role;
} }
public RbacView<E> wouldBeGrantedTo(final String entityAlias, final Role role) { public RbacView wouldBeGrantedTo(final String entityAlias, final Role role) {
this.superRoleEntity = findEntityAlias(entityAlias);
this.superRole = role;
return RbacView.this; return RbacView.this;
} }
} }
public class RbacPermissionDefinition<EC> { @Getter
@EqualsAndHashCode
public class RbacPermissionDefinition {
public RbacPermissionDefinition(final Permission permission) { final EntityAlias entityAlias;
final Permission permission;
final boolean toCreate;
public RbacPermissionDefinition(final EntityAlias entityAlias, final Permission permission, final boolean toCreate) {
this.entityAlias = entityAlias;
this.permission = permission;
this.toCreate = toCreate;
} }
public RbacView<E> pop() { public RbacView pop() {
return RbacView.this; return RbacView.this;
} }
public RbacPermissionDefinition<EC> withIncomingSuperRole( public RbacPermissionDefinition withIncomingSuperRole(
final Class<HsOfficeRelationshipEntity> hsOfficeRelationshipEntityClass, final Class<HsOfficeRelationshipEntity> hsOfficeRelationshipEntityClass,
final Role owner) { final Role owner) {
return this; return this;
} }
public RbacPermissionDefinition<EC> grantedTo(final String entityAlias, final Role owner) { public RbacPermissionDefinition grantedTo(final String entityAlias, final Role role) {
return this; new RbacGrantDefinition(this, findRbacRole(entityAlias, role) );
}
}
public class RbacRoleDefinition<EC> {
public RbacRoleDefinition(final Role role) {
}
public RbacRoleDefinition<EC> withCurrentUserAsOwner() {
return this; return this;
} }
public RbacRoleDefinition<EC> withPermission(final Permission permission) { @Override
public String toString() {
return "perm:" + entityAlias.aliasName + permission;
}
}
@Getter
@EqualsAndHashCode
public class RbacRoleDefinition {
private final EntityAlias entityAlias;
private final Role role;
private boolean toCreate;
public RbacRoleDefinition(final EntityAlias entityAlias, final Role role) {
this.entityAlias = entityAlias;
this.role = role;
roleDefs.add(this);
}
public RbacRoleDefinition toCreate() {
this.toCreate = true;
return this; return this;
} }
public RbacRoleDefinition<EC> withIncomingSuperRole(final String tableName, final Role role) { public RbacRoleDefinition withCurrentUserAsOwner() {
addGrant(grantRoleToCurrentUser(this));
return this; return this;
} }
public RbacRoleDefinition<EC> createSubRole(final Role role) { public RbacRoleDefinition withPermission(final Permission permission) {
addGrant(grantPermissionToRole( createPermission(entityAlias, permission) , this));
return this; return this;
} }
public RbacView<E> pop() { public RbacRoleDefinition withIncomingSuperRole(final String entityAlias, final Role role) {
final var incomingSuperRole = findRbacRole(entityAlias, role);
addGrant(grantSubRoleToSuperRole(this, incomingSuperRole));
return this;
}
public RbacRoleDefinition createSubRole(final Role role) {
final var roleDef = findRbacRole(entityAlias, role).toCreate();
new RbacGrantDefinition(roleDef, this);
return roleDef;
}
public RbacView pop() {
return RbacView.this; return RbacView.this;
} }
@Override
public String toString() {
return "role:" + entityAlias.aliasName + role;
}
} }
public static class Role { public RbacUserDefinition currentUser() {
return userDefs.stream().filter(u -> u.role == CREATOR).findFirst().orElseThrow();
}
@EqualsAndHashCode
public class RbacUserDefinition {
public enum UserRole {
CREATOR
}
final UserRole role;
public RbacUserDefinition(final UserRole creator) {
this.role = creator;
userDefs.add(this);
}
@Override
public String toString() {
return "user:" + role;
}
}
private EntityAlias findEntityAlias(final String aliasName) {
final var found = entityAliases.get(aliasName);
if ( found == null ) {
throw new IllegalArgumentException("entityAlias not found: " + aliasName);
}
return found;
}
private RbacRoleDefinition findRbacRole(final EntityAlias entityAlias, final Role role) {
return roleDefs.stream()
.filter(r -> r.getEntityAlias() == entityAlias && r.getRole().equals(role))
.findFirst()
.orElseGet(() -> new RbacRoleDefinition(entityAlias, role));
}
private RbacRoleDefinition findRbacRole(final String entityAliasName, final Role role) {
return findRbacRole(findEntityAlias(entityAliasName), role);
}
record EntityAlias(String aliasName, Class<? extends HasUuid> entityClass, SQL fetchSql, Column dependsOnColum) {
public EntityAlias(final String aliasName) {
this(aliasName, null, null, null);
}
public EntityAlias(final String aliasName, final Class<? extends HasUuid> entityClass) {
this(aliasName, entityClass, null, null);
}
}
public record Role(String roleName) {
public static final Role OWNER = new Role("owner"); public static final Role OWNER = new Role("owner");
public static final Role ADMIN = new Role("admin"); public static final Role ADMIN = new Role("admin");
public static final Role AGENT = new Role("agent"); public static final Role AGENT = new Role("agent");
public static final Role TENANT = new Role("tenant"); public static final Role TENANT = new Role("tenant");
public static final Role REFERRER = new Role("referrer"); public static final Role REFERRER = new Role("referrer");
private final String roleName;
public Role(final String roleName) { @Override
this.roleName = roleName; public String toString() {
return "." + roleName;
}
@Override
public boolean equals(final Object obj) {
return ((obj instanceof Role) && ((Role)obj).roleName.equals(this.roleName));
} }
} }
public static class Permission { public record Permission(String permission) {
public static final Permission ALL = new Permission("*"); public static final Permission ALL = new Permission("*");
public static final Permission UPDATE = new Permission("edit"); public static final Permission EDIT = new Permission("edit");
public static final Permission READ = new Permission("view"); public static final Permission VIEW = new Permission("view");
public static Permission extraPermission(final String permission) { public static Permission custom(final String permission) {
return new Permission(permission); return new Permission(permission);
} }
final String permission; @Override
public String toString() {
private Permission(final String permission) { return "." + permission;
this.permission = permission;
} }
} }
public static class SQL { public static class SQL {
public static SQL fetchedBySql(final String sql) {
return new SQL(sql);
}
public static SQL query(final String sql) {
return new SQL(sql);
}
public final String sql; public final String sql;
public SQL(final String sql) { private SQL(final String sql) {
this.sql = sql; this.sql = sql;
} }
} }
public static class Column { public static class Column {
public static Column dependsOnColumn(final String column) {
return new Column(column);
}
public final String column; public final String column;
public Column(final String column) { private Column(final String column) {
this.column = column; this.column = column;
} }
} }
private static class AliasNameMapper {
private final RbacView importedRbacView;
private final String outerAliasName;
AliasNameMapper(final RbacView importedRbacView, final String outerAliasName) {
this.importedRbacView = importedRbacView;
this.outerAliasName = outerAliasName;
}
String map(final String originalAliasName) {
if (originalAliasName.equals(importedRbacView.entityAlias.aliasName) ) {
return outerAliasName;
}
return originalAliasName;
}
}
} }

View File

@ -0,0 +1,144 @@
package net.hostsharing.hsadminng.rbac.rbacdef;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.nio.file.*;
import java.time.LocalDateTime;
import static java.util.stream.Collectors.joining;
public class RbacViewMermaidFlowchart {
private final RbacView rbacDef;
private final StringBuilder flowchart = new StringBuilder();
public RbacViewMermaidFlowchart(final RbacView rbacDef) {
this.rbacDef = rbacDef;
flowchart.append("""
### rbac %{entityAlias} %{timestamp}
```mermaid
flowchart TB
"""
.replace("%{entityAlias}", rbacDef.getEntityAlias().aliasName())
.replace("%{timestamp}", LocalDateTime.now().toString()));
renderSubgraphGlobal();
renderEntitySubgraphs();
renderGrants();
flowchart.append("```");
}
void renderSubgraphGlobal() {
flowchart.append("""
subgraph global
style global fill: lightgray
role:global.admin[global.admin]
end
""");
}
private void renderEntitySubgraphs() {
rbacDef.getEntityAliases().values().stream()
.filter(entityAlias -> !rbacDef.isEntityAliasProxy(entityAlias))
.forEach(this::renderEntitySubgraph);
}
private void renderEntitySubgraph(final RbacView.EntityAlias entity) {
final var color = rbacDef.isMainEntityAlias(entity) ? "lightgreen" : "lightgray";
flowchart.append("""
subgraph %{aliasName}
direction TB
style %{aliasName} fill: %{color}
"""
.replace("%{aliasName}", entity.aliasName())
.replace("%{color}", color ));
wrapOutputInSubgraph(entity.aliasName() + ".roles", color,
rbacDef.getRoleDefs().stream()
.filter(r -> r.getEntityAlias() == entity)
.map(r -> " " + roleDef(r))
.collect(joining("\n")));
wrapOutputInSubgraph(entity.aliasName() + "permissions", color,
rbacDef.getPermDefs().stream()
.filter(p -> p.getEntityAlias() == entity)
.map(p -> " " + permDef(p) )
.collect(joining("\n")));
if (rbacDef.isMainEntityAlias(entity) && rbacDef.getEntityAliasProxy() != null ) {
renderEntitySubgraph(rbacDef.getEntityAliasProxy());
}
flowchart.append("end\n\n");
}
private void wrapOutputInSubgraph(final String name, final String color, final String content) {
if (!StringUtils.isEmpty(content)) {
flowchart.append("subgraph " + name + "[ ]\n");
flowchart.append("style %{aliasName} fill: %{color}\n\n"
.replace("%{aliasName}", name)
.replace("%{color}", color));
flowchart.append(content);
flowchart.append("\nend\n\n");
}
}
private void renderGrants() {
rbacDef.getGrantDefs()
.forEach(g -> {
flowchart.append(grantDef(g) + "\n" );
});
}
private String grantDef(final RbacView.RbacGrantDefinition grant) {
return switch (grant.grantType()) {
case USER_TO_ROLE ->
// TODO: other user types not implemented yet
"user:creator" + (grant.isAssumed() ? " -.-> " : " --> ") + roleId(grant.getSubRoleDef());
case ROLE_TO_ROLE ->
roleId(grant.getSuperRoleDef()) + (grant.isAssumed() ? " -.-> " : " --> ") + roleId(grant.getSubRoleDef());
case ROLE_TO_PERM -> roleId(grant.getSuperRoleDef()) + " --> " + permId(grant.getPermDef());
};
}
private String permDef(final RbacView.RbacPermissionDefinition perm) {
return permId(perm) + "{{" + perm.getEntityAlias().aliasName() + perm.getPermission() + "}}";
}
private static String permId(final RbacView.RbacPermissionDefinition permDef) {
return "perm:" + permDef.getEntityAlias().aliasName() + permDef.getPermission();
}
private String roleDef(final RbacView.RbacRoleDefinition roleDef) {
return roleId(roleDef) + "[[" + roleDef.getEntityAlias().aliasName() + roleDef.getRole() + "]]";
}
private static String roleId(final RbacView.RbacRoleDefinition r) {
return "role:" + r.getEntityAlias().aliasName() + r.getRole();
}
@Override
public String toString() {
return flowchart.toString();
}
public static void main(String[] args) throws IOException {
Files.writeString(
Paths.get("doc", "hsOfficeBankAccount.md"),
new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.hsOfficeBankAccount()).toString(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
Files.writeString(
Paths.get("doc", "hsOfficeDebitor.md"),
new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.hsOfficeDebitor()).toString(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
}

View File

@ -4,14 +4,14 @@
flowchart TB flowchart TB
subgraph global subgraph global
style hsOfficeBankAccount fill: #e9f7ef style global fill: lightgray
role:global.admin[global.admin] role:global.admin[global.admin]
end end
subgraph hsOfficeBankAccount subgraph hsOfficeBankAccount
direction TB direction TB
style hsOfficeBankAccount fill: #e9f7ef style hsOfficeBankAccount fill: lightgreen
user:hsOfficeBankAccount.creator([bankAccount.creator]) user:hsOfficeBankAccount.creator([bankAccount.creator])