RBAC Diagram+PostgreSQL Generator #21
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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) -> {
|
||||||
|
@ -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
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user