RBAC Diagram+PostgreSQL Generator #21

Merged
hsh-michaelhoennig merged 54 commits from experimental-rbacview-generator into master 2024-03-11 12:30:44 +01:00
6 changed files with 59 additions and 42 deletions
Showing only changes of commit 4ba78a70c2 - Show all commits

View File

@ -212,9 +212,9 @@ public class RbacView {
public String toString() {
final var arrow = isAssumed() ? " --> " : " -- // --> ";
return switch (grantType()) {
case USER_TO_ROLE -> userDef.toString() + arrow + subRoleDef.toString();
case ROLE_TO_USER -> userDef.toString() + arrow + subRoleDef.toString();
case ROLE_TO_ROLE -> superRoleDef + arrow + subRoleDef;
case ROLE_TO_PERM -> superRoleDef + arrow + permDef;
case PERM_TO_ROLE -> superRoleDef + arrow + permDef;
};
}
@ -248,8 +248,8 @@ public class RbacView {
@NotNull
GrantType grantType() {
return permDef != null ? GrantType.ROLE_TO_PERM
: userDef != null ? GrantType.USER_TO_ROLE
return permDef != null ? GrantType.PERM_TO_ROLE
: userDef != null ? GrantType.ROLE_TO_USER
: GrantType.ROLE_TO_ROLE;
}
@ -268,9 +268,9 @@ public class RbacView {
}
public enum GrantType {
USER_TO_ROLE,
ROLE_TO_USER,
ROLE_TO_ROLE,
ROLE_TO_PERM
PERM_TO_ROLE
}
}
@ -511,6 +511,9 @@ public class RbacView {
}
public static SQL query(final String sql) {
if (sql.matches(";[ \t]*$")) {
throw new IllegalArgumentException("SQL expression must not end with ';': " + sql);
}
return new SQL(sql);
}

View File

@ -10,6 +10,7 @@ import java.nio.file.*;
import java.time.LocalDateTime;
import static java.util.stream.Collectors.joining;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.*;
public class RbacViewMermaidFlowchart {
@ -24,7 +25,6 @@ public class RbacViewMermaidFlowchart {
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
""");
flowchart.writeLn();
renderEntitySubgraphs();
renderGrants();
}
@ -37,8 +37,7 @@ public class RbacViewMermaidFlowchart {
private void renderEntitySubgraph(final RbacView.EntityAlias entity) {
final var color = rbacDef.isRootEntityAlias(entity) ? HOSTSHARING_ORANGE : HOSTSHARING_LIGHTBLUE;
flowchart.writeLn("""
flowchart.writeLn("""
subgraph %{aliasName}["`**%{aliasName}**`"]
direction TB
style %{aliasName} fill:%{color},stroke:darkblue,stroke-width:8px
@ -54,13 +53,13 @@ public class RbacViewMermaidFlowchart {
wrapOutputInSubgraph(entity.aliasName() + ":roles", color,
rbacDef.getRoleDefs().stream()
.filter(r -> r.getEntityAlias() == entity)
.map(r -> " " + roleDef(r))
.map(this::roleDef)
.collect(joining("\n")));
wrapOutputInSubgraph(entity.aliasName() + ":permissions", color,
rbacDef.getPermDefs().stream()
.filter(p -> p.getEntityAlias() == entity)
.map(p -> " " + permDef(p) )
.map(this::permDef)
.collect(joining("\n")));
if (rbacDef.isRootEntityAlias(entity) && rbacDef.getRootEntityAliasProxy() != null ) {
@ -91,10 +90,20 @@ public class RbacViewMermaidFlowchart {
}
private void renderGrants() {
rbacDef.getGrantDefs()
.forEach(g -> {
flowchart.writeLn(grantDef(g) + "\n");
});
renderGrants(ROLE_TO_USER, "%% granting roles to users");
renderGrants(ROLE_TO_ROLE, "%% granting roles to roles");
renderGrants(PERM_TO_ROLE, "%% granting permissions to roles");
}
private void renderGrants(final RbacView.RbacGrantDefinition.GrantType f, final String t) {
final var userGrants = rbacDef.getGrantDefs().stream()
.filter(g -> g.grantType() == f)
.toList();
if ( !userGrants.isEmpty()) {
flowchart.emptyLine();
flowchart.writeLn(t);
userGrants.forEach(g -> flowchart.writeLn(grantDef(g)));
}
}
private String grantDef(final RbacView.RbacGrantDefinition grant) {
@ -102,12 +111,12 @@ public class RbacViewMermaidFlowchart {
? grant.isAssumed() ? " ==> " : " == // ==> "
: grant.isAssumed() ? " -.-> " : " -.- // -.-> ";
return switch (grant.grantType()) {
case USER_TO_ROLE ->
case ROLE_TO_USER ->
// TODO: other user types not implemented yet
"user:creator" + arrow + roleId(grant.getSubRoleDef());
case ROLE_TO_ROLE ->
roleId(grant.getSuperRoleDef()) + arrow + roleId(grant.getSubRoleDef());
case ROLE_TO_PERM -> roleId(grant.getSuperRoleDef()) + arrow + permId(grant.getPermDef());
case PERM_TO_ROLE -> roleId(grant.getSuperRoleDef()) + arrow + permId(grant.getPermDef());
};
}

View File

@ -204,14 +204,14 @@ class RolesGrantsAndPermissionsGenerator {
private Set<RbacView.RbacGrantDefinition> findPermissionsGrantsForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
return rbacGrants.stream()
.filter(g -> g.grantType() == ROLE_TO_PERM && g.getSuperRoleDef()==roleDef )
.filter(g -> g.grantType() == PERM_TO_ROLE && g.getSuperRoleDef()==roleDef )
.collect(toSet());
}
private Set<RbacView.RbacGrantDefinition> findGrantsToUserForRole(final RbacView.EntityAlias entityAlias, final RbacView.Role role) {
final var roleDef = rbacDef.findRbacRole(entityAlias, role);
return rbacGrants.stream()
.filter(g -> g.grantType() == USER_TO_ROLE && g.getSubRoleDef() == roleDef )
.filter(g -> g.grantType() == ROLE_TO_USER && g.getSubRoleDef() == roleDef )
.collect(toSet());
}

View File

@ -21,11 +21,11 @@ public class StringWriter {
private String indented(final String text) {
if ( indentLevel == 0) {
return text.trim();
return text;
}
final var indentation = StringUtils.repeat(" ", indentLevel);
final var indented = stream(text.split("\n"))
.map(line -> line.trim().isBlank() ? "" : indentation + line.trim())
.map(line -> line.trim().isBlank() ? "" : indentation + line)
.collect(joining("\n"));
return indented;
}

View File

@ -38,7 +38,7 @@ public class TestCustomerEntity implements RbacObject {
public static RbacView rbac() {
return rbacViewFor("contact", TestCustomerEntity.class)
return rbacViewFor("customer", TestCustomerEntity.class)
.withIdentityView(RbacView.SQL.query("target.prefix"))
.withUpdatableColumns("reference", "prefix", "adminUserName")
.createRole(OWNER, (with) -> {

View File

@ -10,38 +10,43 @@ class TestCustomerEntityTest {
@Test
void definesRbac() {
final var rbacFlowchart = new RbacViewMermaidFlowchart(TestCustomerEntity.rbac()).toString();
assertThat(rbacFlowchart).isEqualToIgnoringWhitespace("""
assertThat(rbacFlowchart).isEqualTo("""
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph contact["`**contact**`"]
subgraph customer["`**customer**`"]
direction TB
style contact fill:#dd4901,stroke:darkblue,stroke-width:8px
style customer fill:#dd4901,stroke:darkblue,stroke-width:8px
subgraph contact:roles[ ]
style contact:roles fill: #dd4901
subgraph customer:roles[ ]
style customer:roles fill: #dd4901
role:contact:owner[[contact:owner]]
role:contact:admin[[contact:admin]]
role:contact:tenant[[contact:tenant]]
role:customer:owner[[customer:owner]]
role:customer:admin[[customer:admin]]
role:customer:tenant[[customer:tenant]]
end
subgraph contact:permissions[ ]
style contact:permissions fill: #dd4901
subgraph customer:permissions[ ]
style customer:permissions fill: #dd4901
perm:contact:*{{contact:*}}
perm:contact:add-package{{contact:add-package}}
perm:contact:view{{contact:view}}
perm:customer:*{{customer:*}}
perm:customer:add-package{{customer:add-package}}
perm:customer:view{{customer:view}}
end
end
user:creator ==> role:contact:owner
role:global:admin ==> role:contact:owner
role:contact:owner ==> perm:contact:*
role:contact:owner ==> role:contact:admin
role:contact:admin ==> perm:contact:add-package
role:contact:admin ==> role:contact:tenant
role:contact:tenant ==> perm:contact:view
%% granting roles to users
user:creator ==> role:customer:owner
%% granting roles to roles
role:global:admin ==> role:customer:owner
role:customer:owner ==> role:customer:admin
role:customer:admin ==> role:customer:tenant
%% granting permissions to roles
role:customer:owner ==> perm:customer:*
role:customer:admin ==> perm:customer:add-package
role:customer:tenant ==> perm:customer:view
""");
}
}