introduce RbacObject and initial test for RbacViewMermaidFlowchart

This commit is contained in:
Michael Hoennig 2024-02-25 07:15:15 +01:00
parent 8d3fb4e951
commit fc1cc5815f
9 changed files with 120 additions and 29 deletions

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.errors;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import java.util.UUID;
@ -8,7 +8,7 @@ public class ReferenceNotFoundException extends RuntimeException {
private final Class<?> entityClass;
private final UUID uuid;
public <E extends HasUuid> ReferenceNotFoundException(final Class<E> entityClass, final UUID uuid, final Throwable exc) {
public <E extends RbacObject> ReferenceNotFoundException(final Class<E> entityClass, final UUID uuid, final Throwable exc) {
super(exc);
this.entityClass = entityClass;
this.uuid = uuid;

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.persistence;
import java.util.UUID;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
public interface HasUuid {
UUID getUuid();
// TODO: remove this interface, I just wanted to avoid to many changes in that PR
public interface HasUuid extends RbacObject {
}

View File

@ -3,7 +3,7 @@ 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;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import jakarta.validation.constraints.NotNull;
import java.lang.reflect.InvocationTargetException;
@ -37,11 +37,11 @@ public class RbacView {
private SQL identityViewSqlQuery;
private EntityAlias entityAliasProxy;
public static <E extends HasUuid> RbacView rbacViewFor(final String alias, final Class<? extends HasUuid> entityClass) {
public static <E extends RbacObject> RbacView rbacViewFor(final String alias, final Class<E> entityClass) {
return new RbacView(alias, entityClass);
}
RbacView(final String alias, final Class<? extends HasUuid> entityClass) {
RbacView(final String alias, final Class<? extends RbacObject> entityClass) {
entityAlias = new EntityAlias(alias, entityClass);
entityAliases.put(alias, entityAlias);
new RbacUserReference(CREATOR);
@ -72,14 +72,14 @@ public class RbacView {
return permDef;
}
public <EC extends HasUuid> RbacView declarePlaceholderEntityAliases(final String... aliasNames) {
public <EC extends RbacObject> RbacView declarePlaceholderEntityAliases(final String... aliasNames) {
for ( String alias: aliasNames ) {
entityAliases.put(alias, new EntityAlias(alias));
}
return this;
}
public <EC extends HasUuid> RbacView importProxyEntity(
public <EC extends RbacObject> RbacView importProxyEntity(
final String aliasName, final Class entityClass, final SQL fetchSql, final Column dependsOnColum) {
if ( entityAliasProxy != null ) {
throw new IllegalStateException("there is already an entityAliasProxy: " + entityAliasProxy);
@ -105,7 +105,7 @@ public class RbacView {
return entityAlias;
}
private static RbacView rbacDefinition(final Class<? extends HasUuid> entityClass)
private static RbacView rbacDefinition(final Class<? extends RbacObject> entityClass)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
return (RbacView) entityClass.getMethod("rbac").invoke(null);
}
@ -398,13 +398,13 @@ public class RbacView {
return findRbacRole(findEntityAlias(entityAliasName), role);
}
record EntityAlias(String aliasName, Class<? extends HasUuid> entityClass, SQL fetchSql, Column dependsOnColum) {
record EntityAlias(String aliasName, Class<? extends RbacObject> 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) {
public EntityAlias(final String aliasName, final Class<? extends RbacObject> entityClass) {
this(aliasName, entityClass, null, null);
}

View File

@ -19,15 +19,10 @@ public class RbacViewMermaidFlowchart {
public RbacViewMermaidFlowchart(final RbacView rbacDef) {
this.rbacDef = rbacDef;
flowchart.append("""
### rbac %{entityAlias} %{timestamp}
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
"""
.replace("%{entityAlias}", rbacDef.getEntityAlias().aliasName())
.replace("%{timestamp}", LocalDateTime.now().toString()));
""");
renderEntitySubgraphs();
renderGrants();
flowchart.append("```");
@ -51,13 +46,13 @@ public class RbacViewMermaidFlowchart {
.replace("%{aliasName}", entity.aliasName())
.replace("%{color}", color ));
wrapOutputInSubgraph(entity.aliasName() + ".roles", 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,
wrapOutputInSubgraph(entity.aliasName() + ":permissions", color,
rbacDef.getPermDefs().stream()
.filter(p -> p.getEntityAlias() == entity)
.map(p -> " " + permDef(p) )
@ -138,7 +133,17 @@ public class RbacViewMermaidFlowchart {
Files.writeString(
Paths.get("doc", "hsOfficeDebitor.md"),
new RbacViewMermaidFlowchart(HsOfficeDebitorEntity.rbac()).toString(),
"""
### rbac %{entityAlias} %{timestamp}
```mermaid
%{flowchart}
```
"""
.replace("%{entityAlias}", "contact")
.replace("%{timestamp}", LocalDateTime.now().toString())
.replace("%{flowchart}", new RbacViewMermaidFlowchart(HsOfficeDebitorEntity.rbac()).toString()),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
}

View File

@ -0,0 +1,8 @@
package net.hostsharing.hsadminng.rbac.rbacobject;
import java.util.UUID;
public interface RbacObject {
UUID getUuid();
}

View File

@ -4,17 +4,24 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
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;
@Entity
@Table(name = "test_customer_rv")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TestCustomerEntity {
public class TestCustomerEntity implements RbacObject {
@Id
@GeneratedValue
@ -25,4 +32,23 @@ public class TestCustomerEntity {
@Column(name = "adminusername")
private String adminUserName;
public static RbacView rbac() {
// @formatter:off
return rbacViewFor("contact", TestCustomerEntity.class)
.withIdentityView(RbacView.SQL.query("target.prefix"))
.withUpdatableColumns("reference", "prefix", "adminUserName")
.createRole(OWNER)
.withPermission(ALL)
.withCurrentUserAsOwner()
.withIncomingSuperRole(GLOBAL, ADMIN)
.createSubRole(ADMIN)
.withPermission(RbacView.Permission.custom("add-package"))
.createSubRole(TENANT)
.withPermission(VIEW)
.pop();
// @formatter:on
}
}

View File

@ -21,7 +21,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType;
import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.test.JpaAttempt;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
@ -520,7 +520,7 @@ public class ImportOfficeData extends ContextBasedTest {
}
private void persist(final Integer id, final HasUuid entity) {
private void persist(final Integer id, final RbacObject entity) {
try {
//System.out.println("persisting #" + entity.hashCode() + ": " + entity);
em.persist(entity);
@ -591,7 +591,7 @@ public class ImportOfficeData extends ContextBasedTest {
}).assertSuccessful();
}
private <E extends HasUuid> void updateLegacyIds(
private <E extends RbacObject> void updateLegacyIds(
Map<Integer, E> entities,
final String legacyIdTable,
final String legacyIdColumn) {

View File

@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.context.ContextBasedTest;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantEntity;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleEntity;
import net.hostsharing.hsadminng.rbac.rbacrole.RbacRoleRepository;
import net.hostsharing.test.JpaAttempt;
@ -43,7 +44,7 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
@Autowired
JpaAttempt jpaAttempt;
private TreeMap<UUID, Class<? extends HasUuid>> entitiesToCleanup = new TreeMap<>();
private TreeMap<UUID, Class<? extends RbacObject>> entitiesToCleanup = new TreeMap<>();
private static Long latestIntialTestDataSerialId;
private static boolean countersInitialized = false;
@ -61,7 +62,7 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
return uuidToCleanup;
}
public <E extends HasUuid> E toCleanup(final E entity) {
public <E extends RbacObject> E toCleanup(final E entity) {
out.println("toCleanup(" + entity.getClass() + ", " + entity.getUuid());
if ( entity.getUuid() == null ) {
throw new IllegalArgumentException("only persisted entities with valid uuid allowed");

View File

@ -0,0 +1,51 @@
package net.hostsharing.hsadminng.test.cust;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacViewMermaidFlowchart;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class TestCustomerEntityTest {
@Test
void definesRbac() {
final var rbacFlowchart = new RbacViewMermaidFlowchart(TestCustomerEntity.rbac()).toString();
assertThat(rbacFlowchart).isEqualToIgnoringWhitespace("""
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph contact["`**contact**`"]
direction TB
style contact fill:#dd4901,stroke:darkblue,stroke-width:8px
subgraph contact:roles[ ]
style contact:roles fill: #dd4901
role:contact:owner[[contact:owner]]
role:contact:admin[[contact:admin]]
role:contact:tenant[[contact:tenant]]
end
subgraph contact:permissions[ ]
style contact:permissions fill: #dd4901
perm:contact:*{{contact:*}}
perm:contact:add-package{{contact:add-package}}
perm:contact:view{{contact:view}}
end
end
role:contact:owner ==> perm:contact:*
role:contact:owner ==> perm:contact:*
user:creator ==> role:contact:owner
role:global:admin ==> role:contact:owner
role:global:admin ==> role:contact:owner
role:contact:owner ==> role:contact:admin
role:contact:admin ==> perm:contact:add-package
role:contact:admin ==> perm:contact:add-package
role:contact:admin ==> role:contact:tenant
role:contact:tenant ==> perm:contact:view
role:contact:tenant ==> perm:contact:view
""");
}
}