RBAC Diagram+PostgreSQL Generator #21
@ -4,6 +4,7 @@ import lombok.*;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
@ -11,6 +12,10 @@ import org.hibernate.annotations.GenericGenerator;
|
||||
import jakarta.persistence.*;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -53,4 +58,21 @@ public class HsOfficeContactEntity implements Stringifyable, HasUuid {
|
||||
public String toShortString() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
// @formatter:off
|
||||
return rbacViewFor("contact", HsOfficeContactEntity.class)
|
||||
.withIdentityView(RbacView.SQL.query("target.label"))
|
||||
.withUpdatableColumns("label", "postalAddress", "emailAddresses", "phoneNumbers")
|
||||
.createRole(OWNER)
|
||||
.withPermission(ALL)
|
||||
.withCurrentUserAsOwner()
|
||||
.withIncomingSuperRole(GLOBAL, ADMIN)
|
||||
.createSubRole(ADMIN)
|
||||
.withPermission(EDIT)
|
||||
.createSubRole(REFERRER)
|
||||
.withPermission(VIEW)
|
||||
.pop();
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,12 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartne
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerRoleInsertResource;
|
||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType;
|
||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -158,7 +158,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
return entity;
|
||||
}
|
||||
|
||||
private <E extends HasUuid> E ref(final Class<E> entityClass, final UUID uuid) {
|
||||
private <E extends RbacObject> E ref(final Class<E> entityClass, final UUID uuid) {
|
||||
try {
|
||||
return em.getReference(entityClass, uuid);
|
||||
} catch (final Throwable exc) {
|
||||
|
@ -91,6 +91,9 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
|
||||
.importEntityAlias("holderPerson", HsOfficePersonEntity.class,
|
||||
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relHolderUuid"),
|
||||
dependsOnColumn("relHolderUuid"))
|
||||
.importEntityAlias("contact", HsOfficeContactEntity.class,
|
||||
fetchedBySql("select * from hs_office_contact as c where c.uuid = ${REF}.contactUuid"),
|
||||
dependsOnColumn("contactUuid"))
|
||||
.createRole(OWNER)
|
||||
.withCurrentUserAsOwner()
|
||||
.withPermission(ALL)
|
||||
@ -99,9 +102,14 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
|
||||
.createSubRole(ADMIN)
|
||||
.withPermission(EDIT)
|
||||
.createSubRole(AGENT)
|
||||
.withIncomingSuperRole("holderPerson", ADMIN)
|
||||
.createSubRole(TENANT)
|
||||
.withPermission(VIEW)
|
||||
.withIncomingSuperRole("anchorPerson", ADMIN)
|
||||
.withIncomingSuperRole("holderPerson", ADMIN)
|
||||
.withIncomingSuperRole("contact", ADMIN)
|
||||
.withOutgoingSubRole("anchorPerson", REFERRER)
|
||||
.withOutgoingSubRole("holderPerson", REFERRER)
|
||||
.withOutgoingSubRole("contact", REFERRER)
|
||||
.pop();
|
||||
// @formatter:on
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
|
||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
||||
|
||||
@Getter
|
||||
public class RbacView {
|
||||
@ -18,9 +19,9 @@ public class RbacView {
|
||||
|
||||
private final EntityAlias entityAlias;
|
||||
|
||||
private final Set<RbacUserReference> userDefs = new HashSet<>();
|
||||
private final Set<RbacRoleDefinition> roleDefs = new HashSet<>();
|
||||
private final Set<RbacPermissionDefinition> permDefs = new HashSet<>();
|
||||
private final Set<RbacUserReference> userDefs = new LinkedHashSet<>();
|
||||
private final Set<RbacRoleDefinition> roleDefs = new LinkedHashSet<>();
|
||||
private final Set<RbacPermissionDefinition> permDefs = new LinkedHashSet<>();
|
||||
private final Map<String, EntityAlias> entityAliases = new HashMap<>() {
|
||||
|
||||
@Override
|
||||
@ -31,8 +32,8 @@ public class RbacView {
|
||||
return super.put(key, value);
|
||||
}
|
||||
};
|
||||
private final Set<String> updatableColumns = new TreeSet<>();
|
||||
private final Set<RbacGrantDefinition> grantDefs = new HashSet<>();
|
||||
private final Set<String> updatableColumns = new LinkedHashSet<>();
|
||||
private final Set<RbacGrantDefinition> grantDefs = new LinkedHashSet<>();
|
||||
|
||||
private SQL identityViewSqlQuery;
|
||||
private EntityAlias entityAliasProxy;
|
||||
@ -340,6 +341,12 @@ public class RbacView {
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacRoleDefinition withOutgoingSubRole(final String entityAlias, final Role role) {
|
||||
final var outgoingSubRole = findRbacRole(entityAlias, role);
|
||||
addGrant(grantSubRoleToSuperRole(outgoingSubRole, this));
|
||||
return this;
|
||||
}
|
||||
|
||||
public RbacRoleDefinition createSubRole(final Role role) {
|
||||
final var roleDef = findRbacRole(entityAlias, role).toCreate();
|
||||
new RbacGrantDefinition(roleDef, this).toCreate();
|
||||
@ -415,6 +422,18 @@ public class RbacView {
|
||||
boolean isPlaceholder() {
|
||||
return entityClass == null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private String withoutEntitySuffix(final String simpleEntityName) {
|
||||
return simpleEntityName.substring(0, simpleEntityName.length()-"Entity".length());
|
||||
}
|
||||
|
||||
String simpleName() {
|
||||
return isGlobal()
|
||||
? aliasName
|
||||
: uncapitalize(withoutEntitySuffix(entityClass.getSimpleName()));
|
||||
}
|
||||
}
|
||||
|
||||
public record Role(String roleName) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -21,11 +23,9 @@ public class RbacViewMermaidFlowchart {
|
||||
flowchart.append("""
|
||||
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||
flowchart TB
|
||||
|
||||
""");
|
||||
renderEntitySubgraphs();
|
||||
renderGrants();
|
||||
flowchart.append("```");
|
||||
}
|
||||
private void renderEntitySubgraphs() {
|
||||
rbacDef.getEntityAliases().values().stream()
|
||||
@ -40,12 +40,16 @@ public class RbacViewMermaidFlowchart {
|
||||
|
||||
subgraph %{aliasName}["`**%{aliasName}**`"]
|
||||
direction TB
|
||||
style %{aliasName} fill: %{color}
|
||||
style %{aliasName} fill:%{color},stroke:darkblue,stroke-width:8px
|
||||
|
||||
"""
|
||||
.replace("%{aliasName}", entity.aliasName())
|
||||
.replace("%{color}", color ));
|
||||
|
||||
rbacDef.getEntityAliases().values().stream()
|
||||
.filter(e -> e.aliasName().startsWith(entity.aliasName() + "."))
|
||||
.forEach(this::renderEntitySubgraph);
|
||||
|
||||
wrapOutputInSubgraph(entity.aliasName() + ":roles", color,
|
||||
rbacDef.getRoleDefs().stream()
|
||||
.filter(r -> r.getEntityAlias() == entity)
|
||||
@ -118,32 +122,27 @@ public class RbacViewMermaidFlowchart {
|
||||
return flowchart.toString();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
// Files.writeString(
|
||||
// Paths.get("doc", "hsOfficeRelationship.md"),
|
||||
// new RbacViewMermaidFlowchart(HsOfficeRelationshipEntity.rbac()).toString(),
|
||||
// StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
//
|
||||
//
|
||||
// Files.writeString(
|
||||
// Paths.get("doc", "hsOfficeBankAccount.md"),
|
||||
// new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.rbac()).toString(),
|
||||
// StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
|
||||
void generateToMarkdownFile() throws IOException {
|
||||
final Path path = Paths.get("doc", rbacDef.getEntityAlias().simpleName() + ".md");
|
||||
Files.writeString(
|
||||
Paths.get("doc", "hsOfficeDebitor.md"),
|
||||
path,
|
||||
"""
|
||||
### rbac %{entityAlias} %{timestamp}
|
||||
### rbac %{entityAlias} %{timestamp}
|
||||
|
||||
```mermaid
|
||||
%{flowchart}
|
||||
```
|
||||
"""
|
||||
.replace("%{entityAlias}", "contact")
|
||||
.replace("%{timestamp}", LocalDateTime.now().toString())
|
||||
.replace("%{flowchart}", new RbacViewMermaidFlowchart(HsOfficeDebitorEntity.rbac()).toString()),
|
||||
```mermaid
|
||||
%{flowchart}
|
||||
```
|
||||
"""
|
||||
.replace("%{entityAlias}", rbacDef.getEntityAlias().aliasName())
|
||||
.replace("%{timestamp}", LocalDateTime.now().toString())
|
||||
.replace("%{flowchart}", flowchart.toString()),
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
System.out.println("Markdown-File: " + path.toAbsolutePath());
|
||||
}
|
||||
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
public static void main(String[] args) throws IOException {
|
||||
new RbacViewMermaidFlowchart(HsOfficeBankAccountEntity.rbac()).generateToMarkdownFile();
|
||||
new RbacViewMermaidFlowchart(HsOfficeRelationshipEntity.rbac()).generateToMarkdownFile();
|
||||
new RbacViewMermaidFlowchart(HsOfficeDebitorEntity.rbac()).generateToMarkdownFile();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacdef;
|
||||
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacPermissionDefinition;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import jakarta.persistence.Table;
|
||||
import java.util.HashSet;
|
||||
@ -31,7 +30,7 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
this.liquibaseTagPrefix = liquibaseTagPrefix;
|
||||
|
||||
entityClass = rbacDef.getEntityAlias().entityClass();
|
||||
simpleEntityName = withoutEntitySuffix(entityClass.getSimpleName());
|
||||
simpleEntityName = entityClass.getSimpleName();
|
||||
simpleEntityVarName = uncapitalize(simpleEntityName);
|
||||
rawTableName = withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
||||
}
|
||||
@ -209,10 +208,6 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
return tableName.substring(0, tableName.length()-"_rv".length());
|
||||
}
|
||||
|
||||
private String withoutEntitySuffix(final String simpleEntityName) {
|
||||
return simpleEntityName.substring(0, simpleEntityName.length()-"Entity".length());
|
||||
}
|
||||
|
||||
private String toPlPgSqlReference(final RbacView.RbacUserReference userRef) {
|
||||
return switch (userRef.role) {
|
||||
case CREATOR -> "currentUserUuid()";
|
||||
@ -221,20 +216,18 @@ class RolesGrantsAndPermissionsGenerator {
|
||||
}
|
||||
|
||||
private String toPlPgSqlReference(final PostgresTriggerReference triggerRef, final RbacView.RbacRoleDefinition roleDef) {
|
||||
return toSimpleVarName(roleDef.getEntityAlias()) + StringUtils.capitalize(roleDef.getRole().roleName()) +
|
||||
( roleDef.getEntityAlias().isGlobal() ? "()"
|
||||
: rbacDef.isMainEntityAlias(roleDef.getEntityAlias()) ? ("("+triggerRef.name()+")")
|
||||
return toVar(roleDef) +
|
||||
(roleDef.getEntityAlias().isGlobal() ? "()"
|
||||
: rbacDef.isMainEntityAlias(roleDef.getEntityAlias()) ? ("(" + triggerRef.name() + ")")
|
||||
: "(" + toTriggerReference(triggerRef, roleDef.getEntityAlias()) + ")");
|
||||
}
|
||||
|
||||
private static String toTriggerReference(final PostgresTriggerReference triggerRef, final RbacView.EntityAlias entityAlias) {
|
||||
return triggerRef.name().toLowerCase() + StringUtils.capitalize(entityAlias.aliasName());
|
||||
private static String toVar(final RbacView.RbacRoleDefinition roleDef) {
|
||||
return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName());
|
||||
}
|
||||
|
||||
private String toSimpleVarName(final RbacView.EntityAlias entityAlias) {
|
||||
return entityAlias.isGlobal()
|
||||
? entityAlias.aliasName()
|
||||
: uncapitalize(withoutEntitySuffix(entityAlias.entityClass().getSimpleName()));
|
||||
private static String toTriggerReference(final PostgresTriggerReference triggerRef, final RbacView.EntityAlias entityAlias) {
|
||||
return triggerRef.name().toLowerCase() + capitalize(entityAlias.aliasName());
|
||||
}
|
||||
|
||||
private String indent(final int tabs) {
|
||||
|
Loading…
Reference in New Issue
Block a user