RBAC Diagram+PostgreSQL Generator #21
@ -197,7 +197,7 @@ public class RbacView {
|
|||||||
});
|
});
|
||||||
importedRbacView.getGrantDefs().forEach(grantDef -> {
|
importedRbacView.getGrantDefs().forEach(grantDef -> {
|
||||||
if (grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE) {
|
if (grantDef.grantType() == RbacGrantDefinition.GrantType.ROLE_TO_ROLE) {
|
||||||
findOrCreateGrantDef(
|
final var importedGrantDef = findOrCreateGrantDef(
|
||||||
findRbacRole(
|
findRbacRole(
|
||||||
mapper.map(grantDef.getSubRoleDef().entityAlias.aliasName),
|
mapper.map(grantDef.getSubRoleDef().entityAlias.aliasName),
|
||||||
grantDef.getSubRoleDef().getRole()),
|
grantDef.getSubRoleDef().getRole()),
|
||||||
@ -205,6 +205,9 @@ public class RbacView {
|
|||||||
mapper.map(grantDef.getSuperRoleDef().entityAlias.aliasName),
|
mapper.map(grantDef.getSuperRoleDef().entityAlias.aliasName),
|
||||||
grantDef.getSuperRoleDef().getRole())
|
grantDef.getSuperRoleDef().getRole())
|
||||||
);
|
);
|
||||||
|
if (!grantDef.isAssumed()) {
|
||||||
|
importedGrantDef.unassumed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
|
@ -109,9 +109,8 @@ public class RbacViewMermaidFlowchart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String grantDef(final RbacView.RbacGrantDefinition grant) {
|
private String grantDef(final RbacView.RbacGrantDefinition grant) {
|
||||||
final var arrow = grant.isToCreate()
|
final var arrow = (grant.isToCreate() ? " ==>" : " -.->")
|
||||||
? grant.isAssumed() ? " ==> " : " == /// ==> "
|
+ (grant.isAssumed() ? " " : "|XX| ");
|
||||||
: grant.isAssumed() ? " -.-> " : " -.- /// -.-> ";
|
|
||||||
return switch (grant.grantType()) {
|
return switch (grant.grantType()) {
|
||||||
case ROLE_TO_USER ->
|
case ROLE_TO_USER ->
|
||||||
// TODO: other user types not implemented yet
|
// TODO: other user types not implemented yet
|
||||||
|
@ -230,7 +230,8 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
private String generateGrant(RbacView.RbacGrantDefinition grantDef) {
|
private String generateGrant(RbacView.RbacGrantDefinition grantDef) {
|
||||||
return switch (grantDef.grantType()) {
|
return switch (grantDef.grantType()) {
|
||||||
case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant");
|
case ROLE_TO_USER -> throw new IllegalArgumentException("unexpected grant");
|
||||||
case ROLE_TO_ROLE -> "call grantRoleToRole(${subRoleRef}, ${superRoleRef});"
|
case ROLE_TO_ROLE -> "call grantRoleToRole(${subRoleRef}, ${superRoleRef}${assumed});"
|
||||||
|
.replace("${assumed}", grantDef.isAssumed() ? "" : ", unassumed()")
|
||||||
.replace("${subRoleRef}", roleRef(NEW, grantDef.getSubRoleDef()))
|
.replace("${subRoleRef}", roleRef(NEW, grantDef.getSubRoleDef()))
|
||||||
.replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef()));
|
.replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef()));
|
||||||
case PERM_TO_ROLE ->
|
case PERM_TO_ROLE ->
|
||||||
@ -345,12 +346,11 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
private void generateIncomingSuperRolesForRole(final StringWriter plPgSql, final RbacView.Role role) {
|
||||||
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
final var incomingGrants = findIncomingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
||||||
if (!incomingGrants.isEmpty()) {
|
if (!incomingGrants.isEmpty()) {
|
||||||
final var arraElements = incomingGrants.stream()
|
final var arrayElements = incomingGrants.stream()
|
||||||
.map(RbacView.RbacGrantDefinition::getSuperRoleDef)
|
.map(g -> toPlPgSqlReference(NEW, g.getSuperRoleDef(), g.isAssumed()))
|
||||||
.map(r -> toPlPgSqlReference(NEW, r))
|
|
||||||
.toList();
|
.toList();
|
||||||
plPgSql.indented(() ->
|
plPgSql.indented(() ->
|
||||||
plPgSql.writeLn("incomingSuperRoles => array[" + joinArrayElements(arraElements, 1) + "],\n"));
|
plPgSql.writeLn("incomingSuperRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n"));
|
||||||
rbacGrants.removeAll(incomingGrants);
|
rbacGrants.removeAll(incomingGrants);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -359,8 +359,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
final var outgoingGrants = findOutgoingSuperRolesForRole(rbacDef.getRootEntityAlias(), role);
|
||||||
if (!outgoingGrants.isEmpty()) {
|
if (!outgoingGrants.isEmpty()) {
|
||||||
final var arrayElements = outgoingGrants.stream()
|
final var arrayElements = outgoingGrants.stream()
|
||||||
.map(RbacView.RbacGrantDefinition::getSubRoleDef)
|
.map(g -> toPlPgSqlReference(NEW, g.getSubRoleDef(), g.isAssumed()))
|
||||||
.map(r -> toPlPgSqlReference(NEW, r))
|
|
||||||
.toList();
|
.toList();
|
||||||
plPgSql.indented(() ->
|
plPgSql.indented(() ->
|
||||||
plPgSql.writeLn("outgoingSubRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n"));
|
plPgSql.writeLn("outgoingSubRoles => array[" + joinArrayElements(arrayElements, 1) + "],\n"));
|
||||||
@ -485,14 +484,18 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toPlPgSqlReference(final PostgresTriggerReference triggerRef, final RbacView.RbacRoleDefinition roleDef) {
|
private String toPlPgSqlReference(
|
||||||
return toVar(roleDef) +
|
final PostgresTriggerReference triggerRef,
|
||||||
(roleDef.getEntityAlias().isGlobal() ? "()"
|
final RbacView.RbacRoleDefinition roleDef,
|
||||||
|
final boolean assumed) {
|
||||||
|
final var assumedArg = assumed ? "" : ", unassumed()";
|
||||||
|
return toRoleRef(roleDef) +
|
||||||
|
(roleDef.getEntityAlias().isGlobal() ? ( assumed ? "()" : "(unassumed())")
|
||||||
: rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) ? ("(" + triggerRef.name() + ")")
|
: rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) ? ("(" + triggerRef.name() + ")")
|
||||||
: "(" + toTriggerReference(triggerRef, roleDef.getEntityAlias()) + ")");
|
: "(" + toTriggerReference(triggerRef, roleDef.getEntityAlias()) + assumedArg + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String toVar(final RbacView.RbacRoleDefinition roleDef) {
|
private static String toRoleRef(final RbacView.RbacRoleDefinition roleDef) {
|
||||||
return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName());
|
return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,8 +43,8 @@ public class TestCustomerEntity implements HasUuid {
|
|||||||
.withUpdatableColumns("reference", "prefix", "adminUserName")
|
.withUpdatableColumns("reference", "prefix", "adminUserName")
|
||||||
|
|
||||||
.createRole(OWNER, (with) -> {
|
.createRole(OWNER, (with) -> {
|
||||||
with.owningUser(CREATOR);
|
with.owningUser(CREATOR).unassumed();
|
||||||
with.incomingSuperRole(GLOBAL, ADMIN);
|
with.incomingSuperRole(GLOBAL, ADMIN).unassumed();
|
||||||
with.permission(DELETE);
|
with.permission(DELETE);
|
||||||
})
|
})
|
||||||
.createSubRole(ADMIN, (with) -> {
|
.createSubRole(ADMIN, (with) -> {
|
||||||
|
@ -15,7 +15,6 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
|
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.*;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||||
@ -58,7 +57,7 @@ public class TestPackageEntity implements HasUuid {
|
|||||||
.toRole("customer", ADMIN).grantPermission("package", INSERT)
|
.toRole("customer", ADMIN).grantPermission("package", INSERT)
|
||||||
|
|
||||||
.createRole(OWNER, (with) -> {
|
.createRole(OWNER, (with) -> {
|
||||||
with.incomingSuperRole("customer", ADMIN).unassumed();
|
with.incomingSuperRole("customer", ADMIN);
|
||||||
with.permission(DELETE);
|
with.permission(DELETE);
|
||||||
with.permission(UPDATE);
|
with.permission(UPDATE);
|
||||||
})
|
})
|
||||||
|
@ -203,15 +203,33 @@ create type RbacRoleDescriptor as
|
|||||||
(
|
(
|
||||||
objectTable varchar(63), -- for human readability and easier debugging
|
objectTable varchar(63), -- for human readability and easier debugging
|
||||||
objectUuid uuid,
|
objectUuid uuid,
|
||||||
roleType RbacRoleType
|
roleType RbacRoleType,
|
||||||
|
assumed boolean
|
||||||
);
|
);
|
||||||
|
|
||||||
create or replace function roleDescriptor(objectTable varchar(63), objectUuid uuid, roleType RbacRoleType)
|
create or replace function assumed()
|
||||||
|
returns boolean
|
||||||
|
stable -- leakproof
|
||||||
|
language sql as $$
|
||||||
|
select true;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
create or replace function unassumed()
|
||||||
|
returns boolean
|
||||||
|
stable -- leakproof
|
||||||
|
language sql as $$
|
||||||
|
select false;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
|
||||||
|
create or replace function roleDescriptor(
|
||||||
|
objectTable varchar(63), objectUuid uuid, roleType RbacRoleType,
|
||||||
|
assumed boolean = true) -- just for DSL readability, belongs actually to the grant
|
||||||
returns RbacRoleDescriptor
|
returns RbacRoleDescriptor
|
||||||
returns null on null input
|
returns null on null input
|
||||||
stable -- leakproof
|
stable -- leakproof
|
||||||
language sql as $$
|
language sql as $$
|
||||||
select objectTable, objectUuid, roleType::RbacRoleType;
|
select objectTable, objectUuid, roleType::RbacRoleType, assumed;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
create or replace function createRole(roleDescriptor RbacRoleDescriptor)
|
create or replace function createRole(roleDescriptor RbacRoleDescriptor)
|
||||||
|
@ -13,24 +13,6 @@ begin
|
|||||||
return createPermissions(forObjectUuid, permitOps);
|
return createPermissions(forObjectUuid, permitOps);
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
create or replace function toRoleUuids(roleDescriptors RbacRoleDescriptor[])
|
|
||||||
returns uuid[]
|
|
||||||
language plpgsql
|
|
||||||
strict as $$
|
|
||||||
declare
|
|
||||||
superRoleDescriptor RbacRoleDescriptor;
|
|
||||||
superRoleUuids uuid[] := array []::uuid[];
|
|
||||||
begin
|
|
||||||
foreach superRoleDescriptor in array roleDescriptors
|
|
||||||
loop
|
|
||||||
if superRoleDescriptor is not null then
|
|
||||||
superRoleUuids := superRoleUuids || getRoleId(superRoleDescriptor, 'fail');
|
|
||||||
end if;
|
|
||||||
end loop;
|
|
||||||
|
|
||||||
return superRoleUuids;
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
|
|
||||||
-- =================================================================
|
-- =================================================================
|
||||||
-- CREATE ROLE
|
-- CREATE ROLE
|
||||||
@ -50,25 +32,29 @@ create or replace function createRoleWithGrants(
|
|||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
declare
|
declare
|
||||||
roleUuid uuid;
|
roleUuid uuid;
|
||||||
superRoleUuid uuid;
|
subRoleDesc RbacRoleDescriptor;
|
||||||
|
superRoleDesc RbacRoleDescriptor;
|
||||||
subRoleUuid uuid;
|
subRoleUuid uuid;
|
||||||
|
superRoleUuid uuid;
|
||||||
userUuid uuid;
|
userUuid uuid;
|
||||||
grantedByRoleUuid uuid;
|
grantedByRoleUuid uuid;
|
||||||
begin
|
begin
|
||||||
roleUuid := createRole(roleDescriptor);
|
roleUuid := createRole(roleDescriptor);
|
||||||
|
|
||||||
if cardinality(permissions) >0 then
|
if cardinality(permissions) > 0 then
|
||||||
call grantPermissionsToRole(roleUuid, toPermissionUuids(roleDescriptor.objectuuid, permissions));
|
call grantPermissionsToRole(roleUuid, toPermissionUuids(roleDescriptor.objectuuid, permissions));
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
foreach superRoleUuid in array toRoleUuids(incomingSuperRoles)
|
foreach superRoleDesc in array incomingSuperRoles
|
||||||
loop
|
loop
|
||||||
call grantRoleToRole(roleUuid, superRoleUuid);
|
superRoleUuid = getRoleId(superRoleDesc, 'fail');
|
||||||
|
call grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed);
|
||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
foreach subRoleUuid in array toRoleUuids(outgoingSubRoles)
|
foreach subRoleDesc in array outgoingSubRoles
|
||||||
loop
|
loop
|
||||||
call grantRoleToRole(subRoleUuid, roleUuid);
|
subRoleUuid = getRoleId(subRoleDesc, 'fail');
|
||||||
|
call grantRoleToRole(subRoleUuid, roleUuid, subRoleDesc.assumed);
|
||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
if cardinality(userUuids) > 0 then
|
if cardinality(userUuids) > 0 then
|
||||||
|
@ -35,50 +35,50 @@ end; $$;
|
|||||||
--changeset rbac-generators-ROLE-DESCRIPTORS:1 endDelimiter:--//
|
--changeset rbac-generators-ROLE-DESCRIPTORS:1 endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
create or replace procedure generateRbacRoleDescriptors(prefix text, targetTable text)
|
create procedure generateRbacRoleDescriptors(prefix text, targetTable text)
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
declare
|
declare
|
||||||
sql text;
|
sql text;
|
||||||
begin
|
begin
|
||||||
sql = format($sql$
|
sql = format($sql$
|
||||||
create or replace function %1$sOwner(entity %2$s)
|
create or replace function %1$sOwner(entity %2$s, assumed boolean = true)
|
||||||
returns RbacRoleDescriptor
|
returns RbacRoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return roleDescriptor('%2$s', entity.uuid, 'owner');
|
return roleDescriptor('%2$s', entity.uuid, 'owner', assumed);
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
create or replace function %1$sAdmin(entity %2$s)
|
create or replace function %1$sAdmin(entity %2$s, assumed boolean = true)
|
||||||
returns RbacRoleDescriptor
|
returns RbacRoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return roleDescriptor('%2$s', entity.uuid, 'admin');
|
return roleDescriptor('%2$s', entity.uuid, 'admin', assumed);
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
create or replace function %1$sAgent(entity %2$s)
|
create or replace function %1$sAgent(entity %2$s, assumed boolean = true)
|
||||||
returns RbacRoleDescriptor
|
returns RbacRoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return roleDescriptor('%2$s', entity.uuid, 'agent');
|
return roleDescriptor('%2$s', entity.uuid, 'agent', assumed);
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
create or replace function %1$sTenant(entity %2$s)
|
create or replace function %1$sTenant(entity %2$s, assumed boolean = true)
|
||||||
returns RbacRoleDescriptor
|
returns RbacRoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return roleDescriptor('%2$s', entity.uuid, 'tenant');
|
return roleDescriptor('%2$s', entity.uuid, 'tenant', assumed);
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
create or replace function %1$sGuest(entity %2$s)
|
create or replace function %1$sGuest(entity %2$s, assumed boolean = true)
|
||||||
returns RbacRoleDescriptor
|
returns RbacRoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return roleDescriptor('%2$s', entity.uuid, 'guest');
|
return roleDescriptor('%2$s', entity.uuid, 'guest', assumed);
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
$sql$, prefix, targetTable);
|
$sql$, prefix, targetTable);
|
||||||
|
@ -109,12 +109,12 @@ commit;
|
|||||||
/*
|
/*
|
||||||
A global administrator role.
|
A global administrator role.
|
||||||
*/
|
*/
|
||||||
create or replace function globalAdmin()
|
create or replace function globalAdmin(assumed boolean = true)
|
||||||
returns RbacRoleDescriptor
|
returns RbacRoleDescriptor
|
||||||
returns null on null input
|
returns null on null input
|
||||||
stable -- leakproof
|
stable -- leakproof
|
||||||
language sql as $$
|
language sql as $$
|
||||||
select 'global', (select uuid from RbacObject where objectTable = 'global'), 'admin'::RbacRoleType;
|
select 'global', (select uuid from RbacObject where objectTable = 'global'), 'admin'::RbacRoleType, assumed;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
begin transaction;
|
begin transaction;
|
||||||
|
41
src/main/resources/db/changelog/113-test-customer-rbac.md
Normal file
41
src/main/resources/db/changelog/113-test-customer-rbac.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
### rbac customer 2024-03-08T13:03:39.397294085
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||||
|
flowchart TB
|
||||||
|
|
||||||
|
subgraph customer["`**customer**`"]
|
||||||
|
direction TB
|
||||||
|
style customer fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph customer:roles[ ]
|
||||||
|
style customer:roles fill:#dd4901,stroke:white
|
||||||
|
|
||||||
|
role:customer:owner[[customer:owner]]
|
||||||
|
role:customer:admin[[customer:admin]]
|
||||||
|
role:customer:tenant[[customer:tenant]]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph customer:permissions[ ]
|
||||||
|
style customer:permissions fill:#dd4901,stroke:white
|
||||||
|
|
||||||
|
perm:customer:DELETE{{customer:DELETE}}
|
||||||
|
perm:customer:UPDATE{{customer:UPDATE}}
|
||||||
|
perm:customer:SELECT{{customer:SELECT}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
%% granting roles to users
|
||||||
|
user:creator ==>|XX| role:customer:owner
|
||||||
|
|
||||||
|
%% granting roles to roles
|
||||||
|
role:global:admin ==>|XX| role:customer:owner
|
||||||
|
role:customer:owner ==> role:customer:admin
|
||||||
|
role:customer:admin ==> role:customer:tenant
|
||||||
|
|
||||||
|
%% granting permissions to roles
|
||||||
|
role:customer:owner ==> perm:customer:DELETE
|
||||||
|
role:customer:admin ==> perm:customer:UPDATE
|
||||||
|
role:customer:tenant ==> perm:customer:SELECT
|
||||||
|
|
||||||
|
```
|
@ -1,5 +1,5 @@
|
|||||||
--liquibase formatted sql
|
--liquibase formatted sql
|
||||||
-- This code generated was by RbacViewPostgresGenerator at 2024-03-08T08:48:56.112505380.
|
-- This code generated was by RbacViewPostgresGenerator at 2024-03-08T13:03:39.428165899.
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset test-customer-rbac-OBJECT:1 endDelimiter:--//
|
--changeset test-customer-rbac-OBJECT:1 endDelimiter:--//
|
||||||
@ -37,7 +37,7 @@ begin
|
|||||||
testCustomerOwner(NEW),
|
testCustomerOwner(NEW),
|
||||||
permissions => array['DELETE'],
|
permissions => array['DELETE'],
|
||||||
userUuids => array[currentUserUuid()],
|
userUuids => array[currentUserUuid()],
|
||||||
incomingSuperRoles => array[globalAdmin()]
|
incomingSuperRoles => array[globalAdmin(unassumed())]
|
||||||
);
|
);
|
||||||
|
|
||||||
perform createRoleWithGrants(
|
perform createRoleWithGrants(
|
||||||
|
57
src/main/resources/db/changelog/123-test-package-rbac.md
Normal file
57
src/main/resources/db/changelog/123-test-package-rbac.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
### rbac package 2024-03-08T13:03:39.472333368
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||||
|
flowchart TB
|
||||||
|
|
||||||
|
subgraph package["`**package**`"]
|
||||||
|
direction TB
|
||||||
|
style package fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph package:roles[ ]
|
||||||
|
style package:roles fill:#dd4901,stroke:white
|
||||||
|
|
||||||
|
role:package:owner[[package:owner]]
|
||||||
|
role:package:admin[[package:admin]]
|
||||||
|
role:package:tenant[[package:tenant]]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph package:permissions[ ]
|
||||||
|
style package:permissions fill:#dd4901,stroke:white
|
||||||
|
|
||||||
|
perm:package:INSERT{{package:INSERT}}
|
||||||
|
perm:package:DELETE{{package:DELETE}}
|
||||||
|
perm:package:UPDATE{{package:UPDATE}}
|
||||||
|
perm:package:SELECT{{package:SELECT}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph customer["`**customer**`"]
|
||||||
|
direction TB
|
||||||
|
style customer fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph customer:roles[ ]
|
||||||
|
style customer:roles fill:#99bcdb,stroke:white
|
||||||
|
|
||||||
|
role:customer:owner[[customer:owner]]
|
||||||
|
role:customer:admin[[customer:admin]]
|
||||||
|
role:customer:tenant[[customer:tenant]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
%% granting roles to roles
|
||||||
|
role:global:admin -.->|XX| role:customer:owner
|
||||||
|
role:customer:owner -.-> role:customer:admin
|
||||||
|
role:customer:admin -.-> role:customer:tenant
|
||||||
|
role:customer:admin ==> role:package:owner
|
||||||
|
role:package:owner ==> role:package:admin
|
||||||
|
role:package:admin ==> role:package:tenant
|
||||||
|
role:package:tenant ==> role:customer:tenant
|
||||||
|
|
||||||
|
%% granting permissions to roles
|
||||||
|
role:customer:admin ==> perm:package:INSERT
|
||||||
|
role:package:owner ==> perm:package:DELETE
|
||||||
|
role:package:owner ==> perm:package:UPDATE
|
||||||
|
role:package:tenant ==> perm:package:SELECT
|
||||||
|
|
||||||
|
```
|
@ -1,5 +1,5 @@
|
|||||||
--liquibase formatted sql
|
--liquibase formatted sql
|
||||||
-- This code generated was by RbacViewPostgresGenerator at 2024-03-08T08:48:56.148164198.
|
-- This code generated was by RbacViewPostgresGenerator at 2024-03-08T13:03:39.473061981.
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset test-package-rbac-OBJECT:1 endDelimiter:--//
|
--changeset test-package-rbac-OBJECT:1 endDelimiter:--//
|
||||||
|
@ -6,14 +6,31 @@ import org.junit.jupiter.api.TestInfo;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
|
||||||
@Import(RbacGrantsDiagramService.class)
|
@Import(RbacGrantsDiagramService.class)
|
||||||
public abstract class ContextBasedTest {
|
public abstract class ContextBasedTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected Context context;
|
protected Context context;
|
||||||
|
|
||||||
|
@PersistenceContext
|
||||||
|
protected EntityManager em; // just to be used in subclasses
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To generate a flowchart diagram from the database use something like this in a defined context:
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
RbacGrantsDiagramService.writeToFile(
|
||||||
|
"title",
|
||||||
|
diagramService.allGrantsToCurrentUser(of(RbacGrantsDiagramService.Include.USERS, RbacGrantsDiagramService.Include.TEST_ENTITIES, RbacGrantsDiagramService.Include.NOT_ASSUMED, RbacGrantsDiagramService.Include.DETAILS, RbacGrantsDiagramService.Include.PERMISSIONS)),
|
||||||
|
"filename.md
|
||||||
|
);
|
||||||
|
</pre>
|
||||||
|
*/
|
||||||
@Autowired
|
@Autowired
|
||||||
protected RbacGrantsDiagramService diagramService;
|
protected RbacGrantsDiagramService diagramService; // just to be used in subclasses
|
||||||
|
|
||||||
TestInfo test;
|
TestInfo test;
|
||||||
|
|
||||||
|
@ -36,10 +36,10 @@ class TestCustomerEntityTest {
|
|||||||
end
|
end
|
||||||
|
|
||||||
%% granting roles to users
|
%% granting roles to users
|
||||||
user:creator ==> role:customer:owner
|
user:creator ==>|XX| role:customer:owner
|
||||||
|
|
||||||
%% granting roles to roles
|
%% granting roles to roles
|
||||||
role:global:admin ==> role:customer:owner
|
role:global:admin ==>|XX| role:customer:owner
|
||||||
role:customer:owner ==> role:customer:admin
|
role:customer:owner ==> role:customer:admin
|
||||||
role:customer:admin ==> role:customer:tenant
|
role:customer:admin ==> role:customer:tenant
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@ package net.hostsharing.hsadminng.test.cust;
|
|||||||
|
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.context.ContextBasedTest;
|
import net.hostsharing.hsadminng.context.ContextBasedTest;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService;
|
|
||||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService.Include;
|
|
||||||
import net.hostsharing.test.JpaAttempt;
|
import net.hostsharing.test.JpaAttempt;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -12,14 +10,11 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
|||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
import jakarta.persistence.EntityManager;
|
|
||||||
import jakarta.persistence.PersistenceContext;
|
|
||||||
import jakarta.persistence.PersistenceException;
|
import jakarta.persistence.PersistenceException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static java.util.EnumSet.of;
|
|
||||||
import static net.hostsharing.test.JpaAttempt.attempt;
|
import static net.hostsharing.test.JpaAttempt.attempt;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@ -30,9 +25,6 @@ class TestCustomerRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
TestCustomerRepository testCustomerRepository;
|
TestCustomerRepository testCustomerRepository;
|
||||||
|
|
||||||
@PersistenceContext
|
|
||||||
EntityManager em;
|
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
HttpServletRequest request;
|
HttpServletRequest request;
|
||||||
|
|
||||||
@ -118,15 +110,15 @@ class TestCustomerRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void globalAdmin_withAssumedglobalAdminRole_canViewAllCustomers() {
|
public void globalAdmin_withAssumedCustomerOwnerRole_canViewExactlyThatCustomer() {
|
||||||
given:
|
given:
|
||||||
context("superuser-alex@hostsharing.net", "global#global.admin");
|
context("superuser-alex@hostsharing.net", "test_customer#yyy.owner");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = testCustomerRepository.findCustomerByOptionalPrefixLike(null);
|
final var result = testCustomerRepository.findCustomerByOptionalPrefixLike(null);
|
||||||
|
|
||||||
then:
|
then:
|
||||||
allTheseCustomersAreReturned(result, "xxx", "yyy", "zzz");
|
allTheseCustomersAreReturned(result, "yyy");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -144,11 +136,6 @@ class TestCustomerRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
@Test
|
@Test
|
||||||
public void customerAdmin_withAssumedOwnedPackageAdminRole_canViewOnlyItsOwnCustomer() {
|
public void customerAdmin_withAssumedOwnedPackageAdminRole_canViewOnlyItsOwnCustomer() {
|
||||||
context("customer-admin@xxx.example.com");
|
context("customer-admin@xxx.example.com");
|
||||||
RbacGrantsDiagramService.writeToFile(
|
|
||||||
"customerAdmin_withAssumedOwnedPackageAdminRole_canViewOnlyItsOwnCustomer",
|
|
||||||
diagramService.allGrantsToCurrentUser(of(Include.USERS, Include.TEST_ENTITIES, Include.NOT_ASSUMED, Include.DETAILS, Include.PERMISSIONS)),
|
|
||||||
"doc/customerAdmin_withAssumedOwnedPackageAdminRole_canViewOnlyItsOwnCustomer.md"
|
|
||||||
);
|
|
||||||
|
|
||||||
context("customer-admin@xxx.example.com", "test_package#xxx00.admin");
|
context("customer-admin@xxx.example.com", "test_package#xxx00.admin");
|
||||||
|
|
||||||
|
@ -49,14 +49,11 @@ class TestPackageEntityTest {
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
%% granting roles to users
|
|
||||||
user:creator ==> role:package:owner
|
|
||||||
|
|
||||||
%% granting roles to roles
|
%% granting roles to roles
|
||||||
role:global:admin -.-> role:customer:owner
|
role:global:admin -.->|XX| role:customer:owner
|
||||||
role:customer:owner -.-> role:customer:admin
|
role:customer:owner -.-> role:customer:admin
|
||||||
role:customer:admin -.-> role:customer:tenant
|
role:customer:admin -.-> role:customer:tenant
|
||||||
role:customer:admin == /// ==> role:package:owner
|
role:customer:admin ==> role:package:owner
|
||||||
role:package:owner ==> role:package:admin
|
role:package:owner ==> role:package:admin
|
||||||
role:package:admin ==> role:package:tenant
|
role:package:admin ==> role:package:tenant
|
||||||
role:package:tenant ==> role:customer:tenant
|
role:package:tenant ==> role:customer:tenant
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.test.pac;
|
package net.hostsharing.hsadminng.test.pac;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
|
import net.hostsharing.hsadminng.context.ContextBasedTest;
|
||||||
import net.hostsharing.test.JpaAttempt;
|
import net.hostsharing.test.JpaAttempt;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -19,10 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@Import( { Context.class, JpaAttempt.class })
|
@Import( { Context.class, JpaAttempt.class })
|
||||||
class TestPackageRepositoryIntegrationTest {
|
class TestPackageRepositoryIntegrationTest extends ContextBasedTest {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
Context context;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
TestPackageRepository testPackageRepository;
|
TestPackageRepository testPackageRepository;
|
||||||
@ -40,9 +38,10 @@ class TestPackageRepositoryIntegrationTest {
|
|||||||
class FindAllByOptionalNameLike {
|
class FindAllByOptionalNameLike {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void globalAdmin_withoutAssumedRole_canNotViewAnyPackages_becauseThoseGrantsAreNotassumedd() {
|
public void globalAdmin_withoutAssumedRole_canNotViewAnyPackages_becauseThoseGrantsAreNotAssumed() {
|
||||||
// given
|
// given
|
||||||
context.define("superuser-alex@hostsharing.net");
|
// alex is not just global-admin but lso the creating user, thus we use fran
|
||||||
|
context.define("superuser-fran@hostsharing.net");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = testPackageRepository.findAllByOptionalNameLike(null);
|
final var result = testPackageRepository.findAllByOptionalNameLike(null);
|
||||||
@ -52,7 +51,7 @@ class TestPackageRepositoryIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void globalAdmin_withAssumedglobalAdminRole__canNotViewAnyPackages_becauseThoseGrantsAreNotassumedd() {
|
public void globalAdmin_withAssumedglobalAdminRole__canNotViewAnyPackages_becauseThoseGrantsAreNotAssumed() {
|
||||||
given:
|
given:
|
||||||
context.define("superuser-alex@hostsharing.net", "global#global.admin");
|
context.define("superuser-alex@hostsharing.net", "global#global.admin");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user