test-data-generation working up to membership, fails in coop-shares

This commit is contained in:
Michael Hoennig 2024-03-12 17:36:29 +01:00
parent 76b98eab2e
commit fe23a496e6
13 changed files with 334 additions and 265 deletions

View File

@ -12,8 +12,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.validation.Valid;
import java.util.List;
import java.util.UUID;
@ -32,9 +30,6 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Autowired
private HsOfficeMembershipRepository membershipRepo;
@PersistenceContext
private EntityManager em;
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeMembershipResource>> listMemberships(
@ -121,7 +116,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
final var current = membershipRepo.findByUuid(membershipUuid).orElseThrow();
new HsOfficeMembershipEntityPatcher(em, mapper, current).apply(body);
new HsOfficeMembershipEntityPatcher(mapper, current).apply(body);
final var saved = membershipRepo.save(current);
final var mapped = mapper.map(saved, HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER);

View File

@ -4,20 +4,29 @@ import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType;
import com.vladmihalcea.hibernate.type.range.Range;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.Type;
import jakarta.persistence.*;
import java.io.IOException;
import java.time.LocalDate;
import java.util.UUID;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.REFERRER;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity
@ -35,7 +44,6 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
private static Stringify<HsOfficeMembershipEntity> stringify = stringify(HsOfficeMembershipEntity.class)
.withProp(e -> MEMBER_NUMBER_TAG + e.getMemberNumber())
.withProp(e -> e.getPartner().toShortString())
.withProp(e -> e.getMainDebitor().toShortString())
.withProp(e -> e.getValidity().asString())
.withProp(HsOfficeMembershipEntity::getReasonForTermination)
.quotedValues(false);
@ -48,11 +56,6 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
@JoinColumn(name = "partneruuid")
private HsOfficePartnerEntity partner;
@ManyToOne
@Fetch(FetchMode.JOIN)
@JoinColumn(name = "maindebitoruuid")
private HsOfficeDebitorEntity mainDebitor;
@Column(name = "membernumbersuffix", length = 2)
private String memberNumberSuffix;
@ -113,4 +116,43 @@ public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
setReasonForTermination(HsOfficeReasonForTermination.NONE);
}
}
public static RbacView rbac() {
return rbacViewFor("membership", HsOfficeMembershipEntity.class)
.withIdentityView(SQL.query("""
SELECT m.uuid AS uuid,
'M-' || p.partnerNumber || m.memberNumberSuffix as idName
FROM hs_office_membership AS m
JOIN hs_office_partner AS p ON p.uuid = m.partnerUuid
"""))
.withRestrictedViewOrderBy(SQL.projection("validity"))
.withUpdatableColumns("validity", "membershipFeeBillable", "reasonForTermination")
.importEntityAlias("partnerRel", HsOfficeRelationshipEntity.class,
dependsOnColumn("partnerUuid"),
fetchedBySql("""
SELECT r.*
FROM hs_office_partner AS p
JOIN hs_office_relationship AS r ON r.uuid = p.partnerRoleUuid
WHERE p.uuid = ${REF}.partnerUuid
"""))
.toRole("partnerRel", ADMIN).grantPermission("membership", INSERT)
.createRole(OWNER, (with) -> {
with.owningUser(CREATOR);
with.incomingSuperRole("partnerRel", ADMIN);
with.permission(DELETE);
})
.createSubRole(ADMIN, (with) -> {
with.permission(UPDATE);
})
.createSubRole(REFERRER, (with) -> {
with.outgoingSubRole("partnerRel", TENANT);
with.permission(SELECT);
});
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("303-hs-office-membership-rbac");
}
}

View File

@ -1,37 +1,26 @@
package net.hostsharing.hsadminng.hs.office.membership;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.Mapper;
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import jakarta.persistence.EntityManager;
import java.util.Optional;
import java.util.UUID;
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
private final EntityManager em;
private final Mapper mapper;
private final HsOfficeMembershipEntity entity;
public HsOfficeMembershipEntityPatcher(
final EntityManager em,
final Mapper mapper,
final HsOfficeMembershipEntity entity) {
this.em = em;
this.mapper = mapper;
this.entity = entity;
}
@Override
public void apply(final HsOfficeMembershipPatchResource resource) {
OptionalFromJson.of(resource.getMainDebitorUuid())
.ifPresent(newValue -> {
verifyNotNull(newValue, "debitor");
entity.setMainDebitor(em.getReference(HsOfficeDebitorEntity.class, newValue));
});
OptionalFromJson.of(resource.getValidTo()).ifPresent(
entity::setValidTo);
Optional.ofNullable(resource.getReasonForTermination())
@ -40,10 +29,4 @@ public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMe
OptionalFromJson.of(resource.getMembershipFeeBillable()).ifPresent(
entity::setMembershipFeeBillable);
}
private void verifyNotNull(final UUID newValue, final String propertyName) {
if (newValue == null) {
throw new IllegalArgumentException("property '" + propertyName + "' must not be null");
}
}
}

View File

@ -12,7 +12,6 @@ create table if not exists hs_office_membership
(
uuid uuid unique references RbacObject (uuid) initially deferred,
partnerUuid uuid not null references hs_office_partner(uuid),
mainDebitorUuid uuid not null references hs_office_debitor(uuid),
memberNumberSuffix char(2) not null check (
memberNumberSuffix::text ~ '^[0-9][0-9]$'),
validity daterange not null,

View File

@ -1,75 +1,158 @@
### hs_office_membership RBAC
### rbac membership
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-12T17:26:50.174602110.
```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%%
flowchart TB
subgraph global
style global fill:#eee
role:global.admin[global.admin]
end
subgraph hsOfficeDebitor
subgraph partnerRel["`**partnerRel**`"]
direction TB
style hsOfficeDebitor fill:#eee
role:hsOfficeDebitor.owner[debitor.owner]
--> role:hsOfficeDebitor.admin[debitor.admin]
--> role:hsOfficeDebitor.tenant[debitor.tenant]
--> role:hsOfficeDebitor.guest[debitor.guest]
style partnerRel fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
subgraph partnerRel:roles[ ]
style partnerRel:roles fill:#99bcdb,stroke:white
role:partnerRel:owner[[partnerRel:owner]]
role:partnerRel:admin[[partnerRel:admin]]
role:partnerRel:agent[[partnerRel:agent]]
role:partnerRel:tenant[[partnerRel:tenant]]
end
end
subgraph hsOfficePartner
subgraph partnerRel.contact["`**partnerRel.contact**`"]
direction TB
style hsOfficePartner fill:#eee
role:hsOfficePartner.owner[partner.admin]
--> role:hsOfficePartner.admin[partner.admin]
--> role:hsOfficePartner.agent[partner.agent]
--> role:hsOfficePartner.tenant[partner.tenant]
--> role:hsOfficePartner.guest[partner.guest]
style partnerRel.contact fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.contact:roles[ ]
style partnerRel.contact:roles fill:#99bcdb,stroke:white
role:partnerRel.contact:owner[[partnerRel.contact:owner]]
role:partnerRel.contact:admin[[partnerRel.contact:admin]]
role:partnerRel.contact:referrer[[partnerRel.contact:referrer]]
end
end
subgraph hsOfficeMembership
role:hsOfficeMembership.owner[membership.owner]
%% permissions
role:hsOfficeMembership.owner --> perm:hsOfficeMembership.*{{membership.*}}
%% incoming
role:global.admin ---> role:hsOfficeMembership.owner
role:hsOfficeMembership.admin[membership.admin]
%% permissions
role:hsOfficeMembership.admin --> perm:hsOfficeMembership.edit{{membership.edit}}
%% incoming
role:hsOfficeMembership.owner ---> role:hsOfficeMembership.admin
role:hsOfficeMembership.agent[membership.agent]
%% incoming
role:hsOfficeMembership.admin ---> role:hsOfficeMembership.agent
role:hsOfficePartner.admin --> role:hsOfficeMembership.agent
role:hsOfficeDebitor.admin --> role:hsOfficeMembership.agent
%% outgoing
role:hsOfficeMembership.agent --> role:hsOfficePartner.tenant
role:hsOfficeMembership.agent --> role:hsOfficeDebitor.tenant
role:hsOfficeMembership.tenant[membership.tenant]
%% incoming
role:hsOfficeMembership.agent --> role:hsOfficeMembership.tenant
role:hsOfficePartner.agent --> role:hsOfficeMembership.tenant
role:hsOfficeDebitor.agent --> role:hsOfficeMembership.tenant
%% outgoing
role:hsOfficeMembership.tenant --> role:hsOfficePartner.guest
role:hsOfficeMembership.tenant --> role:hsOfficeDebitor.guest
subgraph membership["`**membership**`"]
direction TB
style membership fill:#dd4901,stroke:#274d6e,stroke-width:8px
role:hsOfficeMembership.guest[membership.guest]
%% permissions
role:hsOfficeMembership.guest --> perm:hsOfficeMembership.view{{membership.view}}
%% incoming
role:hsOfficeMembership.tenant --> role:hsOfficeMembership.guest
role:hsOfficePartner.tenant --> role:hsOfficeMembership.guest
role:hsOfficeDebitor.tenant --> role:hsOfficeMembership.guest
subgraph membership:roles[ ]
style membership:roles fill:#dd4901,stroke:white
role:membership:owner[[membership:owner]]
role:membership:admin[[membership:admin]]
role:membership:referrer[[membership:referrer]]
end
subgraph membership:permissions[ ]
style membership:permissions fill:#dd4901,stroke:white
perm:membership:INSERT{{membership:INSERT}}
perm:membership:DELETE{{membership:DELETE}}
perm:membership:UPDATE{{membership:UPDATE}}
perm:membership:SELECT{{membership:SELECT}}
end
end
subgraph partnerRel.anchorPerson["`**partnerRel.anchorPerson**`"]
direction TB
style partnerRel.anchorPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.anchorPerson:roles[ ]
style partnerRel.anchorPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.anchorPerson:owner[[partnerRel.anchorPerson:owner]]
role:partnerRel.anchorPerson:admin[[partnerRel.anchorPerson:admin]]
role:partnerRel.anchorPerson:referrer[[partnerRel.anchorPerson:referrer]]
end
end
subgraph partnerRel.holderPerson["`**partnerRel.holderPerson**`"]
direction TB
style partnerRel.holderPerson fill:#99bcdb,stroke:#274d6e,stroke-width:8px
subgraph partnerRel.holderPerson:roles[ ]
style partnerRel.holderPerson:roles fill:#99bcdb,stroke:white
role:partnerRel.holderPerson:owner[[partnerRel.holderPerson:owner]]
role:partnerRel.holderPerson:admin[[partnerRel.holderPerson:admin]]
role:partnerRel.holderPerson:referrer[[partnerRel.holderPerson:referrer]]
end
end
%% granting roles to users
user:creator ==> role:membership:owner
%% granting roles to roles
role:global:admin -.-> role:partnerRel.anchorPerson:owner
role:partnerRel.anchorPerson:owner -.-> role:partnerRel.anchorPerson:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel.anchorPerson:referrer
role:global:admin -.-> role:partnerRel.holderPerson:owner
role:partnerRel.holderPerson:owner -.-> role:partnerRel.holderPerson:admin
role:partnerRel.holderPerson:admin -.-> role:partnerRel.holderPerson:referrer
role:global:admin -.-> role:partnerRel.contact:owner
role:partnerRel.contact:owner -.-> role:partnerRel.contact:admin
role:partnerRel.contact:admin -.-> role:partnerRel.contact:referrer
role:global:admin -.-> role:partnerRel:owner
role:partnerRel:owner -.-> role:partnerRel:admin
role:partnerRel.anchorPerson:admin -.-> role:partnerRel:admin
role:partnerRel:admin -.-> role:partnerRel:agent
role:partnerRel.holderPerson:admin -.-> role:partnerRel:agent
role:partnerRel:agent -.-> role:partnerRel:tenant
role:partnerRel.holderPerson:admin -.-> role:partnerRel:tenant
role:partnerRel.contact:admin -.-> role:partnerRel:tenant
role:partnerRel:tenant -.-> role:partnerRel.anchorPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.holderPerson:referrer
role:partnerRel:tenant -.-> role:partnerRel.contact:referrer
role:partnerRel:admin ==> role:membership:owner
role:membership:owner ==> role:membership:admin
role:membership:admin ==> role:membership:referrer
role:membership:referrer ==> role:partnerRel:tenant
%% granting permissions to roles
role:global: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,4 +1,6 @@
--liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-12T17:26:50.179864268.
-- ============================================================================
--changeset hs-office-membership-rbac-OBJECT:1 endDelimiter:--//
@ -15,150 +17,160 @@ call generateRbacRoleDescriptors('hsOfficeMembership', 'hs_office_membership');
-- ============================================================================
--changeset hs-office-membership-rbac-ROLES-CREATION:1 endDelimiter:--//
--changeset hs-office-membership-rbac-insert-trigger:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates and updates the roles and their assignments for membership entities.
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
*/
create or replace function hsOfficeMembershipRbacRolesTrigger()
returns trigger
language plpgsql
strict as $$
create or replace procedure buildRbacSystemForHsOfficeMembership(
NEW hs_office_membership
)
language plpgsql as $$
declare
newHsOfficePartner hs_office_partner;
newHsOfficePartnerRel hs_office_relationship;
newHsOfficeDebitor hs_office_debitor;
newPartnerRel hs_office_relationship;
begin
call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_partner as p where p.uuid = NEW.partnerUuid into newHsOfficePartner;
select * from hs_office_relationship as r where r.relType = 'PARTNER' and r.relHolderUuid = NEW.partnerUuid into newHsOfficePartnerRel;
select * from hs_office_debitor as c where c.uuid = NEW.mainDebitorUuid into newHsOfficeDebitor;
SELECT r.*
FROM hs_office_partner AS p
JOIN hs_office_relationship AS r ON r.uuid = p.partnerRoleUuid
WHERE p.uuid = NEW.partnerUuid
INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerUuid = %s', NEW.partnerUuid);
if TG_OP = 'INSERT' then
-- === ATTENTION: code generated from related Mermaid flowchart: ===
perform createRoleWithGrants(
hsOfficeMembershipOwner(NEW),
permissions => array['DELETE'],
userUuids => array[currentUserUuid()],
incomingSuperRoles => array[hsOfficeRelationshipAdmin(newPartnerRel)]
);
perform createRoleWithGrants(
hsOfficeMembershipOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[globalAdmin()]
);
perform createRoleWithGrants(
hsOfficeMembershipAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeMembershipOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeMembershipAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeMembershipOwner(NEW)]
);
perform createRoleWithGrants(
hsOfficeMembershipAgent(NEW),
incomingSuperRoles => array[hsOfficeMembershipAdmin(NEW), hsOfficeRelationshipAdmin(newHsOfficePartnerRel), hsOfficeDebitorAdmin(newHsOfficeDebitor)],
outgoingSubRoles => array[hsOfficeRelationshipTenant(newHsOfficePartnerRel), hsOfficeDebitorTenant(newHsOfficeDebitor)]
);
perform createRoleWithGrants(
hsOfficeMembershipTenant(NEW),
incomingSuperRoles => array[hsOfficeMembershipAgent(NEW), hsOfficeRelationshipAgent(newHsOfficePartnerRel), hsOfficeDebitorAgent(newHsOfficeDebitor)],
outgoingSubRoles => array[hsOfficeRelationshipGuest(newHsOfficePartnerRel), hsOfficeDebitorGuest(newHsOfficeDebitor)]
);
perform createRoleWithGrants(
hsOfficeMembershipGuest(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeMembershipTenant(NEW), hsOfficeRelationshipTenant(newHsOfficePartnerRel), hsOfficeDebitorTenant(newHsOfficeDebitor)]
);
-- === END of code generated from Mermaid flowchart. ===
else
raise exception 'invalid usage of TRIGGER';
end if;
perform createRoleWithGrants(
hsOfficeMembershipReferrer(NEW),
permissions => array['SELECT'],
incomingSuperRoles => array[hsOfficeMembershipAdmin(NEW)],
outgoingSubRoles => array[hsOfficeRelationshipTenant(newPartnerRel)]
);
call leaveTriggerForObjectUuid(NEW.uuid);
return NEW;
end; $$;
/*
An AFTER INSERT TRIGGER which creates the role structure for a new customer.
AFTER INSERT TRIGGER to create the role+grant structure for a new hs_office_membership row.
*/
create trigger createRbacRolesForHsOfficeMembership_Trigger
after insert
on hs_office_membership
create or replace function insertTriggerForHsOfficeMembership_tf()
returns trigger
language plpgsql
strict as $$
begin
call buildRbacSystemForHsOfficeMembership(NEW);
return NEW;
end; $$;
create trigger insertTriggerForHsOfficeMembership_tg
after insert on hs_office_membership
for each row
execute procedure hsOfficeMembershipRbacRolesTrigger();
execute procedure insertTriggerForHsOfficeMembership_tf();
--//
-- ============================================================================
--changeset hs-office-membership-rbac-IDENTITY-VIEW:1 endDelimiter:--//
--changeset hs-office-membership-rbac-INSERT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_membership', $idName$
'#' ||
(select partnerNumber from hs_office_partner p where p.uuid = target.partnerUuid) ||
memberNumberSuffix ||
':' || (select split_part(idName, ':', 2) from hs_office_partner_iv p where p.uuid = target.partnerUuid)
$idName$);
/*
Creates INSERT INTO hs_office_membership permissions for the related global rows.
*/
do language plpgsql $$
declare
row global;
permissionUuid uuid;
roleUuid uuid;
begin
call defineContext('create INSERT INTO hs_office_membership permissions for the related global rows');
FOR row IN SELECT * FROM global
LOOP
roleUuid := findRoleId(globalAdmin());
permissionUuid := createPermission(row.uuid, 'INSERT', 'hs_office_membership');
call grantPermissionToRole(permissionUuid, roleUuid);
END LOOP;
END;
$$;
/**
Adds hs_office_membership INSERT permission to specified role of new global rows.
*/
create or replace function hs_office_membership_global_insert_tf()
returns trigger
language plpgsql
strict as $$
begin
call grantPermissionToRole(
globalAdmin(),
createPermission(NEW.uuid, 'INSERT', 'hs_office_membership'));
return NEW;
end; $$;
create trigger hs_office_membership_global_insert_tg
after insert on global
for each row
execute procedure hs_office_membership_global_insert_tf();
/**
Checks if the user or assumed roles are allowed to insert a row to hs_office_membership.
*/
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 % (%)',
currentSubjects(), currentSubjectsUuids();
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();
--//
-- ============================================================================
--changeset hs-office-membership-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromQuery('hs_office_membership', $idName$
SELECT m.uuid AS uuid,
'M-' || p.partnerNumber || m.memberNumberSuffix as idName
FROM hs_office_membership AS m
JOIN hs_office_partner AS p ON p.uuid = m.partnerUuid
$idName$);
--//
-- ============================================================================
--changeset hs-office-membership-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_membership',
orderby => 'target.memberNumberSuffix',
columnUpdates => $updates$
validity = new.validity,
reasonForTermination = new.reasonForTermination,
membershipFeeBillable = new.membershipFeeBillable
$orderBy$
validity
$orderBy$,
$updates$
validity = new.validity,
membershipFeeBillable = new.membershipFeeBillable,
reasonForTermination = new.reasonForTermination
$updates$);
--//
-- ============================================================================
--changeset hs-office-membership-rbac-NEW-Membership:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-membership and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-membership permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-membership']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficeMembershipNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-membership 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_membership_insert_trigger
before insert
on hs_office_membership
for each row
-- TODO.spec: who is allowed to create new memberships
when ( not hasAssumedRole() )
execute procedure addHsOfficeMembershipNotAllowedForCurrentSubjects();
--//

View File

@ -10,42 +10,26 @@
*/
create or replace procedure createHsOfficeMembershipTestData(
forPartnerNumber numeric(5),
forMainDebitorNumberSuffix numeric,
newMemberNumberSuffix char(2) )
language plpgsql as $$
declare
currentTask varchar;
relatedPartner hs_office_partner;
relatedDebitorRel hs_office_relationship;
relatedDebitor hs_office_debitor;
begin
currentTask := 'creating Membership test-data ' ||
'P-' || forPartnerNumber::text ||
'D-...' || forMainDebitorNumberSuffix ||
'M-...' || newMemberNumberSuffix;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
execute format('set local hsadminng.currentTask to %L', currentTask);
select partner.* from hs_office_partner partner
where partner.partnerNumber = forPartnerNumber into relatedPartner;
select debitorRel.* from hs_office_relationship debitorRel
join hs_office_relationship partnerRel
on debitorRel.relAnchorUuid=partnerRel.relHolderUuid and partnerRel.relType='PARTNER'
join hs_office_partner partner
on partner.partnerRoleUuid = partnerRel.uuid
where debitorRel.relType='ACCOUNTING' -- FIXME: 'DEBITOR'
into relatedDebitorRel;
select debitor.* from hs_office_debitor debitor
where debitor.debitorRelUuid = relatedDebitorRel.uuid
and debitor.debitorNumberSuffix = forMainDebitorNumberSuffix
into relatedDebitor;
raise notice 'creating test Membership: M-% %', forPartnerNumber, newMemberNumberSuffix;
raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner;
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
insert
into hs_office_membership (uuid, partneruuid, maindebitoruuid, memberNumberSuffix, validity, reasonfortermination)
values (uuid_generate_v4(), relatedPartner.uuid, relatedDebitor.uuid, newMemberNumberSuffix, daterange('20221001' , null, '[]'), 'NONE');
into hs_office_membership (uuid, partneruuid, memberNumberSuffix, validity, reasonfortermination)
values (uuid_generate_v4(), relatedPartner.uuid, newMemberNumberSuffix, daterange('20221001' , null, '[]'), 'NONE');
end; $$;
--//
@ -56,9 +40,9 @@ end; $$;
do language plpgsql $$
begin
call createHsOfficeMembershipTestData(10001, 11, '01');
call createHsOfficeMembershipTestData(10002, 12, '02');
call createHsOfficeMembershipTestData(10003, 13, '03');
call createHsOfficeMembershipTestData(10001, '01');
call createHsOfficeMembershipTestData(10002, '02');
call createHsOfficeMembershipTestData(10003, '03');
end;
$$;
--//

View File

@ -334,9 +334,6 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("partner.person.tradeName", is(givenMembership.getPartner().getPartnerRole().getRelHolder().getTradeName()))
.body("mainDebitor.debitorNumber", is(givenMembership.getMainDebitor().getDebitorNumber()))
.body("mainDebitor.debitorNumberSuffix", is((int) givenMembership.getMainDebitor().getDebitorNumberSuffix()))
.body("mainDebitor.debitorNumberSuffix", is((int) givenMembership.getMainDebitor().getDebitorNumberSuffix()))
.body("memberNumberSuffix", is(givenMembership.getMemberNumberSuffix()))
.body("validFrom", is("2022-11-01"))
.body("validTo", is("2023-12-31"))
@ -347,7 +344,6 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get()
.matches(mandate -> {
assertThat(mandate.getPartner().toShortString()).isEqualTo("LP First GmbH");
assertThat(mandate.getMainDebitor().toString()).isEqualTo(givenMembership.getMainDebitor().toString());
assertThat(mandate.getMemberNumberSuffix()).isEqualTo(givenMembership.getMemberNumberSuffix());
assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-01)");
assertThat(mandate.getReasonForTermination()).isEqualTo(HsOfficeReasonForTermination.CANCELLATION);
@ -390,7 +386,6 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get()
.matches(mandate -> {
assertThat(mandate.getPartner().toShortString()).isEqualTo("LP First GmbH");
assertThat(mandate.getMainDebitor().toString()).isEqualTo(givenMembership.getMainDebitor().toString());
assertThat(mandate.getMemberNumberSuffix()).isEqualTo(givenMembership.getMemberNumberSuffix());
assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,)");
assertThat(mandate.getReasonForTermination()).isEqualTo(NONE);
@ -501,7 +496,6 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle
final var newMembership = HsOfficeMembershipEntity.builder()
.uuid(UUID.randomUUID())
.partner(givenPartner)
.mainDebitor(givenDebitor)
.memberNumberSuffix(TEMP_MEMBER_NUMBER_SUFFIX)
.validity(Range.closedInfinite(LocalDate.parse("2022-11-01")))
.reasonForTermination(NONE)

View File

@ -17,7 +17,6 @@ import java.time.LocalDate;
import java.util.UUID;
import java.util.stream.Stream;
import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.TEST_DEBITOR;
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.mockito.ArgumentMatchers.any;
@ -32,7 +31,6 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
> {
private static final UUID INITIAL_MEMBERSHIP_UUID = UUID.randomUUID();
private static final UUID PATCHED_MAIN_DEBITOR_UUID = UUID.randomUUID();
private static final LocalDate GIVEN_VALID_FROM = LocalDate.parse("2020-04-15");
private static final LocalDate PATCHED_VALID_TO = LocalDate.parse("2022-12-31");
@ -56,7 +54,6 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
protected HsOfficeMembershipEntity newInitialEntity() {
final var entity = new HsOfficeMembershipEntity();
entity.setUuid(INITIAL_MEMBERSHIP_UUID);
entity.setMainDebitor(TEST_DEBITOR);
entity.setPartner(TEST_PARTNER);
entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM));
entity.setMembershipFeeBillable(GIVEN_MEMBERSHIP_FEE_BILLABLE);
@ -70,19 +67,12 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
@Override
protected HsOfficeMembershipEntityPatcher createPatcher(final HsOfficeMembershipEntity membership) {
return new HsOfficeMembershipEntityPatcher(em, mapper, membership);
return new HsOfficeMembershipEntityPatcher(mapper, membership);
}
@Override
protected Stream<Property> propertyTestDescriptors() {
return Stream.of(
new JsonNullableProperty<>(
"debitor",
HsOfficeMembershipPatchResource::setMainDebitorUuid,
PATCHED_MAIN_DEBITOR_UUID,
HsOfficeMembershipEntity::setMainDebitor,
newDebitor(PATCHED_MAIN_DEBITOR_UUID))
.notNullable(),
new JsonNullableProperty<>(
"valid",
HsOfficeMembershipPatchResource::setValidTo,
@ -102,10 +92,4 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeMembershipEntity::setMembershipFeeBillable)
);
}
private static HsOfficeDebitorEntity newDebitor(final UUID uuid) {
final var newDebitor = new HsOfficeDebitorEntity();
newDebitor.setUuid(uuid);
return newDebitor;
}
}

View File

@ -9,7 +9,6 @@ import java.lang.reflect.InvocationTargetException;
import java.time.LocalDate;
import java.util.Arrays;
import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.TEST_DEBITOR;
import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER;
import static org.assertj.core.api.Assertions.assertThat;
@ -20,7 +19,6 @@ class HsOfficeMembershipEntityUnitTest {
final HsOfficeMembershipEntity givenMembership = HsOfficeMembershipEntity.builder()
.memberNumberSuffix("01")
.partner(TEST_PARTNER)
.mainDebitor(TEST_DEBITOR)
.validity(Range.closedInfinite(GIVEN_VALID_FROM))
.build();

View File

@ -28,6 +28,7 @@ import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.distin
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.distinctRoleNamesOf;
import static net.hostsharing.test.JpaAttempt.attempt;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
@Import( { Context.class, JpaAttempt.class })
class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
@ -72,7 +73,6 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
final var newMembership = HsOfficeMembershipEntity.builder()
.memberNumberSuffix("11")
.partner(givenPartner)
.mainDebitor(givenDebitor)
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
.membershipFeeBillable(true)
.build();
@ -99,11 +99,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
// when
attempt(em, () -> {
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike("First").get(0);
final var newMembership = HsOfficeMembershipEntity.builder()
.memberNumberSuffix("17")
.partner(givenPartner)
.mainDebitor(givenDebitor)
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
.membershipFeeBillable(true)
.build();
@ -219,7 +217,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
public void globalAdmin_canUpdateValidityOfArbitraryMembership() {
// given
context("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembership("First", "First", "11");
final var givenMembership = givenSomeTemporaryMembership("First", "11");
assertThatMembershipIsVisibleForUserWithRole(
givenMembership,
"hs_office_debitor#1000111:FirstGmbH-firstcontact.admin");
@ -246,7 +244,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
public void debitorAdmin_canViewButNotUpdateRelatedMembership() {
// given
context("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembership("First", "First", "13");
final var givenMembership = givenSomeTemporaryMembership("First", "13");
assertThatMembershipIsVisibleForUserWithRole(
givenMembership,
"hs_office_debitor#1000111:FirstGmbH-firstcontact.admin");
@ -299,7 +297,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
public void globalAdmin_withoutAssumedRole_canDeleteAnyMembership() {
// given
context("superuser-alex@hostsharing.net", null);
final var givenMembership = givenSomeTemporaryMembership("First", "Second", "12");
final var givenMembership = givenSomeTemporaryMembership("First", "12");
// when
final var result = jpaAttempt.transacted(() -> {
@ -319,7 +317,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
public void nonGlobalAdmin_canNotDeleteTheirRelatedMembership() {
// given
context("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembership("First", "Third", "14");
final var givenMembership = givenSomeTemporaryMembership("First", "14");
// when
final var result = jpaAttempt.transacted(() -> {
@ -345,7 +343,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
context("superuser-alex@hostsharing.net");
final var initialRoleNames = Array.from(distinctRoleNamesOf(rawRoleRepo.findAll()));
final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll()));
final var givenMembership = givenSomeTemporaryMembership("First", "First", "15");
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")
@ -383,15 +381,13 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
"[creating Membership test-data Seconde.K.12, hs_office_membership, INSERT]");
}
private HsOfficeMembershipEntity givenSomeTemporaryMembership(final String partnerTradeName, final String debitorName, final String memberNumberSuffix) {
private HsOfficeMembershipEntity givenSomeTemporaryMembership(final String partnerTradeName, final String memberNumberSuffix) {
return jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partnerTradeName).get(0);
final var givenDebitor = debitorRepo.findDebitorByOptionalNameLike(debitorName).get(0);
final var newMembership = HsOfficeMembershipEntity.builder()
.memberNumberSuffix(memberNumberSuffix)
.partner(givenPartner)
.mainDebitor(givenDebitor)
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
.membershipFeeBillable(true)
.build();

View File

@ -704,7 +704,6 @@ public class ImportOfficeData extends ContextBasedTest {
isBlank(rec.getString("member_until"))
? HsOfficeReasonForTermination.NONE
: HsOfficeReasonForTermination.UNKNOWN)
.mainDebitor(debitor)
.build();
memberships.put(rec.getInteger("bp_id"), membership);
}

View File

@ -4,8 +4,8 @@ spring:
platform: postgres
datasource:
url-tc: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers
url: jdbc:postgresql://localhost:5432/postgres
url: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers
url-local: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: password