From f45f88ba7756a9c3405527d5fa51ec086d4a6068 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Sun, 25 Feb 2024 09:28:15 +0100 Subject: [PATCH] add customer and outgoing grants to RelationshipEntity --- .../office/contact/HsOfficeContactEntity.java | 22 ++++++++ .../partner/HsOfficePartnerController.java | 4 +- .../HsOfficeRelationshipEntity.java | 10 +++- .../hsadminng/rbac/rbacdef/RbacView.java | 29 ++++++++-- .../rbacdef/RbacViewMermaidFlowchart.java | 53 +++++++++---------- .../RolesGrantsAndPermissionsGenerator.java | 23 +++----- 6 files changed, 91 insertions(+), 50 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java index 69555dc4..922d3065 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java @@ -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 + } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java index 04dcbb6a..6fdd0732 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java @@ -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 ref(final Class entityClass, final UUID uuid) { + private E ref(final Class entityClass, final UUID uuid) { try { return em.getReference(entityClass, uuid); } catch (final Throwable exc) { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java index 9a94279b..ebb17d58 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java @@ -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 } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index b89ac18b..628e3c03 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -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 userDefs = new HashSet<>(); - private final Set roleDefs = new HashSet<>(); - private final Set permDefs = new HashSet<>(); + private final Set userDefs = new LinkedHashSet<>(); + private final Set roleDefs = new LinkedHashSet<>(); + private final Set permDefs = new LinkedHashSet<>(); private final Map entityAliases = new HashMap<>() { @Override @@ -31,8 +32,8 @@ public class RbacView { return super.put(key, value); } }; - private final Set updatableColumns = new TreeSet<>(); - private final Set grantDefs = new HashSet<>(); + private final Set updatableColumns = new LinkedHashSet<>(); + private final Set 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) { diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchart.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchart.java index 5c63334f..9f34673f 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchart.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchart.java @@ -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} - - ```mermaid - %{flowchart} - ``` - """ - .replace("%{entityAlias}", "contact") - .replace("%{timestamp}", LocalDateTime.now().toString()) - .replace("%{flowchart}", new RbacViewMermaidFlowchart(HsOfficeDebitorEntity.rbac()).toString()), + ### rbac %{entityAlias} %{timestamp} + + ```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(); } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java index 50175146..75501578 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java @@ -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) {