RBAC Diagram+PostgreSQL Generator #21
@ -16,6 +16,7 @@ import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity
|
|||||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||||
import net.hostsharing.hsadminng.test.cust.TestCustomerEntity;
|
import net.hostsharing.hsadminng.test.cust.TestCustomerEntity;
|
||||||
|
import net.hostsharing.hsadminng.test.dom.TestDomainEntity;
|
||||||
import net.hostsharing.hsadminng.test.pac.TestPackageEntity;
|
import net.hostsharing.hsadminng.test.pac.TestPackageEntity;
|
||||||
|
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
@ -596,6 +597,9 @@ public class RbacView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String getRawTableName() {
|
String getRawTableName() {
|
||||||
|
if ( aliasName.equals("global")) {
|
||||||
|
return "global"; // TODO: maybe we should introduce a GlobalEntity class?
|
||||||
|
}
|
||||||
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -784,6 +788,7 @@ public class RbacView {
|
|||||||
Stream.of(
|
Stream.of(
|
||||||
TestCustomerEntity.class,
|
TestCustomerEntity.class,
|
||||||
TestPackageEntity.class,
|
TestPackageEntity.class,
|
||||||
|
TestDomainEntity.class,
|
||||||
HsOfficePersonEntity.class,
|
HsOfficePersonEntity.class,
|
||||||
HsOfficePartnerEntity.class,
|
HsOfficePartnerEntity.class,
|
||||||
HsOfficePartnerDetailsEntity.class,
|
HsOfficePartnerDetailsEntity.class,
|
||||||
|
@ -41,6 +41,8 @@ public class TestCustomerEntity implements HasUuid {
|
|||||||
.withIdentityView(SQL.projection("prefix"))
|
.withIdentityView(SQL.projection("prefix"))
|
||||||
.withRestrictedViewOrderBy(SQL.expression("reference"))
|
.withRestrictedViewOrderBy(SQL.expression("reference"))
|
||||||
.withUpdatableColumns("reference", "prefix", "adminUserName")
|
.withUpdatableColumns("reference", "prefix", "adminUserName")
|
||||||
|
// TODO: do we want explicit specification of parent-indenpendent insert permissions?
|
||||||
|
// .toRole("global", ADMIN).grantPermission("customer", INSERT)
|
||||||
|
|
||||||
.createRole(OWNER, (with) -> {
|
.createRole(OWNER, (with) -> {
|
||||||
with.owningUser(CREATOR).unassumed();
|
with.owningUser(CREATOR).unassumed();
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
package net.hostsharing.hsadminng.test.dom;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||||
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||||
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||||
|
import net.hostsharing.hsadminng.test.pac.TestPackageEntity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
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.Role.*;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
|
||||||
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "test_domain_rv")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class TestDomainEntity implements HasUuid {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private UUID uuid;
|
||||||
|
|
||||||
|
@Version
|
||||||
|
private int version;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "packageuuid")
|
||||||
|
private TestPackageEntity pac;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
public static RbacView rbac() {
|
||||||
|
return rbacViewFor("domain", TestDomainEntity.class)
|
||||||
|
.withIdentityView(SQL.projection("name"))
|
||||||
|
.withUpdatableColumns("version", "packageUuid", "description")
|
||||||
|
|
||||||
|
.importEntityAlias("package", TestPackageEntity.class,
|
||||||
|
dependsOnColumn("packageUuid"),
|
||||||
|
fetchedBySql("""
|
||||||
|
SELECT * FROM test_package p
|
||||||
|
WHERE p.uuid= ${ref}.packageUuid
|
||||||
|
"""))
|
||||||
|
.toRole("package", ADMIN).grantPermission("domain", INSERT)
|
||||||
|
|
||||||
|
.createRole(OWNER, (with) -> {
|
||||||
|
with.incomingSuperRole("package", ADMIN);
|
||||||
|
with.outgoingSubRole("package", TENANT);
|
||||||
|
with.permission(DELETE);
|
||||||
|
with.permission(UPDATE);
|
||||||
|
})
|
||||||
|
.createSubRole(ADMIN, (with) -> {
|
||||||
|
with.outgoingSubRole("package", TENANT);
|
||||||
|
with.permission(SELECT);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
rbac().generateWithBaseFileName("133-test-domain-rbac");
|
||||||
|
}
|
||||||
|
}
|
@ -337,10 +337,8 @@ grant all privileges on RbacOwnGrantedPermissions_rv to ${HSADMINNG_POSTGRES_RES
|
|||||||
/*
|
/*
|
||||||
Returns all permissions granted to the given user,
|
Returns all permissions granted to the given user,
|
||||||
which are also visible to the current user or assumed roles.
|
which are also visible to the current user or assumed roles.
|
||||||
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
create or replace function grantedPermissions(targetUserUuid uuid)
|
create or replace function grantedPermissionsRaw(targetUserUuid uuid)
|
||||||
returns table(roleUuid uuid, roleName text, permissionUuid uuid, op RbacOp, opTableName varchar(60), objectTable varchar(60), objectIdName varchar, objectUuid uuid)
|
returns table(roleUuid uuid, roleName text, permissionUuid uuid, op RbacOp, opTableName varchar(60), objectTable varchar(60), objectIdName varchar, objectUuid uuid)
|
||||||
returns null on null input
|
returns null on null input
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
@ -375,4 +373,15 @@ begin
|
|||||||
) xp;
|
) xp;
|
||||||
-- @formatter:on
|
-- @formatter:on
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
|
create or replace function grantedPermissions(targetUserUuid uuid)
|
||||||
|
returns table(roleUuid uuid, roleName text, permissionUuid uuid, op RbacOp, opTableName varchar(60), objectTable varchar(60), objectIdName varchar, objectUuid uuid)
|
||||||
|
returns null on null input
|
||||||
|
language sql as $$
|
||||||
|
select * from grantedPermissionsRaw(targetUserUuid)
|
||||||
|
union all
|
||||||
|
select roleUuid, roleName, permissionUuid, 'SELECT'::RbacOp, opTableName, objectTable, objectIdName, objectUuid
|
||||||
|
from grantedPermissionsRaw(targetUserUuid)
|
||||||
|
where op <> 'SELECT'::RbacOp;
|
||||||
|
$$;
|
||||||
--//
|
--//
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
### rbac customer 2024-03-08T13:03:39.397294085
|
### rbac customer 2024-03-09T08:56:16.396142507
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
--liquibase formatted sql
|
--liquibase formatted sql
|
||||||
-- This code generated was by RbacViewPostgresGenerator at 2024-03-08T13:03:39.428165899.
|
-- This code generated was by RbacViewPostgresGenerator at 2024-03-09T08:56:16.421821997.
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset test-customer-rbac-OBJECT:1 endDelimiter:--//
|
--changeset test-customer-rbac-OBJECT:1 endDelimiter:--//
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
### rbac package 2024-03-08T13:03:39.472333368
|
### rbac package 2024-03-09T08:56:16.449886471
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
--liquibase formatted sql
|
--liquibase formatted sql
|
||||||
-- This code generated was by RbacViewPostgresGenerator at 2024-03-08T13:03:39.473061981.
|
-- This code generated was by RbacViewPostgresGenerator at 2024-03-09T08:56:16.450322125.
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset test-package-rbac-OBJECT:1 endDelimiter:--//
|
--changeset test-package-rbac-OBJECT:1 endDelimiter:--//
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
--liquibase formatted sql
|
--liquibase formatted sql
|
||||||
|
-- This code generated was by RbacViewPostgresGenerator at 2024-03-09T08:56:16.469632602.
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset test-domain-rbac-OBJECT:1 endDelimiter:--//
|
--changeset test-domain-rbac-OBJECT:1 endDelimiter:--//
|
||||||
@ -11,91 +12,200 @@ call generateRelatedRbacObject('test_domain');
|
|||||||
--changeset test-domain-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
|
--changeset test-domain-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
call generateRbacRoleDescriptors('testDomain', 'test_domain');
|
call generateRbacRoleDescriptors('testDomain', 'test_domain');
|
||||||
|
|
||||||
create or replace function createTestDomainTenantRoleIfNotExists(domain test_domain)
|
|
||||||
returns uuid
|
|
||||||
returns null on null input
|
|
||||||
language plpgsql as $$
|
|
||||||
declare
|
|
||||||
domainTenantRoleDesc RbacRoleDescriptor;
|
|
||||||
domainTenantRoleUuid uuid;
|
|
||||||
begin
|
|
||||||
domainTenantRoleDesc = testdomainTenant(domain);
|
|
||||||
domainTenantRoleUuid = findRoleId(domainTenantRoleDesc);
|
|
||||||
if domainTenantRoleUuid is not null then
|
|
||||||
return domainTenantRoleUuid;
|
|
||||||
end if;
|
|
||||||
|
|
||||||
return createRoleWithGrants(
|
|
||||||
domainTenantRoleDesc,
|
|
||||||
permissions => array['SELECT'],
|
|
||||||
incomingSuperRoles => array[testdomainAdmin(domain)]
|
|
||||||
);
|
|
||||||
end; $$;
|
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset test-domain-rbac-ROLES-CREATION:1 endDelimiter:--//
|
--changeset test-domain-rbac-insert-trigger:1 endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Creates the roles and their assignments for a new domain for the AFTER INSERT TRIGGER.
|
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create or replace function createRbacRulesForTestDomain()
|
create or replace procedure buildRbacSystemForTestDomain(
|
||||||
|
NEW test_domain
|
||||||
|
)
|
||||||
|
language plpgsql as $$
|
||||||
|
|
||||||
|
declare
|
||||||
|
newPackage test_package;
|
||||||
|
|
||||||
|
begin
|
||||||
|
call enterTriggerForObjectUuid(NEW.uuid);
|
||||||
|
SELECT * FROM test_package p
|
||||||
|
WHERE p.uuid= NEW.packageUuid
|
||||||
|
into newPackage;
|
||||||
|
|
||||||
|
perform createRoleWithGrants(
|
||||||
|
testDomainOwner(NEW),
|
||||||
|
permissions => array['DELETE', 'UPDATE'],
|
||||||
|
incomingSuperRoles => array[testPackageAdmin(newPackage)],
|
||||||
|
outgoingSubRoles => array[testPackageTenant(newPackage)]
|
||||||
|
);
|
||||||
|
|
||||||
|
perform createRoleWithGrants(
|
||||||
|
testDomainAdmin(NEW),
|
||||||
|
permissions => array['SELECT'],
|
||||||
|
incomingSuperRoles => array[testDomainOwner(NEW)],
|
||||||
|
outgoingSubRoles => array[testPackageTenant(newPackage)]
|
||||||
|
);
|
||||||
|
|
||||||
|
call leaveTriggerForObjectUuid(NEW.uuid);
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
/*
|
||||||
|
AFTER INSERT TRIGGER to create the role+grant structure for a new test_domain row.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace function insertTriggerForTestDomain_tf()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $$
|
strict as $$
|
||||||
declare
|
|
||||||
parentPackage test_package;
|
|
||||||
begin
|
begin
|
||||||
if TG_OP <> 'INSERT' then
|
call buildRbacSystemForTestDomain(NEW);
|
||||||
raise exception 'invalid usage of TRIGGER AFTER INSERT';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
call enterTriggerForObjectUuid(NEW.uuid);
|
|
||||||
|
|
||||||
select * from test_package where uuid = NEW.packageUuid into parentPackage;
|
|
||||||
|
|
||||||
-- an owner role is created and assigned to the package's admin group
|
|
||||||
perform createRoleWithGrants(
|
|
||||||
testDomainOwner(NEW),
|
|
||||||
permissions => array['DELETE'],
|
|
||||||
incomingSuperRoles => array[testPackageAdmin(parentPackage)]
|
|
||||||
);
|
|
||||||
|
|
||||||
-- and a domain admin role is created and assigned to the domain owner as well
|
|
||||||
perform createRoleWithGrants(
|
|
||||||
testDomainAdmin(NEW),
|
|
||||||
permissions => array['UPDATE'],
|
|
||||||
incomingSuperRoles => array[testDomainOwner(NEW)],
|
|
||||||
outgoingSubRoles => array[testPackageTenant(parentPackage)]
|
|
||||||
);
|
|
||||||
|
|
||||||
-- a tenent role is only created on demand
|
|
||||||
|
|
||||||
call leaveTriggerForObjectUuid(NEW.uuid);
|
|
||||||
return NEW;
|
return NEW;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
|
create trigger insertTriggerForTestDomain_tg
|
||||||
/*
|
after insert on test_domain
|
||||||
An AFTER INSERT TRIGGER which creates the role structure for a new domain.
|
|
||||||
*/
|
|
||||||
drop trigger if exists createRbacRulesForTestDomain_Trigger on test_domain;
|
|
||||||
create trigger createRbacRulesForTestDomain_Trigger
|
|
||||||
after insert
|
|
||||||
on test_domain
|
|
||||||
for each row
|
for each row
|
||||||
execute procedure createRbacRulesForTestDomain();
|
execute procedure insertTriggerForTestDomain_tf();
|
||||||
|
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset test-domain-rbac-update-trigger:1 endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace procedure updateRbacRulesForTestDomain(
|
||||||
|
OLD test_domain,
|
||||||
|
NEW test_domain
|
||||||
|
)
|
||||||
|
language plpgsql as $$
|
||||||
|
|
||||||
|
declare
|
||||||
|
oldPackage test_package;
|
||||||
|
newPackage test_package;
|
||||||
|
|
||||||
|
begin
|
||||||
|
call enterTriggerForObjectUuid(NEW.uuid);
|
||||||
|
|
||||||
|
SELECT * FROM test_package p
|
||||||
|
WHERE p.uuid= OLD.packageUuid
|
||||||
|
into oldPackage;
|
||||||
|
SELECT * FROM test_package p
|
||||||
|
WHERE p.uuid= NEW.packageUuid
|
||||||
|
into newPackage;
|
||||||
|
|
||||||
|
if NEW.packageUuid <> OLD.packageUuid then
|
||||||
|
|
||||||
|
call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testPackageAdmin(oldPackage));
|
||||||
|
|
||||||
|
call revokeRoleFromRole(testDomainOwner(OLD), testPackageAdmin(oldPackage));
|
||||||
|
call grantRoleToRole(testDomainOwner(NEW), testPackageAdmin(newPackage));
|
||||||
|
|
||||||
|
call revokeRoleFromRole(testPackageTenant(oldPackage), testDomainOwner(OLD));
|
||||||
|
call grantRoleToRole(testPackageTenant(newPackage), testDomainOwner(NEW));
|
||||||
|
|
||||||
|
call revokeRoleFromRole(testPackageTenant(oldPackage), testDomainAdmin(OLD));
|
||||||
|
call grantRoleToRole(testPackageTenant(newPackage), testDomainAdmin(NEW));
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
call leaveTriggerForObjectUuid(NEW.uuid);
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
/*
|
||||||
|
AFTER INSERT TRIGGER to re-wire the grant structure for a new test_domain row.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace function updateTriggerForTestDomain_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql
|
||||||
|
strict as $$
|
||||||
|
begin
|
||||||
|
call updateRbacRulesForTestDomain(OLD, NEW);
|
||||||
|
return NEW;
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
create trigger updateTriggerForTestDomain_tg
|
||||||
|
after update on test_domain
|
||||||
|
for each row
|
||||||
|
execute procedure updateTriggerForTestDomain_tf();
|
||||||
|
|
||||||
|
--//
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset test-domain-rbac-INSERT:1 endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
Creates INSERT INTO test_domain permissions for the related test_package rows.
|
||||||
|
*/
|
||||||
|
do language plpgsql $$
|
||||||
|
declare
|
||||||
|
row test_package;
|
||||||
|
permissionUuid uuid;
|
||||||
|
roleUuid uuid;
|
||||||
|
begin
|
||||||
|
call defineContext('create INSERT INTO test_domain permissions for the related test_package rows');
|
||||||
|
|
||||||
|
FOR row IN SELECT * FROM test_package
|
||||||
|
LOOP
|
||||||
|
roleUuid := findRoleId(testPackageAdmin(row));
|
||||||
|
permissionUuid := createPermission(row.uuid, 'INSERT', 'test_domain');
|
||||||
|
call grantPermissionToRole(roleUuid, permissionUuid);
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Adds test_domain INSERT permission to specified role of new test_package rows.
|
||||||
|
*/
|
||||||
|
create or replace function test_domain_test_package_insert_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql
|
||||||
|
strict as $$
|
||||||
|
begin
|
||||||
|
call grantPermissionToRole(
|
||||||
|
testPackageAdmin(NEW),
|
||||||
|
createPermission(NEW.uuid, 'INSERT', 'test_domain'));
|
||||||
|
return NEW;
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
create trigger test_domain_test_package_insert_tg
|
||||||
|
after insert on test_package
|
||||||
|
for each row
|
||||||
|
execute procedure test_domain_test_package_insert_tf();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if the user or assumed roles are allowed to insert a row to test_domain.
|
||||||
|
*/
|
||||||
|
create or replace function test_domain_insert_permission_missing_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql as $$
|
||||||
|
begin
|
||||||
|
raise exception '[403] insert into test_domain not allowed for current subjects % (%)',
|
||||||
|
currentSubjects(), currentSubjectsUuids();
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
create trigger test_domain_insert_permission_check_tg
|
||||||
|
before insert on test_domain
|
||||||
|
for each row
|
||||||
|
when ( not hasInsertPermission(NEW.packageUuid, 'INSERT', 'test_domain') )
|
||||||
|
execute procedure test_domain_insert_permission_missing_tf();
|
||||||
|
|
||||||
|
--//
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset test-domain-rbac-IDENTITY-VIEW:1 endDelimiter:--//
|
--changeset test-domain-rbac-IDENTITY-VIEW:1 endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
call generateRbacIdentityView('test_domain', $idName$
|
call generateRbacIdentityView('test_domain', $idName$
|
||||||
target.name
|
name
|
||||||
$idName$);
|
$idName$);
|
||||||
--//
|
--//
|
||||||
|
|
||||||
@ -103,15 +213,13 @@ call generateRbacIdentityView('test_domain', $idName$
|
|||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset test-domain-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
|
--changeset test-domain-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
call generateRbacRestrictedView('test_domain',
|
||||||
/*
|
'name',
|
||||||
Creates a view to the customer main table which maps the identifying name
|
$updates$
|
||||||
(in this case, the prefix) to the objectUuid.
|
version = new.version,
|
||||||
*/
|
packageUuid = new.packageUuid,
|
||||||
drop view if exists test_domain_rv;
|
description = new.description
|
||||||
create or replace view test_domain_rv as
|
$updates$);
|
||||||
select target.*
|
|
||||||
from test_domain as target
|
|
||||||
where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('SELECT', 'domain', currentSubjectsUuids()));
|
|
||||||
grant all privileges on test_domain_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
|
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,151 +42,3 @@ subgraph hsOfficeRelationship
|
|||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
if TG_OP = 'INSERT' then
|
|
||||||
|
|
||||||
-- the owner role with full access for admins of the relAnchor global admins
|
|
||||||
ownerRole = createRole(
|
|
||||||
hsOfficeRelationshipOwner(NEW),
|
|
||||||
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']),
|
|
||||||
beneathRoles(array[
|
|
||||||
globalAdmin(),
|
|
||||||
hsOfficePersonAdmin(newRelAnchor)])
|
|
||||||
);
|
|
||||||
|
|
||||||
-- the admin role with full access for the owner
|
|
||||||
adminRole = createRole(
|
|
||||||
hsOfficeRelationshipAdmin(NEW),
|
|
||||||
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']),
|
|
||||||
beneathRole(ownerRole)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- the tenant role for those related users who can view the data
|
|
||||||
perform createRole(
|
|
||||||
hsOfficeRelationshipTenant,
|
|
||||||
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']),
|
|
||||||
beneathRoles(array[
|
|
||||||
hsOfficePersonAdmin(newRelAnchor),
|
|
||||||
hsOfficePersonAdmin(newRelHolder),
|
|
||||||
hsOfficeContactAdmin(newContact)]),
|
|
||||||
withSubRoles(array[
|
|
||||||
hsOfficePersonTenant(newRelAnchor),
|
|
||||||
hsOfficePersonTenant(newRelHolder),
|
|
||||||
hsOfficeContactTenant(newContact)])
|
|
||||||
);
|
|
||||||
|
|
||||||
-- anchor and holder admin roles need each others tenant role
|
|
||||||
-- to be able to see the joined relationship
|
|
||||||
call grantRoleToRole(hsOfficePersonTenant(newRelAnchor), hsOfficePersonAdmin(newRelHolder));
|
|
||||||
call grantRoleToRole(hsOfficePersonTenant(newRelHolder), hsOfficePersonAdmin(newRelAnchor));
|
|
||||||
call grantRoleToRoleIfNotNull(hsOfficePersonTenant(newRelHolder), hsOfficeContactAdmin(newContact));
|
|
||||||
|
|
||||||
elsif TG_OP = 'UPDATE' then
|
|
||||||
|
|
||||||
if OLD.contactUuid <> NEW.contactUuid then
|
|
||||||
-- nothing but the contact can be updated,
|
|
||||||
-- in other cases, a new relationship needs to be created and the old updated
|
|
||||||
|
|
||||||
select * from hs_office_contact as c where c.uuid = OLD.contactUuid into oldContact;
|
|
||||||
|
|
||||||
call revokeRoleFromRole( hsOfficeRelationshipTenant, hsOfficeContactAdmin(oldContact) );
|
|
||||||
call grantRoleToRole( hsOfficeRelationshipTenant, hsOfficeContactAdmin(newContact) );
|
|
||||||
|
|
||||||
call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficeRelationshipTenant );
|
|
||||||
call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficeRelationshipTenant );
|
|
||||||
end if;
|
|
||||||
else
|
|
||||||
raise exception 'invalid usage of TRIGGER';
|
|
||||||
end if;
|
|
||||||
|
|
||||||
return NEW;
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
/*
|
|
||||||
An AFTER INSERT TRIGGER which creates the role structure for a new customer.
|
|
||||||
*/
|
|
||||||
create trigger createRbacRolesForHsOfficeRelationship_Trigger
|
|
||||||
after insert
|
|
||||||
on hs_office_relationship
|
|
||||||
for each row
|
|
||||||
execute procedure hsOfficeRelationshipRbacRolesTrigger();
|
|
||||||
|
|
||||||
/*
|
|
||||||
An AFTER UPDATE TRIGGER which updates the role structure of a customer.
|
|
||||||
*/
|
|
||||||
create trigger updateRbacRolesForHsOfficeRelationship_Trigger
|
|
||||||
after update
|
|
||||||
on hs_office_relationship
|
|
||||||
for each row
|
|
||||||
execute procedure hsOfficeRelationshipRbacRolesTrigger();
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset hs-office-relationship-rbac-IDENTITY-VIEW:1 endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
call generateRbacIdentityView('hs_office_relationship', $idName$
|
|
||||||
(select idName from hs_office_person_iv p where p.uuid = target.relAnchorUuid)
|
|
||||||
|| '-with-' || target.relType || '-' ||
|
|
||||||
(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)
|
|
||||||
$idName$);
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset hs-office-relationship-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
call generateRbacRestrictedView('hs_office_relationship',
|
|
||||||
'(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)',
|
|
||||||
$updates$
|
|
||||||
contactUuid = new.contactUuid
|
|
||||||
$updates$);
|
|
||||||
--//
|
|
||||||
|
|
||||||
-- TODO: exception if one tries to amend any other column
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset hs-office-relationship-rbac-NEW-RELATHIONSHIP:1 endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
/*
|
|
||||||
Creates a global permission for new-relationship and assigns it to the hostsharing admins role.
|
|
||||||
*/
|
|
||||||
do language plpgsql $$
|
|
||||||
declare
|
|
||||||
addCustomerPermissions uuid[];
|
|
||||||
globalObjectUuid uuid;
|
|
||||||
globalAdminRoleUuid uuid ;
|
|
||||||
begin
|
|
||||||
call defineContext('granting global new-relationship permission to global admin role', null, null, null);
|
|
||||||
|
|
||||||
globalAdminRoleUuid := findRoleId(globalAdmin());
|
|
||||||
globalObjectUuid := (select uuid from global);
|
|
||||||
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-relationship']);
|
|
||||||
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
|
|
||||||
end;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
|
|
||||||
*/
|
|
||||||
create or replace function addHsOfficeRelationshipNotAllowedForCurrentSubjects()
|
|
||||||
returns trigger
|
|
||||||
language PLPGSQL
|
|
||||||
as $$
|
|
||||||
begin
|
|
||||||
raise exception '[403] new-relationship not permitted for %',
|
|
||||||
array_to_string(currentSubjects(), ';', 'null');
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Checks if the user or assumed roles are allowed to create a new customer.
|
|
||||||
*/
|
|
||||||
create trigger hs_office_relationship_insert_trigger
|
|
||||||
before insert
|
|
||||||
on hs_office_relationship
|
|
||||||
for each row
|
|
||||||
-- TODO.spec: who is allowed to create new relationships
|
|
||||||
when ( not hasAssumedRole() )
|
|
||||||
execute procedure addHsOfficeRelationshipNotAllowedForCurrentSubjects();
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ public class ArchitectureTest {
|
|||||||
"..test",
|
"..test",
|
||||||
"..test.cust",
|
"..test.cust",
|
||||||
"..test.pac",
|
"..test.pac",
|
||||||
|
"..test.dom",
|
||||||
"..context",
|
"..context",
|
||||||
"..generated..",
|
"..generated..",
|
||||||
"..persistence..",
|
"..persistence..",
|
||||||
|
@ -62,6 +62,7 @@ class RbacGrantsDiagramServiceIntegrationTest extends ContextBasedTestWithCleanu
|
|||||||
|
|
||||||
role:test_domain#xxx00-aaaa.admin --> role:test_package#xxx00.tenant
|
role:test_domain#xxx00-aaaa.admin --> role:test_package#xxx00.tenant
|
||||||
role:test_domain#xxx00-aaaa.owner --> role:test_domain#xxx00-aaaa.admin
|
role:test_domain#xxx00-aaaa.owner --> role:test_domain#xxx00-aaaa.admin
|
||||||
|
role:test_domain#xxx00-aaaa.owner --> role:test_package#xxx00.tenant
|
||||||
role:test_package#xxx00.tenant --> role:test_customer#xxx.tenant
|
role:test_package#xxx00.tenant --> role:test_customer#xxx.tenant
|
||||||
""".trim());
|
""".trim());
|
||||||
}
|
}
|
||||||
@ -75,10 +76,12 @@ class RbacGrantsDiagramServiceIntegrationTest extends ContextBasedTestWithCleanu
|
|||||||
flowchart TB
|
flowchart TB
|
||||||
|
|
||||||
role:test_customer#xxx.tenant --> perm:SELECT:on:test_customer#xxx
|
role:test_customer#xxx.tenant --> perm:SELECT:on:test_customer#xxx
|
||||||
role:test_domain#xxx00-aaaa.admin --> perm:UPDATE:on:test_domain#xxx00-aaaa
|
role:test_domain#xxx00-aaaa.admin --> perm:SELECT:on:test_domain#xxx00-aaaa
|
||||||
role:test_domain#xxx00-aaaa.admin --> role:test_package#xxx00.tenant
|
role:test_domain#xxx00-aaaa.admin --> role:test_package#xxx00.tenant
|
||||||
role:test_domain#xxx00-aaaa.owner --> perm:DELETE:on:test_domain#xxx00-aaaa
|
role:test_domain#xxx00-aaaa.owner --> perm:DELETE:on:test_domain#xxx00-aaaa
|
||||||
|
role:test_domain#xxx00-aaaa.owner --> perm:UPDATE:on:test_domain#xxx00-aaaa
|
||||||
role:test_domain#xxx00-aaaa.owner --> role:test_domain#xxx00-aaaa.admin
|
role:test_domain#xxx00-aaaa.owner --> role:test_domain#xxx00-aaaa.admin
|
||||||
|
role:test_domain#xxx00-aaaa.owner --> role:test_package#xxx00.tenant
|
||||||
role:test_package#xxx00.tenant --> perm:SELECT:on:test_package#xxx00
|
role:test_package#xxx00.tenant --> perm:SELECT:on:test_package#xxx00
|
||||||
role:test_package#xxx00.tenant --> role:test_customer#xxx.tenant
|
role:test_package#xxx00.tenant --> role:test_customer#xxx.tenant
|
||||||
""".trim());
|
""".trim());
|
||||||
|
@ -295,7 +295,8 @@ class RbacUserControllerAcceptanceTest {
|
|||||||
hasEntry("roleName", "test_domain#yyy00-aaaa.owner"),
|
hasEntry("roleName", "test_domain#yyy00-aaaa.owner"),
|
||||||
hasEntry("op", "DELETE"))
|
hasEntry("op", "DELETE"))
|
||||||
))
|
))
|
||||||
.body("size()", is(6));
|
// actual content tested in integration test, so this is enough for here:
|
||||||
|
.body("size()", greaterThanOrEqualTo(6));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,7 +326,8 @@ class RbacUserControllerAcceptanceTest {
|
|||||||
hasEntry("roleName", "test_domain#yyy00-aaaa.owner"),
|
hasEntry("roleName", "test_domain#yyy00-aaaa.owner"),
|
||||||
hasEntry("op", "DELETE"))
|
hasEntry("op", "DELETE"))
|
||||||
))
|
))
|
||||||
.body("size()", is(6));
|
// actual content tested in integration test, so this is enough for here:
|
||||||
|
.body("size()", greaterThanOrEqualTo(6));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,7 +356,8 @@ class RbacUserControllerAcceptanceTest {
|
|||||||
hasEntry("roleName", "test_domain#yyy00-aaaa.owner"),
|
hasEntry("roleName", "test_domain#yyy00-aaaa.owner"),
|
||||||
hasEntry("op", "DELETE"))
|
hasEntry("op", "DELETE"))
|
||||||
))
|
))
|
||||||
.body("size()", is(6));
|
// actual content tested in integration test, so this is enough for here:
|
||||||
|
.body("size()", greaterThanOrEqualTo(6));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ 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.Comparator.comparing;
|
||||||
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;
|
||||||
|
|
||||||
@ -181,8 +182,6 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
|
|
||||||
private static final String[] ALL_USER_PERMISSIONS = Array.of(
|
private static final String[] ALL_USER_PERMISSIONS = Array.of(
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
"global#global.admin -> global#global: add-customer",
|
|
||||||
|
|
||||||
"test_customer#xxx.admin -> test_customer#xxx: SELECT",
|
"test_customer#xxx.admin -> test_customer#xxx: SELECT",
|
||||||
"test_customer#xxx.owner -> test_customer#xxx: DELETE",
|
"test_customer#xxx.owner -> test_customer#xxx: DELETE",
|
||||||
"test_customer#xxx.tenant -> test_customer#xxx: SELECT",
|
"test_customer#xxx.tenant -> test_customer#xxx: SELECT",
|
||||||
@ -233,7 +232,9 @@ class RbacUserRepositoryIntegrationTest extends ContextBasedTest {
|
|||||||
context("superuser-alex@hostsharing.net");
|
context("superuser-alex@hostsharing.net");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = rbacUserRepository.findPermissionsOfUserByUuid(userUUID("superuser-alex@hostsharing.net"));
|
final var result = rbacUserRepository.findPermissionsOfUserByUuid(userUUID("superuser-fran@hostsharing.net"))
|
||||||
|
.stream().filter(p -> p.getObjectTable().contains("test_"))
|
||||||
|
.sorted(comparing(RbacUserPermission::toString)).toList();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
allTheseRbacPermissionsAreReturned(result, ALL_USER_PERMISSIONS);
|
allTheseRbacPermissionsAreReturned(result, ALL_USER_PERMISSIONS);
|
||||||
|
Loading…
Reference in New Issue
Block a user