RBAC Diagram+PostgreSQL Generator #21
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
@ -38,7 +38,6 @@ public class RbacViewMermaidFlowchart {
|
||||
private void renderEntitySubgraph(final RbacView.EntityAlias entity) {
|
||||
final var color = rbacDef.isRootEntityAlias(entity) ? HOSTSHARING_ORANGE : HOSTSHARING_LIGHTBLUE;
|
||||
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());
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) -> {
|
||||
|
@ -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
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user