Merge remote-tracking branch 'origin/master' into add-salut-and-title-to-person
This commit is contained in:
commit
4438e7abd5
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,7 +4,6 @@
|
||||
/build/www/**
|
||||
/src/test/javascript/coverage/
|
||||
/worktrees/
|
||||
TODO-progress.png
|
||||
|
||||
######################
|
||||
# Node
|
||||
|
11
README.md
11
README.md
@ -82,7 +82,7 @@ If you have at least Docker and the Java JDK installed in appropriate versions a
|
||||
|
||||
# the following command should return a JSON array with just all packages visible for the admin of the customer yyy:
|
||||
curl \
|
||||
-H 'current-user: superuser-alex@hostsharing.net' -H 'assumed-roles: test_customer#yyy.admin' \
|
||||
-H 'current-user: superuser-alex@hostsharing.net' -H 'assumed-roles: test_customer#yyy:ADMIN' \
|
||||
http://localhost:8080/api/test/packages
|
||||
|
||||
# add a new customer
|
||||
@ -380,12 +380,6 @@ You can explore the prototype as follows:
|
||||
`src/`
|
||||
The actual source-code, see [Source Code Package Structure](#source-code-package-structure) for details.
|
||||
|
||||
`TODO.md`
|
||||
Requirements of initial project. Do not touch!
|
||||
|
||||
`TODO-progress.png`
|
||||
Generated diagram image of the project progress.
|
||||
|
||||
`tools/`
|
||||
Some shell-scripts to useful tasks.
|
||||
|
||||
@ -765,5 +759,4 @@ The output will list the generated files.
|
||||
## Further Documentation
|
||||
|
||||
- the `doc` directory contains architecture concepts and a glossary
|
||||
- TODO.md tracks requirements and progress for the contract of the initial project,
|
||||
please do not amend anything in this document
|
||||
- the `ideas` directory contains unstructured ideas for future development or documentation
|
||||
|
34
build.gradle
34
build.gradle
@ -1,15 +1,15 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.1.7'
|
||||
id 'org.springframework.boot' version '3.2.4'
|
||||
id 'io.spring.dependency-management' version '1.1.4'
|
||||
id 'io.openapiprocessor.openapi-processor' version '2023.2'
|
||||
id 'com.github.jk1.dependency-license-report' version '2.5'
|
||||
id "org.owasp.dependencycheck" version "9.0.7"
|
||||
id "com.diffplug.spotless" version "6.23.3"
|
||||
id 'com.github.jk1.dependency-license-report' version '2.6'
|
||||
id "org.owasp.dependencycheck" version "9.0.10"
|
||||
id "com.diffplug.spotless" version "6.25.0"
|
||||
id 'jacoco'
|
||||
id 'info.solidsoft.pitest' version '1.15.0'
|
||||
id 'se.patrikerdes.use-latest-versions' version '0.2.18'
|
||||
id 'com.github.ben-manes.versions' version '0.50.0'
|
||||
id 'com.github.ben-manes.versions' version '0.51.0'
|
||||
}
|
||||
|
||||
group = 'net.hostsharing'
|
||||
@ -59,28 +59,16 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.1'
|
||||
implementation 'org.springdoc:springdoc-openapi:2.3.0'
|
||||
implementation 'org.postgresql:postgresql:42.7.1'
|
||||
implementation 'org.liquibase:liquibase-core:4.25.1'
|
||||
implementation 'com.vladmihalcea:hibernate-types-60:2.21.1'
|
||||
implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.7.0'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.1'
|
||||
implementation 'org.springdoc:springdoc-openapi:2.4.0'
|
||||
implementation 'org.postgresql:postgresql:42.7.3'
|
||||
implementation 'org.liquibase:liquibase-core:4.27.0'
|
||||
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.7.3'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0'
|
||||
implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
|
||||
implementation 'org.apache.commons:commons-text:1.11.0'
|
||||
implementation 'org.modelmapper:modelmapper:3.2.0'
|
||||
implementation 'org.iban4j:iban4j:3.2.7-RELEASE'
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
|
||||
|
||||
// fixes vulnerability CVE-2022-1471
|
||||
// The dependency usually comes from Spring Boot, just in the wrong version.
|
||||
// TODO: Remove this explicit dependency once we are on SpringBoot 3.2.x
|
||||
// as well as the related exclude in settings.gradle
|
||||
// and the dependency suppression in owasp-dependency-check-suppression.xml.
|
||||
implementation('org.yaml:snakeyaml') {
|
||||
version {
|
||||
strictly('2.2')
|
||||
}
|
||||
}
|
||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
testCompileOnly 'org.projectlombok:lombok'
|
||||
|
@ -10,7 +10,7 @@ classDiagram
|
||||
|
||||
namespace Partner {
|
||||
class partner-MeierGmbH
|
||||
class role-MeierGmbH
|
||||
class rel-MeierGmbH
|
||||
class personDetails-MeierGmbH
|
||||
class contactData-MeierGmbH
|
||||
class person-MeierGmbH
|
||||
@ -19,28 +19,29 @@ classDiagram
|
||||
namespace Representatives {
|
||||
class person-FrankMeier
|
||||
class contactData-FrankMeier
|
||||
class role-MeierGmbH-FrankMeier
|
||||
class rel-MeierGmbH-FrankMeier
|
||||
}
|
||||
|
||||
namespace Debitors {
|
||||
class debitor-MeierGmbH
|
||||
class contactData-MeierGmbH-Buha
|
||||
class role-MeierGmbH-Buha
|
||||
class rel-MeierGmbH-Buha
|
||||
}
|
||||
|
||||
namespace Operations {
|
||||
class person-SabineMeier
|
||||
class contactData-SabineMeier
|
||||
class role-MeierGmbH-SabineMeier
|
||||
class rel-MeierGmbH-SabineMeier
|
||||
}
|
||||
|
||||
namespace Enums {
|
||||
|
||||
class RoleType {
|
||||
class RelationType {
|
||||
<<enumeration>>
|
||||
UNKNOWN
|
||||
PARTNER
|
||||
DEBITOR
|
||||
REPRESENTATIVE
|
||||
ACCOUNTING
|
||||
OPERATIONS
|
||||
}
|
||||
|
||||
@ -64,9 +65,9 @@ classDiagram
|
||||
|
||||
class partner-MeierGmbH {
|
||||
+Numeric partnerNumber: 12345
|
||||
+Role partnerRole
|
||||
+Relation partnerRel
|
||||
}
|
||||
partner-MeierGmbH *-- role-MeierGmbH
|
||||
partner-MeierGmbH *-- rel-MeierGmbH
|
||||
|
||||
class person-MeierGmbH {
|
||||
+personType: LEGAL
|
||||
@ -90,32 +91,32 @@ classDiagram
|
||||
+emailAddresses: office@meier-gmbh.de
|
||||
}
|
||||
|
||||
class role-MeierGmbH {
|
||||
+RoleType RoleType PARTNER
|
||||
class rel-MeierGmbH {
|
||||
+RelationType type PARTNER
|
||||
+Person anchor
|
||||
+Person holder
|
||||
+Contact roleContact
|
||||
+Contact contact
|
||||
}
|
||||
role-MeierGmbH o-- person-HostsharingEG : anchor
|
||||
role-MeierGmbH o-- person-MeierGmbH : holder
|
||||
role-MeierGmbH o-- contactData-MeierGmbH
|
||||
rel-MeierGmbH o-- person-HostsharingEG : anchor
|
||||
rel-MeierGmbH o-- person-MeierGmbH : holder
|
||||
rel-MeierGmbH o-- contactData-MeierGmbH
|
||||
|
||||
%% --- Debitors ---
|
||||
|
||||
class debitor-MeierGmbH {
|
||||
+Partner partner
|
||||
+Numeric[2] debitorNumberSuffix: 00
|
||||
+Role billingRole
|
||||
+boolean billable: true
|
||||
+String vatId: ID123456789
|
||||
+String vatCountryCode: DE
|
||||
+boolean vatBusiness: true
|
||||
+boolean vatReverseCharge: false
|
||||
+Partner partner
|
||||
+Numeric[2] debitorNumberSuffix: 00
|
||||
+Relation debitorRel
|
||||
+boolean billable: true
|
||||
+String vatId: ID123456789
|
||||
+String vatCountryCode: DE
|
||||
+boolean vatBusiness: true
|
||||
+boolean vatReverseCharge: false
|
||||
+BankAccount refundBankAccount
|
||||
+String defaultPrefix: mei
|
||||
+String defaultPrefix: mei
|
||||
}
|
||||
debitor-MeierGmbH o-- partner-MeierGmbH
|
||||
debitor-MeierGmbH *-- role-MeierGmbH-Buha
|
||||
debitor-MeierGmbH *-- rel-MeierGmbH-Buha
|
||||
|
||||
class contactData-MeierGmbH-Buha {
|
||||
+postalAddress: Hauptstraße 5, 22345 Hamburg
|
||||
@ -123,15 +124,15 @@ classDiagram
|
||||
+emailAddresses: buha@meier-gmbh.de
|
||||
}
|
||||
|
||||
class role-MeierGmbH-Buha {
|
||||
+RoleType RoleType ACCOUNTING
|
||||
class rel-MeierGmbH-Buha {
|
||||
+RelationType type DEBITOR
|
||||
+Person anchor
|
||||
+Person holder
|
||||
+Contact roleContact
|
||||
+Contact contact
|
||||
}
|
||||
role-MeierGmbH-Buha o-- person-MeierGmbH : anchor
|
||||
role-MeierGmbH-Buha o-- person-MeierGmbH : holder
|
||||
role-MeierGmbH-Buha o-- contactData-MeierGmbH-Buha
|
||||
rel-MeierGmbH-Buha o-- person-MeierGmbH : anchor
|
||||
rel-MeierGmbH-Buha o-- person-MeierGmbH : holder
|
||||
rel-MeierGmbH-Buha o-- contactData-MeierGmbH-Buha
|
||||
|
||||
%% --- Representatives ---
|
||||
|
||||
@ -148,15 +149,15 @@ classDiagram
|
||||
+emailAddresses: frank.meier@meier-gmbh.de
|
||||
}
|
||||
|
||||
class role-MeierGmbH-FrankMeier {
|
||||
+RoleType RoleType REPRESENTATIVE
|
||||
class rel-MeierGmbH-FrankMeier {
|
||||
+RelationType type REPRESENTATIVE
|
||||
+Person anchor
|
||||
+Person holder
|
||||
+Contact roleContact
|
||||
+Contact contact
|
||||
}
|
||||
role-MeierGmbH-FrankMeier o-- person-MeierGmbH : anchor
|
||||
role-MeierGmbH-FrankMeier o-- person-FrankMeier : holder
|
||||
role-MeierGmbH-FrankMeier o-- contactData-FrankMeier
|
||||
rel-MeierGmbH-FrankMeier o-- person-MeierGmbH : anchor
|
||||
rel-MeierGmbH-FrankMeier o-- person-FrankMeier : holder
|
||||
rel-MeierGmbH-FrankMeier o-- contactData-FrankMeier
|
||||
|
||||
%% --- Operations ---
|
||||
|
||||
@ -173,14 +174,14 @@ classDiagram
|
||||
+emailAddresses: sabine.meier@meier-gmbh.de
|
||||
}
|
||||
|
||||
class role-MeierGmbH-SabineMeier {
|
||||
+RoleType RoleType OPERATIONAL
|
||||
class rel-MeierGmbH-SabineMeier {
|
||||
+RelationType type OPERATIONAL
|
||||
+Person anchor
|
||||
+Person holder
|
||||
+Contact roleContact
|
||||
+Contact contact
|
||||
}
|
||||
role-MeierGmbH-SabineMeier o-- person-MeierGmbH : anchor
|
||||
role-MeierGmbH-SabineMeier o-- person-SabineMeier : holder
|
||||
role-MeierGmbH-SabineMeier o-- contactData-SabineMeier
|
||||
rel-MeierGmbH-SabineMeier o-- person-MeierGmbH : anchor
|
||||
rel-MeierGmbH-SabineMeier o-- person-SabineMeier : holder
|
||||
rel-MeierGmbH-SabineMeier o-- contactData-SabineMeier
|
||||
|
||||
```
|
||||
|
82
doc/ideas/rbac-schema-f.md
Normal file
82
doc/ideas/rbac-schema-f.md
Normal file
@ -0,0 +1,82 @@
|
||||
*(this is just a scribbled draft, that's why it's still in German)*
|
||||
|
||||
### *Schema-F* für Permissions, Rollen und Grants
|
||||
|
||||
Permissions, Rollen und Grants werden in den INSERT/UPDATE/DELETE-Triggern von Geschäftsobjekten erzeugt und gelöscht. Das Löschen erfolgt meistens automatisch über das zugehörige RbacObject, die INSERT- und UPDATE-Trigger müssen jedoch in *pl/pgsql* ausprogrammiert werden.
|
||||
|
||||
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.)
|
||||
|
||||
#### 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)
|
||||
|
||||
Objektorientiert gedacht, enthalten solche Objekte die Zusatzdaten einer Subklasse; die Daten im Partner erweitern also eine Relation vom Typ `partner`.
|
||||
|
||||
- Dann muss dieses Objekt zeitlich nach dem Objekt erzeugt werden, auf dass es sich bezieht, also z.B. zeitlich nach der Relation.
|
||||
- Es werden Delete (\*), Edit und View Permissions für dieses Objekt erzeugt.
|
||||
- Es werden **keine** Rollen für dieses Objekt erzeugt.
|
||||
- Statt eigener Rollen werden die o.g. Permissions passenden Rollen des Hauptobjekts zugewiesen (granted) bzw. aus denen entfernt (revoked).
|
||||
- Handelt es sich um Zusatzdaten zum Zwecke der Spezialisierung, dann z.B. so:
|
||||
- Delete (\*) <-- Owner des Hauptobjektes
|
||||
- Edit <-- **Admin** des Hauptobjektes
|
||||
- View <-- Agent des Hauptobjektes
|
||||
- Handelt es sich um Zusatzdaten, für die sich Edit-Rechte delegieren lassen sollen (wie im Falle der Partner-Details eines Partners), dann z.B. so:
|
||||
- Delete (\*) <-- Owner des Hauptobjektes
|
||||
- Edit <-- **Agent** des Hauptobjektes
|
||||
- View <-- Agent des Hauptobjektes
|
||||
- Für die Rollenzuordnung zwischen referenzierten Objekten gilt:
|
||||
- Für Objekte vom Typ Root werden die Rollen des zugehörigen Aggregator-Objektes verwendet.
|
||||
- Gibt es Referenzen auf hierarchisch verbundene Objekte (z.B. Debitor.refundBankAccount) gilt folgende Faustregel:
|
||||
***Nach oben absteigen, nach unten halten oder aufsteigen.*** An einem fachlich übergeordneten Objekt wird also eine niedrigere Rolle (z.B. Debitor.ADMIN -> Partner.AGENT), einem fachlich untergeordneten Objekt eine gleichwertige Rolle (z.B. Partner.ADMIN -> Debitor.ADMIN) zugewiesen oder sogar aufgestiegen (Debitor.ADMIN -> Package.TENANT).
|
||||
- Für Referenzen zwischen Objekten, die nicht hierarchisch zueinander stehen (z.B. Debitor und Bankverbindung), wird auf beiden seiten abgestiegen (also Debitor.ADMIN -> BankAccount.REFERRER und BankAccount.ADMIN -> Debitor.TENANT).
|
||||
|
||||
Anmerkung: Der Typ-Begriff *Root* bezieht sich auf die Rolle im fachlichen Datenmodell. Im Bezug auf den Teilgraphen eines fachlichen Kontexts ist dies auch eine Wurzel im Sinne der Graphentheorie. Aber in anderen fachlichen Kontexten können auch diese Objekte von anderen Teilgraphen referenziert werden und werden dann zum inneren Knoten.
|
||||
|
||||
|
||||
#### Typ Aggregator: Objekte, welche weitere Objekte zusammenfassen (z.B. Relation fasst zwei Persons und einen Contact zusammen)
|
||||
|
||||
Solche Objekte verweisen üblicherweise auf Objekte vom Typ Leaf und werden oft von Objekten des Typs Root referenziert.
|
||||
|
||||
- Es werden i.d.R. folgende Rollen für diese Objekte erzeugt:
|
||||
- Owner, Admin, Agent, Tenent(, Guest?)
|
||||
- Es werden Delete (\*), Edit und View Permissions für dieses Objekt erzeugt.
|
||||
- Die Permissions werden den Rollen sinnvoll zugewiesen, z.B.:
|
||||
- Owner -> Delete (\*)
|
||||
- Admin --> Edit
|
||||
- Tenant (oder ggf. Guest) --> View
|
||||
- Außerdem werden folgende Grants erstellt bzw. entzogen:
|
||||
- Initiator --> Owner
|
||||
- Owner --> Admin
|
||||
- Admin --> Referrer
|
||||
- Admins der referenzierten Objekte werden Agent des Aggregators
|
||||
- Tenants des Aggregators werden Referrer der referenzierten Objekte
|
||||
|
||||
### Typ Leaf: Handelt es sich um ein Objekt, welches (außer zur Modellierung separater Permissions) keine Unterobjekte enthält (z.B. Person, Customer)?
|
||||
|
||||
Solche Objekte werden üblicherweise von Objekten des Typs Aggregator, manchmal auch von Objekten des Typs Root, referenziert.
|
||||
|
||||
- Es werden i.d.R. folgende Rollen für diese Objekte erzeugt:
|
||||
- Owner, Admin, Referrer
|
||||
- Es werden Delete (\*), Edit und View Permissions für dieses Objekt erzeugt.
|
||||
- Die Permissions werden den Rollen sinnvoll zugewiesen, z.B.:
|
||||
- Delete (\*) <-- Owner
|
||||
- Edit <-- Admin
|
||||
- View <-- Referrer
|
||||
- Außerdem werden folgende Grants erstellt bzw. entzogen:
|
||||
- Owner --> Admin
|
||||
- Admin --> Referrer
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
|
||||
subgraph partnerDetails
|
||||
direction TB
|
||||
style partnerDetails fill:#eee
|
||||
|
||||
perm:partnerDetails.*{{partnerDetails.*}}
|
||||
role:partnerDetails.edit{{partnerDetails.edit}}
|
||||
role:partnerDetails.view{{partnerDetails.view}}
|
||||
|
||||
|
||||
end
|
||||
```
|
29
doc/ideas/simplified-grant-structure.md
Normal file
29
doc/ideas/simplified-grant-structure.md
Normal file
@ -0,0 +1,29 @@
|
||||
(this is just a scribbled idea, that's why it's still in German)
|
||||
|
||||
Ich habe mal wieder vom RBAC-System geträumt 🙈 Ok, im Halbschlaf darüber nachgedacht trifft es wohl besser. Und jetzt frage ich mich, ob wir viel zu kompliziert gedacht haben.
|
||||
|
||||
Bislang gingen wir ja davon aus, dass, wenn komplexe Entitäten (z.B. Partner) erzeugt werden, wir wir über den INSERT-Trigger den Rollen der verknüpften Entitäten (z.B. den Rollen der Personendaten des Partners) auch Rechte an den komplexeren Entitäten und umgekehrt geben müssen.
|
||||
|
||||
Da die komplexen Entitäten nur mit gewissen verbundenen Entitäten überhaupt sinnvoll nutzbar sind und diese daher über INNSER JOINs mitladen, könnte sonst auch nur jemand diese Entitäten, der auch die SELECT-Permission an den verküpften Entitäten hat.
|
||||
|
||||
Vor einigen Wochen hatten wir schon einmal darüber geredet, ob wir dieses Geflecht wirklich komplett durchplanen müssen, also über mehrere Stufen hinweg, oder ob sehr warscheinlich eh dieselben Leuten an den weiter entfernten Entitäten die nötien Rechte haben, weil dahinter dieselben User stehen. Also z.B. dass gewährleistet ist, dass jemand mit ADMIN-Recht an den Personendaten des Partners auch bis in die SEPA-Mandate eines Debitors hineinsehen kann.
|
||||
|
||||
Und nun gehe ich noch einen Schritt weiter: Könnte es nicht auch andersherum sein? Also wenn jemand z.B. SELECT-Recht am Partner hat, dass wir davon ausgehen können, dass derjenige auch die Partner-Personen- und Kontaktdaten sehen darf, und zwar implizit durch seine Partner-SELECT-Permission und ohne dass er explizit Rollen für diese Partner-Personen oder Kontaktdaten inne hat?
|
||||
|
||||
Im Halbschlaf kam mir nur die Idee, warum wir nicht einfach die komplexen JPA-Entitäten zwar auf die restricted View setzen, wie bisher, aber für die verknüpften Entitäten auf die direkten (bisher "Raw..." genannt) Entitäten gehen. Dann könnte jemand mit einer Rolle, welche die SELECT-Permission auf die komplexe JPA-Entität (z.B.) Partner inne hat, auch die dazugehörige Relation(ship) ["Relation" wurde vor kurzem auf kurz "Relation" umbenannt] und die wiederum dazu gehörigen Personen- und Kontaktdaten lesen, ohne dass in einem INSERT- und UPDATE-Trigger der Partner-Entität die ganzen Grants mit den verknüpften Entäten aufgebaut und aktualisiert werden müssen.
|
||||
|
||||
Beim Debitor ist das nämlich selbst mit Generator die Hölle, zumal eben auch Querverbindungen gegranted werden müssen, z.B. von der Debitor-Person zum Sema-Mandat - jedenfalls wenn man nicht Gefahr laufen wollte, dass jemand mit Admin-Rechten an der Partner-Person (also z.B. ein Repräsentant des Partners) die Sepa-Mandate der Debitoren gar nicht mehr sehen kann. Natürlich bräuchte man immer noch die Agent-Rolle am Partner und Debitor (evtl. repräsentiert durch die jeweils zugehörigen Relation - falls dieser Trick überhaupt noch nötig wäre), sowie ein Grant vom Partner-Agent auf den Debitor-Agent und vom Debitor-Agent auf die Sepa-Mandate-Admins, aber eben ohne filigran die ganzen Neben-Entäten (Personen- und Kontaktdaten von Partner und Debitor sowie Bank-Account) in jedem Trigger berücksichtigen zu müssen. Beim Refund-Bank-Account sogar besonders ätzend, weil der optional ist und dadurch zig "if ...refundBankAccountUuid is not null then ..." im Code enstehen (wenn der auch generiert ist).
|
||||
|
||||
Mit anderen Worten, um als Repräsentant eines Geschäftspartners auf den Bank-Account der Sepa-Mandate sehen zu dürfen, wird derzeut folgende Grant-Kette durchlaufen (bzw. eben noch nicht, weil es noch nicht funktioniert):
|
||||
|
||||
User -> Partner-Holder-Person:ADMIN -> Partner-Relation:AGENT -> Debitor-Relation:AGENT -> Sepa-Mandat:ADMIN -> BankAccount:ADMIN -> BankAccount:SELECT
|
||||
|
||||
Daraus würde:
|
||||
|
||||
User -> Partner-Relation:AGENT -> Debitor-Relation:AGENT -> Sepa-Mandat:ADMIN -> Sepa-Mandat:SELECT*
|
||||
|
||||
(*mit JOIN auf RawBankAccount, also implizitem Leserecht)
|
||||
|
||||
Das klingt zunächst nach nur einer marginalen Vereinfachung, die eigentlich Vereinfachung liegt aber im Erzeugen der Grants in den Triggern, denn da sind zudem noch Partner-Anchor-Person, Debitor-Holder- und Anchor-Person, Partner- und Debitor-Contact sowie der RefundBankAccount zu berücksichtigen. Und genau diese Grants würden großteils wegfallen, und durch implizite Persmissions über die JOINs auf die Raw-Tables ersetzt werden. Den refundBankAccound müssten wir dann, analog zu den Sepa-Mandataten, umgedreht modellieren, da den sonst
|
||||
|
||||
Man könnte das Ganze auch als "Entwicklung der Rechtestruktur für Hosting-Entitäten auf der obersten Ebene" (Manged Webspace, Managed Server, Cloud Server etc.) sehen, denn die hängen alle unter dem Mega-komplexen Debitor.
|
196
doc/rbac.md
196
doc/rbac.md
@ -1,6 +1,6 @@
|
||||
## *hsadmin-ng*'s Role-Based-Access-Management (RBAC)
|
||||
|
||||
The requirements of *hsadmin-ng* include table-m row- and column-level-security for read and write access to business-objects.
|
||||
The requirements of *hsadmin-ng* include table-, row- and column-level-security for read and write access to business-objects.
|
||||
More precisely, any access has to be controlled according to given rules depending on the accessing users, their roles and the accessed business-object.
|
||||
Further, roles and business-objects are hierarchical.
|
||||
|
||||
@ -11,7 +11,7 @@ Our implementation is based on Role-Based-Access-Management (RBAC) in conjunctio
|
||||
As far as possible, we are using the same terms as defined in the RBAC standard, for our function names though, we chose more expressive names.
|
||||
|
||||
In RBAC, subjects can be assigned to roles, roles can be hierarchical and eventually have assigned permissions.
|
||||
A permission allows a specific operation (e.g. view or edit) on a specific (business-) object.
|
||||
A permission allows a specific operation (e.g. SELECT or UPDATE) on a specific (business-) object.
|
||||
|
||||
You can find the entity structure as a UML class diagram as follows:
|
||||
|
||||
@ -101,13 +101,12 @@ package RBAC {
|
||||
RbacPermission *-- RbacObject
|
||||
|
||||
enum RbacOperation {
|
||||
add-package
|
||||
add-domain
|
||||
add-domain
|
||||
INSERT:package
|
||||
INSERT:domain
|
||||
...
|
||||
view
|
||||
edit
|
||||
delete
|
||||
SELECT
|
||||
UPDATE
|
||||
DELETE
|
||||
}
|
||||
|
||||
entity RbacObject {
|
||||
@ -172,11 +171,10 @@ An *RbacPermission* allows a specific *RbacOperation* on a specific *RbacObject*
|
||||
An *RbacOperation* determines, <u>what</u> an *RbacPermission* allows to do.
|
||||
It can be one of:
|
||||
|
||||
- **'add-...'** - permits creating new instances of specific entity types underneath the object specified by the permission, e.g. "add-package"
|
||||
- **'view'** - permits reading the contents of the object specified by the permission
|
||||
- **'edit'** - change the contents of the object specified by the permission
|
||||
- **'delete'** - delete the object specified by the permission
|
||||
- **'\*'**
|
||||
- **'INSERT'** - permits inserting new rows related to the row, to which the permission belongs, in the table which is specified an extra column, includes 'SELECT'
|
||||
- **'SELECT'** - permits selecting the row specified by the permission, is included in all other permissions
|
||||
- **'UPDATE'** - permits updating (only the updatable columns of) the row specified by the permission, includes 'SELECT'
|
||||
- **'DELETE'** - permits deleting the row specified by the permission, includes 'SELECT'
|
||||
|
||||
This list is extensible according to the needs of the access rule system.
|
||||
|
||||
@ -198,56 +196,60 @@ E.g. if a new package is added, the admin-role of the related customer has to be
|
||||
There can be global roles like 'administrators'.
|
||||
Most roles, though, are specific for certain business-objects and automatically generated as such:
|
||||
|
||||
business-object-table#business-object-name.relative-role
|
||||
business-object-table#business-object-name.role-stereotype
|
||||
|
||||
|
||||
Where *business-object-table* is the name of the SQL table of the business object (e.g *customer* or 'package'),
|
||||
*business-object-name* is generated from an immutable business key(e.g. a prefix like 'xyz' or 'xyz00')
|
||||
and the *relative-role*' describes the role relative to the referenced business-object as follows:
|
||||
and the *role-stereotype* describes a role relative to a referenced business-object as follows:
|
||||
|
||||
#### owner
|
||||
|
||||
The owner-role is granted to the subject which created the business object.
|
||||
E.g. for a new *customer* it would be granted to 'administrators' and for a new *package* to the 'customer#...admin'.
|
||||
E.g. for a new *customer* it would be granted to 'administrators' and for a new *package* to the 'customer#...:ADMIN'.
|
||||
|
||||
Whoever has the owner-role assigned can do everything with the related business-object, including deleting (or deactivating) it.
|
||||
|
||||
In most cases, the permissions to other operations than 'delete' are granted through the 'admin' role.
|
||||
In most cases, the permissions to other operations than 'DELETE' are granted through the 'admin' role.
|
||||
By this, all roles ob sub-objects, which are assigned to the 'admin' role, are also granted to the 'owner'.
|
||||
|
||||
#### admin
|
||||
#### ADMIN
|
||||
|
||||
The admin-role is granted to a role of those subjects who manage the business object.
|
||||
E.g. a 'package' is manged by the admin of the customer.
|
||||
|
||||
Whoever has the admin-role assigned, can usually edit the related business-object but not deleting (or deactivating) it.
|
||||
Whoever has the admin-role assigned, can usually update the related business-object but not delete (or deactivating) it.
|
||||
|
||||
The admin-role also comprises lesser roles, through which the view-permission is granted.
|
||||
The admin-role also comprises lesser roles, through which the SELECT-permission is granted.
|
||||
|
||||
#### agent
|
||||
#### AGENT
|
||||
|
||||
The agent-role is not used in the examples of this document, because it's for more complex cases.
|
||||
It's usually granted to those roles and users who represent the related business-object, but are not allowed to edit it.
|
||||
It's usually granted to those roles and users who represent the related business-object, but are not allowed to update it.
|
||||
|
||||
Other than the tenant-role, it usually offers broader visibility of sub-business-objects (joined entities).
|
||||
E.g. a package-admin is allowed to see the related debitor-business-object,
|
||||
but not its banking data.
|
||||
|
||||
#### tenant
|
||||
#### TENANT
|
||||
|
||||
The tenant-role is granted to everybody who needs to be able to view the business-object and (probably some) related business-objects.
|
||||
The tenant-role is granted to everybody who needs to be able to select the business-object and (probably some) related business-objects.
|
||||
Usually all owners, admins and tenants of sub-objects get this role granted.
|
||||
|
||||
Some business-objects only have very limited data directly in the main business-object and store more sensitive data in special sub-objects (e.g. 'customer-details') to which tenants of sub-objects of the main-object (e.g. package admins) do not get view permission.
|
||||
Some business-objects only have very limited data directly in the main business-object and store more sensitive data in special sub-objects (e.g. 'customer-details') to which tenants of sub-objects of the main-object (e.g. package admins) do not get SELECT permission.
|
||||
|
||||
#### guest
|
||||
#### GUEST
|
||||
|
||||
(Deprecated)
|
||||
|
||||
#### REFERRER
|
||||
|
||||
Like the agent-role, the guest-role too is not used in the examples of this document, because it's for more complex cases.
|
||||
|
||||
If the guest-role exists, the view-permission is granted to it, instead of to the tenant-role.
|
||||
Other than the tenant-role, the guest-roles does never grant any roles of related objects.
|
||||
If the referrer-role exists, the SELECT-permission is granted to it, instead of to the tenant-role.
|
||||
Other than the tenant-role, the referrer-roles does never grant any roles of related objects.
|
||||
|
||||
Also, if the guest-role exists, the tenant-role receives the view-permission through the guest-role.
|
||||
Also, if the referrer-role exists, the tenant-role receives the SELECT-permission through the referrer-role.
|
||||
|
||||
|
||||
### Referenced Business Objects and Role-Depreciation
|
||||
@ -263,7 +265,7 @@ The admin-role of one object could be granted visibility to another object throu
|
||||
|
||||
But not in all cases role-depreciation takes place.
|
||||
E.g. often a tenant-role is granted another tenant-role,
|
||||
because it should be again allowed to view sub-objects.
|
||||
because it should be again allowed to select sub-objects.
|
||||
The same for the agent-role, often it is granted another agent-role.
|
||||
|
||||
|
||||
@ -297,14 +299,14 @@ package RbacRoles {
|
||||
RbacUsers -[hidden]> RbacRoles
|
||||
|
||||
package RbacPermissions {
|
||||
object PermCustXyz_View
|
||||
object PermCustXyz_Edit
|
||||
object PermCustXyz_Delete
|
||||
object PermCustXyz_AddPackage
|
||||
object PermPackXyz00_View
|
||||
object PermPackXyz00_Edit
|
||||
object PermPackXyz00_Delete
|
||||
object PermPackXyz00_AddUser
|
||||
object PermCustXyz_SELECT
|
||||
object PermCustXyz_UPDATE
|
||||
object PermCustXyz_DELETE
|
||||
object PermCustXyz_INSERT:Package
|
||||
object PermPackXyz00_SELECT
|
||||
object PermPackXyz00_EDIT
|
||||
object PermPackXyz00_DELETE
|
||||
object PermPackXyz00_INSERT:USER
|
||||
}
|
||||
RbacRoles -[hidden]> RbacPermissions
|
||||
|
||||
@ -322,23 +324,23 @@ RoleAdministrators o..> RoleCustXyz_Owner
|
||||
RoleCustXyz_Owner o-> RoleCustXyz_Admin
|
||||
RoleCustXyz_Admin o-> RolePackXyz00_Owner
|
||||
|
||||
RoleCustXyz_Owner o--> PermCustXyz_Edit
|
||||
RoleCustXyz_Owner o--> PermCustXyz_Delete
|
||||
RoleCustXyz_Admin o--> PermCustXyz_View
|
||||
RoleCustXyz_Admin o--> PermCustXyz_AddPackage
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_View
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_Edit
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_Delete
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_AddUser
|
||||
RoleCustXyz_Owner o--> PermCustXyz_UPDATE
|
||||
RoleCustXyz_Owner o--> PermCustXyz_DELETE
|
||||
RoleCustXyz_Admin o--> PermCustXyz_SELECT
|
||||
RoleCustXyz_Admin o--> PermCustXyz_INSERT:Package
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_SELECT
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_UPDATE
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_DELETE
|
||||
RolePackXyz00_Owner o--> PermPackXyz00_INSERT:User
|
||||
|
||||
PermCustXyz_View o--> CustXyz
|
||||
PermCustXyz_Edit o--> CustXyz
|
||||
PermCustXyz_Delete o--> CustXyz
|
||||
PermCustXyz_AddPackage o--> CustXyz
|
||||
PermPackXyz00_View o--> PackXyz00
|
||||
PermPackXyz00_Edit o--> PackXyz00
|
||||
PermPackXyz00_Delete o--> PackXyz00
|
||||
PermPackXyz00_AddUser o--> PackXyz00
|
||||
PermCustXyz_SELECT o--> CustXyz
|
||||
PermCustXyz_UPDATE o--> CustXyz
|
||||
PermCustXyz_DELETE o--> CustXyz
|
||||
PermCustXyz_INSERT:Package o--> CustXyz
|
||||
PermPackXyz00_SELECT o--> PackXyz00
|
||||
PermPackXyz00_UPDATE o--> PackXyz00
|
||||
PermPackXyz00_DELETE o--> PackXyz00
|
||||
PermPackXyz00_INSERT:User o--> PackXyz00
|
||||
|
||||
@enduml
|
||||
```
|
||||
@ -353,12 +355,12 @@ To support the RBAC system, for each business-object-table, some more artifacts
|
||||
|
||||
Not yet implemented, but planned are these actions:
|
||||
|
||||
- an `ON DELETE ... DO INSTEAD` rule to allow `SQL DELETE` if applicable for the business-object-table and the user has 'delete' permission,
|
||||
- an `ON UPDATE ... DO INSTEAD` rule to allow `SQL UPDATE` if the user has 'edit' right,
|
||||
- an `ON INSERT ... DO INSTEAD` rule to allow `SQL INSERT` if the user has 'add-..' right to the parent-business-object.
|
||||
- an `ON DELETE ... DO INSTEAD` rule to allow `SQL DELETE` if applicable for the business-object-table and the user has 'DELETE' permission,
|
||||
- an `ON UPDATE ... DO INSTEAD` rule to allow `SQL UPDATE` if the user has 'UPDATE' right,
|
||||
- an `ON INSERT ... DO INSTEAD` rule to allow `SQL INSERT` if the user has the 'INSERT' right for the parent-business-object.
|
||||
|
||||
The restricted view takes the current user from a session property and applies the hierarchy of its roles all the way down to the permissions related to the respective business-object-table.
|
||||
This way, each user can only view the data they have 'view'-permission for, only create those they have 'add-...'-permission, only update those they have 'edit'- and only delete those they have 'delete'-permission to.
|
||||
This way, each user can only select the data they have 'SELECT'-permission for, only create those they have 'add-...'-permission, only update those they have 'UPDATE'- and only delete those they have 'DELETE'-permission to.
|
||||
|
||||
### Current User
|
||||
|
||||
@ -374,7 +376,7 @@ That user is also used for historicization and audit log, but which is a differe
|
||||
If the session variable `hsadminng.assumedRoles` is set to a non-empty value, its content is interpreted as a list of semicolon-separated role names.
|
||||
Example:
|
||||
|
||||
SET LOCAL hsadminng.assumedRoles = 'customer#aab.admin;customer#aac.admin';
|
||||
SET LOCAL hsadminng.assumedRoles = 'customer#aab:admin;customer#aac:admin';
|
||||
|
||||
In this case, not the current user but the assumed roles are used as a starting point for any further queries.
|
||||
Roles which are not granted to the current user, directly or indirectly, cannot be assumed.
|
||||
@ -387,7 +389,7 @@ A full example is shown here:
|
||||
BEGIN TRANSACTION;
|
||||
SET SESSION SESSION AUTHORIZATION restricted;
|
||||
SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net';
|
||||
SET LOCAL hsadminng.assumedRoles = 'customer#aab.admin;customer#aac.admin';
|
||||
SET LOCAL hsadminng.assumedRoles = 'customer#aab:admin;customer#aac:admin';
|
||||
|
||||
SELECT c.prefix, p.name as "package", ema.localPart || '@' || dom.name as "email-address"
|
||||
FROM emailaddress_rv ema
|
||||
@ -458,26 +460,26 @@ allow_mixing
|
||||
entity "BObj customer#xyz" as boCustXyz
|
||||
|
||||
together {
|
||||
entity "Perm customer#xyz *" as permCustomerXyzAll
|
||||
permCustomerXyzAll --> boCustXyz
|
||||
entity "Perm customer#xyz *" as permCustomerXyzDELETE
|
||||
permCustomerXyzDELETE --> boCustXyz
|
||||
|
||||
entity "Perm customer#xyz add-package" as permCustomerXyzAddPack
|
||||
permCustomerXyzAddPack --> boCustXyz
|
||||
entity "Perm customer#xyz INSERT:package" as permCustomerXyzINSERT:package
|
||||
permCustomerXyzINSERT:package --> boCustXyz
|
||||
|
||||
entity "Perm customer#xyz view" as permCustomerXyzView
|
||||
permCustomerXyzView --> boCustXyz
|
||||
entity "Perm customer#xyz SELECT" as permCustomerXyzSELECT
|
||||
permCustomerXyzSELECT--> boCustXyz
|
||||
}
|
||||
|
||||
entity "Role customer#xyz.tenant" as roleCustXyzTenant
|
||||
roleCustXyzTenant --> permCustomerXyzView
|
||||
entity "Role customer#xyz:TENANT" as roleCustXyzTenant
|
||||
roleCustXyzTenant --> permCustomerXyzSELECT
|
||||
|
||||
entity "Role customer#xyz.admin" as roleCustXyzAdmin
|
||||
entity "Role customer#xyz:ADMIN" as roleCustXyzAdmin
|
||||
roleCustXyzAdmin --> roleCustXyzTenant
|
||||
roleCustXyzAdmin --> permCustomerXyzAddPack
|
||||
roleCustXyzAdmin --> permCustomerXyzINSERT:package
|
||||
|
||||
entity "Role customer#xyz.owner" as roleCustXyzOwner
|
||||
entity "Role customer#xyz:OWNER" as roleCustXyzOwner
|
||||
roleCustXyzOwner ..> roleCustXyzAdmin
|
||||
roleCustXyzOwner --> permCustomerXyzAll
|
||||
roleCustXyzOwner --> permCustomerXyzDELETE
|
||||
|
||||
actor "Customer XYZ Admin" as actorCustXyzAdmin
|
||||
actorCustXyzAdmin --> roleCustXyzAdmin
|
||||
@ -487,13 +489,11 @@ roleAdmins --> roleCustXyzOwner
|
||||
|
||||
actor "Any Hostmaster" as actorHostmaster
|
||||
actorHostmaster --> roleAdmins
|
||||
|
||||
|
||||
@enduml
|
||||
```
|
||||
|
||||
As you can see, there something special:
|
||||
From the 'Role customer#xyz.owner' to the 'Role customer#xyz.admin' there is a dashed line, whereas all other lines are solid lines.
|
||||
From the 'Role customer#xyz:OWNER' to the 'Role customer#xyz:admin' there is a dashed line, whereas all other lines are solid lines.
|
||||
Solid lines means, that one role is granted to another and automatically assumed in all queries to the restricted views.
|
||||
The dashed line means that one role is granted to another but not automatically assumed in queries to the restricted views.
|
||||
|
||||
@ -527,36 +527,36 @@ allow_mixing
|
||||
entity "BObj package#xyz00" as boPacXyz00
|
||||
|
||||
together {
|
||||
entity "Perm package#xyz00 *" as permPackageXyzAll
|
||||
permPackageXyzAll --> boPacXyz00
|
||||
entity "Perm package#xyz00 *" as permPackageXyzDELETE
|
||||
permPackageXyzDELETE --> boPacXyz00
|
||||
|
||||
entity "Perm package#xyz00 add-domain" as permPacXyz00AddUser
|
||||
permPacXyz00AddUser --> boPacXyz00
|
||||
entity "Perm package#xyz00 INSERT:domain" as permPacXyz00INSERT:user
|
||||
permPacXyz00INSERT:user --> boPacXyz00
|
||||
|
||||
entity "Perm package#xyz00 edit" as permPacXyz00Edit
|
||||
permPacXyz00Edit --> boPacXyz00
|
||||
entity "Perm package#xyz00 UPDATE" as permPacXyz00UPDATE
|
||||
permPacXyz00UPDATE --> boPacXyz00
|
||||
|
||||
entity "Perm package#xyz00 view" as permPacXyz00View
|
||||
permPacXyz00View --> boPacXyz00
|
||||
entity "Perm package#xyz00 SELECT" as permPacXyz00SELECT
|
||||
permPacXyz00SELECT --> boPacXyz00
|
||||
}
|
||||
|
||||
package {
|
||||
entity "Role customer#xyz.tenant" as roleCustXyzTenant
|
||||
entity "Role customer#xyz.admin" as roleCustXyzAdmin
|
||||
entity "Role customer#xyz.owner" as roleCustXyzOwner
|
||||
entity "Role customer#xyz:TENANT" as roleCustXyzTenant
|
||||
entity "Role customer#xyz:ADMIN" as roleCustXyzAdmin
|
||||
entity "Role customer#xyz:OWNER" as roleCustXyzOwner
|
||||
}
|
||||
|
||||
package {
|
||||
entity "Role package#xyz00.owner" as rolePacXyz00Owner
|
||||
entity "Role package#xyz00.admin" as rolePacXyz00Admin
|
||||
entity "Role package#xyz00.tenant" as rolePacXyz00Tenant
|
||||
entity "Role package#xyz00:OWNER" as rolePacXyz00Owner
|
||||
entity "Role package#xyz00:ADMIN" as rolePacXyz00Admin
|
||||
entity "Role package#xyz00:TENANT" as rolePacXyz00Tenant
|
||||
}
|
||||
|
||||
rolePacXyz00Tenant --> permPacXyz00View
|
||||
rolePacXyz00Tenant --> permPacXyz00SELECT
|
||||
rolePacXyz00Tenant --> roleCustXyzTenant
|
||||
|
||||
rolePacXyz00Owner --> rolePacXyz00Admin
|
||||
rolePacXyz00Owner --> permPackageXyzAll
|
||||
rolePacXyz00Owner --> permPackageXyzDELETE
|
||||
|
||||
roleCustXyzAdmin --> rolePacXyz00Owner
|
||||
roleCustXyzAdmin --> roleCustXyzTenant
|
||||
@ -564,8 +564,8 @@ roleCustXyzAdmin --> roleCustXyzTenant
|
||||
roleCustXyzOwner ..> roleCustXyzAdmin
|
||||
|
||||
rolePacXyz00Admin --> rolePacXyz00Tenant
|
||||
rolePacXyz00Admin --> permPacXyz00AddUser
|
||||
rolePacXyz00Admin --> permPacXyz00Edit
|
||||
rolePacXyz00Admin --> permPacXyz00INSERT:user
|
||||
rolePacXyz00Admin --> permPacXyz00UPDATE
|
||||
|
||||
actor "Package XYZ00 Admin" as actorPacXyzAdmin
|
||||
actorPacXyzAdmin -l-> rolePacXyz00Admin
|
||||
@ -624,10 +624,10 @@ Let's have a look at the two view queries:
|
||||
WHERE target.uuid IN (
|
||||
SELECT uuid
|
||||
FROM queryAccessibleObjectUuidsOfSubjectIds(
|
||||
'view', 'customer', currentSubjectsUuids()));
|
||||
'SELECT, 'customer', currentSubjectsUuids()));
|
||||
|
||||
This view should be automatically updatable.
|
||||
Where, for updates, we actually have to check for 'edit' instead of 'view' operation, which makes it a bit more complicated.
|
||||
Where, for updates, we actually have to check for 'UPDATE' instead of 'SELECT' operation, which makes it a bit more complicated.
|
||||
|
||||
With the larger dataset, the test suite initially needed over 7 seconds with this view query.
|
||||
At this point the second variant was tried.
|
||||
@ -642,7 +642,7 @@ Looks like the query optimizer needed some statistics to find the best path.
|
||||
SELECT DISTINCT target.*
|
||||
FROM customer AS target
|
||||
JOIN queryAccessibleObjectUuidsOfSubjectIds(
|
||||
'view', 'customer', currentSubjectsUuids()) AS allowedObjId
|
||||
'SELECT, 'customer', currentSubjectsUuids()) AS allowedObjId
|
||||
ON target.uuid = allowedObjId;
|
||||
|
||||
This view cannot is not updatable automatically,
|
||||
@ -688,13 +688,13 @@ Otherwise, it would not be possible to assign roles to new users.
|
||||
|
||||
All roles are system-defined and cannot be created or modified by any external API.
|
||||
|
||||
Users can view only the roles to which they are assigned.
|
||||
Users can view only the roles to which are granted to them.
|
||||
|
||||
## RbacGrant
|
||||
|
||||
Grant can be `empowered`, this means that the grantee user can grant the granted role to other users
|
||||
and revoke grants to that role.
|
||||
(TODO: access control part not yet implemented)
|
||||
(TODO: access control part not yet implemented, currently all accessible roles can be granted to other users)
|
||||
|
||||
Grants can be `managed`, which means they are created and deleted by system-defined rules.
|
||||
If a grant is not managed, it was created by an empowered user and can be deleted by empowered users.
|
||||
|
@ -87,7 +87,7 @@ Acceptance-Tests run on a fully integrated and deployed system with deployed dou
|
||||
|
||||
Acceptance-tests, are blackbox-tests and do <u>not</u> count into test-code-coverage.
|
||||
|
||||
TODO: Complete the Acceptance-Tests test concept.
|
||||
TODO.test: Complete the Acceptance-Tests test concept.
|
||||
|
||||
|
||||
#### Performance-Tests
|
||||
@ -107,4 +107,4 @@ We define System-Integration-Tests as test in which this system is deployed in a
|
||||
|
||||
System-Integration-tests, are blackbox-tests and do <u>not</u> count into test-code-coverage.
|
||||
|
||||
TODO: Complete the System-Integration-Tests test concept.
|
||||
TODO.test: Complete the System-Integration-Tests test concept.
|
||||
|
@ -1,33 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
We don't use the Spring HTTP invoker which causes this vulnerability due to Java deserialization.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.springframework/spring-web@.*$</packageUrl>
|
||||
<cve>CVE-2016-1000027</cve>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
We don't use the UNWRAP_SINGLE_VALUE_ARRAYS feature and thus are not affected.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/com\.fasterxml\.jackson\.core/jackson\-databind@.*$</packageUrl>
|
||||
<cve>CVE-2022-42003</cve>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
We don't parse external XML.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.eclipse\.angus/angus\-activation@.*$</packageUrl>
|
||||
<cpe>cpe:/a:eclipse:eclipse_ide</cpe>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
We don't parse external XML.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/jakarta\.activation/jakarta\.activation\-api@.*$</packageUrl>
|
||||
<cpe>cpe:/a:eclipse:eclipse_ide</cpe>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
Cyclic references are not possible if file comes in JSON text format.
|
||||
@ -35,13 +7,6 @@
|
||||
<packageUrl regex="true">^pkg:maven/com\.fasterxml\.jackson\.core/jackson\-databind@.*$</packageUrl>
|
||||
<cpe>cpe:/a:fasterxml:jackson-databind</cpe>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
As far as I see Criteria.parse(...) cannot be reached with external data.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/com\.jayway\.jsonpath/json\-path@.*$</packageUrl>
|
||||
<vulnerabilityName>CVE-2023-51074</vulnerabilityName>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
Internal tooling, not exposed to the Internet.
|
||||
@ -49,17 +14,4 @@
|
||||
<packageUrl regex="true">^pkg:maven/org\.pitest/pitest\-command\-line@.*$</packageUrl>
|
||||
<cpe>cpe:/a:line:line</cpe>
|
||||
</suppress>
|
||||
<suppress>
|
||||
<notes><![CDATA[
|
||||
Spring Boot 3.1.x has a transient dependency to snakeyaml 1.3
|
||||
which contains this vulnerability.
|
||||
|
||||
We've explicitly bumped to 2.2, but the vulnerability checker does not seem to notice that.
|
||||
|
||||
TODO: Remove this suppression once we are on SpringBoot 3.2,
|
||||
as well as the explicit version bump and the transient dependency exclude.
|
||||
]]></notes>
|
||||
<packageUrl regex="true">^pkg:maven/org\.yaml/snakeyaml@.*$</packageUrl>
|
||||
<cve>CVE-2022-1471</cve>
|
||||
</suppress>
|
||||
</suppressions>
|
||||
|
@ -11,28 +11,4 @@ plugins {
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
components {
|
||||
all {
|
||||
allVariants {
|
||||
withDependencies {
|
||||
removeAll {
|
||||
// Spring Boot 3.1.x has a transient dependency to snakeyaml 1.3
|
||||
// which contains a severe vulnerability.
|
||||
// Here we remove this transient dependency and in build.gradle
|
||||
// we add an explicit dependency to snakeyaml 2.2,
|
||||
// which does not have this vulnerability anymore.
|
||||
//
|
||||
// TODO: Check Once we are on SpringBoot 3.2.x, check if this exclude
|
||||
// is still neccessary. If not:
|
||||
// Remove it // as well as the related explicit dependency in build.gradle
|
||||
// and the dependency suppression in owasp-dependency-check-suppression.xml.
|
||||
it.module in [ 'snakeyaml' ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = 'hsadmin-ng'
|
||||
|
@ -18,8 +18,8 @@ CREATE OR REPLACE FUNCTION historicize()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql STRICT AS $$
|
||||
DECLARE
|
||||
currentUser VARCHAR(64);
|
||||
currentTask varchar;
|
||||
currentUser VARCHAR(63);
|
||||
currentTask VARCHAR(127);
|
||||
"row" RECORD;
|
||||
"alive" BOOLEAN;
|
||||
"sql" varchar;
|
||||
@ -37,27 +37,27 @@ END IF;
|
||||
|
||||
-- determine task
|
||||
currentTask = current_setting('hsadminng.currentTask');
|
||||
IF (currentTask IS NULL OR length(currentTask) < 12) THEN
|
||||
RAISE EXCEPTION 'hsadminng.currentTask (%) must be defined and min 12 characters long, please use "SET LOCAL ...;"', currentTask;
|
||||
END IF;
|
||||
RAISE NOTICE 'currentTask: %', currentTask;
|
||||
assert currentTask IS NOT NULL AND length(currentTask) >= 12,
|
||||
format('hsadminng.currentTask (%s) must be defined and min 12 characters long, please use "SET LOCAL ...;"', currentTask);
|
||||
assert length(currentTask) <= 127,
|
||||
format('hsadminng.currentTask (%s) must not be longer than 127 characters"', currentTask);
|
||||
|
||||
IF (TG_OP = 'INSERT') OR (TG_OP = 'UPDATE') THEN
|
||||
"row" := NEW;
|
||||
"alive" := TRUE;
|
||||
ELSE -- DELETE or TRUNCATE
|
||||
"row" := OLD;
|
||||
"alive" := FALSE;
|
||||
END IF;
|
||||
ELSE -- DELETE or TRUNCATE
|
||||
"row" := OLD;
|
||||
"alive" := FALSE;
|
||||
END IF;
|
||||
|
||||
sql := format('INSERT INTO tx_history VALUES (txid_current(), now(), %1L, %2L) ON CONFLICT DO NOTHING', currentUser, currentTask);
|
||||
sql := format('INSERT INTO tx_history VALUES (txid_current(), now(), %1L, %2L) ON CONFLICT DO NOTHING', currentUser, currentTask);
|
||||
RAISE NOTICE 'sql: %', sql;
|
||||
EXECUTE sql;
|
||||
sql := format('INSERT INTO %3$I_versions VALUES (DEFAULT, txid_current(), %1$L, %2$L, $1.*)', TG_OP, alive, TG_TABLE_NAME);
|
||||
RAISE NOTICE 'sql: %', sql;
|
||||
EXECUTE sql USING "row";
|
||||
EXECUTE sql;
|
||||
sql := format('INSERT INTO %3$I_versions VALUES (DEFAULT, txid_current(), %1$L, %2$L, $1.*)', TG_OP, alive, TG_TABLE_NAME);
|
||||
RAISE NOTICE 'sql: %', sql;
|
||||
EXECUTE sql USING "row";
|
||||
|
||||
RETURN "row";
|
||||
RETURN "row";
|
||||
END; $$;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE create_historical_view(baseTable varchar)
|
||||
|
@ -3,10 +3,10 @@
|
||||
-- --------------------------------------------------------
|
||||
|
||||
|
||||
select isGranted(findRoleId('administrators'), findRoleId('test_package#aaa00.owner'));
|
||||
select isGranted(findRoleId('test_package#aaa00.owner'), findRoleId('administrators'));
|
||||
-- call grantRoleToRole(findRoleId('test_package#aaa00.owner'), findRoleId('administrators'));
|
||||
-- call grantRoleToRole(findRoleId('administrators'), findRoleId('test_package#aaa00.owner'));
|
||||
select isGranted(findRoleId('administrators'), findRoleId('test_package#aaa00:OWNER'));
|
||||
select isGranted(findRoleId('test_package#aaa00:OWNER'), findRoleId('administrators'));
|
||||
-- call grantRoleToRole(findRoleId('test_package#aaa00:OWNER'), findRoleId('administrators'));
|
||||
-- call grantRoleToRole(findRoleId('administrators'), findRoleId('test_package#aaa00:OWNER'));
|
||||
|
||||
select count(*)
|
||||
FROM queryAllPermissionsOfSubjectIdForObjectUuids(findRbacUser('superuser-fran@hostsharing.net'),
|
||||
@ -25,7 +25,7 @@ FROM queryAllRbacUsersWithPermissionsFor(findEffectivePermissionId('customer',
|
||||
select *
|
||||
FROM queryAllRbacUsersWithPermissionsFor(findEffectivePermissionId('package',
|
||||
(SELECT uuid FROM RbacObject WHERE objectTable = 'package' LIMIT 1),
|
||||
'delete'));
|
||||
'DELETE'));
|
||||
|
||||
DO LANGUAGE plpgsql
|
||||
$$
|
||||
@ -34,12 +34,12 @@ $$
|
||||
result bool;
|
||||
BEGIN
|
||||
userId = findRbacUser('superuser-alex@hostsharing.net');
|
||||
result = (SELECT * FROM isPermissionGrantedToSubject(findEffectivePermissionId('package', 94928, 'add-package'), userId));
|
||||
result = (SELECT * FROM isPermissionGrantedToSubject(findPermissionId('package', 94928, 'add-package'), userId));
|
||||
IF (result) THEN
|
||||
RAISE EXCEPTION 'expected permission NOT to be granted, but it is';
|
||||
end if;
|
||||
|
||||
result = (SELECT * FROM isPermissionGrantedToSubject(findEffectivePermissionId('package', 94928, 'view'), userId));
|
||||
result = (SELECT * FROM isPermissionGrantedToSubject(findPermissionId('package', 94928, 'SELECT'), userId));
|
||||
IF (NOT result) THEN
|
||||
RAISE EXCEPTION 'expected permission to be granted, but it is NOT';
|
||||
end if;
|
||||
|
@ -20,7 +20,7 @@ CREATE POLICY customer_policy ON customer
|
||||
TO restricted
|
||||
USING (
|
||||
-- id=1000
|
||||
isPermissionGrantedToSubject(findEffectivePermissionId('test_customer', id, 'view'), currentUserUuid())
|
||||
isPermissionGrantedToSubject(findEffectivePermissionId('test_customer', id, 'SELECT'), currentUserUuid())
|
||||
);
|
||||
|
||||
SET SESSION AUTHORIZATION restricted;
|
||||
@ -35,7 +35,7 @@ SELECT * FROM customer;
|
||||
CREATE OR REPLACE RULE "_RETURN" AS
|
||||
ON SELECT TO cust_view
|
||||
DO INSTEAD
|
||||
SELECT * FROM customer WHERE isPermissionGrantedToSubject(findEffectivePermissionId('test_customer', id, 'view'), currentUserUuid());
|
||||
SELECT * FROM customer WHERE isPermissionGrantedToSubject(findEffectivePermissionId('test_customer', id, 'SELECT'), currentUserUuid());
|
||||
SELECT * from cust_view LIMIT 10;
|
||||
|
||||
select queryAllPermissionsOfSubjectId(findRbacUser('superuser-alex@hostsharing.net'));
|
||||
@ -52,7 +52,7 @@ CREATE OR REPLACE RULE "_RETURN" AS
|
||||
DO INSTEAD
|
||||
SELECT c.uuid, c.reference, c.prefix FROM customer AS c
|
||||
JOIN queryAllPermissionsOfSubjectId(currentUserUuid()) AS p
|
||||
ON p.objectTable='test_customer' AND p.objectUuid=c.uuid AND p.op in ('*', 'view');
|
||||
ON p.objectTable='test_customer' AND p.objectUuid=c.uuid;
|
||||
GRANT ALL PRIVILEGES ON cust_view TO restricted;
|
||||
|
||||
SET SESSION SESSION AUTHORIZATION restricted;
|
||||
@ -68,7 +68,7 @@ CREATE OR REPLACE VIEW cust_view AS
|
||||
SELECT c.uuid, c.reference, c.prefix
|
||||
FROM customer AS c
|
||||
JOIN queryAllPermissionsOfSubjectId(currentUserUuid()) AS p
|
||||
ON p.objectUuid=c.uuid AND p.op in ('*', 'view');
|
||||
ON p.objectUuid=c.uuid;
|
||||
GRANT ALL PRIVILEGES ON cust_view TO restricted;
|
||||
|
||||
SET SESSION SESSION AUTHORIZATION restricted;
|
||||
@ -81,9 +81,9 @@ select rr.uuid, rr.type from RbacGrants g
|
||||
join RbacReference RR on g.ascendantUuid = RR.uuid
|
||||
where g.descendantUuid in (
|
||||
select uuid from queryAllPermissionsOfSubjectId(findRbacUser('alex@example.com'))
|
||||
where objectTable='test_customer' and op in ('*', 'view'));
|
||||
where objectTable='test_customer');
|
||||
|
||||
call grantRoleToUser(findRoleId('test_customer#aaa.admin'), findRbacUser('aaaaouq@example.com'));
|
||||
call grantRoleToUser(findRoleId('test_customer#aaa:ADMIN'), findRbacUser('aaaaouq@example.com'));
|
||||
|
||||
select queryAllPermissionsOfSubjectId(findRbacUser('aaaaouq@example.com'));
|
||||
|
||||
|
@ -15,11 +15,9 @@ import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.function.Predicate.not;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresArray.fromPostgresArray;
|
||||
import static org.springframework.transaction.annotation.Propagation.MANDATORY;
|
||||
|
||||
@Service
|
||||
@ -55,16 +53,15 @@ public class Context {
|
||||
final String currentRequest,
|
||||
final String currentUser,
|
||||
final String assumedRoles) {
|
||||
final var query = em.createNativeQuery(
|
||||
"""
|
||||
call defineContext(
|
||||
cast(:currentTask as varchar),
|
||||
cast(:currentRequest as varchar),
|
||||
cast(:currentUser as varchar),
|
||||
cast(:assumedRoles as varchar));
|
||||
""");
|
||||
query.setParameter("currentTask", shortenToMaxLength(currentTask, 96));
|
||||
query.setParameter("currentRequest", shortenToMaxLength(currentRequest, 512)); // TODO.spec: length?
|
||||
final var query = em.createNativeQuery("""
|
||||
call defineContext(
|
||||
cast(:currentTask as varchar(127)),
|
||||
cast(:currentRequest as text),
|
||||
cast(:currentUser as varchar(63)),
|
||||
cast(:assumedRoles as varchar(1023)));
|
||||
""");
|
||||
query.setParameter("currentTask", shortenToMaxLength(currentTask, 127));
|
||||
query.setParameter("currentRequest", currentRequest);
|
||||
query.setParameter("currentUser", currentUser);
|
||||
query.setParameter("assumedRoles", assumedRoles != null ? assumedRoles : "");
|
||||
query.executeUpdate();
|
||||
@ -83,14 +80,11 @@ public class Context {
|
||||
}
|
||||
|
||||
public String[] getAssumedRoles() {
|
||||
final byte[] result = (byte[]) em.createNativeQuery("select assumedRoles() as roles", String[].class).getSingleResult();
|
||||
return fromPostgresArray(result, String.class, Function.identity());
|
||||
return (String[]) em.createNativeQuery("select assumedRoles() as roles", String[].class).getSingleResult();
|
||||
}
|
||||
|
||||
public UUID[] currentSubjectsUuids() {
|
||||
final byte[] result = (byte[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class)
|
||||
.getSingleResult();
|
||||
return fromPostgresArray(result, UUID.class, UUID::fromString);
|
||||
return (UUID[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class).getSingleResult();
|
||||
}
|
||||
|
||||
public static String getCallerMethodNameFromStackFrame(final int skipFrames) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.errors;
|
||||
|
||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ -8,7 +8,7 @@ public class ReferenceNotFoundException extends RuntimeException {
|
||||
|
||||
private final Class<?> entityClass;
|
||||
private final UUID uuid;
|
||||
public <E extends HasUuid> ReferenceNotFoundException(final Class<E> entityClass, final UUID uuid, final Throwable exc) {
|
||||
public <E extends RbacObject> ReferenceNotFoundException(final Class<E> entityClass, final UUID uuid, final Throwable exc) {
|
||||
super(exc);
|
||||
this.entityClass = entityClass;
|
||||
this.uuid = uuid;
|
||||
|
@ -11,16 +11,18 @@ import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.method.ParameterValidationResult;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.method.annotation.HandlerMethodValidationException;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static net.hostsharing.hsadminng.errors.CustomErrorResponse.*;
|
||||
@ -119,6 +121,28 @@ public class RestResponseEntityExceptionHandler
|
||||
return errorResponse(request, HttpStatus.BAD_REQUEST, errorList.toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked,rawtypes")
|
||||
|
||||
@Override
|
||||
protected ResponseEntity handleHandlerMethodValidationException(
|
||||
final HandlerMethodValidationException exc,
|
||||
final HttpHeaders headers,
|
||||
final HttpStatusCode status,
|
||||
final WebRequest request) {
|
||||
final var errorList = exc
|
||||
.getAllValidationResults()
|
||||
.stream()
|
||||
.map(ParameterValidationResult::getResolvableErrors)
|
||||
.flatMap(Collection::stream)
|
||||
.filter(FieldError.class::isInstance)
|
||||
.map(FieldError.class::cast)
|
||||
.map(fieldError -> fieldError.getField() + " " + fieldError.getDefaultMessage() + " but is \""
|
||||
+ fieldError.getRejectedValue() + "\"")
|
||||
.toList();
|
||||
return errorResponse(request, HttpStatus.BAD_REQUEST, errorList.toString());
|
||||
}
|
||||
|
||||
|
||||
private String userReadableEntityClassName(final String exceptionMessage) {
|
||||
final var regex = "(net.hostsharing.hsadminng.[a-z0-9_.]*.[A-Za-z0-9_$]*Entity) ";
|
||||
final var pattern = Pattern.compile(regex);
|
||||
|
@ -3,7 +3,8 @@ package net.hostsharing.hsadminng.hs.office.bankaccount;
|
||||
import lombok.*;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
|
||||
@ -11,8 +12,13 @@ import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.*;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
|
||||
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.stringify.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -24,11 +30,11 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
@DisplayName("BankAccount")
|
||||
public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
|
||||
public class HsOfficeBankAccountEntity implements RbacObject, Stringifyable {
|
||||
|
||||
private static Stringify<HsOfficeBankAccountEntity> toString = stringify(HsOfficeBankAccountEntity.class, "bankAccount")
|
||||
.withIdProp(HsOfficeBankAccountEntity::getIban)
|
||||
.withProp(Fields.holder, HsOfficeBankAccountEntity::getHolder)
|
||||
.withProp(Fields.iban, HsOfficeBankAccountEntity::getIban)
|
||||
.withProp(Fields.bic, HsOfficeBankAccountEntity::getBic);
|
||||
|
||||
@Id
|
||||
@ -50,4 +56,28 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
|
||||
public String toShortString() {
|
||||
return holder;
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("bankAccount", HsOfficeBankAccountEntity.class)
|
||||
.withIdentityView(SQL.projection("iban"))
|
||||
.withUpdatableColumns("holder", "iban", "bic")
|
||||
|
||||
.toRole("global", GUEST).grantPermission(INSERT)
|
||||
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.owningUser(CREATOR);
|
||||
with.incomingSuperRole(GLOBAL, ADMIN);
|
||||
with.permission(DELETE);
|
||||
})
|
||||
.createSubRole(ADMIN, (with) -> {
|
||||
with.permission(UPDATE);
|
||||
})
|
||||
.createSubRole(REFERRER, (with) -> {
|
||||
with.permission(SELECT);
|
||||
});
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
rbac().generateWithBaseFileName("5-hs-office/505-bankaccount/5053-hs-office-bankaccount-rbac");
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,22 @@ package net.hostsharing.hsadminng.hs.office.contact;
|
||||
import lombok.*;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||
import net.hostsharing.hsadminng.persistence.HasUuid;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
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.GenericGenerator;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
|
||||
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.rbacViewFor;
|
||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@ -22,13 +30,12 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
@DisplayName("Contact")
|
||||
public class HsOfficeContactEntity implements Stringifyable, HasUuid {
|
||||
public class HsOfficeContactEntity implements Stringifyable, RbacObject {
|
||||
|
||||
private static Stringify<HsOfficeContactEntity> toString = stringify(HsOfficeContactEntity.class, "contact")
|
||||
.withProp(Fields.label, HsOfficeContactEntity::getLabel)
|
||||
.withProp(Fields.emailAddresses, HsOfficeContactEntity::getEmailAddresses);
|
||||
|
||||
|
||||
@Id
|
||||
@GeneratedValue(generator = "UUID")
|
||||
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
|
||||
@ -36,13 +43,13 @@ public class HsOfficeContactEntity implements Stringifyable, HasUuid {
|
||||
private String label;
|
||||
|
||||
@Column(name = "postaladdress")
|
||||
private String postalAddress; // TODO: check if we really want multiple, if so: JSON-Array or Postgres-Array?
|
||||
private String postalAddress; // TODO.spec: check if we really want multiple, if so: JSON-Array or Postgres-Array?
|
||||
|
||||
@Column(name = "emailaddresses", columnDefinition = "json")
|
||||
private String emailAddresses; // TODO: check if we can really add multiple. format: ["eins@...", "zwei@..."]
|
||||
private String emailAddresses; // TODO.spec: check if we can really add multiple. format: ["eins@...", "zwei@..."]
|
||||
|
||||
@Column(name = "phonenumbers", columnDefinition = "json")
|
||||
private String phoneNumbers; // TODO: check if we can really add multiple. format: { "office": "+49 40 12345-10", "fax": "+49 40 12345-05" }
|
||||
private String phoneNumbers; // TODO.spec: check if we can really add multiple. format: { "office": "+49 40 12345-10", "fax": "+49 40 12345-05" }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@ -53,4 +60,26 @@ public class HsOfficeContactEntity implements Stringifyable, HasUuid {
|
||||
public String toShortString() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
return rbacViewFor("contact", HsOfficeContactEntity.class)
|
||||
.withIdentityView(SQL.projection("label"))
|
||||
.withUpdatableColumns("label", "postalAddress", "emailAddresses", "phoneNumbers")
|
||||
.createRole(OWNER, (with) -> {
|
||||
with.owningUser(CREATOR);
|
||||
with.incomingSuperRole(GLOBAL, ADMIN);
|
||||
with.permission(DELETE);
|
||||
})
|
||||
.createSubRole(ADMIN, (with) -> {
|
||||