1
0

implements table hs_office_relationship to HsOfficeRelationshipController

This commit is contained in:
Michael Hoennig
2022-09-26 10:57:22 +02:00
parent 49ba141ca5
commit 956ee581c6
43 changed files with 2292 additions and 102 deletions

View File

@ -20,6 +20,10 @@ create or replace function assertReferenceType(argument varchar, referenceId uui
declare
actualType ReferenceType;
begin
if referenceId is null then
raise exception '% must be a % and not null', argument, expectedType;
end if;
actualType = (select type from RbacReference where uuid = referenceId);
if (actualType <> expectedType) then
raise exception '% must reference a %, but got a %', argument, expectedType, actualType;
@ -608,21 +612,29 @@ begin
into RbacGrants (ascendantuuid, descendantUuid, assumed)
values (superRoleId, subRoleId, doAssume)
on conflict do nothing; -- allow granting multiple times
delete from RbacGrants where ascendantUuid = superRoleId and descendantUuid = subRoleId;
insert
into RbacGrants (ascendantuuid, descendantUuid, assumed)
values (superRoleId, subRoleId, doAssume); -- allow granting multiple times
end; $$;
create or replace procedure revokeRoleFromRole(subRoleId uuid, superRoleId uuid)
create or replace procedure grantRoleToRoleIfNotNull(subRole RbacRoleDescriptor, superRole RbacRoleDescriptor, doAssume bool = true)
language plpgsql as $$
declare
superRoleId uuid;
subRoleId uuid;
begin
superRoleId := findRoleId(superRole);
if ( subRoleId is null ) then return; end if;
subRoleId := findRoleId(subRole);
perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole');
perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole');
if (isGranted(superRoleId, subRoleId)) then
delete from RbacGrants where ascendantUuid = superRoleId and descendantUuid = subRoleId;
if isGranted(subRoleId, superRoleId) then
raise exception '[400] Cyclic role grant detected between % and %', subRoleId, superRoleId;
end if;
insert
into RbacGrants (ascendantuuid, descendantUuid, assumed)
values (superRoleId, subRoleId, doAssume)
on conflict do nothing; -- allow granting multiple times
end; $$;
create or replace procedure revokeRoleFromRole(subRole RbacRoleDescriptor, superRole RbacRoleDescriptor)

View File

@ -61,7 +61,7 @@ create or replace view rbacgrants_ev as
x.descendingIdName as descendantIdName,
x.grantedByRoleUuid,
x.ascendantUuid as ascendantUuid,
x.descendantUuid as descenantUuid,
x.descendantUuid as descendantUuid,
x.assumed
from (
select g.uuid as grantUuid,

View File

@ -51,7 +51,9 @@ declare
begin
foreach superRoleDescriptor in array roleDescriptors
loop
superRoleUuids := superRoleUuids || getRoleId(superRoleDescriptor, 'fail');
if superRoleDescriptor is not null then
superRoleUuids := superRoleUuids || getRoleId(superRoleDescriptor, 'fail');
end if;
end loop;
return row (superRoleUuids)::RbacSuperRoles;
@ -96,7 +98,6 @@ create type RbacSubRoles as
roleUuids uuid[]
);
-- drop FUNCTION beingItselfA(roleUuid uuid)
create or replace function beingItselfA(roleUuid uuid)
returns RbacSubRoles
language plpgsql
@ -105,7 +106,6 @@ begin
return row (array [roleUuid]::uuid[])::RbacSubRoles;
end; $$;
-- drop FUNCTION beingItselfA(roleDescriptor RbacRoleDescriptor)
create or replace function beingItselfA(roleDescriptor RbacRoleDescriptor)
returns RbacSubRoles
language plpgsql
@ -124,7 +124,9 @@ declare
begin
foreach subRoleDescriptor in array roleDescriptors
loop
subRoleUuids := subRoleUuids || getRoleId(subRoleDescriptor, 'fail');
if subRoleDescriptor is not null then
subRoleUuids := subRoleUuids || getRoleId(subRoleDescriptor, 'fail');
end if;
end loop;
return row (subRoleUuids)::RbacSubRoles;

View File

@ -127,6 +127,7 @@ begin
/*
Creates a restricted view based on the 'view' permission of the current subject.
*/
-- TODO.refa: hoist `select queryAccessibleObjectUuidsOfSubjectIds(...)` into WITH CTE for performance
sql := format($sql$
set session session authorization default;
create view %1$s_rv as

View File

@ -17,7 +17,7 @@ begin
currentTask = 'creating RBAC test contact ' || contLabel;
execute format('set local hsadminng.currentTask to %L', currentTask);
emailAddr = 'customer-admin@' || cleanIdentifier(contLabel) || '.example.com';
emailAddr = 'contact-admin@' || cleanIdentifier(contLabel) || '.example.com';
call defineContext(currentTask);
perform createRbacUser(emailAddr);
call defineContext(currentTask, null, emailAddr);
@ -64,6 +64,7 @@ do language plpgsql $$
call createHsOfficeContactTestData('forth contact');
call createHsOfficeContactTestData('fifth contact');
call createHsOfficeContactTestData('sixth contact');
call createHsOfficeContactTestData('seventh contact');
call createHsOfficeContactTestData('eighth contact');
call createHsOfficeContactTestData('ninth contact');
call createHsOfficeContactTestData('tenth contact');

View File

@ -60,10 +60,12 @@ end; $$;
do language plpgsql $$
begin
call createHsOfficePersonTestData('LEGAL', 'First Impressions GmbH');
call createHsOfficePersonTestData('NATURAL', null, 'Peter', 'Smith');
call createHsOfficePersonTestData('NATURAL', null, 'Smith', 'Peter');
call createHsOfficePersonTestData('LEGAL', 'Rockshop e.K.', 'Sandra', 'Miller');
call createHsOfficePersonTestData('SOLE_REPRESENTATION', 'Ostfriesische Kuhhandel OHG');
call createHsOfficePersonTestData('JOINT_REPRESENTATION', 'Erben Bessler', 'Mel', 'Bessler');
call createHsOfficePersonTestData('NATURAL', null, 'Bessler', 'Anita');
call createHsOfficePersonTestData('NATURAL', null, 'Winkler', 'Paul');
end;
$$;
--//

View File

@ -0,0 +1,19 @@
--liquibase formatted sql
-- ============================================================================
--changeset hs-office-relationship-MAIN-TABLE:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TYPE HsOfficeRelationshipType AS ENUM ('SOLE_AGENT', 'JOINT_AGENT', 'CO_OWNER', 'ACCOUNTING_CONTACT', 'TECHNICAL_CONTACT');
CREATE CAST (character varying as HsOfficeRelationshipType) WITH INOUT AS IMPLICIT;
create table if not exists hs_office_relationship
(
uuid uuid unique references RbacObject (uuid) initially deferred, -- on delete cascade
relAnchorUuid uuid not null references hs_office_person(uuid),
relHolderUuid uuid not null references hs_office_person(uuid),
contactUuid uuid references hs_office_contact(uuid),
relType HsOfficeRelationshipType not null
);
--//

View File

@ -0,0 +1,192 @@
--liquibase formatted sql
-- ============================================================================
--changeset hs-office-relationship-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_relationship');
--//
-- ============================================================================
--changeset hs-office-relationship-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsOfficeRelationship', 'hs_office_relationship');
--//
-- ============================================================================
--changeset hs-office-relationship-rbac-ROLES-CREATION:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates and updates the roles and their assignments for relationship entities.
*/
create or replace function hsOfficeRelationshipRbacRolesTrigger()
returns trigger
language plpgsql
strict as $$
declare
hsOfficeRelationshipTenant RbacRoleDescriptor;
ownerRole uuid;
adminRole uuid;
newRelAnchor hs_office_person;
newRelHolder hs_office_person;
oldContact hs_office_contact;
newContact hs_office_contact;
begin
hsOfficeRelationshipTenant := hsOfficeRelationshipTenant(NEW);
select * from hs_office_person as p where p.uuid = NEW.relAnchorUuid into newRelAnchor;
select * from hs_office_person as p where p.uuid = NEW.relHolderUuid into newRelHolder;
select * from hs_office_contact as c where c.uuid = NEW.contactUuid into newContact;
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();
--//

View File

@ -0,0 +1,81 @@
--liquibase formatted sql
-- ============================================================================
--changeset hs-office-relationship-TEST-DATA-GENERATOR:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a single relationship test record.
*/
create or replace procedure createHsOfficeRelationshipTestData(
anchorPersonTradeName varchar,
holderPersonFamilyName varchar,
relationshipType HsOfficeRelationshipType,
contactLabel varchar)
language plpgsql as $$
declare
currentTask varchar;
idName varchar;
anchorPerson hs_office_person;
holderPerson hs_office_person;
contact hs_office_contact;
begin
idName := cleanIdentifier( anchorPersonTradeName|| '-' || holderPersonFamilyName);
currentTask := 'creating RBAC test relationship ' || idName;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
execute format('set local hsadminng.currentTask to %L', currentTask);
select p.* from hs_office_person p where p.tradeName = anchorPersonTradeName into anchorPerson;
select p.* from hs_office_person p where p.familyName = holderPersonFamilyName into holderPerson;
select c.* from hs_office_contact c where c.label = contactLabel into contact;
raise notice 'creating test relationship: %', idName;
raise notice '- using anchor person (%): %', anchorPerson.uuid, anchorPerson;
raise notice '- using holder person (%): %', holderPerson.uuid, holderPerson;
raise notice '- using contact (%): %', contact.uuid, contact;
insert
into hs_office_relationship (uuid, relanchoruuid, relholderuuid, reltype, contactUuid)
values (uuid_generate_v4(), anchorPerson.uuid, holderPerson.uuid, relationshipType, contact.uuid);
end; $$;
--//
/*
Creates a range of test relationship for mass data generation.
*/
create or replace procedure createHsOfficeRelationshipTestData(
startCount integer, -- count of auto generated rows before the run
endCount integer -- count of auto generated rows after the run
)
language plpgsql as $$
declare
person hs_office_person;
contact hs_office_contact;
begin
for t in startCount..endCount
loop
select p.* from hs_office_person p where tradeName = intToVarChar(t, 4) into person;
select c.* from hs_office_contact c where c.label = intToVarChar(t, 4) || '#' || t into contact;
call createHsOfficeRelationshipTestData(person.uuid, contact.uuid, 'SOLE_AGENT');
commit;
end loop;
end; $$;
--//
-- ============================================================================
--changeset hs-office-relationship-TEST-DATA-GENERATION:1 context=dev,tc endDelimiter:--//
-- ----------------------------------------------------------------------------
do language plpgsql $$
begin
call createHsOfficeRelationshipTestData('First Impressions GmbH', 'Smith', 'SOLE_AGENT', 'first contact');
call createHsOfficeRelationshipTestData('Rockshop e.K.', 'Smith', 'SOLE_AGENT', 'second contact');
call createHsOfficeRelationshipTestData('Ostfriesische Kuhhandel OHG', 'Smith', 'SOLE_AGENT', 'third contact');
end;
$$;
--//

View File

@ -65,3 +65,9 @@ databaseChangeLog:
file: db/changelog/223-hs-office-partner-rbac.sql
- include:
file: db/changelog/228-hs-office-partner-test-data.sql
- include:
file: db/changelog/230-hs-office-relationship.sql
- include:
file: db/changelog/233-hs-office-relationship-rbac.sql
- include:
file: db/changelog/238-hs-office-relationship-test-data.sql