diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index 88cfa329..3f31ff08 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -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); } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchart.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchart.java index 04cfbbe6..634c4bf6 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchart.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacViewMermaidFlowchart.java @@ -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()); }; } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java index ccb2988f..7921cae6 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java @@ -204,14 +204,14 @@ class RolesGrantsAndPermissionsGenerator { private Set 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 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()); } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java index 9b0bfc7a..6e615dba 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/StringWriter.java @@ -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; } diff --git a/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java b/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java index fecee7b7..80bdf940 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java @@ -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) -> { diff --git a/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityTest.java b/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityTest.java index 10f1c4ce..09aeb1f8 100644 --- a/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityTest.java +++ b/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityTest.java @@ -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 """); } }