fix partner rbac system and tests

This commit is contained in:
Michael Hoennig 2024-03-19 09:06:05 +01:00
parent 5e0d9df6f1
commit 74b20ed86c
26 changed files with 371 additions and 190 deletions

View File

@ -0,0 +1,27 @@
Ich habe mal wieder vom RBAC-System geträumt 🙈 Ok, im Halbschlaf darüber nachgedacht trifft es wohl besser. Und jetzt frage ich mich, ob wir viel zu kompliziert gedacht haben.
Bislang gingen wir ja davon aus, dass, wenn komplexe Entitäten (z.B. Partner) erzeugt werden, wir wir über den INSERT-Trigger den Rollen der verknüpften Entitäten (z.B. den Rollen der Personendaten des Partners) auch Rechte an den komplexeren Entitäten und umgekehrt geben müssen.
Da die komplexen Entitäten nur mit gewissen verbundenen Entitäten überhaupt sinnvoll nutzbar sind und diese daher über INNSER JOINs mitladen, könnte sonst auch nur jemand diese Entitäten, der auch die SELECT-Permission an den verküpften Entitäten hat.
Vor einigen Wochen hatten wir schon einmal darüber geredet, ob wir dieses Geflecht wirklich komplett durchplanen müssen, also über mehrere Stufen hinweg, oder ob sehr warscheinlich eh dieselben Leuten an den weiter entfernten Entitäten die nötien Rechte haben, weil dahinter dieselben User stehen. Also z.B. dass gewährleistet ist, dass jemand mit ADMIN-Recht an den Personendaten des Partners auch bis in die SEPA-Mandate eines Debitors hineinsehen kann.
Und nun gehe ich noch einen Schritt weiter: Könnte es nicht auch andersherum sein? Also wenn jemand z.B. SELECT-Recht am Partner hat, dass wir davon ausgehen können, dass derjenige auch die Partner-Personen- und Kontaktdaten sehen darf, und zwar implizit durch seine Partner-SELECT-Permission und ohne dass er explizit Rollen für diese Partner-Personen oder Kontaktdaten inne hat?
Im Halbschlaf kam mir nur die Idee, warum wir nicht einfach die komplexen JPA-Entitäten zwar auf die restricted View setzen, wie bisher, aber für die verknüpften Entitäten auf die direkten (bisher "Raw..." genannt) Entitäten gehen. Dann könnte jemand mit einer Rolle, welche die SELECT-Permission auf die komplexe JPA-Entität (z.B.) Partner inne hat, auch die dazugehörige Relation(ship) ["Relationship" wurde vor kurzem auf kurz "Relation" umbenannt] und die wiederum dazu gehörigen Personen- und Kontaktdaten lesen, ohne dass in einem INSERT- und UPDATE-Trigger der Partner-Entität die ganzen Grants mit den verknüpften Entäten aufgebaut und aktualisiert werden müssen.
Beim Debitor ist das nämlich selbst mit Generator die Hölle, zumal eben auch Querverbindungen gegranted werden müssen, z.B. von der Debitor-Person zum Sema-Mandat - jedenfalls wenn man nicht Gefahr laufen wollte, dass jemand mit Admin-Rechten an der Partner-Person (also z.B. ein Repräsentant des Partners) die Sepa-Mandate der Debitoren gar nicht mehr sehen kann. Natürlich bräuchte man immer noch die Agent-Rolle am Partner und Debitor (evtl. repräsentiert durch die jeweils zugehörigen Relation - falls dieser Trick überhaupt noch nötig wäre), sowie ein Grant vom Partner-Agent auf den Debitor-Agent und vom Debitor-Agent auf die Sepa-Mandate-Admins, aber eben ohne filigran die ganzen Neben-Entäten (Personen- und Kontaktdaten von Partner und Debitor sowie Bank-Account) in jedem Trigger berücksichtigen zu müssen. Beim Refund-Bank-Account sogar besonders ätzend, weil der optional ist und dadurch zig "if ...refundBankAccountUuid is not null then ..." im Code enstehen (wenn der auch generiert ist).
Mit anderen Worten, um als Repräsentant eines Geschäftspartners auf den Bank-Account der Sepa-Mandate sehen zu dürfen, wird derzeut folgende Grant-Kette durchlaufen (bzw. eben noch nicht, weil es noch nicht funktioniert):
User -> Partner-Holder-Person:Admin -> Partner-Relation:Agent -> Debitor-Relation:Agent -> Sepa-Mandat:Admin -> BankAccount:Admin -> BankAccount:SELECT
Daraus würde:
User -> Partner-Relation:Agent -> Debitor-Relation:Agent -> Sepa-Mandat:Admin -> Sepa-Mandat:SELECT*
(*mit JOIN auf RawBankAccount, also implizitem Leserecht)
Das klingt zunächst nach nur einer marginalen Vereinfachung, die eigentlich Vereinfachung liegt aber im Erzeugen der Grants in den Triggern, denn da sind zudem noch Partner-Anchor-Person, Debitor-Holder- und Anchor-Person, Partner- und Debitor-Contact sowie der RefundBankAccount zu berücksichtigen. Und genau diese Grants würden großteils wegfallen, und durch implizite Persmissions über die JOINs auf die Raw-Tables ersetzt werden. Den refundBankAccound müssten wir dann, analog zu den Sepa-Mandataten, umgedreht modellieren, da den sonst
Man könnte das Ganze auch als "Entwicklung der Rechtestruktur für Hosting-Entitäten auf der obersten Ebene" (Manged Webspace, Managed Server, Cloud Server etc.) sehen, denn die hängen alle unter dem Mega-komplexen Debitor.

View File

@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.office.debitor;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;

View File

@ -94,12 +94,12 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
public static RbacView rbac() {
return rbacViewFor("partner", HsOfficePartnerEntity.class)
.withIdentityView(SQL.projection("'P-' || partnerNumber"))
.withUpdatableColumns("partnerroleuuid")
.withUpdatableColumns("partnerRoleUuid")
.toRole("global", ADMIN).grantPermission(INSERT) // FIXME: global -> partnerRel.relAnchor?
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationshipEntity.class,
fetchedBySql("SELECT * FROM hs_office_relationship AS r WHERE r.uuid = ${ref}.partnerRoleUuid"),
dependsOnColumn("partnerRelUuid"))
dependsOnColumn("partnerRoleUuid"))
.createPermission(DELETE).grantedTo("partnerRel", ADMIN)
.createPermission(UPDATE).grantedTo("partnerRel", AGENT)
.createPermission(SELECT).grantedTo("partnerRel", TENANT)

View File

@ -132,6 +132,7 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1));
}
resource.getDebitor().setDebitorNumber(entity.getDebitor().getDebitorNumber());
};
final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {

View File

@ -46,6 +46,7 @@ public class RbacGrantsDiagramService {
public static final EnumSet<Include> ALL = EnumSet.allOf(Include.class);
public static final EnumSet<Include> ALL_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, TEST_ENTITIES, PERMISSIONS);
public static final EnumSet<Include> ALL_NON_TEST_ENTITY_RELATED = EnumSet.of(USERS, DETAILS, NOT_ASSUMED, NON_TEST_ENTITIES, PERMISSIONS);
}
@Autowired

View File

@ -3,7 +3,7 @@
-- ============================================================================
-- NUMERIC-HASH-FUNCTIONS
--changeset hash:1 endDelimiter:--//
--changeset numeric-hash-functions:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create function bigIntHash(text) returns bigint as $$

View File

@ -0,0 +1,20 @@
--liquibase formatted sql
-- ============================================================================
-- TABLE-COLUMNS-FUNCTION
--changeset table-columns-function:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function columnsNames( tableName text )
returns text
stable
language 'plpgsql' as $$
declare columns text[];
begin
columns := (select array(select column_name::text
from information_schema.columns
where table_name = tableName));
return array_to_string(columns, ', ');
end; $$
--//

View File

@ -224,6 +224,7 @@ create or replace function currentSubjects()
declare
assumedRoles varchar(63)[];
begin
return assumedRoles();
assumedRoles := assumedRoles();
if array_length(assumedRoles, 1) > 0 then
return assumedRoles();

View File

@ -397,7 +397,10 @@ begin
raise exception 'forOpTableName must only be specified for ops: [INSERT]'; -- currently no other
end if;
permissionUuid = (select uuid from RbacPermission where objectUuid = forObjectUuid and op = forOp and opTableName = forOpTableName);
permissionUuid := (
select uuid from RbacPermission
where objectUuid = forObjectUuid
and op = forOp and opTableName is not distinct from forOpTableName);
if (permissionUuid is null) then
insert into RbacReference ("type")
values ('RbacPermission')

View File

@ -157,12 +157,16 @@ end; $$;
--changeset rbac-generators-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace procedure generateRbacRestrictedView(targetTable text, orderBy text, columnUpdates text = null)
create or replace procedure generateRbacRestrictedView(targetTable text, orderBy text, columnUpdates text = null, columnNames text = '*')
language plpgsql as $$
declare
sql text;
newColumns text;
begin
targetTable := lower(targetTable);
if columnNames = '*' then
columnNames := columnsNames(targetTable);
end if;
/*
Creates a restricted view based on the 'SELECT' permission of the current subject.
@ -184,6 +188,7 @@ begin
/**
Instead of insert trigger function for the restricted view.
*/
newColumns := 'new.' || replace(columnNames, ',', ', new.');
sql := format($sql$
create or replace function %1$sInsert()
returns trigger
@ -192,12 +197,12 @@ begin
newTargetRow %1$s;
begin
insert
into %1$s
values (new.*)
into %1$s (%2$s)
values (%3$s)
returning * into newTargetRow;
return newTargetRow;
end; $f$;
$sql$, targetTable);
$sql$, targetTable, columnNames, newColumns);
execute sql;
/*

View File

@ -1,6 +1,6 @@
### rbac person
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-13T15:28:12.347550922.
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-18T13:35:44.716916229.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-13T15:28:12.357028926.
-- This code generated was by RbacViewPostgresGenerator at 2024-03-18T13:35:44.726508114.
-- ============================================================================
@ -37,8 +37,8 @@ begin
perform createRoleWithGrants(
hsOfficePersonOwner(NEW),
permissions => array['DELETE'],
userUuids => array[currentUserUuid()],
incomingSuperRoles => array[globalAdmin()]
incomingSuperRoles => array[globalAdmin()],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
@ -109,26 +109,16 @@ create or replace function hs_office_person_global_insert_tf()
strict as $$
begin
call grantPermissionToRole(
globalGuest(),
createPermission(NEW.uuid, 'INSERT', 'hs_office_person'));
createPermission(NEW.uuid, 'INSERT', 'hs_office_person'),
globalGuest());
return NEW;
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_hs_office_person_global_insert_tg
after insert on global
for each row
execute procedure hs_office_person_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_person.
*/
create or replace function hs_office_person_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_person not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end; $$;
--//
-- ============================================================================
@ -152,6 +142,15 @@ call generateRbacRestrictedView('hs_office_person',
tradeName = new.tradeName,
givenName = new.givenName,
familyName = new.familyName
$updates$);
$updates$
,
$columns$
uuid,
personType,
tradeName,
givenName,
familyName
$columns$
);
--//

View File

@ -1,6 +1,6 @@
### rbac partner
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-16T12:04:46.219584452.
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-18T09:31:47.361311186.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-16T12:04:46.225548817.
-- This code generated was by RbacViewPostgresGenerator at 2024-03-18T09:31:47.368892199.
-- ============================================================================
@ -37,7 +37,7 @@ begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_relationship AS r WHERE r.uuid = NEW.partnerRoleUuid INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid);
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRoleUuid = %s', NEW.partnerRoleUuid);
SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = NEW.detailsUuid INTO newPartnerDetails;
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
@ -72,6 +72,117 @@ execute procedure insertTriggerForHsOfficePartner_tf();
--//
-- ============================================================================
--changeset hs-office-partner-rbac-update-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
*/
create or replace procedure updateRbacRulesForHsOfficePartner(
OLD hs_office_partner,
NEW hs_office_partner
)
language plpgsql as $$
declare
oldPartnerRel hs_office_relationship;
newPartnerRel hs_office_relationship;
oldPartnerDetails hs_office_partner_details;
newPartnerDetails hs_office_partner_details;
begin
call enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office_relationship AS r WHERE r.uuid = OLD.partnerRoleUuid INTO oldPartnerRel;
assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRoleUuid = %s', OLD.partnerRoleUuid);
SELECT * FROM hs_office_relationship AS r WHERE r.uuid = NEW.partnerRoleUuid INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRoleUuid = %s', NEW.partnerRoleUuid);
SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = OLD.detailsUuid INTO oldPartnerDetails;
assert oldPartnerDetails.uuid is not null, format('oldPartnerDetails must not be null for OLD.detailsUuid = %s', OLD.detailsUuid);
SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = NEW.detailsUuid INTO newPartnerDetails;
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
if NEW.partnerRoleUuid <> OLD.partnerRoleUuid then
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'DELETE'), hsOfficeRelationshipAdmin(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'DELETE'), hsOfficeRelationshipAdmin(newPartnerRel));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'UPDATE'), hsOfficeRelationshipAgent(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'UPDATE'), hsOfficeRelationshipAgent(newPartnerRel));
call revokePermissionFromRole(getPermissionId(OLD.uuid, 'SELECT'), hsOfficeRelationshipTenant(oldPartnerRel));
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationshipTenant(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'DELETE'), hsOfficeRelationshipAdmin(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'DELETE'), hsOfficeRelationshipAdmin(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationshipAgent(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'UPDATE'), hsOfficeRelationshipAgent(newPartnerRel));
call revokePermissionFromRole(getPermissionId(oldPartnerDetails.uuid, 'SELECT'), hsOfficeRelationshipAgent(oldPartnerRel));
call grantPermissionToRole(createPermission(newPartnerDetails.uuid, 'SELECT'), hsOfficeRelationshipAgent(newPartnerRel));
end if;
call leaveTriggerForObjectUuid(NEW.uuid);
-- raise exception 'RBAC updated from rel % to %', OLD.partnerroleuuid, NEW.partnerroleuuid;
end; $$;
create or replace procedure updateRbacRulesForHsOfficePartnerX(
OLD hs_office_partner,
NEW hs_office_partner
)
language plpgsql as $$
declare
partnerRel hs_office_relationship;
grantCount int;
begin
assert OLD.uuid = NEW.uuid, 'uuid did change, but should not';
assert OLD.partnerroleuuid <> NEW.partnerroleuuid, 'partnerroleuuid did not change, but should have';
delete from rbacgrants where grantedbytriggerof = OLD.uuid;
select count(*) from rbacgrants where grantedbytriggerof=NEW.uuid into grantCount;
assert grantCount=0, format('unexpected grantCount>0: %d', grantCount);
call buildRbacSystemForHsOfficePartner(NEW);
select * from hs_office_relationship where uuid=NEW.partnerroleuuid into partnerRel;
call grantPermissionToRole(createPermission(NEW.uuid, 'SELECT'), hsOfficeRelationshipTenant(partnerRel));
select count(*) from rbacgrants where grantedbytriggerof=NEW.uuid into grantCount;
assert grantCount>0, format('unexpected grantCount=0: %d', grantCount);
raise warning 'WARNING grantCount=%', grantCount;
end; $$;
/*
AFTER INSERT TRIGGER to re-wire the grant structure for a new hs_office_partner row.
*/
create or replace function updateTriggerForHsOfficePartner_tf()
returns trigger
language plpgsql
strict as $$
begin
call updateRbacRulesForHsOfficePartnerX(OLD, NEW);
return NEW;
end; $$;
create trigger updateTriggerForHsOfficePartner_tg
after update on hs_office_partner
for each row
execute procedure updateTriggerForHsOfficePartner_tf();
--//
-- ============================================================================
--changeset hs-office-partner-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
@ -151,7 +262,7 @@ call generateRbacRestrictedView('hs_office_partner',
'P-' || partnerNumber
$orderBy$,
$updates$
partnerroleuuid = new.partnerroleuuid
partnerRoleUuid = new.partnerRoleUuid
$updates$);
--//

View File

@ -1,6 +1,6 @@
### rbac sepaMandate
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-15T17:18:45.736693565.
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-18T16:07:14.011240343.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-15T17:18:45.747792100.
-- This code generated was by RbacViewPostgresGenerator at 2024-03-18T16:07:14.019894954.
-- ============================================================================
@ -72,9 +72,9 @@ begin
hsOfficeSepaMandateReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[
hsOfficeSepaMandateAgent(NEW),
hsOfficeBankAccountAdmin(newBankAccount),
hsOfficeRelationshipAgent(newDebitorRel),
hsOfficeSepaMandateAgent(NEW)],
hsOfficeRelationshipAgent(newDebitorRel)],
outgoingSubRoles => array[hsOfficeRelationshipTenant(newDebitorRel)]
);
@ -139,6 +139,7 @@ begin
return NEW;
end; $$;
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_hs_office_sepamandate_hs_office_relationship_insert_tg
after insert on hs_office_relationship
for each row

View File

@ -48,9 +48,9 @@ end; $$;
do language plpgsql $$
begin
call createHsOfficeSepaMandateTestData(10001, 11, 'DE02120300000000202051', 'ref-11110001');
call createHsOfficeSepaMandateTestData(10002, 12, 'DE02100500000054540402', 'ref-11120002');
call createHsOfficeSepaMandateTestData(10003, 13, 'DE02300209000106531065', 'ref-11130003');
call createHsOfficeSepaMandateTestData(10001, 11, 'DE02120300000000202051', 'ref-10001-11');
call createHsOfficeSepaMandateTestData(10002, 12, 'DE02100500000054540402', 'ref-10002-12');
call createHsOfficeSepaMandateTestData(10003, 13, 'DE02300209000106531065', 'ref-10003-13');
end;
$$;
--//

View File

@ -1,6 +1,6 @@
### rbac membership
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-12T17:26:50.174602110.
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-18T16:31:23.329131137.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
@ -150,7 +150,7 @@ role:membership:admin ==> role:membership:referrer
role:membership:referrer ==> role:partnerRel:tenant
%% granting permissions to roles
role:global:admin ==> perm:membership:INSERT
role:partnerRel:admin ==> perm:membership:INSERT
role:membership:owner ==> perm:membership:DELETE
role:membership:admin ==> perm:membership:UPDATE
role:membership:referrer ==> perm:membership:SELECT

View File

@ -1,5 +1,5 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-12T17:26:50.179864268.
-- This code generated was by RbacViewPostgresGenerator at 2024-03-18T16:31:23.334581734.
-- ============================================================================
@ -46,8 +46,8 @@ begin
perform createRoleWithGrants(
hsOfficeMembershipOwner(NEW),
permissions => array['DELETE'],
userUuids => array[currentUserUuid()],
incomingSuperRoles => array[hsOfficeRelationshipAdmin(newPartnerRel)]
incomingSuperRoles => array[hsOfficeRelationshipAdmin(newPartnerRel)],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
@ -91,19 +91,19 @@ execute procedure insertTriggerForHsOfficeMembership_tf();
-- ----------------------------------------------------------------------------
/*
Creates INSERT INTO hs_office_membership permissions for the related global rows.
Creates INSERT INTO hs_office_membership permissions for the related hs_office_relationship rows.
*/
do language plpgsql $$
declare
row global;
row hs_office_relationship;
permissionUuid uuid;
roleUuid uuid;
begin
call defineContext('create INSERT INTO hs_office_membership permissions for the related global rows');
call defineContext('create INSERT INTO hs_office_membership permissions for the related hs_office_relationship rows');
FOR row IN SELECT * FROM global
FOR row IN SELECT * FROM hs_office_relationship
LOOP
roleUuid := findRoleId(globalAdmin());
roleUuid := findRoleId(hsOfficeRelationshipAdmin(row));
permissionUuid := createPermission(row.uuid, 'INSERT', 'hs_office_membership');
call grantPermissionToRole(permissionUuid, roleUuid);
END LOOP;
@ -111,23 +111,24 @@ do language plpgsql $$
$$;
/**
Adds hs_office_membership INSERT permission to specified role of new global rows.
Adds hs_office_membership INSERT permission to specified role of new hs_office_relationship rows.
*/
create or replace function hs_office_membership_global_insert_tf()
create or replace function hs_office_membership_hs_office_relationship_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
globalAdmin(),
createPermission(NEW.uuid, 'INSERT', 'hs_office_membership'));
createPermission(NEW.uuid, 'INSERT', 'hs_office_membership'),
hsOfficeRelationshipAdmin(NEW));
return NEW;
end; $$;
create trigger z_hs_office_membership_global_insert_tg
after insert on global
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
create trigger z_hs_office_membership_hs_office_relationship_insert_tg
after insert on hs_office_relationship
for each row
execute procedure hs_office_membership_global_insert_tf();
execute procedure hs_office_membership_hs_office_relationship_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_membership.
@ -136,14 +137,26 @@ create or replace function hs_office_membership_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
raise exception '[403] insert into hs_office_membership not allowed for current subjects % (%)',
if ( not hasInsertPermission(
( SELECT partnerRel.uuid FROM
(SELECT r.*
FROM hs_office_partner AS p
JOIN hs_office_relationship AS r ON r.uuid = p.partnerRoleUuid
WHERE p.uuid = NEW.partnerUuid
) AS partnerRel
), 'INSERT', 'hs_office_membership') ) then
raise exception
'[403] insert into hs_office_membership not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end if;
return NEW;
end; $$;
create trigger hs_office_membership_insert_permission_check_tg
before insert on hs_office_membership
for each row
when ( not isGlobalAdmin() )
execute procedure hs_office_membership_insert_permission_missing_tf();
--//

View File

@ -11,6 +11,8 @@ databaseChangeLog:
file: db/changelog/005-uuid-ossp-extension.sql
- include:
file: db/changelog/006-numeric-hash-functions.sql
- include:
file: db/changelog/007-table-columns.sql
- include:
file: db/changelog/009-check-environment.sql
- include:

View File

@ -10,8 +10,6 @@ import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType
import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService;
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService.Include;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.test.Array;
import net.hostsharing.test.JpaAttempt;
@ -32,7 +30,6 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import static net.hostsharing.hsadminng.hs.office.test.EntityList.one;

View File

@ -25,6 +25,7 @@ import java.util.Arrays;
import java.util.List;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distinctGrantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantsDiagramService.Include.ALL_NON_TEST_ENTITY_RELATED;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;
@ -66,7 +67,6 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
context("superuser-alex@hostsharing.net");
final var count = membershipRepo.count();
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0);
// when
final var result = attempt(em, () -> {
@ -112,11 +112,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
final var all = rawRoleRepo.findAll();
assertThat(distinctRoleNamesOf(all)).containsExactlyInAnyOrder(Array.from(
initialRoleNames,
"hs_office_membership#1000117:FirstGmbH-firstcontact.admin",
"hs_office_membership#1000117:FirstGmbH-firstcontact.agent",
"hs_office_membership#1000117:FirstGmbH-firstcontact.guest",
"hs_office_membership#1000117:FirstGmbH-firstcontact.owner",
"hs_office_membership#1000117:FirstGmbH-firstcontact.tenant"));
"hs_office_membership#M-1000117.admin",
"hs_office_membership#M-1000117.owner",
"hs_office_membership#M-1000117.referrer"));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("GmbH-firstcontact", ""))
.map(s -> s.replace("hs_office_", ""))
@ -124,33 +122,18 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
initialGrantNames,
// owner
"{ grant perm DELETE on membership#1000117:First to role membership#1000117:First.owner by system and assume }",
"{ grant role membership#1000117:First.owner to role global#global.admin by system and assume }",
"{ grant perm DELETE on membership#M-1000117 to role membership#M-1000117.owner by system and assume }",
// admin
"{ grant perm UPDATE on membership#1000117:First to role membership#1000117:First.admin by system and assume }",
"{ grant role membership#1000117:First.admin to role membership#1000117:First.owner by system and assume }",
"{ grant perm UPDATE on membership#M-1000117 to role membership#M-1000117.admin by system and assume }",
"{ grant role membership#M-1000117.admin to role membership#M-1000117.owner by system and assume }",
"{ grant role membership#M-1000117.owner to role relationship#HostsharingeG-with-PARTNER-FirstGmbH.admin by system and assume }",
"{ grant role membership#M-1000117.owner to user superuser-alex@hostsharing.net by membership#M-1000117.owner and assume }",
// agent
"{ grant role membership#1000117:First.agent to role membership#1000117:First.admin by system and assume }",
"{ grant role partner#10001:First.tenant to role membership#1000117:First.agent by system and assume }",
"{ grant role membership#1000117:First.agent to role debitor#1000111:First.admin by system and assume }",
"{ grant role membership#1000117:First.agent to role partner#10001:First.admin by system and assume }",
"{ grant role debitor#1000111:First.tenant to role membership#1000117:First.agent by system and assume }",
// tenant
"{ grant role membership#1000117:First.tenant to role membership#1000117:First.agent by system and assume }",
"{ grant role partner#10001:First.guest to role membership#1000117:First.tenant by system and assume }",
"{ grant role debitor#1000111:First.guest to role membership#1000117:First.tenant by system and assume }",
"{ grant role membership#1000117:First.tenant to role debitor#1000111:First.agent by system and assume }",
"{ grant role membership#1000117:First.tenant to role partner#10001:First.agent by system and assume }",
// guest
"{ grant perm SELECT on membership#1000117:First to role membership#1000117:First.guest by system and assume }",
"{ grant role membership#1000117:First.guest to role membership#1000117:First.tenant by system and assume }",
"{ grant role membership#1000117:First.guest to role partner#10001:First.tenant by system and assume }",
"{ grant role membership#1000117:First.guest to role debitor#1000111:First.tenant by system and assume }",
// referrer
"{ grant perm SELECT on membership#M-1000117 to role membership#M-1000117.referrer by system and assume }",
"{ grant role membership#M-1000117.referrer to role membership#M-1000117.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-FirstGmbH.tenant to role membership#M-1000117.referrer by system and assume }",
null));
}
@ -175,9 +158,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
// then
exactlyTheseMembershipsAreReturned(
result,
"Membership(M-1000101, LP First GmbH, D-1000111, [2022-10-01,), NONE)",
"Membership(M-1000202, LP Second e.K., D-1000212, [2022-10-01,), NONE)",
"Membership(M-1000303, IF Third OHG, D-1000313, [2022-10-01,), NONE)");
"Membership(M-1000101, P-10001, [2022-10-01,), NONE)",
"Membership(M-1000202, P-10002, [2022-10-01,), NONE)",
"Membership(M-1000303, P-10003, [2022-10-01,), NONE)");
}
@Test
@ -191,7 +174,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
// then
exactlyTheseMembershipsAreReturned(result,
"Membership(M-1000101, LP First GmbH, D-1000111, [2022-10-01,), NONE)");
"Membership(M-1000101, P-10001, [2022-10-01,), NONE)");
}
@Test
@ -206,7 +189,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
assertThat(result)
.isNotNull()
.extracting(Object::toString)
.isEqualTo("Membership(M-1000202, LP Second e.K., D-1000212, [2022-10-01,), NONE)");
.isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), NONE)");
}
}
@ -218,9 +201,6 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
// given
context("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembership("First", "11");
assertThatMembershipIsVisibleForUserWithRole(
givenMembership,
"hs_office_debitor#1000111:FirstGmbH-firstcontact.admin");
assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership);
final var newValidityEnd = LocalDate.now();
@ -241,21 +221,22 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
}
@Test
public void debitorAdmin_canViewButNotUpdateRelatedMembership() {
public void membershipAdmin_canViewButNotUpdateRelatedMembership() {
// given
context("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembership("First", "13");
assertThatMembershipIsVisibleForUserWithRole(
givenMembership,
"hs_office_debitor#1000111:FirstGmbH-firstcontact.admin");
assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership);
assertThatMembershipIsVisibleForRole(
givenMembership,
"s_office_membership#M-1000101.admin");
final var newValidityEnd = LocalDate.now();
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", "hs_office_debitor#1000111:FirstGmbH-firstcontact.admin");
givenMembership.setValidity(Range.closedOpen(
givenMembership.getValidity().lower(), newValidityEnd));
// TODO: we should test with debitor- and partner-admin as well
context("superuser-alex@hostsharing.net", "hs_office_membership#M-1000101.admin");
givenMembership.setValidity(
Range.closedOpen(givenMembership.getValidity().lower(), newValidityEnd));
return membershipRepo.save(givenMembership);
});
@ -270,24 +251,15 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
.extracting(Object::toString).isEqualTo(saved.toString());
}
private void assertThatMembershipIsVisibleForUserWithRole(
private void assertThatMembershipIsVisibleForRole(
final HsOfficeMembershipEntity entity,
final String assumedRoles) {
jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", assumedRoles);
generateRbacDiagramForCurrentSubjects(ALL_NON_TEST_ENTITY_RELATED);
assertThatMembershipExistsAndIsAccessibleToCurrentContext(entity);
}).assertSuccessful();
}
private void assertThatMembershipIsNotVisibleForUserWithRole(
final HsOfficeMembershipEntity entity,
final String assumedRoles) {
jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", assumedRoles);
final var found = membershipRepo.findByUuid(entity.getUuid());
assertThat(found).isEmpty();
}).assertSuccessful();
}
}
@Nested
@ -314,14 +286,14 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
}
@Test
public void nonGlobalAdmin_canNotDeleteTheirRelatedMembership() {
public void debitorRelationAgent_canNotDeleteTheirRelatedMembership() {
// given
context("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembership("First", "14");
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", "hs_office_debitor#1000313:ThirdOHG-thirdcontact.admin");
context("superuser-alex@hostsharing.net", "hs_office_relationship#ThirdOHG-with-ACCOUNTING-ThirdOHG.agent");
assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent();
membershipRepo.deleteByUuid(givenMembership.getUuid());
@ -344,10 +316,6 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
final var initialRoleNames = Array.from(distinctRoleNamesOf(rawRoleRepo.findAll()));
final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll()));
final var givenMembership = givenSomeTemporaryMembership("First", "15");
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll()).size()).as("precondition failed: unexpected number of roles created")
.isEqualTo(initialRoleNames.length + 5);
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll()).size()).as("precondition failed: unexpected number of grants created")
.isEqualTo(initialGrantNames.length + 18);
// when
final var result = jpaAttempt.transacted(() -> {
@ -377,8 +345,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
// then
assertThat(customerLogEntries).map(Arrays::toString).contains(
"[creating Membership test-data FirstGmbH11, hs_office_membership, INSERT]",
"[creating Membership test-data Seconde.K.12, hs_office_membership, INSERT]");
"[creating Membership test-data P-10001M-...01, hs_office_membership, INSERT]",
"[creating Membership test-data P-10002M-...02, hs_office_membership, INSERT]",
"[creating Membership test-data P-10003M-...03, hs_office_membership, INSERT]");
}
private HsOfficeMembershipEntity givenSomeTemporaryMembership(final String partnerTradeName, final String memberNumberSuffix) {

View File

@ -142,59 +142,43 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
.map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(distinct(fromFormatted(
initialGrantNames,
// FIXME: this entry is wrong in existance and format
"{ grant perm INSERT on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
// permissions on partner
"{ grant perm * on partner#20032:EBess-4th to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant perm edit on partner#20032:EBess-4th to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant perm view on partner#20032:EBess-4th to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant perm DELETE on partner#P-20032 to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant perm UPDATE on partner#P-20032 to role relationship#HostsharingeG-with-PARTNER-EBess.agent by system and assume }",
"{ grant perm SELECT on partner#P-20032 to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
// permissions on partner-details
"{ grant perm * on partner_details#20032:EBess-4th-details to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant perm edit on partner_details#20032:EBess-4th-details to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant perm view on partner_details#20032:EBess-4th-details to role relationship#HostsharingeG-with-PARTNER-EBess.agent by system and assume }",
"{ grant perm DELETE on partner_details#P-20032-details to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant perm UPDATE on partner_details#P-20032-details to role relationship#HostsharingeG-with-PARTNER-EBess.agent by system and assume }",
"{ grant perm SELECT on partner_details#P-20032-details to role relationship#HostsharingeG-with-PARTNER-EBess.agent by system and assume }",
// permissions on partner-relationship
"{ grant perm DELETE on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant perm UPDATE on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant perm SELECT on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
// relationship owner
"{ grant perm * on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.owner to role global#global.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.owner to user superuser-alex@hostsharing.net by relationship#HostsharingeG-with-PARTNER-EBess.owner and assume }",
// relationship admin
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.admin to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.admin to role person#HostsharingeG.admin by system and assume }",
// relationship agent
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.agent to role person#EBess.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.agent to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
// relationship tenant
"{ grant role contact#4th.referrer to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role person#EBess.referrer to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role person#HostsharingeG.referrer to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role contact#4th.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role person#EBess.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.owner to role person#HostsharingeG.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role person#HostsharingeG.admin by system and assume }",
"{ grant perm UPDATE on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant perm DELETE on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.admin to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant perm SELECT on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role contact#4th.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role person#EBess.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role person#HostsharingeG.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
// owner
"{ grant perm DELETE on partner#20032:EBess-4th to role partner#20032:EBess-4th.owner by system and assume }",
"{ grant perm DELETE on partner_details#20032:EBess-4th-details to role partner#20032:EBess-4th.owner by system and assume }",
"{ grant role partner#20032:EBess-4th.owner to role global#global.admin by system and assume }",
// admin
"{ grant perm UPDATE on partner#20032:EBess-4th to role partner#20032:EBess-4th.admin by system and assume }",
"{ grant perm UPDATE on partner_details#20032:EBess-4th-details to role partner#20032:EBess-4th.admin by system and assume }",
"{ grant role partner#20032:EBess-4th.admin to role partner#20032:EBess-4th.owner by system and assume }",
"{ grant role person#EBess.tenant to role partner#20032:EBess-4th.admin by system and assume }",
"{ grant role contact#4th.tenant to role partner#20032:EBess-4th.admin by system and assume }",
// agent
"{ grant perm SELECT on partner_details#20032:EBess-4th-details to role partner#20032:EBess-4th.agent by system and assume }",
"{ grant role partner#20032:EBess-4th.agent to role partner#20032:EBess-4th.admin by system and assume }",
"{ grant role partner#20032:EBess-4th.agent to role person#EBess.admin by system and assume }",
"{ grant role partner#20032:EBess-4th.agent to role contact#4th.admin by system and assume }",
// tenant
"{ grant role partner#20032:EBess-4th.tenant to role partner#20032:EBess-4th.agent by system and assume }",
"{ grant role person#EBess.guest to role partner#20032:EBess-4th.tenant by system and assume }",
"{ grant role contact#4th.guest to role partner#20032:EBess-4th.tenant by system and assume }",
// guest
"{ grant perm SELECT on partner#20032:EBess-4th to role partner#20032:EBess-4th.guest by system and assume }",
"{ grant role partner#20032:EBess-4th.guest to role partner#20032:EBess-4th.tenant by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.agent by system and assume }",
null)));
}
@ -295,19 +279,21 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
// then
result.assertSuccessful();
generateRbacDiagramForObjectPermission(givenPartner.getUuid(), "SELECT", "partner-updated");
assertThatPartnerIsVisibleForUserWithRole(
result.returnedValue(),
givenPartner,
"global#global.admin");
assertThatPartnerIsVisibleForUserWithRole(
result.returnedValue(),
givenPartner,
"hs_office_person#ThirdOHG.admin");
assertThatPartnerIsNotVisibleForUserWithRole(
result.returnedValue(),
givenPartner,
"hs_office_person#ErbenBesslerMelBessler.admin");
}
@Test
public void partnerAgent_canNotUpdateRelatedPartner() {
public void partnerRelationAgent_canUpdateRelatedPartner() {
// given
context("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryHostsharingPartner(20037, "Erben Bessler", "ninth");
@ -324,9 +310,33 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
return partnerRepo.save(givenPartner);
});
// then
result.assertSuccessful();
}
@Test
public void partnerRelationTenant_canNotUpdateRelatedPartner() {
// given
context("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryHostsharingPartner(20037, "Erben Bessler", "ninth");
assertThatPartnerIsVisibleForUserWithRole(
givenPartner,
"hs_office_person#ErbenBesslerMelBessler.admin");
assertThatPartnerActuallyInDatabase(givenPartner);
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net",
"hs_office_relationship#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.tenant");
givenPartner.getDetails().setBirthName("new birthname");
return partnerRepo.save(givenPartner);
});
// then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
"[403] Subject ", " is not allowed to update hs_office_partner_details uuid");
// FIXME: the assumed role should appear, but it does not:
//"[403] insert into hs_office_partner_details not allowed for current subjects {hs_office_relationship#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.tenant}");
"[403] insert into hs_office_partner_details not allowed for current subjects");
}
private void assertThatPartnerActuallyInDatabase(final HsOfficePartnerEntity saved) {

View File

@ -90,7 +90,6 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
public void createsAndGrantsRoles() {
// given
context("selfregistered-user-drew@hostsharing.org");
final var count = personRepo.count();
final var initialRoleNames = distinctRoleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = distinctGrantDisplaysOf(rawGrantRepo.findAll());
@ -110,6 +109,9 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(
Array.from(
initialGrantNames,
// FIXME: the INSERT grant is wrong in format and existence
"{ grant perm INSERT on hs_office_person#anothernewperson to role hs_office_person#anothernewperson.admin by system and assume }",
"{ grant role hs_office_person#anothernewperson.owner to user selfregistered-user-drew@hostsharing.org by hs_office_person#anothernewperson.owner and assume }",
"{ grant role hs_office_person#anothernewperson.owner to role global#global.admin by system and assume }",
"{ grant perm UPDATE on hs_office_person#anothernewperson to role hs_office_person#anothernewperson.admin by system and assume }",

View File

@ -270,7 +270,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
"holder": "First GmbH",
"iban": "DE02120300000000202051"
},
"reference": "refFirstGmbH",
"reference": "ref-10001-11",
"validFrom": "2022-10-01",
"validTo": "2026-12-31"
}
@ -322,7 +322,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
"holder": "First GmbH",
"iban": "DE02120300000000202051"
},
"reference": "refFirstGmbH",
"reference": "ref-10001-11",
"validFrom": "2022-10-01",
"validTo": "2026-12-31"
}

View File

@ -18,6 +18,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import jakarta.persistence.*;
import java.lang.reflect.Method;
import java.util.*;
import static java.lang.System.out;
@ -57,6 +58,8 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
private Set<String> initialRbacRoles;
private Set<String> initialRbacGrants;
private TestInfo testInfo;
public <T extends RbacObject> T refresh(final T entity) {
final var merged = em.merge(entity);
em.refresh(merged);
@ -159,6 +162,11 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
return currentCount;
}
@BeforeEach
void keepTestInfo(final TestInfo testInfo) {
this.testInfo = testInfo;
}
@AfterEach
void cleanupAndCheckCleanup(final TestInfo testInfo) {
out.println(ContextBasedTestWithCleanup.class.getSimpleName() + ".cleanupAndCheckCleanup");
@ -265,13 +273,25 @@ public abstract class ContextBasedTestWithCleanup extends ContextBasedTest {
/**
* Generates a diagram of the RBAC-Grants to the current subjects (user or assumed roles).
*/
protected void generateRbacGrantsDiagram(final EnumSet<RbacGrantsDiagramService.Include> include, final String title) {
protected void generateRbacDiagramForCurrentSubjects(final EnumSet<RbacGrantsDiagramService.Include> include) {
final var title = testInfo.getTestMethod().map(Method::getName).orElseThrow();
RbacGrantsDiagramService.writeToFile(
title,
diagramService.allGrantsToCurrentUser(include),
"doc/" + title + ".md"
);
}
/**
* Generates a diagram of the RBAC-Grants for the given object and permission.
*/
protected void generateRbacDiagramForObjectPermission(final UUID targetObject, final String rbacOp, final String name) {
RbacGrantsDiagramService.writeToFile(
name,
diagramService.allGrantsFrom(targetObject, rbacOp, RbacGrantsDiagramService.Include.ALL),
"doc/temp/" + name + ".md"
);
}
}
interface RbacObjectRepository extends Repository<RbacObjectEntity, UUID> {