RBAC Diagram+PostgreSQL Generator #21
@ -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")
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
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) {
|
||||||
|
new RbacGrantDefinition(this, findRbacRole(entityAlias, role) );
|
||||||
return this;
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacRoleDefinition<EC> withPermission(final Permission permission) {
|
public RbacRoleDefinition withCurrentUserAsOwner() {
|
||||||
|
addGrant(grantRoleToCurrentUser(this));
|
||||||
return 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;
|
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;
|
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;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
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])
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user