RBAC Diagram+PostgreSQL Generator #21
@ -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;
|
||||
|
@ -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 {
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacobject;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface RbacObject {
|
||||
UUID getUuid();
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
""");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user