rbac-optimization (#80)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/80 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
@@ -70,7 +70,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsBookingItemEntity implements Stringifyable, RbacObject, PropertiesProvider {
|
||||
public class HsBookingItemEntity implements Stringifyable, RbacObject<HsBookingItemEntity>, PropertiesProvider {
|
||||
|
||||
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
|
||||
.withProp(HsBookingItemEntity::getProject)
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsBookingProjectEntity implements Stringifyable, RbacObject {
|
||||
public class HsBookingProjectEntity implements Stringifyable, RbacObject<HsBookingProjectEntity> {
|
||||
|
||||
private static Stringify<HsBookingProjectEntity> stringify = stringify(HsBookingProjectEntity.class)
|
||||
.withProp(HsBookingProjectEntity::getDebitor)
|
||||
|
||||
@@ -70,7 +70,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject, PropertiesProvider {
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject<HsHostingAssetEntity>, PropertiesProvider {
|
||||
|
||||
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
|
||||
.withProp(HsHostingAssetEntity::getType)
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
@DisplayName("BankAccount")
|
||||
public class HsOfficeBankAccountEntity implements RbacObject, Stringifyable {
|
||||
public class HsOfficeBankAccountEntity implements RbacObject<HsOfficeBankAccountEntity>, Stringifyable {
|
||||
|
||||
private static Stringify<HsOfficeBankAccountEntity> toString = stringify(HsOfficeBankAccountEntity.class, "bankAccount")
|
||||
.withIdProp(HsOfficeBankAccountEntity::getIban)
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
@DisplayName("Contact")
|
||||
public class HsOfficeContactEntity implements Stringifyable, RbacObject {
|
||||
public class HsOfficeContactEntity implements Stringifyable, RbacObject<HsOfficeContactEntity> {
|
||||
|
||||
private static Stringify<HsOfficeContactEntity> toString = stringify(HsOfficeContactEntity.class, "contact")
|
||||
.withProp(Fields.caption, HsOfficeContactEntity::getCaption)
|
||||
|
||||
+8
-1
@@ -41,7 +41,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayName("CoopAssetsTransaction")
|
||||
public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacObject {
|
||||
public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacObject<HsOfficeCoopAssetsTransactionEntity> {
|
||||
|
||||
private static Stringify<HsOfficeCoopAssetsTransactionEntity> stringify = stringify(HsOfficeCoopAssetsTransactionEntity.class)
|
||||
.withIdProp(HsOfficeCoopAssetsTransactionEntity::getTaggedMemberNumber)
|
||||
@@ -105,6 +105,13 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacO
|
||||
@OneToOne(mappedBy = "adjustedAssetTx")
|
||||
private HsOfficeCoopAssetsTransactionEntity adjustmentAssetTx;
|
||||
|
||||
@Override
|
||||
public HsOfficeCoopAssetsTransactionEntity load() {
|
||||
RbacObject.super.load();
|
||||
membership.load();
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTaggedMemberNumber() {
|
||||
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse("M-???????");
|
||||
}
|
||||
|
||||
+8
-1
@@ -39,7 +39,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayName("CoopShareTransaction")
|
||||
public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacObject {
|
||||
public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacObject<HsOfficeCoopSharesTransactionEntity> {
|
||||
|
||||
private static Stringify<HsOfficeCoopSharesTransactionEntity> stringify = stringify(HsOfficeCoopSharesTransactionEntity.class)
|
||||
.withIdProp(HsOfficeCoopSharesTransactionEntity::getMemberNumberTagged)
|
||||
@@ -102,6 +102,13 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacO
|
||||
@OneToOne(mappedBy = "adjustedShareTx")
|
||||
private HsOfficeCoopSharesTransactionEntity adjustmentShareTx;
|
||||
|
||||
@Override
|
||||
public HsOfficeCoopSharesTransactionEntity load() {
|
||||
RbacObject.super.load();
|
||||
membership.load();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
|
||||
+20
-5
@@ -21,6 +21,7 @@ import org.hibernate.annotations.NotFoundAction;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
@@ -57,7 +58,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayName("Debitor")
|
||||
public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
|
||||
public class HsOfficeDebitorEntity implements RbacObject<HsOfficeDebitorEntity>, Stringifyable {
|
||||
|
||||
public static final String DEBITOR_NUMBER_TAG = "D-";
|
||||
public static final String TWO_DECIMAL_DIGITS = "^([0-9]{2})$";
|
||||
@@ -77,7 +78,7 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@ManyToOne
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinFormula(
|
||||
referencedColumnName = "uuid",
|
||||
value = """
|
||||
@@ -91,14 +92,14 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
|
||||
WHERE pRel.holderUuid = dRel.anchorUuid
|
||||
)
|
||||
""")
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
@NotFound(action = NotFoundAction.IGNORE) // TODO.impl: map a simplified raw-PartnerEntity, just for the partner-number
|
||||
private HsOfficePartnerEntity partner;
|
||||
|
||||
@Column(name = "debitornumbersuffix", length = 2)
|
||||
@Pattern(regexp = TWO_DECIMAL_DIGITS)
|
||||
private String debitorNumberSuffix;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false)
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "debitorreluuid", nullable = false)
|
||||
private HsOfficeRelationEntity debitorRel;
|
||||
|
||||
@@ -117,13 +118,27 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
|
||||
@Column(name = "vatreversecharge")
|
||||
private boolean vatReverseCharge;
|
||||
|
||||
@ManyToOne
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "refundbankaccountuuid")
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
private HsOfficeBankAccountEntity refundBankAccount;
|
||||
|
||||
@Column(name = "defaultprefix", columnDefinition = "char(3) not null")
|
||||
private String defaultPrefix;
|
||||
|
||||
@Override
|
||||
public HsOfficeDebitorEntity load() {
|
||||
RbacObject.super.load();
|
||||
if (partner != null) {
|
||||
partner.load();
|
||||
}
|
||||
debitorRel.load();
|
||||
if (refundBankAccount != null) {
|
||||
refundBankAccount.load();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private String getDebitorNumberString() {
|
||||
return ofNullable(partner)
|
||||
.filter(partner -> debitorNumberSuffix != null)
|
||||
|
||||
+10
-2
@@ -21,6 +21,7 @@ import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
@@ -61,7 +62,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayName("Membership")
|
||||
public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
|
||||
public class HsOfficeMembershipEntity implements RbacObject<HsOfficeMembershipEntity>, Stringifyable {
|
||||
|
||||
public static final String MEMBER_NUMBER_TAG = "M-";
|
||||
public static final String TWO_DECIMAL_DIGITS = "^([0-9]{2})$";
|
||||
@@ -80,7 +81,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@ManyToOne
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "partneruuid")
|
||||
private HsOfficePartnerEntity partner;
|
||||
|
||||
@@ -99,6 +100,13 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
|
||||
@Enumerated(EnumType.STRING)
|
||||
private HsOfficeMembershipStatus status;
|
||||
|
||||
@Override
|
||||
public HsOfficeMembershipEntity load() {
|
||||
RbacObject.super.load();
|
||||
partner.load();
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setValidFrom(final LocalDate validFrom) {
|
||||
setValidity(toPostgresDateRange(validFrom, getValidTo()));
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayName("PartnerDetails")
|
||||
public class HsOfficePartnerDetailsEntity implements RbacObject, Stringifyable {
|
||||
public class HsOfficePartnerDetailsEntity implements RbacObject<HsOfficePartnerDetailsEntity>, Stringifyable {
|
||||
|
||||
private static Stringify<HsOfficePartnerDetailsEntity> stringify = stringify(
|
||||
HsOfficePartnerDetailsEntity.class,
|
||||
|
||||
+11
-3
@@ -40,7 +40,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayName("Partner")
|
||||
public class HsOfficePartnerEntity implements Stringifyable, RbacObject {
|
||||
public class HsOfficePartnerEntity implements Stringifyable, RbacObject<HsOfficePartnerEntity> {
|
||||
|
||||
public static final String PARTNER_NUMBER_TAG = "P-";
|
||||
|
||||
@@ -66,15 +66,23 @@ public class HsOfficePartnerEntity implements Stringifyable, RbacObject {
|
||||
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
|
||||
private Integer partnerNumber;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false)
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = false, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "partnerreluuid", nullable = false)
|
||||
private HsOfficeRelationEntity partnerRel;
|
||||
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true)
|
||||
@ManyToOne(cascade = { PERSIST, MERGE, REFRESH, DETACH }, optional = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "detailsuuid")
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
private HsOfficePartnerDetailsEntity details;
|
||||
|
||||
@Override
|
||||
public HsOfficePartnerEntity load() {
|
||||
RbacObject.super.load();
|
||||
partnerRel.load();
|
||||
details.load();
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTaggedPartnerNumber() {
|
||||
return PARTNER_NUMBER_TAG + partnerNumber;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
@DisplayName("Person")
|
||||
public class HsOfficePersonEntity implements RbacObject, Stringifyable {
|
||||
public class HsOfficePersonEntity implements RbacObject<HsOfficePersonEntity>, Stringifyable {
|
||||
|
||||
private static Stringify<HsOfficePersonEntity> toString = stringify(HsOfficePersonEntity.class, "person")
|
||||
.withProp(Fields.personType, HsOfficePersonEntity::getPersonType)
|
||||
|
||||
+12
-3
@@ -56,15 +56,15 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@ManyToOne
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "anchoruuid")
|
||||
private HsOfficePersonEntity anchor;
|
||||
|
||||
@ManyToOne
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "holderuuid")
|
||||
private HsOfficePersonEntity holder;
|
||||
|
||||
@ManyToOne
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "contactuuid")
|
||||
private HsOfficeContactEntity contact;
|
||||
|
||||
@@ -75,6 +75,15 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
|
||||
@Column(name = "mark")
|
||||
private String mark;
|
||||
|
||||
@Override
|
||||
public HsOfficeRelationEntity load() {
|
||||
RbacObject.super.load();
|
||||
anchor.load();
|
||||
holder.load();
|
||||
contact.load();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString.apply(this);
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayName("SEPA-Mandate")
|
||||
public class HsOfficeSepaMandateEntity implements Stringifyable, RbacObject {
|
||||
public class HsOfficeSepaMandateEntity implements Stringifyable, RbacObject<HsOfficeSepaMandateEntity> {
|
||||
|
||||
private static Stringify<HsOfficeSepaMandateEntity> stringify = stringify(HsOfficeSepaMandateEntity.class)
|
||||
.withProp(e -> e.getBankAccount().getIban())
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package net.hostsharing.hsadminng.rbac.rbacobject;
|
||||
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface RbacObject {
|
||||
public interface RbacObject<T extends RbacObject<?>> {
|
||||
UUID getUuid();
|
||||
|
||||
int getVersion();
|
||||
|
||||
default T load() {
|
||||
Hibernate.initialize(this);
|
||||
//noinspection unchecked
|
||||
return (T) this;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TestCustomerEntity implements RbacObject {
|
||||
public class TestCustomerEntity implements RbacObject<TestCustomerEntity> {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
|
||||
@@ -27,7 +27,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TestDomainEntity implements RbacObject {
|
||||
public class TestDomainEntity implements RbacObject<TestDomainEntity> {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
|
||||
@@ -27,7 +27,7 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TestPackageEntity implements RbacObject {
|
||||
public class TestPackageEntity implements RbacObject<TestPackageEntity> {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- PG-STAT-STATEMENTS-EXTENSION
|
||||
--changeset pg-stat-statements-extension:1 context:pg_stat_statements endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
/*
|
||||
Makes improved uuid generation available.
|
||||
*/
|
||||
create extension if not exists "pg_stat_statements";
|
||||
--//
|
||||
|
||||
@@ -372,6 +372,9 @@ create table RbacPermission
|
||||
op RbacOp not null,
|
||||
opTableName varchar(60)
|
||||
);
|
||||
-- TODO.perf: check if these indexes are really useful
|
||||
create index on RbacPermission (objectUuid, op);
|
||||
create index on RbacPermission (opTableName, op);
|
||||
|
||||
ALTER TABLE RbacPermission
|
||||
ADD CONSTRAINT RbacPermission_uc UNIQUE NULLS NOT DISTINCT (objectUuid, op, opTableName);
|
||||
@@ -495,78 +498,68 @@ create index on RbacGrants (ascendantUuid);
|
||||
create index on RbacGrants (descendantUuid);
|
||||
|
||||
call create_journal('RbacGrants');
|
||||
|
||||
create or replace function findGrantees(grantedId uuid)
|
||||
returns setof RbacReference
|
||||
returns null on null input
|
||||
language sql as $$
|
||||
select reference.*
|
||||
from (with recursive grants as (select descendantUuid,
|
||||
ascendantUuid
|
||||
from RbacGrants
|
||||
where descendantUuid = grantedId
|
||||
union all
|
||||
select "grant".descendantUuid,
|
||||
"grant".ascendantUuid
|
||||
from RbacGrants "grant"
|
||||
inner join grants recur on recur.ascendantUuid = "grant".descendantUuid)
|
||||
select ascendantUuid
|
||||
from grants) as grantee
|
||||
join RbacReference reference on reference.uuid = grantee.ascendantUuid;
|
||||
with recursive grants as (
|
||||
select descendantUuid, ascendantUuid
|
||||
from RbacGrants
|
||||
where descendantUuid = grantedId
|
||||
union all
|
||||
select g.descendantUuid, g.ascendantUuid
|
||||
from RbacGrants g
|
||||
inner join grants on grants.ascendantUuid = g.descendantUuid
|
||||
)
|
||||
select ref.*
|
||||
from grants
|
||||
join RbacReference ref on ref.uuid = grants.ascendantUuid;
|
||||
$$;
|
||||
|
||||
create or replace function isGranted(granteeIds uuid[], grantedId uuid)
|
||||
returns bool
|
||||
returns null on null input
|
||||
language sql as $$
|
||||
with recursive grants as (
|
||||
select descendantUuid, ascendantUuid
|
||||
from RbacGrants
|
||||
where descendantUuid = grantedId
|
||||
union all
|
||||
select "grant".descendantUuid, "grant".ascendantUuid
|
||||
from RbacGrants "grant"
|
||||
inner join grants recur on recur.ascendantUuid = "grant".descendantUuid
|
||||
)
|
||||
select exists (
|
||||
select true
|
||||
from grants
|
||||
where ascendantUuid = any(granteeIds)
|
||||
) or grantedId = any(granteeIds);
|
||||
$$;
|
||||
|
||||
create or replace function isGranted(granteeId uuid, grantedId uuid)
|
||||
returns bool
|
||||
returns null on null input
|
||||
language sql as $$
|
||||
select granteeId = grantedId or granteeId in (with recursive grants as (select descendantUuid, ascendantUuid
|
||||
from RbacGrants
|
||||
where descendantUuid = grantedId
|
||||
union all
|
||||
select "grant".descendantUuid, "grant".ascendantUuid
|
||||
from RbacGrants "grant"
|
||||
inner join grants recur on recur.ascendantUuid = "grant".descendantUuid)
|
||||
select ascendantUuid
|
||||
from grants);
|
||||
select * from isGranted(array[granteeId], grantedId);
|
||||
$$;
|
||||
|
||||
create or replace function isGranted(granteeIds uuid[], grantedId uuid)
|
||||
returns bool
|
||||
returns null on null input
|
||||
language plpgsql as $$
|
||||
declare
|
||||
granteeId uuid;
|
||||
begin
|
||||
-- TODO.perf: needs optimization
|
||||
foreach granteeId in array granteeIds
|
||||
loop
|
||||
if isGranted(granteeId, grantedId) then
|
||||
return true;
|
||||
end if;
|
||||
end loop;
|
||||
return false;
|
||||
end; $$;
|
||||
|
||||
create or replace function isPermissionGrantedToSubject(permissionId uuid, subjectId uuid)
|
||||
returns BOOL
|
||||
stable -- leakproof
|
||||
language sql as $$
|
||||
with recursive grants as (
|
||||
select descendantUuid, ascendantUuid
|
||||
from RbacGrants
|
||||
where descendantUuid = permissionId
|
||||
union all
|
||||
select g.descendantUuid, g.ascendantUuid
|
||||
from RbacGrants g
|
||||
inner join grants on grants.ascendantUuid = g.descendantUuid
|
||||
)
|
||||
select exists(
|
||||
select *
|
||||
from RbacUser
|
||||
where uuid in (with recursive grants as (select descendantUuid,
|
||||
ascendantUuid
|
||||
from RbacGrants g
|
||||
where g.descendantUuid = permissionId
|
||||
union all
|
||||
select g.descendantUuid,
|
||||
g.ascendantUuid
|
||||
from RbacGrants g
|
||||
inner join grants recur on recur.ascendantUuid = g.descendantUuid)
|
||||
select ascendantUuid
|
||||
from grants
|
||||
where ascendantUuid = subjectId)
|
||||
);
|
||||
select true
|
||||
from grants
|
||||
where ascendantUuid = subjectId
|
||||
);
|
||||
$$;
|
||||
|
||||
create or replace function hasInsertPermission(objectUuid uuid, tableName text )
|
||||
@@ -708,14 +701,14 @@ begin
|
||||
end; $$;
|
||||
|
||||
-- ============================================================================
|
||||
--changeset rbac-base-QUERY-ACCESSIBLE-OBJECT-UUIDS:1 endDelimiter:--//
|
||||
--changeset rbac-base-QUERY-ACCESSIBLE-OBJECT-UUIDS:1 runOnChange=true endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
/*
|
||||
|
||||
*/
|
||||
create or replace function queryAccessibleObjectUuidsOfSubjectIds(
|
||||
requiredOp RbacOp,
|
||||
forObjectTable varchar, -- reduces the result set, but is not really faster when used in restricted view
|
||||
forObjectTable varchar,
|
||||
subjectIds uuid[],
|
||||
maxObjects integer = 8000)
|
||||
returns setof uuid
|
||||
@@ -724,23 +717,29 @@ create or replace function queryAccessibleObjectUuidsOfSubjectIds(
|
||||
declare
|
||||
foundRows bigint;
|
||||
begin
|
||||
return query select distinct perm.objectUuid
|
||||
from (with recursive grants as (select descendantUuid, ascendantUuid, 1 as level
|
||||
from RbacGrants
|
||||
where assumed
|
||||
and ascendantUuid = any (subjectIds)
|
||||
union
|
||||
distinct
|
||||
select "grant".descendantUuid, "grant".ascendantUuid, level + 1 as level
|
||||
from RbacGrants "grant"
|
||||
inner join grants recur on recur.descendantUuid = "grant".ascendantUuid
|
||||
where assumed)
|
||||
select descendantUuid
|
||||
from grants) as granted
|
||||
join RbacPermission perm
|
||||
on granted.descendantUuid = perm.uuid and (requiredOp = 'SELECT' or perm.op = requiredOp)
|
||||
join RbacObject obj on obj.uuid = perm.objectUuid and obj.objectTable = forObjectTable
|
||||
limit maxObjects + 1;
|
||||
return query
|
||||
WITH RECURSIVE grants AS (
|
||||
SELECT descendantUuid, ascendantUuid, 1 AS level
|
||||
FROM RbacGrants
|
||||
WHERE assumed
|
||||
AND ascendantUuid = any(subjectIds)
|
||||
UNION ALL
|
||||
SELECT g.descendantUuid, g.ascendantUuid, grants.level + 1 AS level
|
||||
FROM RbacGrants g
|
||||
INNER JOIN grants ON grants.descendantUuid = g.ascendantUuid
|
||||
WHERE g.assumed
|
||||
),
|
||||
granted AS (
|
||||
SELECT DISTINCT descendantUuid
|
||||
FROM grants
|
||||
)
|
||||
SELECT DISTINCT perm.objectUuid
|
||||
FROM granted
|
||||
JOIN RbacPermission perm ON granted.descendantUuid = perm.uuid
|
||||
JOIN RbacObject obj ON obj.uuid = perm.objectUuid
|
||||
WHERE (requiredOp = 'SELECT' OR perm.op = requiredOp)
|
||||
AND obj.objectTable = forObjectTable
|
||||
LIMIT maxObjects+1;
|
||||
|
||||
foundRows = lastRowCount();
|
||||
if foundRows > maxObjects then
|
||||
@@ -751,7 +750,6 @@ begin
|
||||
end if;
|
||||
end;
|
||||
$$;
|
||||
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
@@ -764,24 +762,23 @@ create or replace function queryPermissionsGrantedToSubjectId(subjectId uuid)
|
||||
returns setof RbacPermission
|
||||
strict
|
||||
language sql as $$
|
||||
-- @formatter:off
|
||||
select *
|
||||
from RbacPermission
|
||||
where uuid in (
|
||||
with recursive grants as (
|
||||
select distinct descendantUuid, ascendantUuid
|
||||
from RbacGrants
|
||||
where ascendantUuid = subjectId
|
||||
union all
|
||||
select "grant".descendantUuid, "grant".ascendantUuid
|
||||
from RbacGrants "grant"
|
||||
inner join grants recur on recur.descendantUuid = "grant".ascendantUuid
|
||||
)
|
||||
select descendantUuid
|
||||
from grants
|
||||
);
|
||||
-- @formatter:on
|
||||
with recursive grants as (
|
||||
select descendantUuid, ascendantUuid
|
||||
from RbacGrants
|
||||
where ascendantUuid = subjectId
|
||||
union all
|
||||
select g.descendantUuid, g.ascendantUuid
|
||||
from RbacGrants g
|
||||
inner join grants on grants.descendantUuid = g.ascendantUuid
|
||||
)
|
||||
select perm.*
|
||||
from RbacPermission perm
|
||||
where perm.uuid in (
|
||||
select descendantUuid
|
||||
from grants
|
||||
);
|
||||
$$;
|
||||
|
||||
--//
|
||||
|
||||
-- ============================================================================
|
||||
|
||||
@@ -175,16 +175,38 @@ begin
|
||||
Creates a restricted view based on the 'SELECT' permission of the current subject.
|
||||
*/
|
||||
sql := format($sql$
|
||||
set session session authorization default;
|
||||
create view %1$s_rv as
|
||||
with accessibleObjects as (
|
||||
select queryAccessibleObjectUuidsOfSubjectIds('SELECT', '%1$s', currentSubjectsUuids())
|
||||
create or replace view %1$s_rv as
|
||||
with accessible_%1$s_uuids as (
|
||||
|
||||
with recursive grants as (
|
||||
select descendantUuid, ascendantUuid, 1 as level
|
||||
from RbacGrants
|
||||
where assumed
|
||||
and ascendantUuid = any (currentSubjectsuUids())
|
||||
union all
|
||||
select g.descendantUuid, g.ascendantUuid, level + 1 as level
|
||||
from RbacGrants g
|
||||
inner join grants on grants.descendantUuid = g.ascendantUuid
|
||||
where g.assumed
|
||||
),
|
||||
granted as (
|
||||
select distinct descendantUuid
|
||||
from grants
|
||||
)
|
||||
select distinct perm.objectUuid as objectUuid
|
||||
from granted
|
||||
join RbacPermission perm on granted.descendantUuid = perm.uuid
|
||||
join RbacObject obj on obj.uuid = perm.objectUuid
|
||||
where perm.op = 'SELECT'
|
||||
and obj.objectTable = '%1$s'
|
||||
limit 8001
|
||||
)
|
||||
select target.*
|
||||
from %1$s as target
|
||||
where target.uuid in (select * from accessibleObjects)
|
||||
where target.uuid in (select * from accessible_%1$s_uuids)
|
||||
order by %2$s;
|
||||
grant all privileges on %1$s_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
|
||||
|
||||
grant all privileges on %1$s_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
|
||||
$sql$, targetTable, orderBy);
|
||||
execute sql;
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ databaseChangeLog:
|
||||
file: db/changelog/0-basis/010-context.sql
|
||||
- include:
|
||||
file: db/changelog/0-basis/020-audit-log.sql
|
||||
- include:
|
||||
file: db/changelog/0-basis/090-log-slow-queries-extensions.sql
|
||||
- include:
|
||||
file: db/changelog/1-rbac/1050-rbac-base.sql
|
||||
- include:
|
||||
|
||||
+4
-1
@@ -287,7 +287,10 @@ class HsOfficeBankAccountControllerAcceptanceTest extends ContextBasedTestWithCl
|
||||
.statusCode(204); // @formatter:on
|
||||
|
||||
// then the given bankaccount is still there
|
||||
assertThat(bankAccountRepo.findByUuid(givenBankAccount.getUuid())).isEmpty();
|
||||
jpaAttempt.transacted(() -> {
|
||||
context("superuser-alex@hostsharing.net", null);
|
||||
assertThat(bankAccountRepo.findByUuid(givenBankAccount.getUuid())).isEmpty();
|
||||
}).assertSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-1
@@ -123,7 +123,7 @@ class HsOfficeBankAccountRepositoryIntegrationTest extends ContextBasedTestWithC
|
||||
|
||||
private void assertThatBankAccountIsPersisted(final HsOfficeBankAccountEntity saved) {
|
||||
final var found = bankAccountRepo.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -309,7 +309,10 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
.statusCode(204); // @formatter:on
|
||||
|
||||
// then the given contact is gone
|
||||
assertThat(contactRepo.findByUuid(givenContact.getUuid())).isEmpty();
|
||||
jpaAttempt.transacted(() -> {
|
||||
context("superuser-alex@hostsharing.net", null);
|
||||
assertThat(contactRepo.findByUuid(givenContact.getUuid())).isEmpty();
|
||||
}).assertSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -326,7 +329,10 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
.statusCode(204); // @formatter:on
|
||||
|
||||
// then the given contact is still there
|
||||
assertThat(contactRepo.findByUuid(givenContact.getUuid())).isEmpty();
|
||||
jpaAttempt.transacted(() -> {
|
||||
context("superuser-alex@hostsharing.net", null);
|
||||
assertThat(contactRepo.findByUuid(givenContact.getUuid())).isEmpty();
|
||||
}).assertSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-1
@@ -122,7 +122,7 @@ class HsOfficeContactRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
|
||||
private void assertThatContactIsPersisted(final HsOfficeContactEntity saved) {
|
||||
final var found = contactRepo.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -62,7 +62,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var count = coopAssetsTransactionRepo.count();
|
||||
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101);
|
||||
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).load();
|
||||
|
||||
// when
|
||||
final var result = attempt(em, () -> {
|
||||
@@ -119,7 +119,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
|
||||
|
||||
private void assertThatCoopAssetsTransactionIsPersisted(final HsOfficeCoopAssetsTransactionEntity saved) {
|
||||
final var found = coopAssetsTransactionRepo.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||
assertThat(found).isNotEmpty().get().extracting(HsOfficeCoopAssetsTransactionEntity::toString).isEqualTo(saved.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -61,7 +61,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var count = coopSharesTransactionRepo.count();
|
||||
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101);
|
||||
final var givenMembership = membershipRepo.findMembershipByMemberNumber(1000101).load();
|
||||
|
||||
// when
|
||||
final var result = attempt(em, () -> {
|
||||
@@ -118,7 +118,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
|
||||
|
||||
private void assertThatCoopSharesTransactionIsPersisted(final HsOfficeCoopSharesTransactionEntity saved) {
|
||||
final var found = coopSharesTransactionRepo.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||
assertThat(found).isNotEmpty().get().extracting(HsOfficeCoopSharesTransactionEntity::toString).isEqualTo(saved.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-14
@@ -609,22 +609,24 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
"defaultPrefix": "for"
|
||||
}
|
||||
"""
|
||||
.replace("${debitorNumberSuffix}", givenDebitor.getDebitorNumberSuffix().toString()))
|
||||
.replace("${debitorNumberSuffix}", givenDebitor.getDebitorNumberSuffix()))
|
||||
);
|
||||
// @formatter:on
|
||||
|
||||
// finally, the debitor is actually updated
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
assertThat(debitorRepo.findByUuid(givenDebitor.getUuid())).isPresent().get()
|
||||
.matches(debitor -> {
|
||||
assertThat(debitor.getDebitorRel().getHolder().getTradeName())
|
||||
.isEqualTo(givenDebitor.getDebitorRel().getHolder().getTradeName());
|
||||
assertThat(debitor.getDebitorRel().getContact().getCaption()).isEqualTo("fourth contact");
|
||||
assertThat(debitor.getVatId()).isEqualTo("VAT222222");
|
||||
assertThat(debitor.getVatCountryCode()).isEqualTo("AA");
|
||||
assertThat(debitor.isVatBusiness()).isEqualTo(true);
|
||||
return true;
|
||||
});
|
||||
jpaAttempt.transacted(() -> {
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
assertThat(debitorRepo.findByUuid(givenDebitor.getUuid())).isPresent().get()
|
||||
.matches(debitor -> {
|
||||
assertThat(debitor.getDebitorRel().getHolder().getTradeName())
|
||||
.isEqualTo(givenDebitor.getDebitorRel().getHolder().getTradeName());
|
||||
assertThat(debitor.getDebitorRel().getContact().getCaption()).isEqualTo("fourth contact");
|
||||
assertThat(debitor.getVatId()).isEqualTo("VAT222222");
|
||||
assertThat(debitor.getVatCountryCode()).isEqualTo("AA");
|
||||
assertThat(debitor.isVatBusiness()).isEqualTo(true);
|
||||
return true;
|
||||
});
|
||||
}).assertSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -718,7 +720,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
private HsOfficeDebitorEntity givenSomeTemporaryDebitor() {
|
||||
return jpaAttempt.transacted(() -> {
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0);
|
||||
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0).load();
|
||||
final var givenContact = contactRepo.findContactByOptionalCaptionLike("fourth contact").get(0);
|
||||
final var newDebitor = HsOfficeDebitorEntity.builder()
|
||||
.debitorNumberSuffix(nextDebitorSuffix())
|
||||
@@ -735,7 +737,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
.vatReverseCharge(false)
|
||||
.build();
|
||||
|
||||
return debitorRepo.save(newDebitor);
|
||||
return debitorRepo.save(newDebitor).load();
|
||||
}).assertSuccessful().returnedValue();
|
||||
}
|
||||
|
||||
|
||||
+20
-17
@@ -23,7 +23,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -83,12 +82,14 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var count = debitorRepo.count();
|
||||
final var givenPartner = partnerRepo.findPartnerByPartnerNumber(10001);
|
||||
final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike("First GmbH"));
|
||||
final var givenContact = one(contactRepo.findContactByOptionalCaptionLike("first contact"));
|
||||
|
||||
// when
|
||||
final var result = attempt(em, () -> {
|
||||
final var newDebitor = HsOfficeDebitorEntity.builder()
|
||||
.partner(givenPartner)
|
||||
.debitorNumberSuffix("21")
|
||||
.debitorRel(HsOfficeRelationEntity.builder()
|
||||
.type(HsOfficeRelationType.DEBITOR)
|
||||
@@ -99,7 +100,8 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
.defaultPrefix("abc")
|
||||
.billable(false)
|
||||
.build();
|
||||
return toCleanup(debitorRepo.save(newDebitor));
|
||||
final HsOfficeDebitorEntity entity = debitorRepo.save(newDebitor);
|
||||
return toCleanup(entity.load());
|
||||
});
|
||||
|
||||
// then
|
||||
@@ -339,14 +341,13 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
givenDebitor.setVatId(givenNewVatId);
|
||||
givenDebitor.setVatCountryCode(givenNewVatCountryCode);
|
||||
givenDebitor.setVatBusiness(givenNewVatBusiness);
|
||||
return toCleanup(debitorRepo.save(givenDebitor));
|
||||
final HsOfficeDebitorEntity entity = debitorRepo.save(givenDebitor);
|
||||
return toCleanup(entity.load());
|
||||
});
|
||||
|
||||
// then
|
||||
result.assertSuccessful();
|
||||
assertThatDebitorIsVisibleForUserWithRole(
|
||||
result.returnedValue(),
|
||||
"global#global:ADMIN", true);
|
||||
assertThatDebitorIsVisibleForUserWithRole(result.returnedValue(), "global#global:ADMIN", true);
|
||||
|
||||
// ... partner role was reassigned:
|
||||
assertThatDebitorIsNotVisibleForUserWithRole(
|
||||
@@ -388,7 +389,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
final var result = jpaAttempt.transacted(() -> {
|
||||
context("superuser-alex@hostsharing.net");
|
||||
givenDebitor.setRefundBankAccount(givenNewBankAccount);
|
||||
return toCleanup(debitorRepo.save(givenDebitor));
|
||||
return toCleanup(debitorRepo.save(givenDebitor).load());
|
||||
});
|
||||
|
||||
// then
|
||||
@@ -417,7 +418,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
final var result = jpaAttempt.transacted(() -> {
|
||||
context("superuser-alex@hostsharing.net");
|
||||
givenDebitor.setRefundBankAccount(null);
|
||||
return toCleanup(debitorRepo.save(givenDebitor));
|
||||
return toCleanup(debitorRepo.save(givenDebitor).load());
|
||||
});
|
||||
|
||||
// then
|
||||
@@ -460,22 +461,21 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "ninth", "Fourth", "nin");
|
||||
assertThatDebitorActuallyInDatabase(givenDebitor, true);
|
||||
assertThatDebitorIsVisibleForUserWithRole(
|
||||
givenDebitor,
|
||||
"hs_office_contact#ninthcontact:ADMIN", false);
|
||||
assertThatDebitorIsVisibleForUserWithRole(givenDebitor, "hs_office_contact#ninthcontact:ADMIN", false);
|
||||
|
||||
// when
|
||||
final var result = jpaAttempt.transacted(() -> {
|
||||
context("superuser-alex@hostsharing.net", "hs_office_contact#ninthcontact:ADMIN");
|
||||
givenDebitor.setVatId("NEW-VAT-ID");
|
||||
return toCleanup(debitorRepo.save(givenDebitor));
|
||||
final HsOfficeDebitorEntity entity = debitorRepo.save(givenDebitor);
|
||||
return toCleanup(entity.load());
|
||||
});
|
||||
|
||||
// then
|
||||
result.assertExceptionWithRootCauseMessage(
|
||||
JpaObjectRetrievalFailureException.class,
|
||||
// this technical error message gets translated to a [403] error at the controller level
|
||||
"Unable to find net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity with id ");
|
||||
JpaSystemException.class,
|
||||
"ERROR: [403]",
|
||||
"is not allowed to update hs_office_debitor uuid");
|
||||
}
|
||||
|
||||
private void assertThatDebitorActuallyInDatabase(final HsOfficeDebitorEntity saved, final boolean withPartner) {
|
||||
@@ -608,11 +608,13 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
final String defaultPrefix) {
|
||||
return jpaAttempt.transacted(() -> {
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var givenPartnerPerson = one(personRepo.findPersonByOptionalNameLike(partnerName));
|
||||
final var givenPartner = one(partnerRepo.findPartnerByOptionalNameLike(partnerName));
|
||||
final var givenPartnerPerson = givenPartner.getPartnerRel().getHolder();
|
||||
final var givenContact = one(contactRepo.findContactByOptionalCaptionLike(contactCaption));
|
||||
final var givenBankAccount =
|
||||
bankAccountHolder != null ? one(bankAccountRepo.findByOptionalHolderLike(bankAccountHolder)) : null;
|
||||
final var newDebitor = HsOfficeDebitorEntity.builder()
|
||||
.partner(givenPartner)
|
||||
.debitorNumberSuffix("20")
|
||||
.debitorRel(HsOfficeRelationEntity.builder()
|
||||
.type(HsOfficeRelationType.DEBITOR)
|
||||
@@ -625,7 +627,8 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
.billable(true)
|
||||
.build();
|
||||
|
||||
return toCleanup(debitorRepo.save(newDebitor));
|
||||
final HsOfficeDebitorEntity entity = debitorRepo.save(newDebitor);
|
||||
return toCleanup(entity.load());
|
||||
}).assertSuccessful().returnedValue();
|
||||
}
|
||||
|
||||
|
||||
+6
-7
@@ -75,7 +75,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
||||
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
|
||||
.membershipFeeBillable(true)
|
||||
.build();
|
||||
return toCleanup(membershipRepo.save(newMembership));
|
||||
return toCleanup(membershipRepo.save(newMembership).load());
|
||||
});
|
||||
|
||||
// then
|
||||
@@ -143,7 +143,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
||||
|
||||
private void assertThatMembershipIsPersisted(final HsOfficeMembershipEntity saved) {
|
||||
final var found = membershipRepo.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString()) ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
||||
public void globalAdmin_canUpdateValidityOfArbitraryMembership() {
|
||||
// given
|
||||
context("superuser-alex@hostsharing.net");
|
||||
final var givenMembership = givenSomeTemporaryMembership("First", "11");
|
||||
final var givenMembership = givenSomeTemporaryMembership("First", "11");
|
||||
assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership);
|
||||
final var newValidityEnd = LocalDate.now();
|
||||
|
||||
@@ -214,13 +214,12 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
||||
givenMembership.setValidity(Range.closedOpen(
|
||||
givenMembership.getValidity().lower(), newValidityEnd));
|
||||
givenMembership.setStatus(HsOfficeMembershipStatus.CANCELLED);
|
||||
return toCleanup(membershipRepo.save(givenMembership));
|
||||
final HsOfficeMembershipEntity entity = membershipRepo.save(givenMembership);
|
||||
return toCleanup(entity.load());
|
||||
});
|
||||
|
||||
// then
|
||||
result.assertSuccessful();
|
||||
|
||||
membershipRepo.deleteByUuid(givenMembership.getUuid());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -363,7 +362,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
||||
.membershipFeeBillable(true)
|
||||
.build();
|
||||
|
||||
return toCleanup(membershipRepo.save(newMembership));
|
||||
return toCleanup(membershipRepo.save(newMembership).load());
|
||||
}).assertSuccessful().returnedValue();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -548,7 +548,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
||||
.build())
|
||||
.build();
|
||||
|
||||
return partnerRepo.save(newPartner);
|
||||
return partnerRepo.save(newPartner).load();
|
||||
}).assertSuccessful().returnedValue();
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -180,7 +180,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
|
||||
private void assertThatPartnerIsPersisted(final HsOfficePartnerEntity saved) {
|
||||
final var found = partnerRepo.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
|
||||
.anchor(givenMandantorPerson)
|
||||
.contact(givenContact)
|
||||
.build();
|
||||
relationRepo.save(partnerRel);
|
||||
relationRepo.save(partnerRel).load();
|
||||
return partnerRel;
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -298,7 +298,10 @@ class HsOfficePersonControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
.statusCode(204); // @formatter:on
|
||||
|
||||
// then the given person is still there
|
||||
assertThat(personRepo.findByUuid(givenPerson.getUuid())).isEmpty();
|
||||
jpaAttempt.transacted(() -> {
|
||||
context.define("superuser-alex@hostsharing.net");
|
||||
assertThat(personRepo.findByUuid(givenPerson.getUuid())).isEmpty();
|
||||
}).assertSuccessful();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -332,7 +335,7 @@ class HsOfficePersonControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
||||
.givenName("Temp Given Name " + RandomStringUtils.randomAlphabetic(10))
|
||||
.build();
|
||||
|
||||
return personRepo.save(newPerson);
|
||||
return personRepo.save(newPerson).load();
|
||||
}).assertSuccessful().returnedValue();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -124,7 +124,7 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTestWithCleanu
|
||||
|
||||
private void assertThatPersonIsPersisted(final HsOfficePersonEntity saved) {
|
||||
final var found = personRepo.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -158,7 +158,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
|
||||
|
||||
private void assertThatRelationIsPersisted(final HsOfficeRelationEntity saved) {
|
||||
final var found = relationRepo.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
|
||||
final var result = jpaAttempt.transacted(() -> {
|
||||
context("superuser-alex@hostsharing.net");
|
||||
givenRelation.setContact(givenContact);
|
||||
return toCleanup(relationRepo.save(givenRelation));
|
||||
return toCleanup(relationRepo.save(givenRelation).load());
|
||||
});
|
||||
|
||||
// then
|
||||
@@ -295,7 +295,8 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
|
||||
final var found = relationRepo.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get()
|
||||
.isNotSameAs(saved)
|
||||
.usingRecursiveComparison().ignoringFields("version").isEqualTo(saved);
|
||||
.extracting(HsOfficeRelationEntity::toString)
|
||||
.isEqualTo(saved.toString());
|
||||
}
|
||||
|
||||
private void assertThatRelationIsVisibleForUserWithRole(
|
||||
|
||||
+2
-2
@@ -152,7 +152,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC
|
||||
|
||||
private void assertThatSepaMandateIsPersisted(final HsOfficeSepaMandateEntity saved) {
|
||||
final var found = sepaMandateRepo.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTestWithC
|
||||
jpaAttempt.transacted(() -> {
|
||||
context("superuser-alex@hostsharing.net");
|
||||
assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isNotEmpty().get()
|
||||
.usingRecursiveComparison().isEqualTo(givenSepaMandate);
|
||||
.extracting(Object::toString).isEqualTo(givenSepaMandate.toString());
|
||||
}).assertSuccessful();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ class TestCustomerRepositoryIntegrationTest extends ContextBasedTest {
|
||||
|
||||
private void assertThatCustomerIsPersisted(final TestCustomerEntity saved) {
|
||||
final var found = testCustomerRepository.findByUuid(saved.getUuid());
|
||||
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
|
||||
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ spring:
|
||||
|
||||
liquibase:
|
||||
change-log: classpath:/db/changelog/db.changelog-master.yaml
|
||||
contexts: tc,test,dev
|
||||
contexts: tc,test,dev,pg_stat_statements
|
||||
|
||||
logging:
|
||||
level:
|
||||
|
||||
Reference in New Issue
Block a user