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() { public String toString() {
final var arrow = isAssumed() ? " --> " : " -- // --> "; final var arrow = isAssumed() ? " --> " : " -- // --> ";
return switch (grantType()) { 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_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 @NotNull
GrantType grantType() { GrantType grantType() {
return permDef != null ? GrantType.ROLE_TO_PERM return permDef != null ? GrantType.PERM_TO_ROLE
: userDef != null ? GrantType.USER_TO_ROLE : userDef != null ? GrantType.ROLE_TO_USER
: GrantType.ROLE_TO_ROLE; : GrantType.ROLE_TO_ROLE;
} }
@ -268,9 +268,9 @@ public class RbacView {
} }
public enum GrantType { public enum GrantType {
USER_TO_ROLE, ROLE_TO_USER,
ROLE_TO_ROLE, ROLE_TO_ROLE,
ROLE_TO_PERM PERM_TO_ROLE
} }
} }
@ -511,6 +511,9 @@ public class RbacView {
} }
public static SQL query(final String sql) { 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); return new SQL(sql);
} }

View File

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

View File

@ -21,11 +21,11 @@ public class StringWriter {
private String indented(final String text) { private String indented(final String text) {
if ( indentLevel == 0) { if ( indentLevel == 0) {
return text.trim(); return text;
} }
final var indentation = StringUtils.repeat(" ", indentLevel); final var indentation = StringUtils.repeat(" ", indentLevel);
final var indented = stream(text.split("\n")) 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")); .collect(joining("\n"));
return indented; return indented;
} }

View File

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

View File

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