From 0b60b9f0fffed35a312af74e37924b1c9e595f00 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 12 Oct 2022 15:48:56 +0200 Subject: [PATCH] introduces agent+guest role for role-system around debitor+partner --- doc/rbac.md | 41 ++- .../hsadminng/rbac/rbacrole/RbacRoleType.java | 2 +- .../resources/db/changelog/050-rbac-base.sql | 2 +- .../db/changelog/057-rbac-role-builder.sql | 260 +++--------------- .../db/changelog/058-rbac-generators.sql | 16 ++ .../db/changelog/113-test-customer-rbac.sql | 18 +- .../db/changelog/123-test-package-rbac.sql | 26 +- .../db/changelog/133-test-domain-rbac.sql | 26 +- .../changelog/203-hs-office-contact-rbac.sql | 35 +-- .../changelog/213-hs-office-person-rbac.sql | 34 ++- .../changelog/223-hs-office-partner-rbac.md | 66 +++++ .../changelog/223-hs-office-partner-rbac.sql | 76 +++-- .../233-hs-office-relationship-rbac.md | 192 +++++++++++++ .../233-hs-office-relationship-rbac.sql | 32 +-- .../243-hs-office-bankaccount-rbac.md | 59 ++-- .../243-hs-office-bankaccount-rbac.sql | 39 ++- .../changelog/273-hs-office-debitor-rbac.md | 239 +++++++++++++--- .../changelog/273-hs-office-debitor-rbac.sql | 89 +++--- .../hsadminng/StringTemplater.java | 27 ++ ...eBankAccountRepositoryIntegrationTest.java | 11 +- ...fficeContactRepositoryIntegrationTest.java | 6 +- ...fficeDebitorRepositoryIntegrationTest.java | 65 +++-- ...fficePartnerRepositoryIntegrationTest.java | 52 +++- ...OfficePersonRepositoryIntegrationTest.java | 6 +- ...RelationshipRepositoryIntegrationTest.java | 3 +- 25 files changed, 899 insertions(+), 523 deletions(-) create mode 100644 src/main/resources/db/changelog/223-hs-office-partner-rbac.md create mode 100644 src/main/resources/db/changelog/233-hs-office-relationship-rbac.md create mode 100644 src/test/java/net/hostsharing/hsadminng/StringTemplater.java diff --git a/doc/rbac.md b/doc/rbac.md index c69229bc..06a6ee7e 100644 --- a/doc/rbac.md +++ b/doc/rbac.md @@ -220,19 +220,52 @@ By this, all roles ob sub-objects, which are assigned to the 'admin' role, are a The admin-role is granted to a role of those subjects who manage the business object. E.g. a 'package' is manged by the admin of the customer. -Whoever has the admin-role assigned, do everything with the related business-object, including deleting (or deactivating) it. +Whoever has the admin-role assigned, can usually edit the related business-object but not deleting (or deactivating) it. -In most cases, the permissions to the 'view' operation is granted through the 'tenant' role. -By this, all roles ob sub-objects, which are assigned to the 'tenent' role, are also granted to the 'admin'. +The admin-role also comprises lesser roles, through which the view-permission is granted. +#### agent + +The agent-role is not used in the examples of this document, because it's for more complex cases. +It's usually granted to those roles and users who represent the related business-object, but are not allowed to edit it. + +Other than the tenant-role, it usually offers broader visibility of sub-business-objects (joined entities). +E.g. a package-admin is allowed to see the related debitor-business-object, +but not its banking data. #### tenant -The tenant-role is granted to everybody who needs to be able to view the business-object. +The tenant-role is granted to everybody who needs to be able to view the business-object and (probably some) related business-objects. Usually all owners, admins and tenants of sub-objects get this role granted. Some business-objects only have very limited data directly in the main business-object and store more sensitive data in special sub-objects (e.g. 'customer-details') to which tenants of sub-objects of the main-object (e.g. package admins) do not get view permission. +#### guest + +Like the agent-role, the guest-role too is not used in the examples of this document, because it's for more complex cases. + +If the guest-role exists, the view-permission is granted to it, instead of to the tenant-role. +Other than the tenant-role, the guest-roles does never grant any roles of related objects. + +Also, if the guest-role exists, the tenant-role receives the view-permission through the guest-role. + + +### Referenced Business Objects and Role-Depreciation + +A general rule is, if one business object *origin* references another object *target* (in other words: one database table joins another table), +**and** a role for *origin* needs also access to *target*, +then usually the *target* role is granted to the *origin* role which is one level lower. + +E.g. the admin-role of the *origin* object gets granted the agent-role (or, if it does not exist, then the tenant-role) of the *target* object. + +Following this rule, also implies, that the number of indirections to which visibility can be granted is limited. +The admin-role of one object could be granted visibility to another object through at maximum 3 joins (agent->tenant->guest). + +But not in all cases role-depreciation takes place. +E.g. often a tenant-role is granted another tenant-role, +because it should be again allowed to view sub-objects. +The same for the agent-role, often it is granted another agent-role. + ## Example Users, Roles, Permissions and Business-Objects diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java index 51d58bc6..153344fa 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleType.java @@ -1,5 +1,5 @@ package net.hostsharing.hsadminng.rbac.rbacrole; public enum RbacRoleType { - owner, admin, tenant + owner, admin, agent, tenant, guest } diff --git a/src/main/resources/db/changelog/050-rbac-base.sql b/src/main/resources/db/changelog/050-rbac-base.sql index ac09e7b8..04e18c4c 100644 --- a/src/main/resources/db/changelog/050-rbac-base.sql +++ b/src/main/resources/db/changelog/050-rbac-base.sql @@ -186,7 +186,7 @@ end; $$; */ -create type RbacRoleType as enum ('owner', 'admin', 'tenant'); +create type RbacRoleType as enum ('owner', 'admin', 'agent', 'tenant', 'guest'); create table RbacRole ( diff --git a/src/main/resources/db/changelog/057-rbac-role-builder.sql b/src/main/resources/db/changelog/057-rbac-role-builder.sql index 32e740ab..e95111b5 100644 --- a/src/main/resources/db/changelog/057-rbac-role-builder.sql +++ b/src/main/resources/db/changelog/057-rbac-role-builder.sql @@ -2,47 +2,19 @@ -- ============================================================================ -- PERMISSIONS ---changeset rbac-role-builder-permissions:1 endDelimiter:--// +--changeset rbac-role-builder-to-uuids:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -/* - */ - -create type RbacPermissions as -( - permissionUuids uuid[] -); - -create or replace function grantingPermissions(forObjectUuid uuid, permitOps RbacOp[]) - returns RbacPermissions +create or replace function toPermissionUuids(forObjectUuid uuid, permitOps RbacOp[]) + returns uuid[] language plpgsql strict as $$ begin - return row (createPermissions(forObjectUuid, permitOps))::RbacPermissions; + return createPermissions(forObjectUuid, permitOps); end; $$; -create or replace function withoutPermissions() - returns RbacPermissions - language plpgsql - strict as $$ -begin - return row (array []::uuid[]); -end; $$; - ---// - ---changeset rbac-role-builder-super-roles:1 endDelimiter:--// - -/* - - */ -create type RbacSuperRoles as -( - roleUuids uuid[] -); - -create or replace function beneathRoles(roleDescriptors RbacRoleDescriptor[]) - returns RbacSuperRoles +create or replace function toRoleUuids(roleDescriptors RbacRoleDescriptor[]) + returns uuid[] language plpgsql strict as $$ declare @@ -56,145 +28,22 @@ begin end if; end loop; - return row (superRoleUuids)::RbacSuperRoles; + return superRoleUuids; end; $$; -create or replace function beneathRole(roleDescriptor RbacRoleDescriptor) - returns RbacSuperRoles - language plpgsql - strict as $$ -begin - return beneathRoles(array [roleDescriptor]); -end; $$; - -create or replace function beneathRole(roleUuid uuid) - returns RbacSuperRoles - language plpgsql - strict as $$ -begin - return row (array [roleUuid]::uuid[])::RbacSuperRoles; -end; $$; - -create or replace function asTopLevelRole() - returns RbacSuperRoles - language plpgsql - strict as $$ -begin - return row (array []::uuid[])::RbacSuperRoles; -end; $$; - ---// - --- ================================================================= --- SUB ROLES ---changeset rbac-role-builder-sub-roles:1 endDelimiter:--// --- ----------------------------------------------------------------- - -/* - - */ -create type RbacSubRoles as -( - roleUuids uuid[] -); - -create or replace function beingItselfA(roleUuid uuid) - returns RbacSubRoles - language plpgsql - strict as $$ -begin - return row (array [roleUuid]::uuid[])::RbacSubRoles; -end; $$; - -create or replace function beingItselfA(roleDescriptor RbacRoleDescriptor) - returns RbacSubRoles - language plpgsql - strict as $$ -begin - return beingItselfA(getRoleId(roleDescriptor, 'fail')); -end; $$; - -create or replace function withSubRoles(roleDescriptors RbacRoleDescriptor[]) - returns RbacSubRoles - language plpgsql - strict as $$ -declare - subRoleDescriptor RbacRoleDescriptor; - subRoleUuids uuid[] := array []::uuid[]; -begin - foreach subRoleDescriptor in array roleDescriptors - loop - if subRoleDescriptor is not null then - subRoleUuids := subRoleUuids || getRoleId(subRoleDescriptor, 'fail'); - end if; - end loop; - - return row (subRoleUuids)::RbacSubRoles; -end; $$; - -create or replace function withoutSubRoles() - returns RbacSubRoles - language plpgsql - strict as $$ -begin - return row (array []::uuid[]); -end; $$; - ---// - --- ================================================================= --- USERS ---changeset rbac-role-builder-users:1 endDelimiter:--// --- ----------------------------------------------------------------- - -/* -*/ -create type RbacUsers as -( - userUuids uuid[] -); - -create or replace function withUsers(userNames varchar[]) - returns RbacUsers - language plpgsql - strict as $$ -declare - userName varchar; - userUuids uuid[] := array []::uuid[]; -begin - foreach userName in array userNames - loop - userUuids := userUuids || getRbacUserId(userName, 'fail'); - end loop; - - return row (userUuids)::RbacUsers; -end; $$; - - -create or replace function withUser(userName varchar, whenNotExists RbacWhenNotExists = 'fail') - returns RbacUsers - returns null on null input - language plpgsql as $$ -begin - return row (array [getRbacUserId(userName, whenNotExists)]); -end; $$; - ---// -- ================================================================= -- CREATE ROLE --changeset rbac-role-builder-create-role:1 endDelimiter:--// -- ----------------------------------------------------------------- -/* -*/ -create or replace function createRole( +create or replace function createRoleWithGrants( roleDescriptor RbacRoleDescriptor, - permissions RbacPermissions, - superRoles RbacSuperRoles, - subRoles RbacSubRoles = null, - users RbacUsers = null, - grantingRoleUuid uuid = null + permissions RbacOp[] = array[]::RbacOp[], + incomingSuperRoles RbacRoleDescriptor[] = array[]::RbacRoleDescriptor[], + outgoingSubRoles RbacRoleDescriptor[] = array[]::RbacRoleDescriptor[], + userUuids uuid[] = array[]::uuid[], + grantedByRole RbacRoleDescriptor = null ) returns uuid called on null input @@ -204,80 +53,37 @@ declare superRoleUuid uuid; subRoleUuid uuid; userUuid uuid; + grantedByRoleUuid uuid; begin raise notice 'will createRole for %', roleDescriptor; - roleUuid = createRole(roleDescriptor); + roleUuid := createRole(roleDescriptor); - call grantPermissionsToRole(roleUuid, permissions.permissionUuids); - - if superRoles is not null then - foreach superRoleUuid in array superRoles.roleuUids - loop - call grantRoleToRole(roleUuid, superRoleUuid); - end loop; + if cardinality(permissions) >0 then + call grantPermissionsToRole(roleUuid, toPermissionUuids(roleDescriptor.objectuuid, permissions)); end if; - if subRoles is not null then - foreach subRoleUuid in array subRoles.roleuUids - loop - call grantRoleToRole(subRoleUuid, roleUuid); - end loop; - end if; + foreach superRoleUuid in array toRoleUuids(incomingSuperRoles) + loop + call grantRoleToRole(roleUuid, superRoleUuid); + end loop; - if users is not null then - foreach userUuid in array users.useruUids + foreach subRoleUuid in array toRoleUuids(outgoingSubRoles) + loop + call grantRoleToRole(subRoleUuid, roleUuid); + end loop; + + if cardinality(userUuids) > 0 then + if grantedByRole is null then + raise exception 'to directly assign users to roles, grantingRole has to be given'; + end if; + grantedByRoleUuid := getRoleId(grantedByRole, 'fail'); + foreach userUuid in array userUuids loop - call grantRoleToUserUnchecked(grantingRoleUuid, roleUuid, userUuid); + call grantRoleToUserUnchecked(grantedByRoleUuid, roleUuid, userUuid); end loop; end if; return roleUuid; end; $$; - -create or replace function createRole( - roleDescriptor RbacRoleDescriptor, - permissions RbacPermissions, - users RbacUsers = null, - grantingRoleUuid uuid = null -) - returns uuid - called on null input - language plpgsql as $$ -begin - return createRole(roleDescriptor, permissions, null, null, users, grantingRoleUuid); -end; $$; - -create or replace function createRole( - roleDescriptor RbacRoleDescriptor, - permissions RbacPermissions, - subRoles RbacSubRoles, - users RbacUsers = null, - grantingRoleUuid uuid = null -) - returns uuid - called on null input - language plpgsql as $$ -begin - return createRole(roleDescriptor, permissions, null, subRoles, users, grantingRoleUuid); -end; $$; --// --- ================================================================= --- CREATE ROLE ---changeset rbac-role-builder-GRANTED-BY-ROLE:1 endDelimiter:--// --- ----------------------------------------------------------------- - -/* - Used in role-builder-DSL to convert a role descriptor to it's uuid - for use as `grantedByRoleUuid`. -*/ -create or replace function grantedByRole(roleDescriptor RbacRoleDescriptor) - returns uuid - strict leakproof - language plpgsql as $$ -begin - return getRoleId(roledescriptor, 'fail'); -end; $$; ---// - - diff --git a/src/main/resources/db/changelog/058-rbac-generators.sql b/src/main/resources/db/changelog/058-rbac-generators.sql index 934005d7..af5422dc 100644 --- a/src/main/resources/db/changelog/058-rbac-generators.sql +++ b/src/main/resources/db/changelog/058-rbac-generators.sql @@ -58,6 +58,14 @@ begin return roleDescriptor('%2$s', entity.uuid, 'admin'); end; $f$; + create or replace function %1$sAgent(entity %2$s) + returns RbacRoleDescriptor + language plpgsql + strict as $f$ + begin + return roleDescriptor('%2$s', entity.uuid, 'agent'); + end; $f$; + create or replace function %1$sTenant(entity %2$s) returns RbacRoleDescriptor language plpgsql @@ -66,6 +74,14 @@ begin return roleDescriptor('%2$s', entity.uuid, 'tenant'); end; $f$; + create or replace function %1$sGuest(entity %2$s) + returns RbacRoleDescriptor + language plpgsql + strict as $f$ + begin + return roleDescriptor('%2$s', entity.uuid, 'guest'); + end; $f$; + $sql$, prefix, targetTable); execute sql; end; $$; diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index 88d804c8..1f563aa2 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -35,28 +35,28 @@ begin end if; -- the owner role with full access for Hostsharing administrators - testCustomerOwnerUuid = createRole( + testCustomerOwnerUuid = createRoleWithGrants( testCustomerOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), - beneathRole(globalAdmin()) + permissions => array['*'], + incomingSuperRoles => array[globalAdmin()] ); -- the admin role for the customer's admins, who can view and add products - customerAdminUuid = createRole( + customerAdminUuid = createRoleWithGrants( testCustomerAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view', 'add-package']), + permissions => array['view', 'add-package'], -- NO auto assume for customer owner to avoid exploding permissions for administrators - withUser(NEW.adminUserName, 'create'), -- implicitly ignored if null - grantedByRole(globalAdmin()) + userUuids => array[getRbacUserId(NEW.adminUserName, 'create')], -- implicitly ignored if null + grantedByRole => globalAdmin() ); -- allow the customer owner role (thus administrators) to assume the customer admin role call grantRoleToRole(customerAdminUuid, testCustomerOwnerUuid, false); -- the tenant role which later can be used by owners+admins of sub-objects - perform createRole( + perform createRoleWithGrants( testCustomerTenant(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']) + permissions => array['view'] ); return NEW; diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index e8858f47..f4eb2edd 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -36,25 +36,25 @@ begin select * from test_customer as c where c.uuid = NEW.customerUuid into parentCustomer; -- an owner role is created and assigned to the customer's admin role - packageOwnerRoleUuid = createRole( - testPackageOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), - beneathRole(testCustomerAdmin(parentCustomer)) + perform createRoleWithGrants( + testPackageOwner(NEW), + permissions => array ['*'], + incomingSuperRoles => array[testCustomerAdmin(parentCustomer)] ); -- an owner role is created and assigned to the package owner role - packageAdminRoleUuid = createRole( - testPackageAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['add-domain']), - beneathRole(packageOwnerRoleUuid) + perform createRoleWithGrants( + testPackageAdmin(NEW), + permissions => array ['add-domain'], + incomingSuperRoles => array[testPackageOwner(NEW)] ); -- and a package tenant role is created and assigned to the package admin as well - perform createRole( - testPackageTenant(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRole(packageAdminRoleUuid), - beingItselfA(testCustomerTenant(parentCustomer)) + perform createRoleWithGrants( + testPackageTenant(NEW), + permissions => array['view'], + incomingsuperroles => array[testPackageAdmin(NEW)], + outgoingSubRoles => array[testCustomerTenant(parentCustomer)] ); return NEW; diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index b1c46143..ceeb5de3 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -26,10 +26,10 @@ begin return domainTenantRoleUuid; end if; - return createRole( + return createRoleWithGrants( domainTenantRoleDesc, - grantingPermissions(forObjectUuid => domain.uuid, permitOps => array ['view']), - beneathRole(testdomainAdmin(domain)) + permissions => array['view'], + incomingSuperRoles => array[testdomainAdmin(domain)] ); end; $$; --// @@ -48,8 +48,6 @@ create or replace function createRbacRulesForTestDomain() strict as $$ declare parentPackage test_package; - domainOwnerRoleId uuid; - domainAdminRoleId uuid; begin if TG_OP <> 'INSERT' then raise exception 'invalid usage of TRIGGER AFTER INSERT'; @@ -58,18 +56,18 @@ begin select * from test_package where uuid = NEW.packageUuid into parentPackage; -- an owner role is created and assigned to the package's admin group - domainOwnerRoleId = createRole( - testdomainOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), - beneathRole(testPackageAdmin(parentPackage)) + perform createRoleWithGrants( + testDomainOwner(NEW), + permissions => array['*'], + incomingSuperRoles => array[testPackageAdmin(parentPackage)] ); -- and a domain admin role is created and assigned to the domain owner as well - domainAdminRoleId = createRole( - testdomainAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), - beneathRole(domainOwnerRoleId), - beingItselfA(testPackageTenant(parentPackage)) + perform createRoleWithGrants( + testDomainAdmin(NEW), + permissions => array['edit'], + incomingSuperRoles => array[testDomainOwner(NEW)], + outgoingSubRoles => array[testPackageTenant(parentPackage)] ); -- a tenent role is only created on demand diff --git a/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql b/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql index 303e11a6..39cdbf5e 100644 --- a/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql +++ b/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql @@ -34,28 +34,29 @@ begin raise exception 'invalid usage of TRIGGER AFTER INSERT'; end if; - -- the owner role with full access for the creator assigned to the current user - ownerRole := createRole( - hsOfficeContactOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), - beneathRole(globalAdmin()), - withoutSubRoles(), - withUser(currentUser()), -- TODO.spec: Who is owner of a new contact? - grantedByRole(globalAdmin()) + perform createRoleWithGrants( + hsOfficeContactOwner(NEW), + permissions => array['*'], + incomingSuperRoles => array[globalAdmin()], + userUuids => array[currentUserUuid()], + grantedByRole => globalAdmin() ); - -- the tenant role for those related users who can view the data - adminRole := createRole( + perform createRoleWithGrants( hsOfficeContactAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), - beneathRole(ownerRole) + permissions => array['edit'], + incomingSuperRoles => array[hsOfficeContactOwner(NEW)] ); - -- the tenant role for those related users who can view the data - perform createRole( - hsOfficeContactTenant(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRole(adminRole) + perform createRoleWithGrants( + hsOfficeContactTenant(NEW), + incomingSuperRoles => array[hsOfficeContactAdmin(NEW)] + ); + + perform createRoleWithGrants( + hsOfficeContactGuest(NEW), + permissions => array['view'], + incomingSuperRoles => array[hsOfficeContactTenant(NEW)] ); return NEW; diff --git a/src/main/resources/db/changelog/213-hs-office-person-rbac.sql b/src/main/resources/db/changelog/213-hs-office-person-rbac.sql index 63a88d2a..4f217e7a 100644 --- a/src/main/resources/db/changelog/213-hs-office-person-rbac.sql +++ b/src/main/resources/db/changelog/213-hs-office-person-rbac.sql @@ -24,36 +24,34 @@ create or replace function createRbacRolesForHsOfficePerson() returns trigger language plpgsql strict as $$ -declare - ownerRole uuid; - adminRole uuid; begin if TG_OP <> 'INSERT' then raise exception 'invalid usage of TRIGGER AFTER INSERT'; end if; - -- the owner role with full access for the creator assigned to the current user - ownerRole := createRole( + perform createRoleWithGrants( hsOfficePersonOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), - beneathRole(globalAdmin()), - withoutSubRoles(), - withUser(currentUser()), -- TODO.spec: Who is owner of a new person? - grantedByRole(globalAdmin()) + permissions => array['*'], + incomingSuperRoles => array[globalAdmin()], + userUuids => array[currentUserUuid()], + grantedByRole => globalAdmin() ); - -- the tenant role for those related users who can view the data - adminRole := createRole( + perform createRoleWithGrants( hsOfficePersonAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), - beneathRole(ownerRole) + permissions => array['edit'], + incomingSuperRoles => array[hsOfficePersonOwner(NEW)] ); - -- the tenant role for those related users who can view the data - perform createRole( + perform createRoleWithGrants( hsOfficePersonTenant(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRole(adminRole) + incomingSuperRoles => array[hsOfficePersonAdmin(NEW)] + ); + + perform createRoleWithGrants( + hsOfficePersonGuest(NEW), + permissions => array['view'], + incomingSuperRoles => array[hsOfficePersonTenant(NEW)] ); return NEW; diff --git a/src/main/resources/db/changelog/223-hs-office-partner-rbac.md b/src/main/resources/db/changelog/223-hs-office-partner-rbac.md new file mode 100644 index 00000000..bb70d327 --- /dev/null +++ b/src/main/resources/db/changelog/223-hs-office-partner-rbac.md @@ -0,0 +1,66 @@ +### hs_office_partner RBAC + +```mermaid +flowchart TB + +subgraph global + style global fill:#eee + + role:global.admin[global.admin] +end + +subgraph hsOfficeContact + direction TB + style hsOfficeContact fill:#eee + + role:hsOfficeContact.admin[contact.admin] + --> role:hsOfficeContact.tenant[contact.tenant] + --> role:hsOfficeContact.guest[contact.guest] +end + +subgraph hsOfficePerson + direction TB + style hsOfficePerson fill:#eee + + role:hsOfficePerson.admin[person.admin] + --> role:hsOfficePerson.tenant[person.tenant] + --> role:hsOfficePerson.guest[person.guest] +end + +subgraph hsOfficePartner + + role:hsOfficePartner.owner[partner.owner] + %% permissions + role:hsOfficePartner.owner --> perm:hsOfficePartner.*{{partner.*}} + %% incoming + role:global.admin ---> role:hsOfficePartner.owner + + role:hsOfficePartner.admin[partner.admin] + %% permissions + role:hsOfficePartner.admin --> perm:hsOfficePartner.edit{{partner.edit}} + %% incoming + role:hsOfficePartner.owner ---> role:hsOfficePartner.admin + %% outgoing + role:hsOfficePartner.admin --> role:hsOfficePerson.tenant + role:hsOfficePartner.admin --> role:hsOfficeContact.tenant + + role:hsOfficePartner.agent[partner.agent] + %% incoming + role:hsOfficePartner.admin ---> role:hsOfficePartner.agent + role:hsOfficePerson.admin --> role:hsOfficePartner.agent + role:hsOfficeContact.admin --> role:hsOfficePartner.agent + + role:hsOfficePartner.tenant[partner.tenant] + %% incoming + role:hsOfficePartner.agent --> role:hsOfficePartner.tenant + %% outgoing + role:hsOfficePartner.tenant --> role:hsOfficePerson.guest + role:hsOfficePartner.tenant --> role:hsOfficeContact.guest + + role:hsOfficePartner.guest[partner.guest] + %% permissions + role:hsOfficePartner.guest --> perm:hsOfficePartner.view{{partner.view}} + %% incoming + role:hsOfficePartner.tenant --> role:hsOfficePartner.guest +end +``` diff --git a/src/main/resources/db/changelog/223-hs-office-partner-rbac.sql b/src/main/resources/db/changelog/223-hs-office-partner-rbac.sql index a55f93f2..3369624e 100644 --- a/src/main/resources/db/changelog/223-hs-office-partner-rbac.sql +++ b/src/main/resources/db/changelog/223-hs-office-partner-rbac.sql @@ -28,46 +28,56 @@ create or replace function hsOfficePartnerRbacRolesTrigger() strict as $$ declare hsOfficePartnerTenant RbacRoleDescriptor; - ownerRole uuid; - adminRole uuid; oldPerson hs_office_person; newPerson hs_office_person; oldContact hs_office_contact; newContact hs_office_contact; begin - hsOfficePartnerTenant := hsOfficePartnerTenant(NEW); - select * from hs_office_person as p where p.uuid = NEW.personUuid into newPerson; 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 the global admins - ownerRole = createRole( + perform createRoleWithGrants( hsOfficePartnerOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), - beneathRole(globalAdmin()) + permissions => array['*'], + incomingSuperRoles => array[globalAdmin()] ); - -- the admin role with full access for owner - adminRole = createRole( + perform createRoleWithGrants( hsOfficePartnerAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), - beneathRole(ownerRole) + permissions => array['edit'], + incomingSuperRoles => array[ + hsOfficePartnerOwner(NEW)], + outgoingSubRoles => array[ + hsOfficePersonTenant(newPerson), + hsOfficeContactTenant(newContact)] ); - -- the tenant role for those related users who can view the data - perform createRole( - hsOfficePartnerTenant, - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRoles(array[ + perform createRoleWithGrants( + hsOfficePartnerAgent(NEW), + incomingSuperRoles => array[ hsOfficePartnerAdmin(NEW), hsOfficePersonAdmin(newPerson), - hsOfficeContactAdmin(newContact)]), - withSubRoles(array[ - hsOfficePersonTenant(newPerson), - hsOfficeContactTenant(newContact)]) + hsOfficeContactAdmin(newContact)] + ); + + perform createRoleWithGrants( + hsOfficePartnerTenant(NEW), + incomingSuperRoles => array[ + hsOfficePartnerAgent(NEW)], + outgoingSubRoles => array[ + hsOfficePersonGuest(newPerson), + hsOfficeContactGuest(newContact)] + ); + + + perform createRoleWithGrants( + hsOfficePartnerGuest(NEW), + permissions => array['view'], + incomingSuperRoles => array[ + hsOfficePartnerTenant(NEW)] ); elsif TG_OP = 'UPDATE' then @@ -75,21 +85,27 @@ begin if OLD.personUuid <> NEW.personUuid then select * from hs_office_person as p where p.uuid = OLD.personUuid into oldPerson; - call revokeRoleFromRole( hsOfficePartnerTenant, hsOfficePersonAdmin(oldPerson) ); - call grantRoleToRole( hsOfficePartnerTenant, hsOfficePersonAdmin(newPerson) ); - - call revokeRoleFromRole( hsOfficePersonTenant(oldPerson), hsOfficePartnerTenant ); - call grantRoleToRole( hsOfficePersonTenant(newPerson), hsOfficePartnerTenant ); + call revokeRoleFromRole(hsOfficePersonTenant(oldPerson), hsOfficePartnerAdmin(OLD)); + call grantRoleToRole(hsOfficePersonTenant(newPerson), hsOfficePartnerAdmin(NEW)); + + call revokeRoleFromRole(hsOfficePartnerAgent(OLD), hsOfficePersonAdmin(oldPerson)); + call grantRoleToRole(hsOfficePartnerAgent(NEW), hsOfficePersonAdmin(newPerson)); + + call revokeRoleFromRole(hsOfficePersonGuest(oldPerson), hsOfficePartnerTenant(OLD)); + call grantRoleToRole(hsOfficePersonGuest(newPerson), hsOfficePartnerTenant(NEW)); end if; if OLD.contactUuid <> NEW.contactUuid then select * from hs_office_contact as c where c.uuid = OLD.contactUuid into oldContact; - call revokeRoleFromRole( hsOfficePartnerTenant, hsOfficeContactAdmin(oldContact) ); - call grantRoleToRole( hsOfficePartnerTenant, hsOfficeContactAdmin(newContact) ); + call revokeRoleFromRole(hsOfficeContactTenant(oldContact), hsOfficePartnerAdmin(OLD)); + call grantRoleToRole(hsOfficeContactTenant(newContact), hsOfficePartnerAdmin(NEW)); - call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficePartnerTenant ); - call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficePartnerTenant ); + call revokeRoleFromRole(hsOfficePartnerAgent(OLD), hsOfficeContactAdmin(oldContact)); + call grantRoleToRole(hsOfficePartnerAgent(NEW), hsOfficeContactAdmin(newContact)); + + call revokeRoleFromRole(hsOfficeContactGuest(oldContact), hsOfficePartnerTenant(OLD)); + call grantRoleToRole(hsOfficeContactGuest(newContact), hsOfficePartnerTenant(NEW)); end if; else raise exception 'invalid usage of TRIGGER'; diff --git a/src/main/resources/db/changelog/233-hs-office-relationship-rbac.md b/src/main/resources/db/changelog/233-hs-office-relationship-rbac.md new file mode 100644 index 00000000..c41de32c --- /dev/null +++ b/src/main/resources/db/changelog/233-hs-office-relationship-rbac.md @@ -0,0 +1,192 @@ +### hs_office_relationship RBAC + +```mermaid + +flowchart TB + +subgraph global + style global fill:#eee + + role:global.admin[global.admin] +end + +subgraph hsOfficeContact + direction TB + style hsOfficeContact fill:#eee + + role:hsOfficeContact.admin[contact.admin] + --> role:hsOfficeContact.tenant[contact.tenant] + --> role:hsOfficeContact.guest[contact.guest] +end + +subgraph hsOfficePerson + direction TB + style hsOfficePerson fill:#eee + + role:hsOfficePerson.admin[person.admin] + --> role:hsOfficePerson.tenant[person.tenant] + --> role:hsOfficePerson.guest[person.guest] +end + +subgraph hsOfficeRelationship + + role:hsOfficePerson#relAnchor.admin[person#anchor.admin] + --- role:hsOfficePerson.admin + + role:hsOfficeRelationship.owner[relationship.owner] + %% permissions + role:hsOfficeRelationship.owner --> perm:hsOfficeRelationship.*{{relationship.*}} + %% incoming + role:global.admin ---> role:hsOfficeRelationship.owner + role:hsOfficePersonAdmin#relAnchor.admin +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(); +--// + diff --git a/src/main/resources/db/changelog/233-hs-office-relationship-rbac.sql b/src/main/resources/db/changelog/233-hs-office-relationship-rbac.sql index 8c494bbe..03b0b748 100644 --- a/src/main/resources/db/changelog/233-hs-office-relationship-rbac.sql +++ b/src/main/resources/db/changelog/233-hs-office-relationship-rbac.sql @@ -28,8 +28,6 @@ create or replace function hsOfficeRelationshipRbacRolesTrigger() strict as $$ declare hsOfficeRelationshipTenant RbacRoleDescriptor; - ownerRole uuid; - adminRole uuid; newRelAnchor hs_office_person; newRelHolder hs_office_person; oldContact hs_office_contact; @@ -44,38 +42,38 @@ begin if TG_OP = 'INSERT' then - -- the owner role with full access for admins of the relAnchor global admins - ownerRole = createRole( + perform createRoleWithGrants( hsOfficeRelationshipOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), - beneathRoles(array[ + permissions => array['*'], + incomingSuperRoles => array[ globalAdmin(), - hsOfficePersonAdmin(newRelAnchor)]) + hsOfficePersonAdmin(newRelAnchor)] ); - -- the admin role with full access for the owner - adminRole = createRole( + perform createRoleWithGrants( hsOfficeRelationshipAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), - beneathRole(ownerRole) + permissions => array['edit'], + incomingSuperRoles => array[hsOfficeRelationshipOwner(NEW)] ); -- the tenant role for those related users who can view the data - perform createRole( + perform createRoleWithGrants( hsOfficeRelationshipTenant, - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRoles(array[ + permissions => array['view'], + incomingSuperRoles => array[ + hsOfficeRelationshipAdmin(NEW), hsOfficePersonAdmin(newRelAnchor), hsOfficePersonAdmin(newRelHolder), - hsOfficeContactAdmin(newContact)]), - withSubRoles(array[ + hsOfficeContactAdmin(newContact)], + outgoingSubRoles => array[ hsOfficePersonTenant(newRelAnchor), hsOfficePersonTenant(newRelHolder), - hsOfficeContactTenant(newContact)]) + hsOfficeContactTenant(newContact)] ); -- anchor and holder admin roles need each others tenant role -- to be able to see the joined relationship + -- TODO: this can probably be avoided through agent+guest roles call grantRoleToRole(hsOfficePersonTenant(newRelAnchor), hsOfficePersonAdmin(newRelHolder)); call grantRoleToRole(hsOfficePersonTenant(newRelHolder), hsOfficePersonAdmin(newRelAnchor)); call grantRoleToRoleIfNotNull(hsOfficePersonTenant(newRelHolder), hsOfficeContactAdmin(newContact)); diff --git a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.md b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.md index 07d60244..fc34f147 100644 --- a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.md +++ b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.md @@ -3,41 +3,38 @@ ```mermaid flowchart TB -%% ---------- generated start: ---------- - subgraph global + style hsOfficeBankAccount fill: #e9f7ef + role:global.admin[global.admin] end -subgraph context - user:current([current]) +subgraph hsOfficeBankAccount + direction TB + style hsOfficeBankAccount fill: #e9f7ef + + user:hsOfficeBankAccount.creator([bankAccount.creator]) + + role:hsOfficeBankAccount.owner[[bankAccount.owner]] + %% permissions + role:hsOfficeBankAccount.owner --> perm:hsOfficeBankAccount.*{{hsOfficeBankAccount.delete}} + %% incoming + role:global.admin --> role:hsOfficeBankAccount.owner + user:hsOfficeBankAccount.creator ---> role:hsOfficeBankAccount.owner + + role:hsOfficeBankAccount.admin[[bankAccount.admin]] + %% incoming + role:hsOfficeBankAccount.owner ---> role:hsOfficeBankAccount.admin + + role:hsOfficeBankAccount.tenant[[bankAccount.tenant]] + %% incoming + role:hsOfficeBankAccount.admin ---> role:hsOfficeBankAccount.tenant + + role:hsOfficeBankAccount.guest[[bankAccount.guest]] + %% permissions + role:hsOfficeBankAccount.guest --> perm:hsOfficeBankAccount.view{{hsOfficeBankAccount.view}} + %% incoming + role:hsOfficeBankAccount.tenant ---> role:hsOfficeBankAccount.guest end - -subgraph bankaccount - - subgraph roles[ ] - role:bankaccount.owner[[bankaccount.owner]] - role:bankaccount.admin[[bankaccount.admin]] - role:bankaccount.tenant[[bankaccount.tenant]] - end - - subgraph perms[ ] - perm:bankaccount.delete{{bankaccount.delete}} - perm:bankaccount.view{{bankaccount.view}} - end - -end - -%% ---------- generated end. ---------- - -role:bankaccount.owner --> perm:bankaccount.delete - -role:global.admin --> role:bankaccount.owner -user:current --> role:bankaccount.owner - -role:bankaccount.owner --> role:bankaccount.admin - -role:bankaccount.admin --> role:bankaccount.tenant -role:bankaccount.tenant --> perm:bankaccount.view ``` diff --git a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql index 3d247e06..148e0ee2 100644 --- a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql +++ b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql @@ -26,40 +26,33 @@ create or replace function createRbacRolesForHsOfficeBankAccount() returns trigger language plpgsql strict as $$ -declare - ownerRole uuid; - adminRole uuid; begin if TG_OP <> 'INSERT' then raise exception 'invalid usage of TRIGGER AFTER INSERT'; end if; - -- the owner role with full access for the creator assigned to the current user - ownerRole := createRole( - hsOfficeBankAccountOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['delete']), - beneathRole(globalAdmin()), - withoutSubRoles(), - withUser(currentUser()), -- TODO.spec: Who is owner of a new bankaccount? - grantedByRole(globalAdmin()) + perform createRoleWithGrants( + hsOfficeBankAccountOwner(NEW), + permissions => array['delete'], + incomingSuperRoles => array[globalAdmin()], + userUuids => array[currentUserUuid()], + grantedByRole => globalAdmin() ); - -- the admin role for those related users who can view the data and related records - adminRole := createRole( + perform createRoleWithGrants( hsOfficeBankAccountAdmin(NEW), - -- Where bankaccounts can be created, assigned, re-assigned and deleted, they cannot be updated. - -- Thus SQL UPDATE and 'edit' permission are being implemented. - withoutPermissions(), - beneathRole(ownerRole) + incomingSuperRoles => array[hsOfficeBankAccountOwner(NEW)] ); - -- TODO.spec: assumption can not be updated + perform createRoleWithGrants( + hsOfficeBankAccountTenant(NEW), + incomingSuperRoles => array[hsOfficeBankAccountAdmin(NEW)] + ); - -- the tenant role for those related users who can view the data - perform createRole( - hsOfficeBankAccountTenant(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRole(adminRole) + perform createRoleWithGrants( + hsOfficeBankAccountGuest(NEW), + permissions => array['view'], + incomingSuperRoles => array[hsOfficeBankAccountTenant(NEW)] ); return NEW; diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.md b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.md index 7d8d0ed0..ab738860 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.md +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.md @@ -1,49 +1,208 @@ ### hs_office_debitor RBAC Roles ```mermaid -flowchart TB; +flowchart TB -subgraph bankaccount; +subgraph global + style global fill:#eee + + role:global.admin[global.admin] +end - %% oversimplified version for now - %% - %% Beware: role:debitor.tenant should NOT be granted role:bankaccount.tenent - %% because otherwise, later in the development, - %% e.g. package admins could see the debitors bank account, - %% except if we do NOT use the debitor in the hosting super module. - - role:bankaccount.tenant --> perm:bankaccount.view{{bankaccount.view}}; -end; - -subgraph debitor[" "]; -direction TB; - - role:debitor.owner[[debitor.owner]] - role:debitor.owner --> perm:debitor.*{{debitor.*}}; - - role:debitor.admin[[debitor.admin]] - %% super-roles - role:debitor.owner --> role:debitor.admin; - role:partner.admin --> role:debitor.admin; - role:person.admin --> role:debitor.admin; - role:contact.admin --> role:debitor.admin; - %% sub-roles - role:debitor.admin --> role:partner.tenant; - role:debitor.admin --> role:person.tenant; - role:debitor.admin --> role:contact.tenant; - role:debitor.admin --> role:bankaccount.tenant; - - role:debitor.tenant[[debitor.tenant]] - role:debitor.tenant --> perm:debitor.view{{debitor.view}}; - %% super-roles - role:debitor.admin --> role:debitor.tenant; - %% sub-roles +subgraph office + style office fill:#eee + + subgraph bankaccount + style bankaccount fill: #e9f7ef -end; - -subgraph global; - role:global.admin --> role:debitor.owner; -end; + user:hsOfficeBankAccount.creator([bankaccount.creator]) + + role:hsOfficeBankAccount.owner[bankaccount.owner] + %% permissions + role:hsOfficeBankAccount.owner --> perm:hsOfficeBankAccount.*{{bankaccount.*}} + %% incoming + role:global.admin --> role:hsOfficeBankAccount.owner + user:hsOfficeBankAccount.creator ---> role:hsOfficeBankAccount.owner + + role:hsOfficeBankAccount.admin[bankaccount.admin] + %% permissions + role:hsOfficeBankAccount.admin --> perm:hsOfficeBankAccount.edit{{bankaccount.edit}} + %% incoming + role:hsOfficeBankAccount.owner ---> role:hsOfficeBankAccount.admin + role:hsOfficeBankAccount.tenant[bankaccount.tenant] + %% incoming + role:hsOfficeBankAccount.admin ---> role:hsOfficeBankAccount.tenant + + role:hsOfficeBankAccount.guest[bankaccount.guest] + %% permissions + role:hsOfficeBankAccount.guest --> perm:hsOfficeBankAccount.view{{bankaccount.view}} + %% incoming + role:hsOfficeBankAccount.tenant ---> role:hsOfficeBankAccount.guest + end + + subgraph contact + style contact fill: #e9f7ef + + user:hsOfficeContact.creator([contact.creator]) + + role:hsOfficeContact.owner[contact.owner] + %% permissions + role:hsOfficeContact.owner --> perm:hsOfficeContact.*{{contact.*}} + %% incoming + role:global.admin --> role:hsOfficeContact.owner + user:hsOfficeContact.creator ---> role:hsOfficeContact.owner + + role:hsOfficeContact.admin[contact.admin] + %% permissions + role:hsOfficeContact.admin ---> perm:hsOfficeContact.edit{{contact.edit}} + %% incoming + role:hsOfficeContact.owner ---> role:hsOfficeContact.admin + + role:hsOfficeContact.tenant[contact.tenant] + %% incoming + role:hsOfficeContact.admin ----> role:hsOfficeContact.tenant + + role:hsOfficeContact.guest[contact.guest] + %% permissions + role:hsOfficeContact.guest --> perm:hsOfficeContact.view{{contact.view}} + %% incoming + role:hsOfficeContact.tenant ---> role:hsOfficeContact.guest + end + + subgraph partner-person + + subgraph person + style person fill: #e9f7ef + + user:hsOfficePerson.creator([personcreator]) + + role:hsOfficePerson.owner[person.owner] + %% permissions + role:hsOfficePerson.owner --> perm:hsOfficePerson.*{{person.*}} + %% incoming + user:hsOfficePerson.creator ---> role:hsOfficePerson.owner + role:global.admin --> role:hsOfficePerson.owner + + role:hsOfficePerson.admin[person.admin] + %% permissions + role:hsOfficePerson.admin --> perm:hsOfficePerson.edit{{person.edit}} + %% incoming + role:hsOfficePerson.owner ---> role:hsOfficePerson.admin + + role:hsOfficePerson.tenant[person.tenant] + %% incoming + role:hsOfficePerson.admin -----> role:hsOfficePerson.tenant + + role:hsOfficePerson.guest[person.guest] + %% permissions + role:hsOfficePerson.guest --> perm:hsOfficePerson.edit{{person.view}} + %% incoming + role:hsOfficePerson.tenant ---> role:hsOfficePerson.guest + end + + subgraph partner + + role:hsOfficePartner.owner[partner.owner] + %% permissions + role:hsOfficePartner.owner --> perm:hsOfficePartner.*{{partner.*}} + %% incoming + role:global.admin ---> role:hsOfficePartner.owner + + role:hsOfficePartner.admin[partner.admin] + %% permissions + role:hsOfficePartner.admin --> perm:hsOfficePartner.edit{{partner.edit}} + %% incoming + role:hsOfficePartner.owner ---> role:hsOfficePartner.admin + %% outgoing + role:hsOfficePartner.admin --> role:hsOfficePerson.tenant + role:hsOfficePartner.admin --> role:hsOfficeContact.tenant + + role:hsOfficePartner.agent[partner.agent] + %% incoming + role:hsOfficePartner.admin --> role:hsOfficePartner.agent + role:hsOfficePerson.admin --> role:hsOfficePartner.agent + role:hsOfficeContact.admin --> role:hsOfficePartner.agent + + role:hsOfficePartner.tenant[partner.tenant] + %% incoming + role:hsOfficePartner.agent ---> role:hsOfficePartner.tenant + %% outgoing + role:hsOfficePartner.tenant --> role:hsOfficePerson.guest + role:hsOfficePartner.tenant --> role:hsOfficeContact.guest + + role:hsOfficePartner.guest[partner.guest] + %% permissions + role:hsOfficePartner.guest --> perm:hsOfficePartner.view{{partner.view}} + %% incoming + role:hsOfficePartner.tenant ---> role:hsOfficePartner.guest + end + + end + + subgraph debitor + style debitor stroke-width:6px + + user:hsOfficeDebitor.creator([debitor.creator]) + %% created by role + user:hsOfficeDebitor.creator --> role:hsOfficePartner.agent + + role:hsOfficeDebitor.owner[debitor.owner] + %% permissions + role:hsOfficeDebitor.owner --> perm:hsOfficeDebitor.*{{debitor.*}} + %% incoming + user:hsOfficeDebitor.creator --> role:hsOfficeDebitor.owner + role:global.admin --> role:hsOfficeDebitor.owner + + role:hsOfficeDebitor.admin[debitor.admin] + %% permissions + role:hsOfficeDebitor.admin --> perm:hsOfficeDebitor.edit{{debitor.edit}} + %% incoming + role:hsOfficeDebitor.owner ---> role:hsOfficeDebitor.admin + + role:hsOfficeDebitor.agent[debitor.agent] + %% incoming + role:hsOfficeDebitor.admin ---> role:hsOfficeDebitor.agent + role:hsOfficePartner.admin --> role:hsOfficeDebitor.agent + role:hsOfficeContact.admin --> role:hsOfficeDebitor.agent + %% outgoing + role:hsOfficeDebitor.agent --> role:hsOfficeBankAccount.tenant + + role:hsOfficeDebitor.tenant[debitor.tenant] + %% incoming + role:hsOfficeDebitor.agent ---> role:hsOfficeDebitor.tenant + role:hsOfficePartner.agent --> role:hsOfficeDebitor.tenant + role:hsOfficeBankAccount.admin --> role:hsOfficeDebitor.tenant + %% outgoing + role:hsOfficeDebitor.tenant --> role:hsOfficePartner.tenant + role:hsOfficeDebitor.tenant --> role:hsOfficeContact.guest + + role:hsOfficeDebitor.guest[debitor.guest] + %% permissions + role:hsOfficeDebitor.guest --> perm:hsOfficeDebitor.view{{debitor.view}} + %% incoming + role:hsOfficeDebitor.tenant --> role:hsOfficeDebitor.guest + end + +end + + +subgraph hosting + style hosting fill:#eee + + subgraph package + style package fill: #e9f7ef + + role:package.owner[package.owner] + --> role:package.admin[package.admin] + --> role:package.tenant[package.tenant] + + role:hsOfficeDebitor.agent --> role:package.owner + role:package.admin --> role:hsOfficeDebitor.tenant + role:hsOfficePartner.tenant --> role:hsOfficeDebitor.guest + end +end + ``` + diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql index 9d0b09e7..6796a84d 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql @@ -47,35 +47,48 @@ begin select * from hs_office_bankaccount as b where b.uuid = NEW.refundBankAccountUuid into newBankAccount; if TG_OP = 'INSERT' then - -- the owner role with full access for the global admins - ownerRole = createRole( + + perform createRoleWithGrants( hsOfficeDebitorOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), - beneathRole(globalAdmin()) + permissions => array['*'], + incomingSuperRoles => array[globalAdmin()], + userUuids => array[currentUserUuid()], + grantedByRole => globalAdmin() ); - -- the admin role with full access for owner - adminRole = createRole( + perform createRoleWithGrants( hsOfficeDebitorAdmin(NEW), - withoutPermissions(), - beneathRoles(array [ - hsOfficeDebitorOwner(NEW), - hsOfficePartnerAdmin(newPartner), - hsOfficePersonAdmin(newPerson), - hsOfficeContactAdmin(newContact), - hsOfficeBankAccountAdmin(newBankAccount)]), - withSubRoles(array [ - hsOfficePartnerTenant(newPartner), - hsOfficePersonTenant(newPerson), - hsOfficeContactTenant(newContact), - hsOfficeBankAccountTenant(newBankAccount)]) + permissions => array['edit'], + incomingSuperRoles => array[hsOfficeDebitorOwner(NEW)] ); - -- the tenant role for those related users who can view the data - perform createRole( - hsOfficeDebitorTenant, - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), - beneathRole(hsOfficeDebitorAdmin(NEW)) + perform createRoleWithGrants( + hsOfficeDebitorAgent(NEW), + incomingSuperRoles => array[ + hsOfficeDebitorAdmin(NEW), + hsOfficePartnerAdmin(newPartner), + hsOfficeContactAdmin(newContact)], + outgoingSubRoles => array[ + hsOfficeBankAccountTenant(newBankaccount)] + ); + + perform createRoleWithGrants( + hsOfficeDebitorTenant(NEW), + incomingSuperRoles => array[ + hsOfficeDebitorAgent(NEW), + hsOfficePartnerAgent(newPartner), + hsOfficeBankAccountAdmin(newBankaccount)], + outgoingSubRoles => array[ + hsOfficePartnerTenant(newPartner), + hsOfficeContactGuest(newContact), + hsOfficeBankAccountGuest(newBankaccount)] + ); + + perform createRoleWithGrants( + hsOfficeDebitorGuest(NEW), + permissions => array['view'], + incomingSuperRoles => array[ + hsOfficeDebitorTenant(NEW)] ); elsif TG_OP = 'UPDATE' then @@ -83,33 +96,37 @@ begin if OLD.partnerUuid <> NEW.partnerUuid then select * from hs_office_partner as p where p.uuid = OLD.partnerUuid into oldPartner; - call revokeRoleFromRole(hsOfficeDebitorAdmin(OLD), hsOfficePartnerAdmin(oldPartner)); - call grantRoleToRole(hsOfficeDebitorAdmin(NEW), hsOfficePartnerAdmin(newPartner)); + call revokeRoleFromRole(hsOfficeDebitorAgent(OLD), hsOfficePartnerAdmin(oldPartner)); + call grantRoleToRole(hsOfficeDebitorAgent(NEW), hsOfficePartnerAdmin(newPartner)); - call revokeRoleFromRole(hsOfficePartnerTenant(oldPartner), hsOfficeDebitorAdmin(OLD)); - call grantRoleToRole(hsOfficePartnerTenant(newPartner), hsOfficeDebitorAdmin(NEW)); + call revokeRoleFromRole(hsOfficeDebitorTenant(OLD), hsOfficePartnerAgent(oldPartner)); + call grantRoleToRole(hsOfficeDebitorTenant(NEW), hsOfficePartnerAgent(newPartner)); - -- TODO: What about the person of the partner? And what if the person of the partner changes? + call revokeRoleFromRole(hsOfficePartnerTenant(oldPartner), hsOfficeDebitorTenant(OLD)); + call grantRoleToRole(hsOfficePartnerTenant(newPartner), hsOfficeDebitorTenant(NEW)); end if; if OLD.billingContactUuid <> NEW.billingContactUuid then select * from hs_office_contact as c where c.uuid = OLD.billingContactUuid into oldContact; - call revokeRoleFromRole(hsOfficeDebitorAdmin(OLD), hsOfficeContactAdmin(oldContact)); - call grantRoleToRole(hsOfficeDebitorAdmin(NEW), hsOfficeContactAdmin(newContact)); + call revokeRoleFromRole(hsOfficeDebitorAgent(OLD), hsOfficeContactAdmin(oldContact)); + call grantRoleToRole(hsOfficeDebitorAgent(NEW), hsOfficeContactAdmin(newContact)); - call revokeRoleFromRole(hsOfficeContactTenant(oldContact), hsOfficeDebitorAdmin(OLD)); - call grantRoleToRole(hsOfficeContactTenant(newContact), hsOfficeDebitorAdmin(NEW)); + call revokeRoleFromRole(hsOfficeContactGuest(oldContact), hsOfficeDebitorTenant(OLD)); + call grantRoleToRole(hsOfficeContactGuest(newContact), hsOfficeDebitorTenant(NEW)); end if; if OLD.refundBankAccountUuid <> NEW.refundBankAccountUuid then select * from hs_office_bankaccount as b where b.uuid = OLD.refundBankAccountUuid into oldBankAccount; - call revokeRoleFromRole(hsOfficeDebitorAdmin(OLD), hsOfficeBankAccountAdmin(oldBankAccount)); - call grantRoleToRole(hsOfficeDebitorAdmin(NEW), hsOfficeBankAccountAdmin(newBankAccount)); + call revokeRoleFromRole(hsOfficeBankAccountTenant(oldBankaccount), hsOfficeDebitorAgent(OLD)); + call grantRoleToRole(hsOfficeBankAccountTenant(newBankaccount), hsOfficeDebitorAgent(NEW)); - call revokeRoleFromRole(hsOfficeBankAccountTenant(oldBankAccount), hsOfficeDebitorAdmin(OLD)); - call grantRoleToRole(hsOfficeBankAccountTenant(newBankAccount), hsOfficeDebitorAdmin(NEW)); + call revokeRoleFromRole(hsOfficeDebitorTenant(OLD), hsOfficeBankAccountAdmin(oldBankaccount)); + call grantRoleToRole(hsOfficeDebitorTenant(NEW), hsOfficeBankAccountAdmin(newBankaccount)); + + call revokeRoleFromRole(hsOfficeBankAccountGuest(oldBankaccount), hsOfficeDebitorTenant(OLD)); + call grantRoleToRole(hsOfficeBankAccountGuest(newBankaccount), hsOfficeDebitorTenant(NEW)); end if; else raise exception 'invalid usage of TRIGGER'; diff --git a/src/test/java/net/hostsharing/hsadminng/StringTemplater.java b/src/test/java/net/hostsharing/hsadminng/StringTemplater.java new file mode 100644 index 00000000..38914a94 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/StringTemplater.java @@ -0,0 +1,27 @@ +package net.hostsharing.hsadminng; + +import lombok.experimental.UtilityClass; + +import javax.validation.constraints.NotNull; +import java.util.Map; + +import static liquibase.repackaged.org.apache.commons.text.StringSubstitutor.replace; +import static org.apache.commons.lang3.StringUtils.stripEnd; + +@UtilityClass +public class StringTemplater { + + @SafeVarargs + public static String indentedMultilineTemplate(final String template, final Map.Entry... properties) { + return stripEnd(replace(template, Map.ofEntries(properties)).indent(4), null); + } + + public static Map.Entry property(final String name, final String value) { + return Map.entry(name, value); + } + + public static Map.Entry property(final String name, @NotNull final Object value) { + return Map.entry(name, value.toString()); + } + +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountRepositoryIntegrationTest.java index 4fc23694..ca9804c5 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountRepositoryIntegrationTest.java @@ -110,7 +110,8 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTest { initialRoleNames, "hs_office_bankaccount#sometempaccC.owner", "hs_office_bankaccount#sometempaccC.admin", - "hs_office_bankaccount#sometempaccC.tenant" + "hs_office_bankaccount#sometempaccC.tenant", + "hs_office_bankaccount#sometempaccC.guest" )); assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted( initialGrantNames, @@ -120,8 +121,10 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTest { "{ grant role hs_office_bankaccount#sometempaccC.admin to role hs_office_bankaccount#sometempaccC.owner by system and assume }", - "{ grant perm view on hs_office_bankaccount#sometempaccC to role hs_office_bankaccount#sometempaccC.tenant by system and assume }", "{ grant role hs_office_bankaccount#sometempaccC.tenant to role hs_office_bankaccount#sometempaccC.admin by system and assume }", + + "{ grant perm view on hs_office_bankaccount#sometempaccC to role hs_office_bankaccount#sometempaccC.guest by system and assume }", + "{ grant role hs_office_bankaccount#sometempaccC.guest to role hs_office_bankaccount#sometempaccC.tenant by system and assume }", null )); } @@ -258,9 +261,9 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTest { final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()); final var givenBankAccount = givenSomeTemporaryBankAccount("selfregistered-user-drew@hostsharing.org"); assertThat(rawRoleRepo.findAll().size()).as("unexpected number of roles created") - .isEqualTo(initialRoleNames.size() + 3); + .isEqualTo(initialRoleNames.size() + 4); assertThat(rawGrantRepo.findAll().size()).as("unexpected number of grants created") - .isEqualTo(initialGrantNames.size() + 6); + .isEqualTo(initialGrantNames.size() + 7); // when final var result = jpaAttempt.transacted(() -> { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactRepositoryIntegrationTest.java index b991fd4a..396b88dd 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactRepositoryIntegrationTest.java @@ -112,7 +112,8 @@ class HsOfficeContactRepositoryIntegrationTest extends ContextBasedTest { initialRoleNames, "hs_office_contact#anothernewcontact.owner", "hs_office_contact#anothernewcontact.admin", - "hs_office_contact#anothernewcontact.tenant" + "hs_office_contact#anothernewcontact.tenant", + "hs_office_contact#anothernewcontact.guest" )); assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.from( initialGrantNames, @@ -121,7 +122,8 @@ class HsOfficeContactRepositoryIntegrationTest extends ContextBasedTest { "{ grant role hs_office_contact#anothernewcontact.tenant to role hs_office_contact#anothernewcontact.admin by system and assume }", "{ grant perm * on hs_office_contact#anothernewcontact to role hs_office_contact#anothernewcontact.owner by system and assume }", "{ grant role hs_office_contact#anothernewcontact.admin to role hs_office_contact#anothernewcontact.owner by system and assume }", - "{ grant perm view on hs_office_contact#anothernewcontact to role hs_office_contact#anothernewcontact.tenant by system and assume }", + "{ grant perm view on hs_office_contact#anothernewcontact to role hs_office_contact#anothernewcontact.guest by system and assume }", + "{ grant role hs_office_contact#anothernewcontact.guest to role hs_office_contact#anothernewcontact.tenant by system and assume }", "{ grant role hs_office_contact#anothernewcontact.owner to user selfregistered-user-drew@hostsharing.org by global#global.admin and assume }" )); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index e8b66df9..1e276893 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -99,7 +99,13 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { // given context("superuser-alex@hostsharing.net"); final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll()); - final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()); + final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()).stream() + .map(s -> s.replace("superuser-alex@hostsharing.net", "superuser-alex")) + .map(s -> s.replace("20002Fourthe.G.-forthcontact", "FeG")) + .map(s -> s.replace("Fourthe.G.-forthcontact", "FeG")) + .map(s -> s.replace("forthcontact", "4th")) + .map(s -> s.replace("hs_office_", "")) + .toList(); // when attempt(em, () -> { @@ -117,26 +123,44 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { // then assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( initialRoleNames, - "hs_office_debitor#20002Fourthe.G.-forthcontact.admin", "hs_office_debitor#20002Fourthe.G.-forthcontact.owner", - "hs_office_debitor#20002Fourthe.G.-forthcontact.tenant")); - assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted( - initialGrantNames, - "{ grant perm * on hs_office_debitor#20002Fourthe.G.-forthcontact to role hs_office_debitor#20002Fourthe.G.-forthcontact.owner by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.owner to role global#global.admin by system and assume }", + "hs_office_debitor#20002Fourthe.G.-forthcontact.admin", + "hs_office_debitor#20002Fourthe.G.-forthcontact.agent", + "hs_office_debitor#20002Fourthe.G.-forthcontact.tenant", + "hs_office_debitor#20002Fourthe.G.-forthcontact.guest")); + assertThat(grantDisplaysOf(rawGrantRepo.findAll())) + .map(s -> s.replace("superuser-alex@hostsharing.net", "superuser-alex")) + .map(s -> s.replace("20002Fourthe.G.-forthcontact", "FeG")) + .map(s -> s.replace("Fourthe.G.-forthcontact", "FeG")) + .map(s -> s.replace("forthcontact", "4th")) + .map(s -> s.replace("hs_office_", "")) + .containsExactlyInAnyOrder(Array.fromFormatted( + initialGrantNames, + // owner + "{ grant perm * on debitor#FeG to role debitor#FeG.owner by system and assume }", + "{ grant role debitor#FeG.owner to role global#global.admin by system and assume }", + "{ grant role debitor#FeG.owner to user superuser-alex by global#global.admin and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.admin to role hs_office_debitor#20002Fourthe.G.-forthcontact.owner by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.admin to role hs_office_partner#Fourthe.G.-forthcontact.admin by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.admin to role hs_office_person#Fourthe.G..admin by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.admin to role hs_office_contact#forthcontact.admin by system and assume }", - "{ grant role hs_office_contact#forthcontact.tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.admin by system and assume }", - "{ grant role hs_office_partner#Fourthe.G.-forthcontact.tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.admin by system and assume }", - "{ grant role hs_office_person#Fourthe.G..tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.admin by system and assume }", - "{ grant role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant to role hs_office_debitor#20002Fourthe.G.-forthcontact.admin by system and assume }", + // admin + "{ grant perm edit on debitor#FeG to role debitor#FeG.admin by system and assume }", + "{ grant role debitor#FeG.admin to role debitor#FeG.owner by system and assume }", - "{ grant perm view on hs_office_debitor#20002Fourthe.G.-forthcontact to role hs_office_debitor#20002Fourthe.G.-forthcontact.tenant by system and assume }", + // agent + "{ grant role debitor#FeG.agent to role debitor#FeG.admin by system and assume }", + "{ grant role debitor#FeG.agent to role contact#4th.admin by system and assume }", + "{ grant role debitor#FeG.agent to role partner#FeG.admin by system and assume }", - null)); + // tenant + "{ grant role contact#4th.guest to role debitor#FeG.tenant by system and assume }", + "{ grant role debitor#FeG.tenant to role debitor#FeG.agent by system and assume }", + "{ grant role debitor#FeG.tenant to role partner#FeG.agent by system and assume }", + "{ grant role partner#FeG.tenant to role debitor#FeG.tenant by system and assume }", + + // guest + "{ grant perm view on debitor#FeG to role debitor#FeG.guest by system and assume }", + "{ grant role debitor#FeG.guest to role debitor#FeG.tenant by system and assume }", + + null)); } private void assertThatDebitorIsPersisted(final HsOfficeDebitorEntity saved) { @@ -247,6 +271,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { final var result = jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); givenDebitor.setBillingContact(rawReference(givenNewContact)); + // TODO.test: also test update of partner+bankAccount + // givenDebitor.setPartner(rawReference(givenNewPartner)); + // givenDebitor.setRefundBankAccount(rawReference(givenNewBankAccount)); givenDebitor.setVatId(givenNewVatId); givenDebitor.setVatCountryCode(givenNewVatCountryCode); givenDebitor.setVatBusiness(givenNewVatBusiness); @@ -390,9 +417,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.findAll())); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "twelfth"); assertThat(rawRoleRepo.findAll().size()).as("precondition failed: unexpected number of roles created") - .isEqualTo(initialRoleNames.length + 3); + .isEqualTo(initialRoleNames.length + 5); assertThat(rawGrantRepo.findAll().size()).as("precondition failed: unexpected number of grants created") - .isEqualTo(initialGrantNames.length + 11); + .isEqualTo(initialGrantNames.length + 14); // when final var result = jpaAttempt.transacted(() -> { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java index 0808e3e4..504d52f3 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java @@ -96,7 +96,11 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { // given context("superuser-alex@hostsharing.net"); final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll()); - final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()); + final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()).stream() + .map(s -> s.replace("ErbenBesslerMelBessler", "EBess")) + .map(s -> s.replace("forthcontact", "4th")) + .map(s -> s.replace("hs_office_", "")) + .toList(); // when attempt(em, () -> { @@ -114,20 +118,40 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( initialRoleNames, "hs_office_partner#ErbenBesslerMelBessler-forthcontact.admin", + "hs_office_partner#ErbenBesslerMelBessler-forthcontact.agent", "hs_office_partner#ErbenBesslerMelBessler-forthcontact.owner", - "hs_office_partner#ErbenBesslerMelBessler-forthcontact.tenant")); - assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.from( - initialGrantNames, - "{ grant role hs_office_partner#ErbenBesslerMelBessler-forthcontact.owner to role global#global.admin by system and assume }", - "{ grant role hs_office_partner#ErbenBesslerMelBessler-forthcontact.tenant to role hs_office_contact#forthcontact.admin by system and assume }", - "{ grant perm edit on hs_office_partner#ErbenBesslerMelBessler-forthcontact to role hs_office_partner#ErbenBesslerMelBessler-forthcontact.admin by system and assume }", - "{ grant role hs_office_partner#ErbenBesslerMelBessler-forthcontact.tenant to role hs_office_partner#ErbenBesslerMelBessler-forthcontact.admin by system and assume }", - "{ grant perm * on hs_office_partner#ErbenBesslerMelBessler-forthcontact to role hs_office_partner#ErbenBesslerMelBessler-forthcontact.owner by system and assume }", - "{ grant role hs_office_partner#ErbenBesslerMelBessler-forthcontact.admin to role hs_office_partner#ErbenBesslerMelBessler-forthcontact.owner by system and assume }", - "{ grant perm view on hs_office_partner#ErbenBesslerMelBessler-forthcontact to role hs_office_partner#ErbenBesslerMelBessler-forthcontact.tenant by system and assume }", - "{ grant role hs_office_contact#forthcontact.tenant to role hs_office_partner#ErbenBesslerMelBessler-forthcontact.tenant by system and assume }", - "{ grant role hs_office_person#ErbenBesslerMelBessler.tenant to role hs_office_partner#ErbenBesslerMelBessler-forthcontact.tenant by system and assume }", - "{ grant role hs_office_partner#ErbenBesslerMelBessler-forthcontact.tenant to role hs_office_person#ErbenBesslerMelBessler.admin by system and assume }")); + "hs_office_partner#ErbenBesslerMelBessler-forthcontact.tenant", + "hs_office_partner#ErbenBesslerMelBessler-forthcontact.guest")); + assertThat(grantDisplaysOf(rawGrantRepo.findAll())) + .map(s -> s.replace("ErbenBesslerMelBessler", "EBess")) + .map(s -> s.replace("forthcontact", "4th")) + .map(s -> s.replace("hs_office_", "")) + .containsExactlyInAnyOrder(Array.fromFormatted( + initialGrantNames, + // owner + "{ grant perm * on partner#EBess-4th to role partner#EBess-4th.owner by system and assume }", + "{ grant role partner#EBess-4th.owner to role global#global.admin by system and assume }", + + // admin + "{ grant perm edit on partner#EBess-4th to role partner#EBess-4th.admin by system and assume }", + "{ grant role partner#EBess-4th.admin to role partner#EBess-4th.owner by system and assume }", + "{ grant role person#EBess.tenant to role partner#EBess-4th.admin by system and assume }", + "{ grant role contact#4th.tenant to role partner#EBess-4th.admin by system and assume }", + + // agent + "{ grant role partner#EBess-4th.agent to role partner#EBess-4th.admin by system and assume }", + "{ grant role partner#EBess-4th.agent to role person#EBess.admin by system and assume }", + "{ grant role partner#EBess-4th.agent to role contact#4th.admin by system and assume }", + + // tenant + "{ grant role partner#EBess-4th.tenant to role partner#EBess-4th.agent by system and assume }", + "{ grant role person#EBess.guest to role partner#EBess-4th.tenant by system and assume }", + "{ grant role contact#4th.guest to role partner#EBess-4th.tenant by system and assume }", + + // guest + "{ grant perm view on partner#EBess-4th to role partner#EBess-4th.guest by system and assume }", + "{ grant role partner#EBess-4th.guest to role partner#EBess-4th.tenant by system and assume }", + null)); } private void assertThatPartnerIsPersisted(final HsOfficePartnerEntity saved) { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonRepositoryIntegrationTest.java index 588bd13d..745f7482 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonRepositoryIntegrationTest.java @@ -110,7 +110,8 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTest { initialRoleNames, "hs_office_person#anothernewperson.owner", "hs_office_person#anothernewperson.admin", - "hs_office_person#anothernewperson.tenant" + "hs_office_person#anothernewperson.tenant", + "hs_office_person#anothernewperson.guest" )); assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder( Array.from( @@ -120,7 +121,8 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTest { "{ grant role hs_office_person#anothernewperson.tenant to role hs_office_person#anothernewperson.admin by system and assume }", "{ grant perm * on hs_office_person#anothernewperson to role hs_office_person#anothernewperson.owner by system and assume }", "{ grant role hs_office_person#anothernewperson.admin to role hs_office_person#anothernewperson.owner by system and assume }", - "{ grant perm view on hs_office_person#anothernewperson to role hs_office_person#anothernewperson.tenant by system and assume }", + "{ grant perm view on hs_office_person#anothernewperson to role hs_office_person#anothernewperson.guest by system and assume }", + "{ grant role hs_office_person#anothernewperson.guest to role hs_office_person#anothernewperson.tenant by system and assume }", "{ grant role hs_office_person#anothernewperson.owner to user selfregistered-user-drew@hostsharing.org by global#global.admin and assume }" )); } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java index be6f3a72..599e2a7f 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java @@ -135,6 +135,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest { "{ grant role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant to role hs_office_contact#forthcontact.admin by system and assume }", "{ grant role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant to role hs_office_person#BesslerAnita.admin by system and assume }", + "{ grant role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.admin by system and assume }", "{ grant role hs_office_contact#forthcontact.tenant to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant by system and assume }", "{ grant role hs_office_person#BesslerAnita.tenant to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant by system and assume }", null) @@ -353,7 +354,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest { assertThat(rawRoleRepo.findAll().size()).as("unexpected number of roles created") .isEqualTo(initialRoleNames.length + 3); assertThat(rawGrantRepo.findAll().size()).as("unexpected number of grants created") - .isEqualTo(initialGrantNames.length + 12); + .isEqualTo(initialGrantNames.length + 13); // when final var result = jpaAttempt.transacted(() -> {