add customer and outgoing grants to RelationshipEntity

This commit is contained in:
Michael Hoennig 2024-02-25 09:28:15 +01:00
parent fc1cc5815f
commit f45f88ba77
6 changed files with 91 additions and 50 deletions

View File

@ -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
}
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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) {