add @Version property to all RBAC-Entities (#34)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #34
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-04-08 11:21:22 +02:00
parent 44ff30c54a
commit ec1deb8903
29 changed files with 65 additions and 38 deletions

View File

@ -6,7 +6,8 @@ Permissions, Rollen und Grants werden in den INSERT/UPDATE/DELETE-Triggern von G
Das folgende Schema soll dabei unterstützen, die richtigen Permissions, Rollen und Grants festzulegen. Das folgende Schema soll dabei unterstützen, die richtigen Permissions, Rollen und Grants festzulegen.
An einigen Stellen ist vom *Initiator* die Rede. Als *Initiator* gilt derjenige User, der die Operation (INSERT oder UPDATE) durchführt bzw. dessen primary assumed Rol. (TODO: bisher gibt es nur assumed roles, das Konzept einer primary assumed Role müsste noch eingeführt werden, derzeit nehmen wir dafür immer den `globalAdmin()`. Bevor Kunden aber selbst Objekte anlegen können, muss das geklärt sein.) An einigen Stellen ist vom *Initiator* die Rede. Als *Initiator* gilt derjenige User, der die Operation (INSERT oder UPDATE) durchführt bzw. eine explizit anzugebende Rolle des Users.
Wird keine solche explizite Rolle angegeben, gilt die granted Rolle als diejenige, als der das Grant erfolgt.
#### Typ Root: Objekte, welche nur eine Spezialisierung bzw. Zusatzdaten für andere Objekte bereitstellen (z.B. Partner für Relations vom Typ Partner oder Partner Details für Partner) #### Typ Root: Objekte, welche nur eine Spezialisierung bzw. Zusatzdaten für andere Objekte bereitstellen (z.B. Partner für Relations vom Typ Partner oder Partner Details für Partner)

View File

@ -8,10 +8,7 @@ import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.Entity; import jakarta.persistence.*;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
@ -41,6 +38,9 @@ public class HsOfficeBankAccountEntity implements RbacObject, Stringifyable {
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
@Version
private int version;
private String holder; private String holder;
private String iban; private String iban;

View File

@ -40,6 +40,11 @@ public class HsOfficeContactEntity implements Stringifyable, RbacObject {
@GeneratedValue(generator = "UUID") @GeneratedValue(generator = "UUID")
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID uuid; private UUID uuid;
@Version
private int version;
@Column(name = "label")
private String label; private String label;
@Column(name = "postaladdress") @Column(name = "postaladdress")

View File

@ -14,15 +14,7 @@ import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import jakarta.persistence.Column; import jakarta.persistence.*;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
@ -65,6 +57,9 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, RbacO
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID uuid; private UUID uuid;
@Version
private int version;
@ManyToOne @ManyToOne
@JoinColumn(name = "membershipuuid") @JoinColumn(name = "membershipuuid")
private HsOfficeMembershipEntity membership; private HsOfficeMembershipEntity membership;

View File

@ -13,15 +13,7 @@ import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.Column; import jakarta.persistence.*;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
@ -61,6 +53,9 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, RbacO
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
@Version
private int version;
@ManyToOne @ManyToOne
@JoinColumn(name = "membershipuuid") @JoinColumn(name = "membershipuuid")
private HsOfficeMembershipEntity membership; private HsOfficeMembershipEntity membership;

View File

@ -71,6 +71,9 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID uuid; private UUID uuid;
@Version
private int version;
@ManyToOne @ManyToOne
@JoinFormula( @JoinFormula(
referencedColumnName = "uuid", referencedColumnName = "uuid",

View File

@ -57,6 +57,9 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
@Version
private int version;
@ManyToOne @ManyToOne
@JoinColumn(name = "partneruuid") @JoinColumn(name = "partneruuid")
private HsOfficePartnerEntity partner; private HsOfficePartnerEntity partner;

View File

@ -43,6 +43,7 @@ public class HsOfficePartnerDetailsEntity implements RbacObject, Stringifyable {
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
private @Version int version;
private @Column(name = "registrationoffice") String registrationOffice; private @Column(name = "registrationoffice") String registrationOffice;
private @Column(name = "registrationnumber") String registrationNumber; private @Column(name = "registrationnumber") String registrationNumber;
private @Column(name = "birthname") String birthName; private @Column(name = "birthname") String birthName;

View File

@ -17,13 +17,7 @@ import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import jakarta.persistence.Column; import jakarta.persistence.*;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.io.IOException; import java.io.IOException;
import java.util.UUID; import java.util.UUID;
@ -66,6 +60,9 @@ public class HsOfficePartnerEntity implements Stringifyable, RbacObject {
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
@Version
private int version;
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null") @Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
private Integer partnerNumber; private Integer partnerNumber;

View File

@ -44,6 +44,9 @@ public class HsOfficePersonEntity implements RbacObject, Stringifyable {
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
@Version
private int version;
@Column(name = "persontype") @Column(name = "persontype")
private HsOfficePersonType personType; private HsOfficePersonType personType;

View File

@ -52,6 +52,9 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
@Version
private int version;
@ManyToOne @ManyToOne
@JoinColumn(name = "anchoruuid") @JoinColumn(name = "anchoruuid")
private HsOfficePersonEntity anchor; private HsOfficePersonEntity anchor;

View File

@ -50,6 +50,9 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, RbacObject {
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
@Version
private int version;
@ManyToOne @ManyToOne
@JoinColumn(name = "debitoruuid") @JoinColumn(name = "debitoruuid")
private HsOfficeDebitorEntity debitor; private HsOfficeDebitorEntity debitor;

View File

@ -426,8 +426,7 @@ public class RbacView {
private void verifyVersionColumnExists() { private void verifyVersionColumnExists() {
if (stream(rootEntityAlias.entityClass.getDeclaredFields()) if (stream(rootEntityAlias.entityClass.getDeclaredFields())
.noneMatch(f -> f.getAnnotation(Version.class) != null)) { .noneMatch(f -> f.getAnnotation(Version.class) != null)) {
// TODO: convert this into throw Exception once RbacEntity is a base class with @Version field throw new IllegalArgumentException("@Version field required in updatable entity " + rootEntityAlias.entityClass);
System.err.println("@Version field required in updatable entity " + rootEntityAlias.entityClass);
} }
} }

View File

@ -5,4 +5,6 @@ import java.util.UUID;
public interface RbacObject { public interface RbacObject {
UUID getUuid(); UUID getUuid();
int getVersion();
} }

View File

@ -30,6 +30,9 @@ public class TestCustomerEntity implements RbacObject {
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
@Version
private int version;
private String prefix; private String prefix;
private int reference; private int reference;

View File

@ -7,6 +7,7 @@
create table if not exists test_customer create table if not exists test_customer
( (
uuid uuid unique references RbacObject (uuid), uuid uuid unique references RbacObject (uuid),
version int not null default 0,
reference int not null unique check (reference between 10000 and 99999), reference int not null unique check (reference between 10000 and 99999),
prefix character(3) unique, prefix character(3) unique,
adminUserName varchar(63) adminUserName varchar(63)

View File

@ -7,6 +7,7 @@
create table if not exists hs_office_contact create table if not exists hs_office_contact
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
version int not null default 0,
label varchar(128) not null, label varchar(128) not null,
postalAddress text, postalAddress text,
emailAddresses text, -- TODO.feat: change to json emailAddresses text, -- TODO.feat: change to json

View File

@ -17,6 +17,7 @@ CREATE CAST (character varying as HsOfficePersonType) WITH INOUT AS IMPLICIT;
create table if not exists hs_office_person create table if not exists hs_office_person
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
version int not null default 0,
personType HsOfficePersonType not null, personType HsOfficePersonType not null,
tradeName varchar(96), tradeName varchar(96),
salutation varchar(30), salutation varchar(30),

View File

@ -19,6 +19,7 @@ CREATE CAST (character varying as HsOfficeRelationType) WITH INOUT AS IMPLICIT;
create table if not exists hs_office_relation create table if not exists hs_office_relation
( (
uuid uuid unique references RbacObject (uuid) initially deferred, -- on delete cascade uuid uuid unique references RbacObject (uuid) initially deferred, -- on delete cascade
version int not null default 0,
anchorUuid uuid not null references hs_office_person(uuid), anchorUuid uuid not null references hs_office_person(uuid),
holderUuid uuid not null references hs_office_person(uuid), holderUuid uuid not null references hs_office_person(uuid),
contactUuid uuid references hs_office_contact(uuid), contactUuid uuid references hs_office_contact(uuid),

View File

@ -8,6 +8,7 @@
create table hs_office_partner_details create table hs_office_partner_details
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
version int not null default 0,
registrationOffice varchar(96), registrationOffice varchar(96),
registrationNumber varchar(96), registrationNumber varchar(96),
birthPlace varchar(96), birthPlace varchar(96),
@ -32,8 +33,9 @@ call create_journal('hs_office_partner_details');
create table hs_office_partner create table hs_office_partner
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
version int not null default 0,
partnerNumber numeric(5) unique not null, partnerNumber numeric(5) unique not null,
partnerRelUuid uuid not null references hs_office_relation(uuid), -- deleted in after delete trigger partnerRelUuid uuid not null references hs_office_relation(uuid), -- deleted in after delete trigger
detailsUuid uuid not null references hs_office_partner_details(uuid) -- deleted in after delete trigger detailsUuid uuid not null references hs_office_partner_details(uuid) -- deleted in after delete trigger
); );
--// --//

View File

@ -6,6 +6,7 @@
create table hs_office_bankaccount create table hs_office_bankaccount
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
version int not null default 0,
holder varchar(64) not null, holder varchar(64) not null,
iban varchar(34) not null, iban varchar(34) not null,
bic varchar(11) not null bic varchar(11) not null

View File

@ -7,6 +7,7 @@
create table hs_office_debitor create table hs_office_debitor
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
version int not null default 0,
debitorNumberSuffix char(2) not null check (debitorNumberSuffix::text ~ '^[0-9][0-9]$'), debitorNumberSuffix char(2) not null check (debitorNumberSuffix::text ~ '^[0-9][0-9]$'),
debitorRelUuid uuid not null references hs_office_relation(uuid), debitorRelUuid uuid not null references hs_office_relation(uuid),
billable boolean not null default true, billable boolean not null default true,

View File

@ -7,6 +7,7 @@
create table if not exists hs_office_sepamandate create table if not exists hs_office_sepamandate
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
version int not null default 0,
debitorUuid uuid not null references hs_office_debitor(uuid), debitorUuid uuid not null references hs_office_debitor(uuid),
bankAccountUuid uuid not null references hs_office_bankaccount(uuid), bankAccountUuid uuid not null references hs_office_bankaccount(uuid),
reference varchar(96) not null, reference varchar(96) not null,

View File

@ -11,6 +11,7 @@ CREATE CAST (character varying as HsOfficeReasonForTermination) WITH INOUT AS IM
create table if not exists hs_office_membership create table if not exists hs_office_membership
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
version int not null default 0,
partnerUuid uuid not null references hs_office_partner(uuid), partnerUuid uuid not null references hs_office_partner(uuid),
memberNumberSuffix char(2) not null check (memberNumberSuffix::text ~ '^[0-9][0-9]$'), memberNumberSuffix char(2) not null check (memberNumberSuffix::text ~ '^[0-9][0-9]$'),
validity daterange not null, validity daterange not null,

View File

@ -11,6 +11,7 @@ CREATE CAST (character varying as HsOfficeCoopSharesTransactionType) WITH INOUT
create table if not exists hs_office_coopsharestransaction create table if not exists hs_office_coopsharestransaction
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
version int not null default 0,
membershipUuid uuid not null references hs_office_membership(uuid), membershipUuid uuid not null references hs_office_membership(uuid),
transactionType HsOfficeCoopSharesTransactionType not null, transactionType HsOfficeCoopSharesTransactionType not null,
valueDate date not null, valueDate date not null,

View File

@ -18,6 +18,7 @@ CREATE CAST (character varying as HsOfficeCoopAssetsTransactionType) WITH INOUT
create table if not exists hs_office_coopassetstransaction create table if not exists hs_office_coopassetstransaction
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
version int not null default 0,
membershipUuid uuid not null references hs_office_membership(uuid), membershipUuid uuid not null references hs_office_membership(uuid),
transactionType HsOfficeCoopAssetsTransactionType not null, transactionType HsOfficeCoopAssetsTransactionType not null,
valueDate date not null, valueDate date not null,

View File

@ -295,7 +295,9 @@ class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithClea
private void assertThatRelationActuallyInDatabase(final HsOfficeRelationEntity saved) { private void assertThatRelationActuallyInDatabase(final HsOfficeRelationEntity saved) {
final var found = relationRepo.findByUuid(saved.getUuid()); final var found = relationRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().isNotSameAs(saved).usingRecursiveComparison().isEqualTo(saved); assertThat(found).isNotEmpty().get()
.isNotSameAs(saved)
.usingRecursiveComparison().ignoringFields("version").isEqualTo(saved);
} }
private void assertThatRelationIsVisibleForUserWithRole( private void assertThatRelationIsVisibleForUserWithRole(

View File

@ -7,6 +7,6 @@ public class TestCustomer {
static final TestCustomerEntity yyy = hsCustomer("yyy", 10002, "yyy@example.com"); static final TestCustomerEntity yyy = hsCustomer("yyy", 10002, "yyy@example.com");
static public TestCustomerEntity hsCustomer(final String prefix, final int reference, final String adminName) { static public TestCustomerEntity hsCustomer(final String prefix, final int reference, final String adminName) {
return new TestCustomerEntity(null, prefix, reference, adminName); return new TestCustomerEntity(null, 0, prefix, reference, adminName);
} }
} }

View File

@ -40,7 +40,7 @@ class TestCustomerRepositoryIntegrationTest extends ContextBasedTest {
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
final var newCustomer = new TestCustomerEntity( final var newCustomer = new TestCustomerEntity(
UUID.randomUUID(), "www", 90001, "customer-admin@www.example.com"); UUID.randomUUID(), 0, "www", 90001, "customer-admin@www.example.com");
return testCustomerRepository.save(newCustomer); return testCustomerRepository.save(newCustomer);
}); });
@ -59,7 +59,7 @@ class TestCustomerRepositoryIntegrationTest extends ContextBasedTest {
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
final var newCustomer = new TestCustomerEntity( final var newCustomer = new TestCustomerEntity(
UUID.randomUUID(), "www", 90001, "customer-admin@www.example.com"); UUID.randomUUID(), 0, "www", 90001, "customer-admin@www.example.com");
return testCustomerRepository.save(newCustomer); return testCustomerRepository.save(newCustomer);
}); });
@ -77,7 +77,7 @@ class TestCustomerRepositoryIntegrationTest extends ContextBasedTest {
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
final var newCustomer = new TestCustomerEntity( final var newCustomer = new TestCustomerEntity(
UUID.randomUUID(), "www", 90001, "customer-admin@www.example.com"); UUID.randomUUID(), 0, "www", 90001, "customer-admin@www.example.com");
return testCustomerRepository.save(newCustomer); return testCustomerRepository.save(newCustomer);
}); });