RBAC Diagram+PostgreSQL Generator #21
@ -17,8 +17,10 @@ import jakarta.persistence.Table;
|
||||
import java.util.UUID;
|
||||
|
||||
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.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
|
||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -57,29 +59,27 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
|
||||
return holder;
|
||||
}
|
||||
|
||||
public static RbacView<HsOfficeBankAccountEntity> hsOfficeBankAccount() {
|
||||
public static RbacView hsOfficeBankAccount() {
|
||||
// @formatter:off
|
||||
return rbacViewFor(HsOfficeBankAccountEntity.class)
|
||||
.alias("bankAccount")
|
||||
.withIdentityViewSqlQuery("target.iban || ':' || target.holder")
|
||||
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
|
||||
.withIdentityView(SQL.query("target.iban || ':' || target.holder"))
|
||||
.withUpdatableColumns("holder", "iban", "bic")
|
||||
.createRole(OWNER)
|
||||
.withCurrentUserAsOwner()
|
||||
.withPermission(ALL)
|
||||
.withIncomingSuperRole(GLOBAL, ADMIN)
|
||||
.createSubRole(ADMIN)
|
||||
.withPermission(UPDATE)
|
||||
.withPermission(EDIT)
|
||||
.createSubRole(REFERRER)
|
||||
.withPermission(READ)
|
||||
.withPermission(VIEW)
|
||||
.pop();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static RbacView<HsOfficeDebitorEntity> hsOfficeDebitor() {
|
||||
public static RbacView hsOfficeDebitor() {
|
||||
// @formatter:off
|
||||
return rbacViewFor(HsOfficeDebitorEntity.class)
|
||||
.alias("debitor")
|
||||
.withIdentityViewSqlQuery("""
|
||||
return rbacViewFor("debitor", HsOfficeDebitorEntity.class)
|
||||
.withIdentityView(SQL.query("""
|
||||
SELECT debitor.uuid,
|
||||
'D-' || (SELECT partner.partnerNumber
|
||||
FROM hs_office_partner partner
|
||||
@ -90,7 +90,7 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
|
||||
WHERE debitorRel.uuid = debitor.debitorRelUuid)
|
||||
|| to_char(debitorNumberSuffix, 'fm00')
|
||||
from hs_office_debitor as debitor;
|
||||
""")
|
||||
"""))
|
||||
.withUpdatableColumns(
|
||||
"debitorRel",
|
||||
"billable",
|
||||
@ -101,32 +101,35 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
|
||||
"vatBusiness",
|
||||
"vatreversecharge",
|
||||
"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 *
|
||||
FROM hs_office_relationship AS r
|
||||
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid;
|
||||
"""), dependsOnColumn("debitorRelUuid"))
|
||||
.createPermission(ALL).grantedTo("hsOfficeRelationship:DEBITOR", OWNER).pop()
|
||||
.createPermission(UPDATE).grantedTo("hsOfficeRelationship:DEBITOR", ADMIN).pop()
|
||||
.createPermission(READ).grantedTo("hsOfficeRelationship:DEBITOR", TENANT).pop()
|
||||
"""),
|
||||
dependsOnColumn("debitorRelUuid"))
|
||||
.createPermission(ALL).grantedTo("debitorRel", OWNER).pop()
|
||||
.createPermission(EDIT).grantedTo("debitorRel", ADMIN).pop()
|
||||
.createPermission(VIEW).grantedTo("debitorRel", TENANT).pop()
|
||||
|
||||
.defineEntityAlias("bankAccount", HsOfficeBankAccountEntity.class, fetchedBySql("""
|
||||
SELECT *
|
||||
FROM hs_office_relationship AS r
|
||||
WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid;
|
||||
"""), dependsOnColumn("bankAccountUuid"))
|
||||
.toRole("hsOfficeBankAccount", ADMIN).grantRole("debitorRel", AGENT)
|
||||
.toRole("debitorRel", AGENT).grantRole("hsOfficeBankAccount", REFERRER)
|
||||
.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"))
|
||||
.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("debitorRel", ADMIN).grantRole("partnerRel", AGENT)
|
||||
.toRole("partnerRel", AGENT).grantRole("debitorRel", AGENT)
|
||||
.toRole("debitorRel", AGENT).grantRole("partnerRel", TENANT)
|
||||
.declareEntityAliases("partnerPerson", "operationalPerson")
|
||||
|
@ -1,174 +1,459 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
|
||||
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 SQL fetchedBySql(final String sql) {
|
||||
return new SQL(sql);
|
||||
private final EntityAlias entityAlias;
|
||||
|
||||
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) {
|
||||
return new Column(column);
|
||||
RbacView(final String alias, final Class entityClass) {
|
||||
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) {
|
||||
return new RbacView<>(entityClass);
|
||||
}
|
||||
|
||||
RbacView(final Class<E> entityClass) {
|
||||
|
||||
}
|
||||
public RbacView<E> alias(final String bankAccount) {
|
||||
public RbacView withUpdatableColumns(final String... columnNames) {
|
||||
Collections.addAll(updatableColumns, columnNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacView<E> withUpdatableColumns(final String... columnNames) {
|
||||
public RbacView withIdentityView(final SQL sqlExpression) {
|
||||
this.identityViewSqlQuery = sqlExpression;
|
||||
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;
|
||||
}
|
||||
|
||||
public RbacRoleDefinition<E> createRole(final Role role) {
|
||||
return new RbacRoleDefinition<>(role);
|
||||
}
|
||||
|
||||
public RbacPermissionDefinition<E> createPermission(final Permission permission) {
|
||||
return new RbacPermissionDefinition<>(permission);
|
||||
}
|
||||
|
||||
public <EC extends HasUuid> RbacView<E> declareEntityAliases(final String... aliases) {
|
||||
public <EC extends HasUuid> RbacView defineProxyEntityAlias(
|
||||
final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) {
|
||||
entityAliasProxy = new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum);
|
||||
entityAliases.put(aliasName, entityAliasProxy);
|
||||
return this;
|
||||
}
|
||||
|
||||
public <EC extends HasUuid> RbacView<E> defineEntityAlias(
|
||||
final String alias, final Class<EC> entityClass, final SQL fetchSql, final Column dependsOnColum) {
|
||||
public <EC extends HasUuid> RbacView defineEntityAlias(
|
||||
final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) {
|
||||
entityAliases.put(aliasName, new EntityAlias(aliasName, entityClass, fetchSql, dependsOnColum));
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacRole toRole(final String hsOfficeBankAccount, final Role role) {
|
||||
return new RbacRole(hsOfficeBankAccount, role);
|
||||
public RbacView importRbacViewAs(final String aliasName, final RbacView importedRbacView) {
|
||||
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) {
|
||||
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());
|
||||
}
|
||||
|
||||
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<E> grantRole(final String entityAlias, final Role role) {
|
||||
public RbacView grantRole(final String entityAlias, final Role role) {
|
||||
new RbacGrantDefinition(findRbacRole(entityAlias, role), superRoleDef);
|
||||
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 {
|
||||
|
||||
final EntityAlias subRoleEntity;
|
||||
final Role subRole;
|
||||
private EntityAlias superRoleEntity;
|
||||
Role superRole;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public RbacPermissionDefinition<EC> withIncomingSuperRole(
|
||||
public RbacPermissionDefinition withIncomingSuperRole(
|
||||
final Class<HsOfficeRelationshipEntity> hsOfficeRelationshipEntityClass,
|
||||
final Role owner) {
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacPermissionDefinition<EC> grantedTo(final String entityAlias, final Role owner) {
|
||||
public RbacPermissionDefinition grantedTo(final String entityAlias, final Role role) {
|
||||
new RbacGrantDefinition(this, findRbacRole(entityAlias, role) );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "perm:" + entityAlias.aliasName + permission;
|
||||
}
|
||||
}
|
||||
|
||||
public class RbacRoleDefinition<EC> {
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class RbacRoleDefinition {
|
||||
|
||||
public RbacRoleDefinition(final Role role) {
|
||||
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<EC> withCurrentUserAsOwner() {
|
||||
public RbacRoleDefinition toCreate() {
|
||||
this.toCreate = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacRoleDefinition<EC> withPermission(final Permission permission) {
|
||||
public RbacRoleDefinition withCurrentUserAsOwner() {
|
||||
addGrant(grantRoleToCurrentUser(this));
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacRoleDefinition<EC> withIncomingSuperRole(final String tableName, final Role role) {
|
||||
public RbacRoleDefinition withPermission(final Permission permission) {
|
||||
addGrant(grantPermissionToRole( createPermission(entityAlias, permission) , this));
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacRoleDefinition<EC> createSubRole(final Role role) {
|
||||
public RbacRoleDefinition withIncomingSuperRole(final String entityAlias, final Role role) {
|
||||
final var incomingSuperRole = findRbacRole(entityAlias, role);
|
||||
addGrant(grantSubRoleToSuperRole(this, incomingSuperRole));
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacView<E> pop() {
|
||||
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;
|
||||
}
|
||||
|
||||
@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 ADMIN = new Role("admin");
|
||||
public static final Role AGENT = new Role("agent");
|
||||
public static final Role TENANT = new Role("tenant");
|
||||
public static final Role REFERRER = new Role("referrer");
|
||||
private final String roleName;
|
||||
|
||||
public Role(final String roleName) {
|
||||
this.roleName = roleName;
|
||||
@Override
|
||||
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 UPDATE = new Permission("edit");
|
||||
public static final Permission READ = new Permission("view");
|
||||
public static final Permission EDIT = new Permission("edit");
|
||||
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);
|
||||
}
|
||||
|
||||
final String permission;
|
||||
|
||||
private Permission(final String permission) {
|
||||
this.permission = permission;
|
||||
@Override
|
||||
public String toString() {
|
||||
return "." + permission;
|
||||
}
|
||||
}
|
||||
|
||||
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 SQL(final String sql) {
|
||||
private SQL(final String sql) {
|
||||
this.sql = sql;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Column {
|
||||
|
||||
public static Column dependsOnColumn(final String column) {
|
||||
return new Column(column);
|
||||
}
|
||||
|
||||
public final String column;
|
||||
|
||||
public Column(final String column) {
|
||||
private Column(final String 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -4,14 +4,14 @@
|
||||
flowchart TB
|
||||
|
||||
subgraph global
|
||||
style hsOfficeBankAccount fill: #e9f7ef
|
||||
style global fill: lightgray
|
||||
|
||||
role:global.admin[global.admin]
|
||||
end
|
||||
|
||||
subgraph hsOfficeBankAccount
|
||||
direction TB
|
||||
style hsOfficeBankAccount fill: #e9f7ef
|
||||
style hsOfficeBankAccount fill: lightgreen
|
||||
|
||||
user:hsOfficeBankAccount.creator([bankAccount.creator])
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user