Compare commits
7 Commits
office-rba
...
master
Author | SHA1 | Date | |
---|---|---|---|
cc2b04472f | |||
4811c0328c | |||
cb4aecb9c8 | |||
d949604d70 | |||
f33a3a2df7 | |||
23b60641e3 | |||
285e6fbeb5 |
29
README.md
29
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:
|
# the following command should return a JSON array with just all packages visible for the admin of the customer yyy:
|
||||||
curl \
|
curl \
|
||||||
-H 'current-subject: superuser-alex@hostsharing.net' -H 'assumed-roles: test_customer#yyy:ADMIN' \
|
-H 'current-subject: superuser-alex@hostsharing.net' -H 'assumed-roles: rbactest.customer#yyy:ADMIN' \
|
||||||
http://localhost:8080/api/test/packages
|
http://localhost:8080/api/test/packages
|
||||||
|
|
||||||
# add a new customer
|
# add a new customer
|
||||||
@ -550,12 +550,37 @@ Dependency versions can be automatically upgraded to the latest available versio
|
|||||||
gw useLatestVersions
|
gw useLatestVersions
|
||||||
```
|
```
|
||||||
|
|
||||||
Afterwards, `gw check` is automatically started.
|
Afterward, `gw check` is automatically started.
|
||||||
Please only commit+push to master if the check run shows no errors.
|
Please only commit+push to master if the check run shows no errors.
|
||||||
|
|
||||||
More infos, e.g. on blacklists see on the [project's website](https://github.com/patrikerdes/gradle-use-latest-versions-plugin).
|
More infos, e.g. on blacklists see on the [project's website](https://github.com/patrikerdes/gradle-use-latest-versions-plugin).
|
||||||
|
|
||||||
|
|
||||||
|
## Biggest Flaws in our Architecture
|
||||||
|
|
||||||
|
### The RBAC System is too Complicated
|
||||||
|
|
||||||
|
Now, where we have a better experience with what we really need from the RBAC system, we have learned
|
||||||
|
that and creates too many (grant- and role-) rows and too even tables which could be avoided completely.
|
||||||
|
|
||||||
|
The basic idea is always to always have a fixed set of ordered role-types which apply for all DB-tables under RBAC,
|
||||||
|
e.g. OWNER>ADMIN>AGENT\[>PROXY?\]>TENENT>REFERRER.
|
||||||
|
Grants between these for the same DB-row would be implicit by order comparision.
|
||||||
|
This way we would get rid of all explicit grants within the same DB-row
|
||||||
|
and would not need the `rbac.role` table anymore.
|
||||||
|
We would also reduce the depth of the expensive recursive CTE-query.
|
||||||
|
|
||||||
|
This has to be explored further.
|
||||||
|
For now, we just keep it in mind and
|
||||||
|
|
||||||
|
### The Mapper is Error-Prone
|
||||||
|
|
||||||
|
Where `org.modelmapper.ModelMapper` reduces bloat-code a lot and has some nice features about recursive data-structure mappings,
|
||||||
|
it often causes strange errors which are hard to fix.
|
||||||
|
E.g. the uuid of the target main object is often taken from an uuid of a sub-subject.
|
||||||
|
(For now, use `StrictMapper` to avoid this, for the case it happens.)
|
||||||
|
|
||||||
|
|
||||||
## How To ...
|
## How To ...
|
||||||
|
|
||||||
### How to Configure .pgpass for the Default PostgreSQL Database?
|
### How to Configure .pgpass for the Default PostgreSQL Database?
|
||||||
|
@ -199,21 +199,21 @@ Limit (cost=6549.08..6549.35 rows=54 width=16)
|
|||||||
Group Key: grants.descendantuuid
|
Group Key: grants.descendantuuid
|
||||||
-> CTE Scan on grants (cost=0.00..22.06 rows=1103 width=16)
|
-> CTE Scan on grants (cost=0.00..22.06 rows=1103 width=16)
|
||||||
-> Index Only Scan using rbacobject_objecttable_uuid_key on rbacobject obj (cost=0.28..0.31 rows=1 width=16)
|
-> Index Only Scan using rbacobject_objecttable_uuid_key on rbacobject obj (cost=0.28..0.31 rows=1 width=16)
|
||||||
Index Cond: ((objecttable = 'hs_hosting_asset'::text) AND (uuid = perm.objectuuid))
|
Index Cond: ((objecttable = 'hs_hosting.asset'::text) AND (uuid = perm.objectuuid))
|
||||||
```
|
```
|
||||||
|
|
||||||
### Office-Relation-Query
|
### Office-Relation-Query
|
||||||
|
|
||||||
```SQL
|
```SQL
|
||||||
SELECT hore1_0.uuid,a1_0.uuid,a1_0.familyname,a1_0.givenname,a1_0.persontype,a1_0.salutation,a1_0.title,a1_0.tradename,a1_0.version,c1_0.uuid,c1_0.caption,c1_0.emailaddresses,c1_0.phonenumbers,c1_0.postaladdress,c1_0.version,h1_0.uuid,h1_0.familyname,h1_0.givenname,h1_0.persontype,h1_0.salutation,h1_0.title,h1_0.tradename,h1_0.version,hore1_0.mark,hore1_0.type,hore1_0.version
|
SELECT hore1_0.uuid,a1_0.uuid,a1_0.familyname,a1_0.givenname,a1_0.persontype,a1_0.salutation,a1_0.title,a1_0.tradename,a1_0.version,c1_0.uuid,c1_0.caption,c1_0.emailaddresses,c1_0.phonenumbers,c1_0.postaladdress,c1_0.version,h1_0.uuid,h1_0.familyname,h1_0.givenname,h1_0.persontype,h1_0.salutation,h1_0.title,h1_0.tradename,h1_0.version,hore1_0.mark,hore1_0.type,hore1_0.version
|
||||||
FROM hs_office_relation_rv hore1_0
|
FROM hs_office.relation_rv hore1_0
|
||||||
LEFT JOIN hs_office_person_rv a1_0 ON a1_0.uuid=hore1_0.anchoruuid
|
LEFT JOIN hs_office.person_rv a1_0 ON a1_0.uuid=hore1_0.anchoruuid
|
||||||
LEFT JOIN hs_office_contact_rv c1_0 ON c1_0.uuid=hore1_0.contactuuid
|
LEFT JOIN hs_office.contact_rv c1_0 ON c1_0.uuid=hore1_0.contactuuid
|
||||||
LEFT JOIN hs_office_person_rv h1_0 ON h1_0.uuid=hore1_0.holderuuid
|
LEFT JOIN hs_office.person_rv h1_0 ON h1_0.uuid=hore1_0.holderuuid
|
||||||
WHERE hore1_0.uuid=$1
|
WHERE hore1_0.uuid=$1
|
||||||
```
|
```
|
||||||
|
|
||||||
That query on the `hs_office_relation_rv`-table joins the three references anchor-person, holder-person and contact.
|
That query on the `hs_office.relation_rv`-table joins the three references anchor-person, holder-person and contact.
|
||||||
|
|
||||||
|
|
||||||
### Total-Query-Time > Total-Import-Runtime
|
### Total-Query-Time > Total-Import-Runtime
|
||||||
@ -270,21 +270,21 @@ At this point, the import took 21mins with these statistics:
|
|||||||
|
|
||||||
| query | calls | total_m | mean_ms |
|
| query | calls | total_m | mean_ms |
|
||||||
|-------|-------|---------|---------|
|
|-------|-------|---------|---------|
|
||||||
| select hore1_0.uuid,a1_0.uuid,a1_0.familyname,a1_0.givenname,a1_0.persontype,a1_0.salutation,a1_0.title,a1_0.tradename,a1_0.version,c1_0.uuid,c1_0.caption,c1_0.emailaddresses,c1_0.phonenumbers,c1_0.postaladdress, c1_0.version,h1_0.uuid,h1_0.familyname,h1_0.givenname,h1_0.persontype,h1_0.salutation,h1_0.title,h1_0.tradename,h1_0.version,hore1_0.mark,hore1_0.type,hore1_0.version from public.hs_office_relation_rv hore1_0 left join public.hs_office_person_rv a1_0 on a1_0.uuid=hore1_0.anchoruuid left join public.hs_office_contact_rv c1_0 on c1_0.uuid=hore1_0.contactuuid left join public.hs_office_person_rv h1_0 on h1_0.uuid=hore1_0.holderuuid where hore1_0.uuid=$1 | 517 | 11 | 1282 |
|
| select hore1_0.uuid,a1_0.uuid,a1_0.familyname,a1_0.givenname,a1_0.persontype,a1_0.salutation,a1_0.title,a1_0.tradename,a1_0.version,c1_0.uuid,c1_0.caption,c1_0.emailaddresses,c1_0.phonenumbers,c1_0.postaladdress, c1_0.version,h1_0.uuid,h1_0.familyname,h1_0.givenname,h1_0.persontype,h1_0.salutation,h1_0.title,h1_0.tradename,h1_0.version,hore1_0.mark,hore1_0.type,hore1_0.version from public.hs_office.relation_rv hore1_0 left join public.hs_office.person_rv a1_0 on a1_0.uuid=hore1_0.anchoruuid left join public.hs_office.contact_rv c1_0 on c1_0.uuid=hore1_0.contactuuid left join public.hs_office.person_rv h1_0 on h1_0.uuid=hore1_0.holderuuid where hore1_0.uuid=$1 | 517 | 11 | 1282 |
|
||||||
| select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office_person_rv hope1_0 where hope1_0.uuid=$1 | 973 | 4 | 254 |
|
| select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office.person_rv hope1_0 where hope1_0.uuid=$1 | 973 | 4 | 254 |
|
||||||
| select hoce1_0.uuid,hoce1_0.caption,hoce1_0.emailaddresses,hoce1_0.phonenumbers,hoce1_0.postaladdress,hoce1_0.version from public.hs_office_contact_rv hoce1_0 where hoce1_0.uuid=$1 | 973 | 4 | 253 |
|
| select hoce1_0.uuid,hoce1_0.caption,hoce1_0.emailaddresses,hoce1_0.phonenumbers,hoce1_0.postaladdress,hoce1_0.version from public.hs_office.contact_rv hoce1_0 where hoce1_0.uuid=$1 | 973 | 4 | 253 |
|
||||||
| call rbac.grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed) | 31316 | 0 | 1 |
|
| call rbac.grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed) | 31316 | 0 | 1 |
|
||||||
| call buildRbacSystemForHsHostingAsset(NEW) | 2258 | 0 | 7 |
|
| call buildRbacSystemForHsHostingAsset(NEW) | 2258 | 0 | 7 |
|
||||||
| select * from rbac.isGranted(array[granteeId], grantedId) | 44613 | 0 | 0 |
|
| select * from rbac.isGranted(array[granteeId], grantedId) | 44613 | 0 | 0 |
|
||||||
| insert into public.hs_hosting_asset_rv (alarmcontactuuid,assignedtoassetuuid,bookingitemuuid,caption,config,identifier,parentassetuuid,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) | 2207 | 0 | 7 |
|
| insert into public.hs_hosting.asset_rv (alarmcontactuuid,assignedtoassetuuid,bookingitemuuid,caption,config,identifier,parentassetuuid,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) | 2207 | 0 | 7 |
|
||||||
| insert into hs_hosting_asset (alarmcontactuuid, version, bookingitemuuid, type, parentassetuuid, assignedtoassetuuid, config, uuid, identifier, caption) values (new.alarmcontactuuid, new. version, new. bookingitemuuid, new. type, new. parentassetuuid, new. assignedtoassetuuid, new. config, new. uuid, new. identifier, new. caption) returning * | 2207 | 0 | 7 |
|
| insert into hs_hosting.asset (alarmcontactuuid, version, bookingitemuuid, type, parentassetuuid, assignedtoassetuuid, config, uuid, identifier, caption) values (new.alarmcontactuuid, new. version, new. bookingitemuuid, new. type, new. parentassetuuid, new. assignedtoassetuuid, new. config, new. uuid, new. identifier, new. caption) returning * | 2207 | 0 | 7 |
|
||||||
| insert into public.hs_office_relation_rv (anchoruuid,contactuuid,holderuuid,mark,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7) | 1261 | 0 | 9 |
|
| insert into public.hs_office.relation_rv (anchoruuid,contactuuid,holderuuid,mark,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7) | 1261 | 0 | 9 |
|
||||||
| insert into hs_office_relation (uuid, version, anchoruuid, holderuuid, contactuuid, type, mark) values (new.uuid, new. version, new. anchoruuid, new. holderuuid, new. contactuuid, new. type, new. mark) returning * | 1261 | 0 | 9 |
|
| insert into hs_office.relation (uuid, version, anchoruuid, holderuuid, contactuuid, type, mark) values (new.uuid, new. version, new. anchoruuid, new. holderuuid, new. contactuuid, new. type, new. mark) returning * | 1261 | 0 | 9 |
|
||||||
| call buildRbacSystemForHsOfficeRelation(NEW) | 1276 | 0 | 8 |
|
| call buildRbacSystemForHsOfficeRelation(NEW) | 1276 | 0 | 8 |
|
||||||
| 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 $3 from grants where ascendantUuid = any(granteeIds) ) or grantedId = any(granteeIds) | 47540 | 0 | 0 |
|
| 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 $3 from grants where ascendantUuid = any(granteeIds) ) or grantedId = any(granteeIds) | 47540 | 0 | 0 |
|
||||||
| insert into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed) values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume) on conflict do nothing" | 40472 | 0 | 0 |
|
| insert into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed) values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume) on conflict do nothing" | 40472 | 0 | 0 |
|
||||||
| insert into public.hs_booking_item_rv (caption,parentitemuuid,projectuuid,resources,type,validity,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8) | 926 | 0 | 7 |
|
| insert into public.hs_booking.item_rv (caption,parentitemuuid,projectuuid,resources,type,validity,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8) | 926 | 0 | 7 |
|
||||||
| insert into hs_booking_item (resources, version, projectuuid, type, parentitemuuid, validity, uuid, caption) values (new.resources, new. version, new. projectuuid, new. type, new. parentitemuuid, new. validity, new. uuid, new. caption) returning * | 926 | 0 | 7 |
|
| insert into hs_booking.item (resources, version, projectuuid, type, parentitemuuid, validity, uuid, caption) values (new.resources, new. version, new. projectuuid, new. type, new. parentitemuuid, new. validity, new. uuid, new. caption) returning * | 926 | 0 | 7 |
|
||||||
|
|
||||||
|
|
||||||
The slowest query now was fetching Relations joined with Contact, Anchor-Person and Holder-Person, for all tables using the restricted (RBAC) views (_rv).
|
The slowest query now was fetching Relations joined with Contact, Anchor-Person and Holder-Person, for all tables using the restricted (RBAC) views (_rv).
|
||||||
@ -294,20 +294,20 @@ We changed these mappings from `EAGER` (default) to `LAZY` to `@ManyToOne(fetch
|
|||||||
:::small
|
:::small
|
||||||
| query | calls | total (min) | mean (ms) |
|
| query | calls | total (min) | mean (ms) |
|
||||||
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|-------------|----------|
|
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|-------------|----------|
|
||||||
| select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office_person_rv hope1_0 where hope1_0.uuid=$1 | 1015 | 4 | 238 |
|
| select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office.person_rv hope1_0 where hope1_0.uuid=$1 | 1015 | 4 | 238 |
|
||||||
| select hore1_0.uuid,hore1_0.anchoruuid,hore1_0.contactuuid,hore1_0.holderuuid,hore1_0.mark,hore1_0.type,hore1_0.version from public.hs_office_relation_rv hore1_0 where hore1_0.uuid=$1 | 517 | 4 | 439 |
|
| select hore1_0.uuid,hore1_0.anchoruuid,hore1_0.contactuuid,hore1_0.holderuuid,hore1_0.mark,hore1_0.type,hore1_0.version from public.hs_office.relation_rv hore1_0 where hore1_0.uuid=$1 | 517 | 4 | 439 |
|
||||||
| select hoce1_0.uuid,hoce1_0.caption,hoce1_0.emailaddresses,hoce1_0.phonenumbers,hoce1_0.postaladdress,hoce1_0.version from public.hs_office_contact_rv hoce1_0 where hoce1_0.uuid=$1 | 497 | 2 | 213 |
|
| select hoce1_0.uuid,hoce1_0.caption,hoce1_0.emailaddresses,hoce1_0.phonenumbers,hoce1_0.postaladdress,hoce1_0.version from public.hs_office.contact_rv hoce1_0 where hoce1_0.uuid=$1 | 497 | 2 | 213 |
|
||||||
| call rbac.grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed) | 31316 | 0 | 1 |
|
| call rbac.grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed) | 31316 | 0 | 1 |
|
||||||
| select * from rbac.isGranted(array[granteeId], grantedId) | 44613 | 0 | 0 |
|
| select * from rbac.isGranted(array[granteeId], grantedId) | 44613 | 0 | 0 |
|
||||||
| call buildRbacSystemForHsHostingAsset(NEW) | 2258 | 0 | 7 |
|
| call buildRbacSystemForHsHostingAsset(NEW) | 2258 | 0 | 7 |
|
||||||
| insert into public.hs_hosting_asset_rv (alarmcontactuuid,assignedtoassetuuid,bookingitemuuid,caption,config,identifier,parentassetuuid,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) | 2207 | 0 | 7 |
|
| insert into public.hs_hosting.asset_rv (alarmcontactuuid,assignedtoassetuuid,bookingitemuuid,caption,config,identifier,parentassetuuid,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) | 2207 | 0 | 7 |
|
||||||
| insert into hs_hosting_asset (alarmcontactuuid, version, bookingitemuuid, type, parentassetuuid, assignedtoassetuuid, config, uuid, identifier, caption) values (new.alarmcontactuuid, new. version, new. bookingitemuuid, new. type, new. parentassetuuid, new. assignedtoassetuuid, new. config, new. uuid, new. identifier, new. caption) returning * | 2207 | 0 | 7 |
|
| insert into hs_hosting.asset (alarmcontactuuid, version, bookingitemuuid, type, parentassetuuid, assignedtoassetuuid, config, uuid, identifier, caption) values (new.alarmcontactuuid, new. version, new. bookingitemuuid, new. type, new. parentassetuuid, new. assignedtoassetuuid, new. config, new. uuid, new. identifier, new. caption) returning * | 2207 | 0 | 7 |
|
||||||
| 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 $3 from grants where ascendantUuid = any(granteeIds) ) or grantedId = any(granteeIds) | 47538 | 0 | 0 |
|
| 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 $3 from grants where ascendantUuid = any(granteeIds) ) or grantedId = any(granteeIds) | 47538 | 0 | 0 |
|
||||||
insert into public.hs_office_relation_rv (anchoruuid,contactuuid,holderuuid,mark,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7) | 1261 | 0 | 8 |
|
insert into public.hs_office.relation_rv (anchoruuid,contactuuid,holderuuid,mark,type,version,uuid) values ($1,$2,$3,$4,$5,$6,$7) | 1261 | 0 | 8 |
|
||||||
| insert into hs_office_relation (uuid, version, anchoruuid, holderuuid, contactuuid, type, mark) values (new.uuid, new. version, new. anchoruuid, new. holderuuid, new. contactuuid, new. type, new. mark) returning * | 1261 | 0 | 8 |
|
| insert into hs_office.relation (uuid, version, anchoruuid, holderuuid, contactuuid, type, mark) values (new.uuid, new. version, new. anchoruuid, new. holderuuid, new. contactuuid, new. type, new. mark) returning * | 1261 | 0 | 8 |
|
||||||
| call buildRbacSystemForHsOfficeRelation(NEW) | 1276 | 0 | 7 |
|
| call buildRbacSystemForHsOfficeRelation(NEW) | 1276 | 0 | 7 |
|
||||||
| insert into public.hs_booking_item_rv (caption,parentitemuuid,projectuuid,resources,type,validity,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8) | 926 | 0 | 7 |
|
| insert into public.hs_booking.item_rv (caption,parentitemuuid,projectuuid,resources,type,validity,version,uuid) values ($1,$2,$3,$4,$5,$6,$7,$8) | 926 | 0 | 7 |
|
||||||
| insert into hs_booking_item (resources, version, projectuuid, type, parentitemuuid, validity, uuid, caption) values (new.resources, new. version, new. projectuuid, new. type, new. parentitemuuid, new. validity, new. uuid, new. caption) returning * | 926 | 0 | 7 |
|
| insert into hs_booking.item (resources, version, projectuuid, type, parentitemuuid, validity, uuid, caption) values (new.resources, new. version, new. projectuuid, new. type, new. parentitemuuid, new. validity, new. uuid, new. caption) returning * | 926 | 0 | 7 |
|
||||||
insert into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed) values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume) on conflict do nothing | 40472 | 0 | 0 |
|
insert into RbacGrants (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed) values (currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume) on conflict do nothing | 40472 | 0 | 0 |
|
||||||
|
|
||||||
Now, finally, the total runtime of the import was down to 12 minutes. This is repeatable, where originally, the import took about 25mins in most cases and just rarely - and for unknown reasons - 10min.
|
Now, finally, the total runtime of the import was down to 12 minutes. This is repeatable, where originally, the import took about 25mins in most cases and just rarely - and for unknown reasons - 10min.
|
||||||
@ -318,7 +318,7 @@ But once UnixUser and EmailAlias assets got added to the import, the total time
|
|||||||
|
|
||||||
This was not acceptable, especially not, considering that domains, email-addresses and database-assets are almost 10 times that number and thus the import would go up to over 1100min which is 20 hours.
|
This was not acceptable, especially not, considering that domains, email-addresses and database-assets are almost 10 times that number and thus the import would go up to over 1100min which is 20 hours.
|
||||||
|
|
||||||
In a first step, a `HsHostingAssetRawEntity` was created, mapped to the raw table (hs_hosting_asset) not to the RBAC-view (hs_hosting_asset_rv). Unfortunately we did not keep measurements, but that was only part of the problem anyway.
|
In a first step, a `HsHostingAssetRawEntity` was created, mapped to the raw table (hs_hosting.asset) not to the RBAC-view (hs_hosting.asset_rv). Unfortunately we did not keep measurements, but that was only part of the problem anyway.
|
||||||
|
|
||||||
The main problem was, that there is something strange with persisting (`EntityManager.persist`) for EmailAlias assets. Where importing UnixUsers was mostly slow due to RBAC SELECT-permission checks, persisting EmailAliases suddenly created about a million (in numbers 1.000.000) SQL UPDATE statements after the INSERT, all with the same data, just increased version number (used for optimistic locking). We were not able to figure out why this happened.
|
The main problem was, that there is something strange with persisting (`EntityManager.persist`) for EmailAlias assets. Where importing UnixUsers was mostly slow due to RBAC SELECT-permission checks, persisting EmailAliases suddenly created about a million (in numbers 1.000.000) SQL UPDATE statements after the INSERT, all with the same data, just increased version number (used for optimistic locking). We were not able to figure out why this happened.
|
||||||
|
|
||||||
@ -330,22 +330,22 @@ Now, the longest running queries are these:
|
|||||||
|
|
||||||
| No.| calls | total_m | mean_ms | query |
|
| No.| calls | total_m | mean_ms | query |
|
||||||
|---:|---------|--------:|--------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|---:|---------|--------:|--------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| 1 | 13.093 | 4 | 21 | insert into hs_hosting_asset( uuid, type, bookingitemuuid, parentassetuuid, assignedtoassetuuid, alarmcontactuuid, identifier, caption, config, version) values ( $1, $2, $3, $4, $5, $6, $7, $8, cast($9 as jsonb), $10) |
|
| 1 | 13.093 | 4 | 21 | insert into hs_hosting.asset( uuid, type, bookingitemuuid, parentassetuuid, assignedtoassetuuid, alarmcontactuuid, identifier, caption, config, version) values ( $1, $2, $3, $4, $5, $6, $7, $8, cast($9 as jsonb), $10) |
|
||||||
| 2 | 517 | 4 | 502 | select hore1_0.uuid,hore1_0.anchoruuid,hore1_0.contactuuid,hore1_0.holderuuid,hore1_0.mark,hore1_0.type,hore1_0.version from public.hs_office_relation_rv hore1_0 where hore1_0.uuid=$1 |
|
| 2 | 517 | 4 | 502 | select hore1_0.uuid,hore1_0.anchoruuid,hore1_0.contactuuid,hore1_0.holderuuid,hore1_0.mark,hore1_0.type,hore1_0.version from public.hs_office.relation_rv hore1_0 where hore1_0.uuid=$1 |
|
||||||
| 3 | 13.144 | 4 | 21 | call buildRbacSystemForHsHostingAsset(NEW) |
|
| 3 | 13.144 | 4 | 21 | call buildRbacSystemForHsHostingAsset(NEW) |
|
||||||
| 4 | 96.632 | 3 | 2 | call rbac.grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed) |
|
| 4 | 96.632 | 3 | 2 | call rbac.grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed) |
|
||||||
| 5 | 120.815 | 3 | 2 | select * from rbac.isGranted(array[granteeId], grantedId) |
|
| 5 | 120.815 | 3 | 2 | select * from rbac.isGranted(array[granteeId], grantedId) |
|
||||||
| 6 | 123.740 | 3 | 2 | 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 $3 from grants where ascendantUuid = any(granteeIds) ) or grantedId = any(granteeIds) |
|
| 6 | 123.740 | 3 | 2 | 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 $3 from grants where ascendantUuid = any(granteeIds) ) or grantedId = any(granteeIds) |
|
||||||
| 7 | 497 | 2 | 259 | select hoce1_0.uuid,hoce1_0.caption,hoce1_0.emailaddresses,hoce1_0.phonenumbers,hoce1_0.postaladdress,hoce1_0.version from public.hs_office_contact_rv hoce1_0 where hoce1_0.uuid=$1 |
|
| 7 | 497 | 2 | 259 | select hoce1_0.uuid,hoce1_0.caption,hoce1_0.emailaddresses,hoce1_0.phonenumbers,hoce1_0.postaladdress,hoce1_0.version from public.hs_office.contact_rv hoce1_0 where hoce1_0.uuid=$1 |
|
||||||
| 8 | 497 | 2 | 255 | select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office_person_rv hope1_0 where hope1_0.uuid=$1 |
|
| 8 | 497 | 2 | 255 | select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office.person_rv hope1_0 where hope1_0.uuid=$1 |
|
||||||
| 9 | 13.144 | 1 | 8 | SELECT createRoleWithGrants( hsHostingAssetTENANT(NEW), permissions => array[$7], incomingSuperRoles => array[ hsHostingAssetAGENT(NEW), hsOfficeContactADMIN(newAlarmContact)], outgoingSubRoles => array[ hsBookingItemTENANT(newBookingItem), hsHostingAssetTENANT(newParentAsset)] ) |
|
| 9 | 13.144 | 1 | 8 | SELECT createRoleWithGrants( hs_hosting.asset_TENANT(NEW), permissions => array[$7], incomingSuperRoles => array[ hs_hosting.asset_AGENT(NEW), hs_office.contact_ADMIN(newAlarmContact)], outgoingSubRoles => array[ hs_booking.item_TENANT(newBookingItem), hs_hosting.asset_TENANT(newParentAsset)] ) |
|
||||||
| 10 | 13.144 | 1 | 5 | SELECT createRoleWithGrants( hsHostingAssetADMIN(NEW), permissions => array[$7], incomingSuperRoles => array[ hsBookingItemAGENT(newBookingItem), hsHostingAssetAGENT(newParentAsset), hsHostingAssetOWNER(NEW)] ) |
|
| 10 | 13.144 | 1 | 5 | SELECT createRoleWithGrants( hs_hosting.asset_ADMIN(NEW), permissions => array[$7], incomingSuperRoles => array[ hs_booking.item_AGENT(newBookingItem), hs_hosting.asset_AGENT(newParentAsset), hs_hosting.asset_OWNER(NEW)] ) |
|
||||||
|
|
||||||
That the `INSERT into hs_hosting_asset` (No. 1) takes up the most time, seems to be normal, and 21ms for each call is also fine.
|
That the `INSERT into hs_hosting.asset` (No. 1) takes up the most time, seems to be normal, and 21ms for each call is also fine.
|
||||||
|
|
||||||
It seems that the trigger effects (eg. No. 3 and No. 4) are included in the measure for the causing INSERT, otherwise summing up the totals would exceed the actual total time of the whole import. And it was to be expected that building the RBAC rules for new business objects takes most of the time.
|
It seems that the trigger effects (eg. No. 3 and No. 4) are included in the measure for the causing INSERT, otherwise summing up the totals would exceed the actual total time of the whole import. And it was to be expected that building the RBAC rules for new business objects takes most of the time.
|
||||||
|
|
||||||
In production, the `SELECT ... FROM hs_office_relation_rv` (No. 2) with about 0.5 seconds could still be a problem. But once we apply the improvements from the hosting asset area also to the office area, this should not be a problem for the import anymore.
|
In production, the `SELECT ... FROM hs_office.relation_rv` (No. 2) with about 0.5 seconds could still be a problem. But once we apply the improvements from the hosting asset area also to the office area, this should not be a problem for the import anymore.
|
||||||
|
|
||||||
|
|
||||||
## Further Options To Explore
|
## Further Options To Explore
|
||||||
@ -408,12 +408,12 @@ We found some solution approaches:
|
|||||||
This optimization idea came from Michael Hierweck and was promising.
|
This optimization idea came from Michael Hierweck and was promising.
|
||||||
The idea is to reduce the size of the result of the recursive CTE query and maybe even speed up that query itself.
|
The idea is to reduce the size of the result of the recursive CTE query and maybe even speed up that query itself.
|
||||||
|
|
||||||
To evaluate this, I added a type column to the `rbacObject` table, initially as an enum hsHostingAssetType. Then I entered the type there for all rows from hs_hosting_asset. This means that 83,886 of 92,545 rows in `rbacobject` have a type set, leaving 8,659 without.
|
To evaluate this, I added a type column to the `rbacObject` table, initially as an enum hsHostingAssetType. Then I entered the type there for all rows from hs_hosting.asset. This means that 83,886 of 92,545 rows in `rbacobject` have a type set, leaving 8,659 without.
|
||||||
|
|
||||||
If we do this for other types (we currently have 1,271 relations and 927 booking items), it gets more complicated because they are different enum types. As varchar(16), we could lose performance again due to the higher storage space requirements.
|
If we do this for other types (we currently have 1,271 relations and 927 booking items), it gets more complicated because they are different enum types. As varchar(16), we could lose performance again due to the higher storage space requirements.
|
||||||
|
|
||||||
But the performance gained is not particularly high anyway.
|
But the performance gained is not particularly high anyway.
|
||||||
See the average seconds per recursive CTE select as role 'hs_hosting_asset:<DEBITOR>defaultproject:ADMIN',
|
See the average seconds per recursive CTE select as role 'hs_hosting.asset:<DEBITOR>defaultproject:ADMIN',
|
||||||
joined with business query for all `'EMAIL_ADDRESSES'`:
|
joined with business query for all `'EMAIL_ADDRESSES'`:
|
||||||
|
|
||||||
| | D-1000000-hsh | D-1000300-mih |
|
| | D-1000000-hsh | D-1000300-mih |
|
||||||
|
@ -6,21 +6,21 @@
|
|||||||
rollback;
|
rollback;
|
||||||
begin transaction;
|
begin transaction;
|
||||||
call defineContext('historization testing', null, 'superuser-alex@hostsharing.net',
|
call defineContext('historization testing', null, 'superuser-alex@hostsharing.net',
|
||||||
-- 'hs_booking_project#D-1000000-hshdefaultproject:ADMIN'); -- prod+test
|
-- 'hs_booking.project#D-1000000-hshdefaultproject:ADMIN'); -- prod+test
|
||||||
'hs_booking_project#D-1000313-D-1000313defaultproject:ADMIN'); -- prod+test
|
'hs_booking.project#D-1000313-D-1000313defaultproject:ADMIN'); -- prod+test
|
||||||
-- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN'); -- prod
|
-- 'hs_booking.project#D-1000300-mihdefaultproject:ADMIN'); -- prod
|
||||||
-- 'hs_booking_project#D-1000300-mimdefaultproject:ADMIN'); -- test
|
-- 'hs_booking.project#D-1000300-mimdefaultproject:ADMIN'); -- test
|
||||||
-- update hs_hosting_asset set caption='lug00 b' where identifier = 'lug00' and type = 'MANAGED_WEBSPACE'; -- prod
|
-- update hs_hosting.asset set caption='lug00 b' where identifier = 'lug00' and type = 'MANAGED_WEBSPACE'; -- prod
|
||||||
-- update hs_hosting_asset set caption='hsh00 A ' || now()::text where identifier = 'hsh00' and type = 'MANAGED_WEBSPACE'; -- test
|
-- update hs_hosting.asset set caption='hsh00 A ' || now()::text where identifier = 'hsh00' and type = 'MANAGED_WEBSPACE'; -- test
|
||||||
-- update hs_hosting_asset set caption='hsh00 B ' || now()::text where identifier = 'hsh00' and type = 'MANAGED_WEBSPACE'; -- test
|
-- update hs_hosting.asset set caption='hsh00 B ' || now()::text where identifier = 'hsh00' and type = 'MANAGED_WEBSPACE'; -- test
|
||||||
|
|
||||||
-- insert into hs_hosting_asset
|
-- insert into hs_hosting.asset
|
||||||
-- (uuid, bookingitemuuid, type, parentassetuuid, assignedtoassetuuid, identifier, caption, config, alarmcontactuuid)
|
-- (uuid, bookingitemuuid, type, parentassetuuid, assignedtoassetuuid, identifier, caption, config, alarmcontactuuid)
|
||||||
-- values
|
-- values
|
||||||
-- (uuid_generate_v4(), null, 'EMAIL_ADDRESS', 'bbda5895-0569-4e20-bb4c-34f3a38f3f63'::uuid, null,
|
-- (uuid_generate_v4(), null, 'EMAIL_ADDRESS', 'bbda5895-0569-4e20-bb4c-34f3a38f3f63'::uuid, null,
|
||||||
-- 'new@thi.example.org', 'some new E-Mail-Address', '{}'::jsonb, null);
|
-- 'new@thi.example.org', 'some new E-Mail-Address', '{}'::jsonb, null);
|
||||||
|
|
||||||
delete from hs_hosting_asset where uuid='5aea68d2-3b55-464f-8362-b05c76c5a681'::uuid;
|
delete from hs_hosting.asset where uuid='5aea68d2-3b55-464f-8362-b05c76c5a681'::uuid;
|
||||||
commit;
|
commit;
|
||||||
|
|
||||||
-- single version at point in time
|
-- single version at point in time
|
||||||
@ -29,11 +29,11 @@ set hsadminng.tx_history_txid to '';
|
|||||||
set hsadminng.tx_history_timestamp to '2024-08-29 12:42';
|
set hsadminng.tx_history_timestamp to '2024-08-29 12:42';
|
||||||
-- all versions
|
-- all versions
|
||||||
select base.tx_history_txid(), txc.txtimestamp, txc.currentSubject, txc.currentTask, haex.*
|
select base.tx_history_txid(), txc.txtimestamp, txc.currentSubject, txc.currentTask, haex.*
|
||||||
from hs_hosting_asset_ex haex
|
from hs_hosting.asset_ex haex
|
||||||
join base.tx_context txc on haex.txid=txc.txid
|
join base.tx_context txc on haex.txid=txc.txid
|
||||||
where haex.identifier = 'test@thi.example.org';
|
where haex.identifier = 'test@thi.example.org';
|
||||||
|
|
||||||
select uuid, version, type, identifier, caption from hs_hosting_asset_hv p where identifier = 'test@thi.example.org';
|
select uuid, version, type, identifier, caption from hs_hosting.asset_hv p where identifier = 'test@thi.example.org';
|
||||||
|
|
||||||
select pg_current_xact_id();
|
select pg_current_xact_id();
|
||||||
|
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
-- --------------------------------------------------------
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
select rbac.isGranted(rbac.findRoleId('administrators'), rbac.findRoleId('test.package#aaa00:OWNER'));
|
select rbac.isGranted(rbac.findRoleId('administrators'), rbac.findRoleId('rbactest.package#aaa00:OWNER'));
|
||||||
select rbac.isGranted(rbac.findRoleId('test.package#aaa00:OWNER'), rbac.findRoleId('administrators'));
|
select rbac.isGranted(rbac.findRoleId('rbactest.package#aaa00:OWNER'), rbac.findRoleId('administrators'));
|
||||||
-- call rbac.grantRoleToRole(findRoleId('test.package#aaa00:OWNER'), findRoleId('administrators'));
|
-- call rbac.grantRoleToRole(findRoleId('rbactest.package#aaa00:OWNER'), findRoleId('administrators'));
|
||||||
-- call rbac.grantRoleToRole(findRoleId('administrators'), findRoleId('test.package#aaa00:OWNER'));
|
-- call rbac.grantRoleToRole(findRoleId('administrators'), findRoleId('rbactest.package#aaa00:OWNER'));
|
||||||
|
|
||||||
select count(*)
|
select count(*)
|
||||||
FROM rbac.queryAllPermissionsOfSubjectIdForObjectUuids(rbac.findRbacSubject('superuser-fran@hostsharing.net'),
|
FROM rbac.queryAllPermissionsOfSubjectIdForObjectUuids(rbac.findRbacSubject('superuser-fran@hostsharing.net'),
|
||||||
ARRAY(select uuid from test.customer where reference < 1100000));
|
ARRAY(select uuid from rbactest.customer where reference < 1100000));
|
||||||
select count(*)
|
select count(*)
|
||||||
FROM rbac.queryAllPermissionsOfSubjectId(findRbacSubject('superuser-fran@hostsharing.net'));
|
FROM rbac.queryAllPermissionsOfSubjectId(findRbacSubject('superuser-fran@hostsharing.net'));
|
||||||
select *
|
select *
|
||||||
|
@ -20,7 +20,7 @@ CREATE POLICY customer_policy ON customer
|
|||||||
TO restricted
|
TO restricted
|
||||||
USING (
|
USING (
|
||||||
-- id=1000
|
-- id=1000
|
||||||
rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('test.customer', id, 'SELECT'), rbac.currentSubjectUuid())
|
rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('rbactest.customer', id, 'SELECT'), rbac.currentSubjectUuid())
|
||||||
);
|
);
|
||||||
|
|
||||||
SET SESSION AUTHORIZATION restricted;
|
SET SESSION AUTHORIZATION restricted;
|
||||||
@ -31,28 +31,28 @@ SELECT * from customer;
|
|||||||
SET SESSION SESSION AUTHORIZATION DEFAULT;
|
SET SESSION SESSION AUTHORIZATION DEFAULT;
|
||||||
DROP VIEW cust_view;
|
DROP VIEW cust_view;
|
||||||
CREATE VIEW cust_view AS
|
CREATE VIEW cust_view AS
|
||||||
SELECT * FROM test.customer;
|
SELECT * FROM rbactest.customer;
|
||||||
CREATE OR REPLACE RULE "_RETURN" AS
|
CREATE OR REPLACE RULE "_RETURN" AS
|
||||||
ON SELECT TO cust_view
|
ON SELECT TO cust_view
|
||||||
DO INSTEAD
|
DO INSTEAD
|
||||||
SELECT * FROM test.customer WHERE rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('test.customer', id, 'SELECT'), rbac.currentSubjectUuid());
|
SELECT * FROM rbactest.customer WHERE rbac.isPermissionGrantedToSubject(rbac.findEffectivePermissionId('rbactest.customer', id, 'SELECT'), rbac.currentSubjectUuid());
|
||||||
SELECT * from cust_view LIMIT 10;
|
SELECT * from cust_view LIMIT 10;
|
||||||
|
|
||||||
select rbac.queryAllPermissionsOfSubjectId(findRbacSubject('superuser-alex@hostsharing.net'));
|
select rbac.queryAllPermissionsOfSubjectId(findRbacSubject('superuser-alex@hostsharing.net'));
|
||||||
|
|
||||||
-- access control via view-rule with join to recursive permissions - really fast (38ms for 1 million rows)
|
-- access control via view-rule with join to recursive permissions - really fast (38ms for 1 million rows)
|
||||||
SET SESSION SESSION AUTHORIZATION DEFAULT;
|
SET SESSION SESSION AUTHORIZATION DEFAULT;
|
||||||
ALTER TABLE test.customer ENABLE ROW LEVEL SECURITY;
|
ALTER TABLE rbactest.customer ENABLE ROW LEVEL SECURITY;
|
||||||
DROP VIEW IF EXISTS cust_view;
|
DROP VIEW IF EXISTS cust_view;
|
||||||
CREATE OR REPLACE VIEW cust_view AS
|
CREATE OR REPLACE VIEW cust_view AS
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM test.customer;
|
FROM rbactest.customer;
|
||||||
CREATE OR REPLACE RULE "_RETURN" AS
|
CREATE OR REPLACE RULE "_RETURN" AS
|
||||||
ON SELECT TO cust_view
|
ON SELECT TO cust_view
|
||||||
DO INSTEAD
|
DO INSTEAD
|
||||||
SELECT c.uuid, c.reference, c.prefix FROM test.customer AS c
|
SELECT c.uuid, c.reference, c.prefix FROM rbactest.customer AS c
|
||||||
JOIN rbac.queryAllPermissionsOfSubjectId(rbac.currentSubjectUuid()) AS p
|
JOIN rbac.queryAllPermissionsOfSubjectId(rbac.currentSubjectUuid()) AS p
|
||||||
ON p.objectTable='test.customer' AND p.objectUuid=c.uuid;
|
ON p.objectTable='rbactest.customer' AND p.objectUuid=c.uuid;
|
||||||
GRANT ALL PRIVILEGES ON cust_view TO restricted;
|
GRANT ALL PRIVILEGES ON cust_view TO restricted;
|
||||||
|
|
||||||
SET SESSION SESSION AUTHORIZATION restricted;
|
SET SESSION SESSION AUTHORIZATION restricted;
|
||||||
@ -81,9 +81,9 @@ select rr.uuid, rr.type from rbac.RbacGrants g
|
|||||||
join rbac.RbacReference RR on g.ascendantUuid = RR.uuid
|
join rbac.RbacReference RR on g.ascendantUuid = RR.uuid
|
||||||
where g.descendantUuid in (
|
where g.descendantUuid in (
|
||||||
select uuid from rbac.queryAllPermissionsOfSubjectId(findRbacSubject('alex@example.com'))
|
select uuid from rbac.queryAllPermissionsOfSubjectId(findRbacSubject('alex@example.com'))
|
||||||
where objectTable='test.customer');
|
where objectTable='rbactest.customer');
|
||||||
|
|
||||||
call rbac.grantRoleToUser(rbac.findRoleId('test.customer#aaa:ADMIN'), rbac.findRbacSubject('aaaaouq@example.com'));
|
call rbac.grantRoleToUser(rbac.findRoleId('rbactest.customer#aaa:ADMIN'), rbac.findRbacSubject('aaaaouq@example.com'));
|
||||||
|
|
||||||
select rbac.queryAllPermissionsOfSubjectId(findRbacSubject('aaaaouq@example.com'));
|
select rbac.queryAllPermissionsOfSubjectId(findRbacSubject('aaaaouq@example.com'));
|
||||||
|
|
||||||
|
@ -6,10 +6,10 @@ select * from hs_statistics_v;
|
|||||||
|
|
||||||
-- This is the extracted recursive CTE query to determine the visible object UUIDs of a single table
|
-- This is the extracted recursive CTE query to determine the visible object UUIDs of a single table
|
||||||
-- (and optionally the hosting-asset-type) as a separate VIEW.
|
-- (and optionally the hosting-asset-type) as a separate VIEW.
|
||||||
-- In the generated code this is part of the hs_hosting_asset_rv VIEW.
|
-- In the generated code this is part of the hs_hosting.asset_rv VIEW.
|
||||||
|
|
||||||
drop view if exists hs_hosting_asset_example_gv;
|
drop view if exists hs_hosting.asset_example_gv;
|
||||||
create view hs_hosting_asset_example_gv as
|
create view hs_hosting.asset_example_gv as
|
||||||
with recursive
|
with recursive
|
||||||
recursive_grants as (
|
recursive_grants as (
|
||||||
select distinct rbacgrants.descendantuuid,
|
select distinct rbacgrants.descendantuuid,
|
||||||
@ -40,7 +40,7 @@ select distinct perm.objectuuid
|
|||||||
join rbacpermission perm on recursive_grants.descendantuuid = perm.uuid
|
join rbacpermission perm on recursive_grants.descendantuuid = perm.uuid
|
||||||
join rbacobject obj on obj.uuid = perm.objectuuid
|
join rbacobject obj on obj.uuid = perm.objectuuid
|
||||||
join count_check cc on cc.valid
|
join count_check cc on cc.valid
|
||||||
where obj.objecttable::text = 'hs_hosting_asset'::text
|
where obj.objecttable::text = 'hs_hosting.asset'::text
|
||||||
-- with/without this type condition
|
-- with/without this type condition
|
||||||
-- and obj.type = 'EMAIL_ADDRESS'::hshostingassettype
|
-- and obj.type = 'EMAIL_ADDRESS'::hshostingassettype
|
||||||
and obj.type = 'EMAIL_ADDRESS'::hshostingassettype
|
and obj.type = 'EMAIL_ADDRESS'::hshostingassettype
|
||||||
@ -53,10 +53,10 @@ select distinct perm.objectuuid
|
|||||||
rollback transaction;
|
rollback transaction;
|
||||||
begin transaction;
|
begin transaction;
|
||||||
CALL defineContext('performance testing', null, 'superuser-alex@hostsharing.net',
|
CALL defineContext('performance testing', null, 'superuser-alex@hostsharing.net',
|
||||||
'hs_booking_project#D-1000000-hshdefaultproject:ADMIN');
|
'hs_booking.project#D-1000000-hshdefaultproject:ADMIN');
|
||||||
-- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN');
|
-- 'hs_booking.project#D-1000300-mihdefaultproject:ADMIN');
|
||||||
SET TRANSACTION READ ONLY;
|
SET TRANSACTION READ ONLY;
|
||||||
EXPLAIN ANALYZE select * from hs_hosting_asset_example_gv;
|
EXPLAIN ANALYZE select * from hs_hosting.asset_example_gv;
|
||||||
end transaction ;
|
end transaction ;
|
||||||
|
|
||||||
-- ========================================================
|
-- ========================================================
|
||||||
@ -64,15 +64,15 @@ end transaction ;
|
|||||||
-- An example for a restricted view (_rv) similar to the one generated by our RBAC system,
|
-- An example for a restricted view (_rv) similar to the one generated by our RBAC system,
|
||||||
-- but using the above separate VIEW to determine the visible objects.
|
-- but using the above separate VIEW to determine the visible objects.
|
||||||
|
|
||||||
drop view if exists hs_hosting_asset_example_rv;
|
drop view if exists hs_hosting.asset_example_rv;
|
||||||
create view hs_hosting_asset_example_rv as
|
create view hs_hosting.asset_example_rv as
|
||||||
with accessible_hs_hosting_asset_uuids as (
|
with accessible_hs_hosting.asset_uuids as (
|
||||||
select * from hs_hosting_asset_example_gv
|
select * from hs_hosting.asset_example_gv
|
||||||
)
|
)
|
||||||
select target.*
|
select target.*
|
||||||
from hs_hosting_asset target
|
from hs_hosting.asset target
|
||||||
where (target.uuid in (select accessible_hs_hosting_asset_uuids.objectuuid
|
where (target.uuid in (select accessible_hs_hosting.asset_uuids.objectuuid
|
||||||
from accessible_hs_hosting_asset_uuids));
|
from accessible_hs_hosting.asset_uuids));
|
||||||
|
|
||||||
-- -------------------------------------------------------------------------------
|
-- -------------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -89,8 +89,8 @@ BEGIN
|
|||||||
start_time := clock_timestamp();
|
start_time := clock_timestamp();
|
||||||
|
|
||||||
CALL defineContext('performance testing', null, 'superuser-alex@hostsharing.net',
|
CALL defineContext('performance testing', null, 'superuser-alex@hostsharing.net',
|
||||||
'hs_booking_project#D-1000000-hshdefaultproject:ADMIN');
|
'hs_booking.project#D-1000000-hshdefaultproject:ADMIN');
|
||||||
-- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN');
|
-- 'hs_booking.project#D-1000300-mihdefaultproject:ADMIN');
|
||||||
SET TRANSACTION READ ONLY;
|
SET TRANSACTION READ ONLY;
|
||||||
|
|
||||||
FOR i IN 0..25 LOOP
|
FOR i IN 0..25 LOOP
|
||||||
@ -99,7 +99,7 @@ BEGIN
|
|||||||
|
|
||||||
-- An example for a business query based on the view:
|
-- An example for a business query based on the view:
|
||||||
select type, uuid, identifier, caption
|
select type, uuid, identifier, caption
|
||||||
from hs_hosting_asset_example_rv
|
from hs_hosting.asset_example_rv
|
||||||
where type = 'EMAIL_ADDRESS'
|
where type = 'EMAIL_ADDRESS'
|
||||||
and identifier like letter || '%'
|
and identifier like letter || '%'
|
||||||
-- end of the business query example.
|
-- end of the business query example.
|
||||||
@ -115,7 +115,7 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
-- average seconds per recursive CTE select as role 'hs_hosting_asset:<DEBITOR>defaultproject:ADMIN'
|
-- average seconds per recursive CTE select as role 'hs_hosting.asset:<DEBITOR>defaultproject:ADMIN'
|
||||||
-- joined with business query for all 'EMAIL_ADDRESSES':
|
-- joined with business query for all 'EMAIL_ADDRESSES':
|
||||||
-- D-1000000-hsh D-1000300-mih
|
-- D-1000000-hsh D-1000300-mih
|
||||||
-- - without type comparison in rbacobject: ~3.30 - ~3.49 ~0.23
|
-- - without type comparison in rbacobject: ~3.30 - ~3.49 ~0.23
|
||||||
@ -128,15 +128,15 @@ $$;
|
|||||||
rollback transaction;
|
rollback transaction;
|
||||||
begin transaction;
|
begin transaction;
|
||||||
CALL defineContext('performance testing', null, 'superuser-alex@hostsharing.net',
|
CALL defineContext('performance testing', null, 'superuser-alex@hostsharing.net',
|
||||||
'hs_booking_project#D-1000000-hshdefaultproject:ADMIN');
|
'hs_booking.project#D-1000000-hshdefaultproject:ADMIN');
|
||||||
-- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN');
|
-- 'hs_booking.project#D-1000300-mihdefaultproject:ADMIN');
|
||||||
SET TRANSACTION READ ONLY;
|
SET TRANSACTION READ ONLY;
|
||||||
|
|
||||||
EXPLAIN SELECT * from (
|
EXPLAIN SELECT * from (
|
||||||
|
|
||||||
-- An example for a business query based on the view:
|
-- An example for a business query based on the view:
|
||||||
select type, uuid, identifier, caption
|
select type, uuid, identifier, caption
|
||||||
from hs_hosting_asset_example_rv
|
from hs_hosting.asset_example_rv
|
||||||
where type = 'EMAIL_ADDRESS'
|
where type = 'EMAIL_ADDRESS'
|
||||||
-- and identifier like 'b%'
|
-- and identifier like 'b%'
|
||||||
-- end of the business query example.
|
-- end of the business query example.
|
||||||
@ -151,17 +151,17 @@ end transaction;
|
|||||||
|
|
||||||
alter table rbacobject
|
alter table rbacobject
|
||||||
-- just for performance testing, we would need a joined enum or a varchar(16) which would make it slow
|
-- just for performance testing, we would need a joined enum or a varchar(16) which would make it slow
|
||||||
add column type hshostingassettype;
|
add column type hs_hosting.AssetType;
|
||||||
|
|
||||||
-- and fill the type column with hs_hosting_asset types:
|
-- and fill the type column with hs_hosting.asset types:
|
||||||
|
|
||||||
rollback transaction;
|
rollback transaction;
|
||||||
begin transaction;
|
begin transaction;
|
||||||
call defineContext('setting rbacobject.type from hs_hosting_asset.type', null, 'superuser-alex@hostsharing.net');
|
call defineContext('setting rbacobject.type from hs_hosting.asset.type', null, 'superuser-alex@hostsharing.net');
|
||||||
|
|
||||||
UPDATE rbacobject
|
UPDATE rbacobject
|
||||||
SET type = hs.type
|
SET type = hs.type
|
||||||
FROM hs_hosting_asset hs
|
FROM hs_hosting.asset hs
|
||||||
WHERE rbacobject.uuid = hs.uuid;
|
WHERE rbacobject.uuid = hs.uuid;
|
||||||
|
|
||||||
end transaction;
|
end transaction;
|
||||||
|
@ -9,15 +9,25 @@ import java.lang.annotation.Target;
|
|||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface DisplayAs {
|
public @interface DisplayAs {
|
||||||
|
|
||||||
class DisplayName {
|
class DisplayName {
|
||||||
|
|
||||||
public static String of(final Class<?> clazz) {
|
public static String of(final Class<?> clazz) {
|
||||||
final var displayNameAnnot = clazz.getAnnotation(DisplayAs.class);
|
final var displayNameAnnot = getDisplayNameAnnotation(clazz);
|
||||||
return displayNameAnnot != null ? displayNameAnnot.value() : clazz.getSimpleName();
|
return displayNameAnnot != null ? displayNameAnnot.value() : clazz.getSimpleName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String of(@NotNull final Object instance) {
|
public static String of(@NotNull final Object instance) {
|
||||||
return of(instance.getClass());
|
return of(instance.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static DisplayAs getDisplayNameAnnotation(final Class<?> clazz) {
|
||||||
|
if (clazz == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final var annot = clazz.getAnnotation(DisplayAs.class);
|
||||||
|
return annot != null ? annot : getDisplayNameAnnotation(clazz.getSuperclass());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String value() default "";
|
String value() default "";
|
||||||
|
@ -18,7 +18,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
|||||||
|
|
||||||
// a partial HsOfficeDebitorEntity to reduce the number of SQL queries to load the entity
|
// a partial HsOfficeDebitorEntity to reduce the number of SQL queries to load the entity
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_booking_debitor_xv")
|
@Table(schema = "hs_booking", name = "debitor_xv")
|
||||||
@Getter
|
@Getter
|
||||||
@Builder
|
@Builder
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.booking.item;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class BookingItemCreatedEvent extends ApplicationEvent {
|
||||||
|
private final @NotNull HsBookingItem newBookingItem;
|
||||||
|
|
||||||
|
public BookingItemCreatedEvent(@NotNull HsBookingItemController source, @NotNull final HsBookingItem newBookingItem) {
|
||||||
|
super(source);
|
||||||
|
this.newBookingItem = newBookingItem;
|
||||||
|
}
|
||||||
|
}
|
@ -5,17 +5,19 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingItemsA
|
|||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource;
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource;
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource;
|
||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemResource;
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemResource;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.validators.BookingItemEntitySaveProcessor;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry;
|
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
|
||||||
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||||
|
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||||
|
|
||||||
import jakarta.persistence.EntityManager;
|
|
||||||
import jakarta.persistence.PersistenceContext;
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -30,13 +32,16 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StrictMapper mapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ApplicationEventPublisher applicationEventPublisher;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsBookingItemRbacRepository bookingItemRepo;
|
private HsBookingItemRbacRepository bookingItemRepo;
|
||||||
|
|
||||||
@PersistenceContext
|
@Autowired
|
||||||
private EntityManager em;
|
private EntityManagerWrapper em;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@ -48,7 +53,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
|||||||
|
|
||||||
final var entities = bookingItemRepo.findAllByProjectUuid(projectUuid);
|
final var entities = bookingItemRepo.findAllByProjectUuid(projectUuid);
|
||||||
|
|
||||||
final var resources = mapper.mapList(entities, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
final var resources = mapper.mapList(entities, HsBookingItemResource.class, RBAC_ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||||
return ResponseEntity.ok(resources);
|
return ResponseEntity.ok(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,15 +67,23 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
|||||||
context.define(currentSubject, assumedRoles);
|
context.define(currentSubject, assumedRoles);
|
||||||
|
|
||||||
final var entityToSave = mapper.map(body, HsBookingItemRbacEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
final var entityToSave = mapper.map(body, HsBookingItemRbacEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||||
|
final var saveProcessor = new BookingItemEntitySaveProcessor(em, entityToSave);
|
||||||
|
final var mapped = saveProcessor
|
||||||
|
.preprocessEntity()
|
||||||
|
.validateEntity()
|
||||||
|
.prepareForSave()
|
||||||
|
.save()
|
||||||
|
.validateContext()
|
||||||
|
.mapUsing(e -> mapper.map(e, HsBookingItemResource.class, ITEM_TO_RESOURCE_POSTMAPPER))
|
||||||
|
.revampProperties();
|
||||||
|
|
||||||
final var saved = HsBookingItemEntityValidatorRegistry.validated(em, bookingItemRepo.save(entityToSave));
|
applicationEventPublisher.publishEvent(new BookingItemCreatedEvent(this, saveProcessor.getEntity()));
|
||||||
|
|
||||||
final var uri =
|
final var uri =
|
||||||
MvcUriComponentsBuilder.fromController(getClass())
|
MvcUriComponentsBuilder.fromController(getClass())
|
||||||
.path("/api/hs/booking/items/{id}")
|
.path("/api/hs/booking/items/{id}")
|
||||||
.buildAndExpand(saved.getUuid())
|
.buildAndExpand(mapped.getUuid())
|
||||||
.toUri();
|
.toUri();
|
||||||
final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
|
||||||
return ResponseEntity.created(uri).body(mapped);
|
return ResponseEntity.created(uri).body(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +100,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
|||||||
result.ifPresent(entity -> em.detach(entity)); // prevent further LAZY-loading
|
result.ifPresent(entity -> em.detach(entity)); // prevent further LAZY-loading
|
||||||
return result
|
return result
|
||||||
.map(bookingItemEntity -> ResponseEntity.ok(
|
.map(bookingItemEntity -> ResponseEntity.ok(
|
||||||
mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)))
|
mapper.map(bookingItemEntity, HsBookingItemResource.class, RBAC_ENTITY_TO_RESOURCE_POSTMAPPER)))
|
||||||
.orElseGet(() -> ResponseEntity.notFound().build());
|
.orElseGet(() -> ResponseEntity.notFound().build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,18 +133,21 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
|||||||
new HsBookingItemEntityPatcher(current).apply(body);
|
new HsBookingItemEntityPatcher(current).apply(body);
|
||||||
|
|
||||||
final var saved = bookingItemRepo.save(HsBookingItemEntityValidatorRegistry.validated(em, current));
|
final var saved = bookingItemRepo.save(HsBookingItemEntityValidatorRegistry.validated(em, current));
|
||||||
final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
final var mapped = mapper.map(saved, HsBookingItemResource.class, RBAC_ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||||
return ResponseEntity.ok(mapped);
|
return ResponseEntity.ok(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
final BiConsumer<HsBookingItemRbacEntity, HsBookingItemResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
final BiConsumer<HsBookingItem, HsBookingItemResource> ITEM_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||||
resource.setValidFrom(entity.getValidity().lower());
|
resource.setValidFrom(entity.getValidity().lower());
|
||||||
if (entity.getValidity().hasUpperBound()) {
|
if (entity.getValidity().hasUpperBound()) {
|
||||||
resource.setValidTo(entity.getValidity().upper().minusDays(1));
|
resource.setValidTo(entity.getValidity().upper().minusDays(1));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
final BiConsumer<HsBookingItemRbacEntity, HsBookingItemResource> RBAC_ENTITY_TO_RESOURCE_POSTMAPPER = ITEM_TO_RESOURCE_POSTMAPPER::accept;
|
||||||
|
|
||||||
final BiConsumer<HsBookingItemInsertResource, HsBookingItemRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
final BiConsumer<HsBookingItemInsertResource, HsBookingItemRbacEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||||
|
entity.setProject(em.find(HsBookingProjectRealEntity.class, resource.getProjectUuid()));
|
||||||
entity.setValidity(toPostgresDateRange(LocalDate.now(), resource.getValidTo()));
|
entity.setValidity(toPostgresDateRange(LocalDate.now(), resource.getValidTo()));
|
||||||
entity.putResources(KeyValueMap.from(resource.getResources()));
|
entity.putResources(KeyValueMap.from(resource.getResources()));
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ import lombok.Getter;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProject;
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRbacEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetc
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_booking_item_rv")
|
@Table(schema = "hs_booking", name = "item_rv")
|
||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ -49,7 +49,7 @@ public class HsBookingItemRbacEntity extends HsBookingItem {
|
|||||||
.toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
|
.toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
|
||||||
.toRole(GLOBAL, ADMIN).grantPermission(DELETE)
|
.toRole(GLOBAL, ADMIN).grantPermission(DELETE)
|
||||||
|
|
||||||
.importEntityAlias("project", HsBookingProject.class, usingDefaultCase(),
|
.importEntityAlias("project", HsBookingProjectRbacEntity.class, usingDefaultCase(),
|
||||||
dependsOnColumn("projectUuid"),
|
dependsOnColumn("projectUuid"),
|
||||||
directlyFetchedByDependsOnColumn(),
|
directlyFetchedByDependsOnColumn(),
|
||||||
NULLABLE)
|
NULLABLE)
|
||||||
|
@ -13,7 +13,7 @@ import jakarta.persistence.Table;
|
|||||||
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_booking_item")
|
@Table(schema = "hs_booking", name = "item")
|
||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@ -0,0 +1,136 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemResource;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem;
|
||||||
|
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||||
|
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
// TODO.refa: introduce common base class with HsHostingAssetEntitySaveProcessor
|
||||||
|
/**
|
||||||
|
* Wraps the steps of the pararation, validation, mapping and revamp around saving of a HsBookingItem into a readable API.
|
||||||
|
*/
|
||||||
|
public class BookingItemEntitySaveProcessor {
|
||||||
|
|
||||||
|
private final HsEntityValidator<HsBookingItem> validator;
|
||||||
|
private String expectedStep = "preprocessEntity";
|
||||||
|
private final EntityManager em;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private HsBookingItem entity;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private HsBookingItemResource resource;
|
||||||
|
|
||||||
|
public BookingItemEntitySaveProcessor(final EntityManager em, final HsBookingItem entity) {
|
||||||
|
this.em = em;
|
||||||
|
this.entity = entity;
|
||||||
|
this.validator = HsBookingItemEntityValidatorRegistry.forType(entity.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// initial step allowing to set default values before any validations
|
||||||
|
public BookingItemEntitySaveProcessor preprocessEntity() {
|
||||||
|
step("preprocessEntity", "validateEntity");
|
||||||
|
validator.preprocessEntity(entity);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// validates the entity itself including its properties
|
||||||
|
public BookingItemEntitySaveProcessor validateEntity() {
|
||||||
|
step("validateEntity", "prepareForSave");
|
||||||
|
MultiValidationException.throwIfNotEmpty(validator.validateEntity(entity));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO.impl: remove once the migration of legacy data is done
|
||||||
|
/// validates the entity itself including its properties, but ignoring some error messages for import of legacy data
|
||||||
|
public BookingItemEntitySaveProcessor validateEntityIgnoring(final String... ignoreRegExp) {
|
||||||
|
step("validateEntity", "prepareForSave");
|
||||||
|
final var ignoreRegExpPatterns = Arrays.stream(ignoreRegExp).map(Pattern::compile).toList();
|
||||||
|
MultiValidationException.throwIfNotEmpty(
|
||||||
|
validator.validateEntity(entity).stream()
|
||||||
|
.filter(error -> ignoreRegExpPatterns.stream().noneMatch(p -> p.matcher(error).matches() ))
|
||||||
|
.toList()
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// hashing passwords etc.
|
||||||
|
public BookingItemEntitySaveProcessor prepareForSave() {
|
||||||
|
step("prepareForSave", "save");
|
||||||
|
validator.prepareProperties(em, entity);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the entity using the given `saveFunction`.
|
||||||
|
*
|
||||||
|
* <p>`validator.postPersist(em, entity)` is NOT called.
|
||||||
|
* If any postprocessing is necessary, the saveFunction has to implement this.</p>
|
||||||
|
* @param saveFunction
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public BookingItemEntitySaveProcessor saveUsing(final Function<HsBookingItem, HsBookingItem> saveFunction) {
|
||||||
|
step("save", "validateContext");
|
||||||
|
entity = saveFunction.apply(entity);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the using the `EntityManager`, but does NOT ever merge the entity.
|
||||||
|
*
|
||||||
|
* <p>`validator.postPersist(em, entity)` is called afterwards with the entity guaranteed to be flushed to the database.</p>
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public BookingItemEntitySaveProcessor save() {
|
||||||
|
return saveUsing(e -> {
|
||||||
|
if (!em.contains(entity)) {
|
||||||
|
em.persist(entity);
|
||||||
|
}
|
||||||
|
em.flush(); // makes RbacEntity available as RealEntity if needed
|
||||||
|
validator.postPersist(em, entity);
|
||||||
|
return entity;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// validates the entity within it's parent and child hierarchy (e.g. totals validators and other limits)
|
||||||
|
public BookingItemEntitySaveProcessor validateContext() {
|
||||||
|
step("validateContext", "mapUsing");
|
||||||
|
return HsEntityValidator.doWithEntityManager(em, () -> {
|
||||||
|
MultiValidationException.throwIfNotEmpty(validator.validateContext(entity));
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// maps entity to JSON resource representation
|
||||||
|
public BookingItemEntitySaveProcessor mapUsing(
|
||||||
|
final Function<HsBookingItem, HsBookingItemResource> mapFunction) {
|
||||||
|
step("mapUsing", "revampProperties");
|
||||||
|
resource = mapFunction.apply(entity);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// removes write-only-properties and ads computed-properties
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public HsBookingItemResource revampProperties() {
|
||||||
|
step("revampProperties", null);
|
||||||
|
final var revampedProps = validator.revampProperties(em, entity, (Map<String, Object>) resource.getResources());
|
||||||
|
resource.setResources(revampedProps);
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Makes sure that the steps are called in the correct order.
|
||||||
|
// Could also be implemented using an interface per method, but that seems exaggerated.
|
||||||
|
private void step(final String current, final String next) {
|
||||||
|
if (!expectedStep.equals(current)) {
|
||||||
|
throw new IllegalStateException("expected " + expectedStep + " but got " + current);
|
||||||
|
}
|
||||||
|
expectedStep = next;
|
||||||
|
}
|
||||||
|
}
|
@ -48,10 +48,11 @@ public class HsBookingItemEntityValidatorRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> doValidate(final EntityManager em, final HsBookingItem bookingItem) {
|
public static List<String> doValidate(final EntityManager em, final HsBookingItem bookingItem) {
|
||||||
|
final var bookingItemValidator = HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType());
|
||||||
return HsEntityValidator.doWithEntityManager(em, () ->
|
return HsEntityValidator.doWithEntityManager(em, () ->
|
||||||
HsEntityValidator.sequentiallyValidate(
|
HsEntityValidator.sequentiallyValidate(
|
||||||
() -> HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateEntity(bookingItem),
|
() -> bookingItemValidator.validateEntity(bookingItem),
|
||||||
() -> HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validateContext(bookingItem))
|
() -> bookingItemValidator.validateContext(bookingItem))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem;
|
||||||
@ -15,6 +16,9 @@ class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator {
|
|||||||
|
|
||||||
public static final String FQDN_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}";
|
public static final String FQDN_REGEX = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,12}";
|
||||||
public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName";
|
public static final String DOMAIN_NAME_PROPERTY_NAME = "domainName";
|
||||||
|
public static final String TARGET_UNIX_USER_PROPERTY_NAME = "targetUnixUser";
|
||||||
|
public static final String WEBSPACE_NAME_REGEX = "[a-z][a-z0-9]{2}[0-9]{2}";
|
||||||
|
public static final String TARGET_UNIX_USER_NAME_REGEX = "^"+WEBSPACE_NAME_REGEX+"$|^"+WEBSPACE_NAME_REGEX+"-[a-z0-9\\._-]+$";
|
||||||
public static final String VERIFICATION_CODE_PROPERTY_NAME = "verificationCode";
|
public static final String VERIFICATION_CODE_PROPERTY_NAME = "verificationCode";
|
||||||
|
|
||||||
HsDomainSetupBookingItemValidator() {
|
HsDomainSetupBookingItemValidator() {
|
||||||
@ -24,6 +28,12 @@ class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator {
|
|||||||
.matchesRegEx(FQDN_REGEX).describedAs("is not a (non-top-level) fully qualified domain name")
|
.matchesRegEx(FQDN_REGEX).describedAs("is not a (non-top-level) fully qualified domain name")
|
||||||
.notMatchesRegEx(REGISTRAR_LEVEL_DOMAINS).describedAs("is a forbidden registrar-level domain name")
|
.notMatchesRegEx(REGISTRAR_LEVEL_DOMAINS).describedAs("is a forbidden registrar-level domain name")
|
||||||
.required(),
|
.required(),
|
||||||
|
// TODO.legacy: remove the following property once we give up legacy compatibility
|
||||||
|
stringProperty(TARGET_UNIX_USER_PROPERTY_NAME).writeOnce()
|
||||||
|
.maxLength(253)
|
||||||
|
.matchesRegEx(TARGET_UNIX_USER_NAME_REGEX).describedAs("is not a valid unix-user name")
|
||||||
|
.writeOnce()
|
||||||
|
.required(),
|
||||||
stringProperty(VERIFICATION_CODE_PROPERTY_NAME)
|
stringProperty(VERIFICATION_CODE_PROPERTY_NAME)
|
||||||
.minLength(12)
|
.minLength(12)
|
||||||
.maxLength(64)
|
.maxLength(64)
|
||||||
@ -45,6 +55,11 @@ class HsDomainSetupBookingItemValidator extends HsBookingItemEntityValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String generateVerificationCode(final EntityManager em, final PropertiesProvider propertiesProvider) {
|
private static String generateVerificationCode(final EntityManager em, final PropertiesProvider propertiesProvider) {
|
||||||
|
final var userDefinedVerificationCode = propertiesProvider.getDirectValue(VERIFICATION_CODE_PROPERTY_NAME, String.class);
|
||||||
|
if (userDefinedVerificationCode != null) {
|
||||||
|
return userDefinedVerificationCode;
|
||||||
|
}
|
||||||
|
|
||||||
final var alphaNumeric = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
final var alphaNumeric = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||||
final var secureRandom = new SecureRandom();
|
final var secureRandom = new SecureRandom();
|
||||||
final var sb = new StringBuilder();
|
final var sb = new StringBuilder();
|
||||||
|
@ -68,11 +68,11 @@ public abstract class HsBookingProject implements Stringifyable, BaseEntity<HsBo
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static RbacView rbac() {
|
public static RbacView rbac() {
|
||||||
return rbacViewFor("project", HsBookingProject.class)
|
return rbacViewFor("project", HsBookingProjectRbacEntity.class)
|
||||||
.withIdentityView(SQL.query("""
|
.withIdentityView(SQL.query("""
|
||||||
SELECT bookingProject.uuid as uuid, debitorIV.idName || '-' || base.cleanIdentifier(bookingProject.caption) as idName
|
SELECT bookingProject.uuid as uuid, debitorIV.idName || '-' || base.cleanIdentifier(bookingProject.caption) as idName
|
||||||
FROM hs_booking_project bookingProject
|
FROM hs_booking.project bookingProject
|
||||||
JOIN hs_office_debitor_iv debitorIV ON debitorIV.uuid = bookingProject.debitorUuid
|
JOIN hs_office.debitor_iv debitorIV ON debitorIV.uuid = bookingProject.debitorUuid
|
||||||
"""))
|
"""))
|
||||||
.withRestrictedViewOrderBy(SQL.expression("caption"))
|
.withRestrictedViewOrderBy(SQL.expression("caption"))
|
||||||
.withUpdatableColumns("version", "caption")
|
.withUpdatableColumns("version", "caption")
|
||||||
@ -86,8 +86,8 @@ public abstract class HsBookingProject implements Stringifyable, BaseEntity<HsBo
|
|||||||
dependsOnColumn("debitorUuid"),
|
dependsOnColumn("debitorUuid"),
|
||||||
fetchedBySql("""
|
fetchedBySql("""
|
||||||
SELECT ${columns}
|
SELECT ${columns}
|
||||||
FROM hs_office_relation debitorRel
|
FROM hs_office.relation debitorRel
|
||||||
JOIN hs_office_debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
|
JOIN hs_office.debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
|
||||||
WHERE debitor.uuid = ${REF}.debitorUuid
|
WHERE debitor.uuid = ${REF}.debitorUuid
|
||||||
"""),
|
"""),
|
||||||
NOT_NULL)
|
NOT_NULL)
|
||||||
|
@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingProjec
|
|||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectInsertResource;
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectPatchResource;
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectPatchResource;
|
||||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectResource;
|
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingProjectResource;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -25,7 +25,7 @@ public class HsBookingProjectController implements HsBookingProjectsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsBookingProjectRbacRepository bookingProjectRepo;
|
private HsBookingProjectRbacRepository bookingProjectRepo;
|
||||||
|
@ -32,7 +32,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_booking_project_rv")
|
@Table(schema = "hs_booking", name = "project_rv")
|
||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ -43,8 +43,8 @@ public class HsBookingProjectRbacEntity extends HsBookingProject {
|
|||||||
return rbacViewFor("project", HsBookingProjectRbacEntity.class)
|
return rbacViewFor("project", HsBookingProjectRbacEntity.class)
|
||||||
.withIdentityView(SQL.query("""
|
.withIdentityView(SQL.query("""
|
||||||
SELECT bookingProject.uuid as uuid, debitorIV.idName || '-' || base.cleanIdentifier(bookingProject.caption) as idName
|
SELECT bookingProject.uuid as uuid, debitorIV.idName || '-' || base.cleanIdentifier(bookingProject.caption) as idName
|
||||||
FROM hs_booking_project bookingProject
|
FROM hs_booking.project bookingProject
|
||||||
JOIN hs_office_debitor_iv debitorIV ON debitorIV.uuid = bookingProject.debitorUuid
|
JOIN hs_office.debitor_iv debitorIV ON debitorIV.uuid = bookingProject.debitorUuid
|
||||||
"""))
|
"""))
|
||||||
.withRestrictedViewOrderBy(SQL.expression("caption"))
|
.withRestrictedViewOrderBy(SQL.expression("caption"))
|
||||||
.withUpdatableColumns("version", "caption")
|
.withUpdatableColumns("version", "caption")
|
||||||
@ -58,8 +58,8 @@ public class HsBookingProjectRbacEntity extends HsBookingProject {
|
|||||||
dependsOnColumn("debitorUuid"),
|
dependsOnColumn("debitorUuid"),
|
||||||
fetchedBySql("""
|
fetchedBySql("""
|
||||||
SELECT ${columns}
|
SELECT ${columns}
|
||||||
FROM hs_office_relation debitorRel
|
FROM hs_office.relation debitorRel
|
||||||
JOIN hs_office_debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
|
JOIN hs_office.debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
|
||||||
WHERE debitor.uuid = ${REF}.debitorUuid
|
WHERE debitor.uuid = ${REF}.debitorUuid
|
||||||
"""),
|
"""),
|
||||||
NOT_NULL)
|
NOT_NULL)
|
||||||
|
@ -10,7 +10,7 @@ import jakarta.persistence.Table;
|
|||||||
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_booking_project")
|
@Table(schema = "hs_booking", name = "project")
|
||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.BookingItemCreatedEvent;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRealEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
|
||||||
|
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class HsBookingItemCreatedListener implements ApplicationListener<BookingItemCreatedEvent> {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EntityManagerWrapper emw;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(final BookingItemCreatedEvent event) {
|
||||||
|
System.out.println("Received newly created booking item: " + event.getNewBookingItem());
|
||||||
|
final var newBookingItemRealEntity =
|
||||||
|
emw.getReference(HsBookingItemRealEntity.class, event.getNewBookingItem().getUuid());
|
||||||
|
final var newHostingAsset = switch (newBookingItemRealEntity.getType()) {
|
||||||
|
case PRIVATE_CLOUD -> null;
|
||||||
|
case CLOUD_SERVER -> null;
|
||||||
|
case MANAGED_SERVER -> null;
|
||||||
|
case MANAGED_WEBSPACE -> null;
|
||||||
|
case DOMAIN_SETUP -> createDomainSetupHostingAsset(newBookingItemRealEntity);
|
||||||
|
};
|
||||||
|
if (newHostingAsset != null) {
|
||||||
|
try {
|
||||||
|
new HostingAssetEntitySaveProcessor(emw, newHostingAsset)
|
||||||
|
.preprocessEntity()
|
||||||
|
.validateEntity()
|
||||||
|
.prepareForSave()
|
||||||
|
.save()
|
||||||
|
.validateContext();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
// TODO.impl: store status in a separate field, maybe enum+message
|
||||||
|
newBookingItemRealEntity.getResources().put("status", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HsHostingAsset createDomainSetupHostingAsset(final HsBookingItemRealEntity fromBookingItem) {
|
||||||
|
return HsHostingAssetRbacEntity.builder()
|
||||||
|
.bookingItem(fromBookingItem)
|
||||||
|
.type(HsHostingAssetType.DOMAIN_SETUP)
|
||||||
|
.identifier(fromBookingItem.getDirectValue("domainName", String.class))
|
||||||
|
.subHostingAssets(List.of(
|
||||||
|
// TARGET_UNIX_USER_PROPERTY_NAME
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAsse
|
|||||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
|
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
|
||||||
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
import net.hostsharing.hsadminng.persistence.EntityManagerWrapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -35,7 +35,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsHostingAssetRbacRepository rbacAssetRepo;
|
private HsHostingAssetRbacRepository rbacAssetRepo;
|
||||||
|
@ -4,7 +4,7 @@ import lombok.Getter;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.experimental.SuperBuilder;
|
import lombok.experimental.SuperBuilder;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItem;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRbacEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity;
|
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
import net.hostsharing.hsadminng.rbac.generator.RbacView;
|
||||||
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
|
||||||
@ -33,7 +33,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetc
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_hosting_asset_rv")
|
@Table(schema = "hs_hosting", name = "asset_rv")
|
||||||
@SuperBuilder(toBuilder = true)
|
@SuperBuilder(toBuilder = true)
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ -47,7 +47,7 @@ public class HsHostingAssetRbacEntity extends HsHostingAsset {
|
|||||||
.withUpdatableColumns("version", "caption", "config", "assignedToAssetUuid", "alarmContactUuid")
|
.withUpdatableColumns("version", "caption", "config", "assignedToAssetUuid", "alarmContactUuid")
|
||||||
.toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
|
.toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
|
||||||
|
|
||||||
.importEntityAlias("bookingItem", HsBookingItem.class, usingDefaultCase(),
|
.importEntityAlias("bookingItem", HsBookingItemRbacEntity.class, usingDefaultCase(),
|
||||||
dependsOnColumn("bookingItemUuid"),
|
dependsOnColumn("bookingItemUuid"),
|
||||||
directlyFetchedByDependsOnColumn(),
|
directlyFetchedByDependsOnColumn(),
|
||||||
NULLABLE)
|
NULLABLE)
|
||||||
|
@ -25,15 +25,15 @@ public interface HsHostingAssetRbacRepository extends HsHostingAssetRepository<H
|
|||||||
ha.parentassetuuid,
|
ha.parentassetuuid,
|
||||||
ha.type,
|
ha.type,
|
||||||
ha.version
|
ha.version
|
||||||
from hs_hosting_asset_rv ha
|
from hs_hosting.asset_rv ha
|
||||||
left join hs_booking_item bi on bi.uuid = ha.bookingitemuuid
|
left join hs_booking.item bi on bi.uuid = ha.bookingitemuuid
|
||||||
left join hs_hosting_asset pha on pha.uuid = ha.parentassetuuid
|
left join hs_hosting.asset pha on pha.uuid = ha.parentassetuuid
|
||||||
where (:projectUuid is null or bi.projectuuid=:projectUuid)
|
where (:projectUuid is null or bi.projectuuid=:projectUuid)
|
||||||
and (:parentAssetUuid is null or pha.uuid=:parentAssetUuid)
|
and (:parentAssetUuid is null or pha.uuid=:parentAssetUuid)
|
||||||
and (:type is null or :type=cast(ha.type as text))
|
and (:type is null or :type=cast(ha.type as text))
|
||||||
""", nativeQuery = true)
|
""", nativeQuery = true)
|
||||||
// The JPQL query did not generate "left join" but just "join".
|
// The JPQL query did not generate "left join" but just "join".
|
||||||
// I also optimized the query by not using the _rv for hs_booking_item and hs_hosting_asset, only for hs_hosting_asset_rv.
|
// I also optimized the query by not using the _rv for hs_booking.item and hs_hosting.asset, only for hs_hosting.asset_rv.
|
||||||
List<HsHostingAssetRbacEntity> findAllByCriteriaImpl(UUID projectUuid, UUID parentAssetUuid, String type);
|
List<HsHostingAssetRbacEntity> findAllByCriteriaImpl(UUID projectUuid, UUID parentAssetUuid, String type);
|
||||||
default List<HsHostingAssetRbacEntity> findAllByCriteria(final UUID projectUuid, final UUID parentAssetUuid, final HsHostingAssetType type) {
|
default List<HsHostingAssetRbacEntity> findAllByCriteria(final UUID projectUuid, final UUID parentAssetUuid, final HsHostingAssetType type) {
|
||||||
return findAllByCriteriaImpl(projectUuid, parentAssetUuid, HsHostingAssetType.asString(type));
|
return findAllByCriteriaImpl(projectUuid, parentAssetUuid, HsHostingAssetType.asString(type));
|
||||||
|
@ -9,7 +9,7 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_hosting_asset")
|
@Table(schema = "hs_hosting", name = "asset")
|
||||||
@SuperBuilder(builderMethodName = "genericBuilder", toBuilder = true)
|
@SuperBuilder(builderMethodName = "genericBuilder", toBuilder = true)
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@ -24,15 +24,15 @@ public interface HsHostingAssetRealRepository extends HsHostingAssetRepository<H
|
|||||||
ha.parentassetuuid,
|
ha.parentassetuuid,
|
||||||
ha.type,
|
ha.type,
|
||||||
ha.version
|
ha.version
|
||||||
from hs_hosting_asset_rv ha
|
from hs_hosting.asset_rv ha
|
||||||
left join hs_booking_item bi on bi.uuid = ha.bookingitemuuid
|
left join hs_booking.item bi on bi.uuid = ha.bookingitemuuid
|
||||||
left join hs_hosting_asset pha on pha.uuid = ha.parentassetuuid
|
left join hs_hosting.asset pha on pha.uuid = ha.parentassetuuid
|
||||||
where (:projectUuid is null or bi.projectuuid=:projectUuid)
|
where (:projectUuid is null or bi.projectuuid=:projectUuid)
|
||||||
and (:parentAssetUuid is null or pha.uuid=:parentAssetUuid)
|
and (:parentAssetUuid is null or pha.uuid=:parentAssetUuid)
|
||||||
and (:type is null or :type=cast(ha.type as text))
|
and (:type is null or :type=cast(ha.type as text))
|
||||||
""", nativeQuery = true)
|
""", nativeQuery = true)
|
||||||
// The JPQL query did not generate "left join" but just "join".
|
// The JPQL query did not generate "left join" but just "join".
|
||||||
// I also optimized the query by not using the _rv for hs_booking_item and hs_hosting_asset, only for hs_hosting_asset_rv.
|
// I also optimized the query by not using the _rv for hs_booking.item and hs_hosting.asset, only for hs_hosting.asset_rv.
|
||||||
List<HsHostingAssetRealEntity> findAllByCriteriaImpl(UUID projectUuid, UUID parentAssetUuid, String type);
|
List<HsHostingAssetRealEntity> findAllByCriteriaImpl(UUID projectUuid, UUID parentAssetUuid, String type);
|
||||||
default List<HsHostingAssetRealEntity> findAllByCriteria(final UUID projectUuid, final UUID parentAssetUuid, final HsHostingAssetType type) {
|
default List<HsHostingAssetRealEntity> findAllByCriteria(final UUID projectUuid, final UUID parentAssetUuid, final HsHostingAssetType type) {
|
||||||
return findAllByCriteriaImpl(projectUuid, parentAssetUuid, HsHostingAssetType.asString(type));
|
return findAllByCriteriaImpl(projectUuid, parentAssetUuid, HsHostingAssetType.asString(type));
|
||||||
|
@ -53,7 +53,7 @@ class HsUnixUserHostingAssetValidator extends HostingAssetEntityValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Integer computeUserId(final EntityManager em, final PropertiesProvider propertiesProvider) {
|
private static Integer computeUserId(final EntityManager em, final PropertiesProvider propertiesProvider) {
|
||||||
final Object result = em.createNativeQuery("SELECT nextval('hs_hosting_asset_unixuser_system_id_seq')", Integer.class)
|
final Object result = em.createNativeQuery("SELECT nextval('hs_hosting.asset_unixuser_system_id_seq')", Integer.class)
|
||||||
.getSingleResult();
|
.getSingleResult();
|
||||||
return (Integer) result;
|
return (Integer) result;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
|
|||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeBankAccountsApi;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeBankAccountsApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountInsertResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeBankAccountResource;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import org.iban4j.BicUtil;
|
import org.iban4j.BicUtil;
|
||||||
import org.iban4j.IbanUtil;
|
import org.iban4j.IbanUtil;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -24,7 +24,7 @@ public class HsOfficeBankAccountController implements HsOfficeBankAccountsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeBankAccountRepository bankAccountRepo;
|
private HsOfficeBankAccountRepository bankAccountRepo;
|
||||||
|
@ -19,7 +19,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
|||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_bankaccount_rv")
|
@Table(schema = "hs_office", name = "bankaccount_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder
|
@Builder
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.contact;
|
package net.hostsharing.hsadminng.hs.office.contact;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeContactsApi;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeContactsApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactInsertResource;
|
||||||
@ -26,7 +26,7 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeContactRbacRepository contactRepo;
|
private HsOfficeContactRbacRepository contactRepo;
|
||||||
|
@ -16,7 +16,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_contact_rv")
|
@Table(schema = "hs_office", name = "contact_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
@ -10,7 +10,7 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_contact")
|
@Table(schema = "hs_office", name = "contact")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
@ -4,7 +4,7 @@ import net.hostsharing.hsadminng.context.Context;
|
|||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopAssetsApi;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopAssetsApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
|
||||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||||
@ -29,7 +29,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo;
|
private HsOfficeCoopAssetsTransactionRepository coopAssetsTransactionRepo;
|
||||||
|
@ -34,7 +34,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_coopassetstransaction_rv")
|
@Table(schema = "hs_office", name = "coopassettx_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder
|
@Builder
|
||||||
|
@ -6,7 +6,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopShar
|
|||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
|
||||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||||
@ -31,7 +31,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
|
private HsOfficeCoopSharesTransactionRepository coopSharesTransactionRepo;
|
||||||
|
@ -32,7 +32,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_coopsharestransaction_rv")
|
@Table(schema = "hs_office", name = "coopsharetx_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder
|
@Builder
|
||||||
|
@ -7,8 +7,8 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebito
|
|||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
import net.hostsharing.hsadminng.persistence.EntityExistsValidator;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -23,7 +23,6 @@ import jakarta.validation.ValidationException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
|
|
||||||
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@ -34,7 +33,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeDebitorRepository debitorRepo;
|
private HsOfficeDebitorRepository debitorRepo;
|
||||||
@ -42,6 +41,9 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeRelationRealRepository relrealRepo;
|
private HsOfficeRelationRealRepository relrealRepo;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EntityExistsValidator entityValidator;
|
||||||
|
|
||||||
@PersistenceContext
|
@PersistenceContext
|
||||||
private EntityManager em;
|
private EntityManager em;
|
||||||
|
|
||||||
@ -84,10 +86,10 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
|||||||
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class);
|
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class);
|
||||||
if ( body.getDebitorRel() != null ) {
|
if ( body.getDebitorRel() != null ) {
|
||||||
body.getDebitorRel().setType(DEBITOR.name());
|
body.getDebitorRel().setType(DEBITOR.name());
|
||||||
final var debitorRel = mapper.map(body.getDebitorRel(), HsOfficeRelationRealEntity.class);
|
final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class);
|
||||||
validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor());
|
entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor());
|
||||||
validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder());
|
entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder());
|
||||||
validateEntityExists("debitorRel.contactUuid", debitorRel.getContact());
|
entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact());
|
||||||
entityToSave.setDebitorRel(relrealRepo.save(debitorRel));
|
entityToSave.setDebitorRel(relrealRepo.save(debitorRel));
|
||||||
} else {
|
} else {
|
||||||
final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid());
|
final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid());
|
||||||
@ -160,15 +162,4 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
|||||||
final var mapped = mapper.map(saved, HsOfficeDebitorResource.class);
|
final var mapped = mapper.map(saved, HsOfficeDebitorResource.class);
|
||||||
return ResponseEntity.ok(mapped);
|
return ResponseEntity.ok(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO.impl: extract this to some generally usable class?
|
|
||||||
private <T extends BaseEntity<T>> T validateEntityExists(final String property, final T entitySkeleton) {
|
|
||||||
final var foundEntity = em.find(entitySkeleton.getClass(), entitySkeleton.getUuid());
|
|
||||||
if ( foundEntity == null) {
|
|
||||||
throw new ValidationException("Unable to find " + DisplayName.of(entitySkeleton) + " by " + property + ": " + entitySkeleton.getUuid());
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection unchecked
|
|
||||||
return (T) foundEntity;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_debitor_rv")
|
@Table(schema = "hs_office", name = "debitor_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder(toBuilder = true)
|
@Builder(toBuilder = true)
|
||||||
@ -87,10 +87,10 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
|||||||
value = """
|
value = """
|
||||||
(
|
(
|
||||||
SELECT DISTINCT partner.uuid
|
SELECT DISTINCT partner.uuid
|
||||||
FROM hs_office_partner_rv partner
|
FROM hs_office.partner_rv partner
|
||||||
JOIN hs_office_relation_rv dRel
|
JOIN hs_office.relation_rv dRel
|
||||||
ON dRel.uuid = debitorreluuid AND dRel.type = 'DEBITOR'
|
ON dRel.uuid = debitorreluuid AND dRel.type = 'DEBITOR'
|
||||||
JOIN hs_office_relation_rv pRel
|
JOIN hs_office.relation_rv pRel
|
||||||
ON pRel.uuid = partner.partnerRelUuid AND pRel.type = 'PARTNER'
|
ON pRel.uuid = partner.partnerRelUuid AND pRel.type = 'PARTNER'
|
||||||
WHERE pRel.holderUuid = dRel.anchorUuid
|
WHERE pRel.holderUuid = dRel.anchorUuid
|
||||||
)
|
)
|
||||||
@ -170,14 +170,14 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
|||||||
.withIdentityView(SQL.query("""
|
.withIdentityView(SQL.query("""
|
||||||
SELECT debitor.uuid AS uuid,
|
SELECT debitor.uuid AS uuid,
|
||||||
'D-' || (SELECT partner.partnerNumber
|
'D-' || (SELECT partner.partnerNumber
|
||||||
FROM hs_office_partner partner
|
FROM hs_office.partner partner
|
||||||
JOIN hs_office_relation partnerRel
|
JOIN hs_office.relation partnerRel
|
||||||
ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER'
|
ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER'
|
||||||
JOIN hs_office_relation debitorRel
|
JOIN hs_office.relation debitorRel
|
||||||
ON debitorRel.anchorUuid = partnerRel.holderUuid AND debitorRel.type = 'DEBITOR'
|
ON debitorRel.anchorUuid = partnerRel.holderUuid AND debitorRel.type = 'DEBITOR'
|
||||||
WHERE debitorRel.uuid = debitor.debitorRelUuid)
|
WHERE debitorRel.uuid = debitor.debitorRelUuid)
|
||||||
|| debitorNumberSuffix as idName
|
|| debitorNumberSuffix as idName
|
||||||
FROM hs_office_debitor AS debitor
|
FROM hs_office.debitor AS debitor
|
||||||
"""))
|
"""))
|
||||||
.withRestrictedViewOrderBy(SQL.projection("defaultPrefix"))
|
.withRestrictedViewOrderBy(SQL.projection("defaultPrefix"))
|
||||||
.withUpdatableColumns(
|
.withUpdatableColumns(
|
||||||
@ -209,8 +209,8 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
|
|||||||
dependsOnColumn("debitorRelUuid"),
|
dependsOnColumn("debitorRelUuid"),
|
||||||
fetchedBySql("""
|
fetchedBySql("""
|
||||||
SELECT ${columns}
|
SELECT ${columns}
|
||||||
FROM hs_office_relation AS partnerRel
|
FROM hs_office.relation AS partnerRel
|
||||||
JOIN hs_office_relation AS debitorRel
|
JOIN hs_office.relation AS debitorRel
|
||||||
ON debitorRel.type = 'DEBITOR' AND debitorRel.anchorUuid = partnerRel.holderUuid
|
ON debitorRel.type = 'DEBITOR' AND debitorRel.anchorUuid = partnerRel.holderUuid
|
||||||
WHERE partnerRel.type = 'PARTNER'
|
WHERE partnerRel.type = 'PARTNER'
|
||||||
AND ${REF}.debitorRelUuid = debitorRel.uuid
|
AND ${REF}.debitorRelUuid = debitorRel.uuid
|
||||||
|
@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeMembersh
|
|||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -24,7 +24,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeMembershipRepository membershipRepo;
|
private HsOfficeMembershipRepository membershipRepo;
|
||||||
|
@ -56,7 +56,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_membership_rv")
|
@Table(schema = "hs_office", name = "membership_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder
|
@Builder
|
||||||
@ -160,8 +160,8 @@ public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEn
|
|||||||
.withIdentityView(SQL.query("""
|
.withIdentityView(SQL.query("""
|
||||||
SELECT m.uuid AS uuid,
|
SELECT m.uuid AS uuid,
|
||||||
'M-' || p.partnerNumber || m.memberNumberSuffix as idName
|
'M-' || p.partnerNumber || m.memberNumberSuffix as idName
|
||||||
FROM hs_office_membership AS m
|
FROM hs_office.membership AS m
|
||||||
JOIN hs_office_partner AS p ON p.uuid = m.partnerUuid
|
JOIN hs_office.partner AS p ON p.uuid = m.partnerUuid
|
||||||
"""))
|
"""))
|
||||||
.withRestrictedViewOrderBy(SQL.projection("validity"))
|
.withRestrictedViewOrderBy(SQL.projection("validity"))
|
||||||
.withUpdatableColumns("validity", "membershipFeeBillable", "status")
|
.withUpdatableColumns("validity", "membershipFeeBillable", "status")
|
||||||
@ -170,8 +170,8 @@ public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEn
|
|||||||
dependsOnColumn("partnerUuid"),
|
dependsOnColumn("partnerUuid"),
|
||||||
fetchedBySql("""
|
fetchedBySql("""
|
||||||
SELECT ${columns}
|
SELECT ${columns}
|
||||||
FROM hs_office_partner AS partner
|
FROM hs_office.partner AS partner
|
||||||
JOIN hs_office_relation AS partnerRel ON partnerRel.uuid = partner.partnerRelUuid
|
JOIN hs_office.relation AS partnerRel ON partnerRel.uuid = partner.partnerRelUuid
|
||||||
WHERE partner.uuid = ${REF}.partnerUuid
|
WHERE partner.uuid = ${REF}.partnerUuid
|
||||||
"""),
|
"""),
|
||||||
NOT_NULL)
|
NOT_NULL)
|
||||||
|
@ -2,18 +2,18 @@ package net.hostsharing.hsadminng.hs.office.membership;
|
|||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
|
||||||
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
|
public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> {
|
||||||
|
|
||||||
private final Mapper mapper;
|
private final StandardMapper mapper;
|
||||||
private final HsOfficeMembershipEntity entity;
|
private final HsOfficeMembershipEntity entity;
|
||||||
|
|
||||||
public HsOfficeMembershipEntityPatcher(
|
public HsOfficeMembershipEntityPatcher(
|
||||||
final Mapper mapper,
|
final StandardMapper mapper,
|
||||||
final HsOfficeMembershipEntity entity) {
|
final HsOfficeMembershipEntity entity) {
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
|
@ -12,7 +12,7 @@ import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
|
|||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
|
||||||
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
|
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@ -36,7 +36,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficePartnerRepository partnerRepo;
|
private HsOfficePartnerRepository partnerRepo;
|
||||||
|
@ -20,7 +20,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_partner_details_rv")
|
@Table(schema = "hs_office", name = "partner_details_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder
|
@Builder
|
||||||
@ -71,9 +71,9 @@ public class HsOfficePartnerDetailsEntity implements BaseEntity<HsOfficePartnerD
|
|||||||
return rbacViewFor("partnerDetails", HsOfficePartnerDetailsEntity.class)
|
return rbacViewFor("partnerDetails", HsOfficePartnerDetailsEntity.class)
|
||||||
.withIdentityView(SQL.query("""
|
.withIdentityView(SQL.query("""
|
||||||
SELECT partnerDetails.uuid as uuid, partner_iv.idName as idName
|
SELECT partnerDetails.uuid as uuid, partner_iv.idName as idName
|
||||||
FROM hs_office_partner_details AS partnerDetails
|
FROM hs_office.partner_details AS partnerDetails
|
||||||
JOIN hs_office_partner partner ON partner.detailsUuid = partnerDetails.uuid
|
JOIN hs_office.partner partner ON partner.detailsUuid = partnerDetails.uuid
|
||||||
JOIN hs_office_partner_iv partner_iv ON partner_iv.uuid = partner.uuid
|
JOIN hs_office.partner_iv partner_iv ON partner_iv.uuid = partner.uuid
|
||||||
"""))
|
"""))
|
||||||
.withRestrictedViewOrderBy(SQL.expression("uuid"))
|
.withRestrictedViewOrderBy(SQL.expression("uuid"))
|
||||||
.withUpdatableColumns(
|
.withUpdatableColumns(
|
||||||
|
@ -36,7 +36,7 @@ import static java.util.Optional.ofNullable;
|
|||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_partner_rv")
|
@Table(schema = "hs_office", name = "partner_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder
|
@Builder
|
||||||
@ -110,7 +110,6 @@ public class HsOfficePartnerEntity implements Stringifyable, BaseEntity<HsOffice
|
|||||||
usingDefaultCase(),
|
usingDefaultCase(),
|
||||||
directlyFetchedByDependsOnColumn(),
|
directlyFetchedByDependsOnColumn(),
|
||||||
dependsOnColumn("partnerRelUuid"))
|
dependsOnColumn("partnerRelUuid"))
|
||||||
.toRole("partnerRel", AGENT).grantPermission(INSERT)
|
|
||||||
.createPermission(DELETE).grantedTo("partnerRel", OWNER)
|
.createPermission(DELETE).grantedTo("partnerRel", OWNER)
|
||||||
.createPermission(UPDATE).grantedTo("partnerRel", ADMIN)
|
.createPermission(UPDATE).grantedTo("partnerRel", ADMIN)
|
||||||
.createPermission(SELECT).grantedTo("partnerRel", TENANT)
|
.createPermission(SELECT).grantedTo("partnerRel", TENANT)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.person;
|
package net.hostsharing.hsadminng.hs.office.person;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePersonsApi;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePersonsApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonInsertResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePersonInsertResource;
|
||||||
@ -23,7 +23,7 @@ public class HsOfficePersonController implements HsOfficePersonsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficePersonRepository personRepo;
|
private HsOfficePersonRepository personRepo;
|
||||||
|
@ -21,8 +21,9 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
|
// TODO.refa: split HsOfficePersonEntity into Real+Rbac-Entity
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_person_rv")
|
@Table(schema = "hs_office", name = "person_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder
|
@Builder
|
||||||
|
@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository
|
|||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationsApi;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationsApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
|
||||||
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
|
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -28,7 +28,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeRelationRbacRepository relationRbacRepo;
|
private HsOfficeRelationRbacRepository relationRbacRepo;
|
||||||
@ -52,7 +52,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
|
|||||||
context.define(currentSubject, assumedRoles);
|
context.define(currentSubject, assumedRoles);
|
||||||
|
|
||||||
final var entities = relationRbacRepo.findRelationRelatedToPersonUuidAndRelationType(personUuid,
|
final var entities = relationRbacRepo.findRelationRelatedToPersonUuidAndRelationType(personUuid,
|
||||||
mapper.map(relationType, HsOfficeRelationType.class));
|
relationType == null ? null : HsOfficeRelationType.valueOf(relationType.name()));
|
||||||
|
|
||||||
final var resources = mapper.mapList(entities, HsOfficeRelationResource.class,
|
final var resources = mapper.mapList(entities, HsOfficeRelationResource.class,
|
||||||
RELATION_ENTITY_TO_RESOURCE_POSTMAPPER);
|
RELATION_ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||||
|
@ -34,7 +34,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetc
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_relation_rv")
|
@Table(schema = "hs_office", name = "relation_rv")
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@ -45,12 +45,12 @@ public class HsOfficeRelationRbacEntity extends HsOfficeRelation {
|
|||||||
public static RbacView rbac() {
|
public static RbacView rbac() {
|
||||||
return rbacViewFor("relation", HsOfficeRelationRbacEntity.class)
|
return rbacViewFor("relation", HsOfficeRelationRbacEntity.class)
|
||||||
.withIdentityView(SQL.projection("""
|
.withIdentityView(SQL.projection("""
|
||||||
(select idName from hs_office_person_iv p where p.uuid = anchorUuid)
|
(select idName from hs_office.person_iv p where p.uuid = anchorUuid)
|
||||||
|| '-with-' || target.type || '-'
|
|| '-with-' || target.type || '-'
|
||||||
|| (select idName from hs_office_person_iv p where p.uuid = holderUuid)
|
|| (select idName from hs_office.person_iv p where p.uuid = holderUuid)
|
||||||
"""))
|
"""))
|
||||||
.withRestrictedViewOrderBy(SQL.expression(
|
.withRestrictedViewOrderBy(SQL.expression(
|
||||||
"(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)"))
|
"(select idName from hs_office.person_iv p where p.uuid = target.holderUuid)"))
|
||||||
.withUpdatableColumns("contactUuid")
|
.withUpdatableColumns("contactUuid")
|
||||||
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class, usingDefaultCase(),
|
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class, usingDefaultCase(),
|
||||||
dependsOnColumn("anchorUuid"),
|
dependsOnColumn("anchorUuid"),
|
||||||
@ -98,7 +98,6 @@ public class HsOfficeRelationRbacEntity extends HsOfficeRelation {
|
|||||||
})
|
})
|
||||||
.createSubRole(ADMIN, (with) -> {
|
.createSubRole(ADMIN, (with) -> {
|
||||||
with.permission(UPDATE);
|
with.permission(UPDATE);
|
||||||
with.outgoingSubRole("holderPerson", ADMIN); // FIXME: only for type=partner
|
|
||||||
})
|
})
|
||||||
.createSubRole(AGENT, (with) -> {
|
.createSubRole(AGENT, (with) -> {
|
||||||
// TODO.rbac: we need relation:PROXY, to allow changing the relation contact.
|
// TODO.rbac: we need relation:PROXY, to allow changing the relation contact.
|
||||||
|
@ -13,20 +13,20 @@ public interface HsOfficeRelationRbacRepository extends Repository<HsOfficeRelat
|
|||||||
Optional<HsOfficeRelationRbacEntity> findByUuid(UUID id);
|
Optional<HsOfficeRelationRbacEntity> findByUuid(UUID id);
|
||||||
|
|
||||||
default List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) {
|
default List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) {
|
||||||
return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType.toString());
|
return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType == null ? null : relationType.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query(value = """
|
@Query(value = """
|
||||||
SELECT p.* FROM hs_office_relation_rv AS p
|
SELECT p.* FROM hs_office.relation_rv AS p
|
||||||
WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid
|
WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid
|
||||||
""", nativeQuery = true)
|
""", nativeQuery = true)
|
||||||
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuid(@NotNull UUID personUuid);
|
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuid(@NotNull UUID personUuid);
|
||||||
|
|
||||||
@Query(value = """
|
@Query(value = """
|
||||||
SELECT p.* FROM hs_office_relation_rv AS p
|
SELECT p.* FROM hs_office.relation_rv AS p
|
||||||
WHERE (:relationType IS NULL OR p.type = cast(:relationType AS HsOfficeRelationType))
|
WHERE (:relationType IS NULL OR p.type = cast(:relationType AS hs_office.RelationType))
|
||||||
AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid)
|
AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid)
|
||||||
""", nativeQuery = true)
|
""", nativeQuery = true)
|
||||||
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType);
|
List<HsOfficeRelationRbacEntity> findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType);
|
||||||
|
|
||||||
HsOfficeRelationRbacEntity save(final HsOfficeRelationRbacEntity entity);
|
HsOfficeRelationRbacEntity save(final HsOfficeRelationRbacEntity entity);
|
||||||
|
@ -11,7 +11,7 @@ import jakarta.persistence.Table;
|
|||||||
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_relation")
|
@Table(schema = "hs_office", name = "relation")
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@ -13,18 +13,18 @@ public interface HsOfficeRelationRealRepository extends Repository<HsOfficeRelat
|
|||||||
Optional<HsOfficeRelationRealEntity> findByUuid(UUID id);
|
Optional<HsOfficeRelationRealEntity> findByUuid(UUID id);
|
||||||
|
|
||||||
default List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) {
|
default List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) {
|
||||||
return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType.toString());
|
return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType == null ? null : relationType.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query(value = """
|
@Query(value = """
|
||||||
SELECT p.* FROM hs_office_relation AS p
|
SELECT p.* FROM hs_office.relation AS p
|
||||||
WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid
|
WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid
|
||||||
""", nativeQuery = true)
|
""", nativeQuery = true)
|
||||||
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuid(@NotNull UUID personUuid);
|
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuid(@NotNull UUID personUuid);
|
||||||
|
|
||||||
@Query(value = """
|
@Query(value = """
|
||||||
SELECT p.* FROM hs_office_relation AS p
|
SELECT p.* FROM hs_office.relation AS p
|
||||||
WHERE (:relationType IS NULL OR p.type = cast(:relationType AS HsOfficeRelationType))
|
WHERE (:relationType IS NULL OR p.type = cast(:relationType AS hs_office.RelationType))
|
||||||
AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid)
|
AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid)
|
||||||
""", nativeQuery = true)
|
""", nativeQuery = true)
|
||||||
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType);
|
List<HsOfficeRelationRealEntity> findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType);
|
||||||
|
@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeSepaMand
|
|||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateInsertResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateInsertResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateResource;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -28,7 +28,7 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private HsOfficeSepaMandateRepository sepaMandateRepo;
|
private HsOfficeSepaMandateRepository sepaMandateRepo;
|
||||||
|
@ -33,7 +33,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
|||||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "hs_office_sepamandate_rv")
|
@Table(schema = "hs_office", name = "sepamandate_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@Builder
|
@Builder
|
||||||
@ -104,8 +104,8 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, BaseEntity<HsOf
|
|||||||
return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class)
|
return rbacViewFor("sepaMandate", HsOfficeSepaMandateEntity.class)
|
||||||
.withIdentityView(query("""
|
.withIdentityView(query("""
|
||||||
select sm.uuid as uuid, ba.iban || '-' || sm.validity as idName
|
select sm.uuid as uuid, ba.iban || '-' || sm.validity as idName
|
||||||
from hs_office_sepamandate sm
|
from hs_office.sepamandate sm
|
||||||
join hs_office_bankaccount ba on ba.uuid = sm.bankAccountUuid
|
join hs_office.bankaccount ba on ba.uuid = sm.bankAccountUuid
|
||||||
"""))
|
"""))
|
||||||
.withRestrictedViewOrderBy(expression("validity"))
|
.withRestrictedViewOrderBy(expression("validity"))
|
||||||
.withUpdatableColumns("reference", "agreement", "validity")
|
.withUpdatableColumns("reference", "agreement", "validity")
|
||||||
@ -114,8 +114,8 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, BaseEntity<HsOf
|
|||||||
dependsOnColumn("debitorUuid"),
|
dependsOnColumn("debitorUuid"),
|
||||||
fetchedBySql("""
|
fetchedBySql("""
|
||||||
SELECT ${columns}
|
SELECT ${columns}
|
||||||
FROM hs_office_relation debitorRel
|
FROM hs_office.relation debitorRel
|
||||||
JOIN hs_office_debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
|
JOIN hs_office.debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
|
||||||
WHERE debitor.uuid = ${REF}.debitorUuid
|
WHERE debitor.uuid = ${REF}.debitorUuid
|
||||||
"""),
|
"""),
|
||||||
NOT_NULL)
|
NOT_NULL)
|
||||||
|
@ -130,7 +130,7 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> revampProperties(final EntityManager em, final E entity, final Map<String, Object> config) {
|
public Map<String, Object> revampProperties(final EntityManager em, final E entity, final Map<String, Object> config) {
|
||||||
final var copy = new HashMap<>(config);
|
final var copy = config != null ? new HashMap<>(config) : new HashMap();
|
||||||
stream(propertyValidators).forEach(p -> {
|
stream(propertyValidators).forEach(p -> {
|
||||||
if (p.isWriteOnly()) {
|
if (p.isWriteOnly()) {
|
||||||
copy.remove(p.propertyName);
|
copy.remove(p.propertyName);
|
||||||
|
@ -11,18 +11,20 @@ import java.lang.reflect.Field;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.Arrays.stream;
|
||||||
import static net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
|
import static net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A nicer API for ModelMapper.
|
* A nicer API for ModelMapper.
|
||||||
*/
|
*/
|
||||||
public class Mapper extends ModelMapper {
|
abstract class Mapper extends ModelMapper {
|
||||||
|
|
||||||
@PersistenceContext
|
@PersistenceContext
|
||||||
EntityManager em;
|
EntityManager em;
|
||||||
|
|
||||||
public Mapper() {
|
Mapper() {
|
||||||
getConfiguration().setAmbiguityIgnored(true);
|
getConfiguration().setAmbiguityIgnored(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,8 +47,12 @@ public class Mapper extends ModelMapper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <D> D map(final Object source, final Class<D> destinationType) {
|
public <D> D map(final Object source, final Class<D> destinationType) {
|
||||||
|
return map("", source, destinationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <D> D map(final String namePrefix, final Object source, final Class<D> destinationType) {
|
||||||
final var target = super.map(source, destinationType);
|
final var target = super.map(source, destinationType);
|
||||||
for (Field f : destinationType.getDeclaredFields()) {
|
for (Field f : getDeclaredFieldsIncludingSuperClasses(destinationType)) {
|
||||||
if (f.getAnnotation(ManyToOne.class) == null) {
|
if (f.getAnnotation(ManyToOne.class) == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -64,18 +70,30 @@ public class Mapper extends ModelMapper {
|
|||||||
if (subEntityUuid == null) {
|
if (subEntityUuid == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ReflectionUtils.setField(f, target, findEntityById(f.getType(), subEntityUuid));
|
ReflectionUtils.setField(f, target, fetchEntity(namePrefix + f.getName() + ".uuid", f.getType(), subEntityUuid));
|
||||||
}
|
}
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object findEntityById(final Class<?> entityClass, final Object subEntityUuid) {
|
private static <D> Field[] getDeclaredFieldsIncludingSuperClasses(final Class<D> destinationType) {
|
||||||
// using getReference would be more efficent, but results in very technical error messages
|
if (destinationType == null) {
|
||||||
final var entity = em.find(entityClass, subEntityUuid);
|
return new Field[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stream.concat(
|
||||||
|
stream(destinationType.getDeclaredFields()),
|
||||||
|
stream(getDeclaredFieldsIncludingSuperClasses(destinationType.getSuperclass())))
|
||||||
|
.toArray(Field[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <E> E fetchEntity(final String propertyName, final Class<E> entityClass, final Object subEntityUuid) {
|
||||||
|
final var entity = em.getReference(entityClass, subEntityUuid);
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
throw new ValidationException("Unable to find " + DisplayName.of(entityClass) + " by uuid: " + subEntityUuid);
|
throw new ValidationException(
|
||||||
|
"Unable to find " + DisplayName.of(entityClass) +
|
||||||
|
" by " + propertyName + ": " + subEntityUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <S, T> T map(final S source, final Class<T> targetClass, final BiConsumer<S, T> postMapper) {
|
public <S, T> T map(final S source, final Class<T> targetClass, final BiConsumer<S, T> postMapper) {
|
||||||
@ -86,4 +104,13 @@ public class Mapper extends ModelMapper {
|
|||||||
postMapper.accept(source, target);
|
postMapper.accept(source, target);
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <S, T> T map(final String namePrefix, final S source, final Class<T> targetClass, final BiConsumer<S, T> postMapper) {
|
||||||
|
if (source == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final var target = map(source, targetClass);
|
||||||
|
postMapper.accept(source, target);
|
||||||
|
return target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.mapper;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
public class MapperConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public Mapper modelMapper() {
|
|
||||||
return new Mapper();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,14 @@
|
|||||||
|
package net.hostsharing.hsadminng.mapper;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A nicer API for ModelMapper in standard mode.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class StandardMapper extends Mapper {
|
||||||
|
|
||||||
|
public StandardMapper() {
|
||||||
|
getConfiguration().setAmbiguityIgnored(true);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.hostsharing.hsadminng.mapper;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import static org.modelmapper.convention.MatchingStrategies.STRICT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A nicer API for ModelMapper in strict mode.
|
||||||
|
*
|
||||||
|
* <p>This makes sure that resource.whateverUuid does not accidentally get mapped to entity.uuid,
|
||||||
|
* if resource.uuid does not exist.</p>
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class StrictMapper extends Mapper {
|
||||||
|
|
||||||
|
public StrictMapper() {
|
||||||
|
getConfiguration().setMatchingStrategy(STRICT);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package net.hostsharing.hsadminng.persistence;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.errors.DisplayAs.DisplayName;
|
||||||
|
import net.hostsharing.hsadminng.rbac.object.BaseEntity;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.validation.ValidationException;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EntityExistsValidator {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EntityManagerWrapper em;
|
||||||
|
|
||||||
|
public <T extends BaseEntity<T>> void validateEntityExists(final String property, final T entitySkeleton) {
|
||||||
|
final var foundEntity = em.find(entityClass(entitySkeleton), entitySkeleton.getUuid());
|
||||||
|
if ( foundEntity == null) {
|
||||||
|
throw new ValidationException("Unable to find " + DisplayName.of(entitySkeleton) + " by " + property + ": " + entitySkeleton.getUuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends BaseEntity<T>> Class<?> entityClass(final T entityOrProxy) {
|
||||||
|
final var entityClass = entityClass(entityOrProxy.getClass());
|
||||||
|
if (entityClass == null) {
|
||||||
|
throw new IllegalArgumentException("@Entity not found in superclass hierarchy of " + entityOrProxy.getClass());
|
||||||
|
}
|
||||||
|
return entityClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> entityClass(final Class<?> entityOrProxyClass) {
|
||||||
|
return entityOrProxyClass.isAnnotationPresent(Entity.class)
|
||||||
|
? entityOrProxyClass
|
||||||
|
: entityOrProxyClass.getSuperclass() == null
|
||||||
|
? null
|
||||||
|
: entityClass(entityOrProxyClass.getSuperclass());
|
||||||
|
}
|
||||||
|
}
|
@ -89,7 +89,7 @@ public class InsertTriggerGenerator {
|
|||||||
with("superRoleRef", toRoleDescriptor(g.getSuperRoleDef(), "row")));
|
with("superRoleRef", toRoleDescriptor(g.getSuperRoleDef(), "row")));
|
||||||
} else {
|
} else {
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
-- Granting INSERT INTO hs_hosting_asset permissions to specified role of pre-existing hs_hosting_asset rows slipped,
|
-- Granting INSERT INTO hs_hosting.asset permissions to specified role of pre-existing hs_hosting.asset rows slipped,
|
||||||
-- because there cannot yet be any pre-existing rows in the same table yet.
|
-- because there cannot yet be any pre-existing rows in the same table yet.
|
||||||
""",
|
""",
|
||||||
with("rawSuperTable", g.getSuperRoleDef().getEntityAlias().getRawTableNameWithSchema()),
|
with("rawSuperTable", g.getSuperRoleDef().getEntityAlias().getRawTableNameWithSchema()),
|
||||||
@ -100,7 +100,7 @@ public class InsertTriggerGenerator {
|
|||||||
/**
|
/**
|
||||||
Grants ${rawSubTable} INSERT permission to specified role of new ${rawSuperTable} rows.
|
Grants ${rawSubTable} INSERT permission to specified role of new ${rawSuperTable} rows.
|
||||||
*/
|
*/
|
||||||
create or replace function ${rawSuperTableSchemaName}new_${rawSubTableShortName}_grants_insert_to_${rawSuperTableShortName}_tf()
|
create or replace function ${rawSubTableSchemaPrefix}${rawSubTableShortName}_grants_insert_to_${rawSuperTableShortName}_tf()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $$
|
strict as $$
|
||||||
@ -113,11 +113,11 @@ public class InsertTriggerGenerator {
|
|||||||
return NEW;
|
return NEW;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
|
-- ..._z_... is to put it at the end of after insert triggers, to make sure the roles exist
|
||||||
create trigger z_new_${rawSubTable}_grants_after_insert_tg
|
create trigger ${rawSubTableName}_z_grants_after_insert_tg
|
||||||
after insert on ${rawSuperTableWithSchema}
|
after insert on ${rawSuperTableWithSchema}
|
||||||
for each row
|
for each row
|
||||||
execute procedure ${rawSuperTableSchemaName}new_${rawSubTableShortName}_grants_insert_to_${rawSuperTableShortName}_tf();
|
execute procedure ${rawSubTableSchemaPrefix}${rawSubTableShortName}_grants_insert_to_${rawSuperTableShortName}_tf();
|
||||||
""",
|
""",
|
||||||
with("ifConditionThen", g.getSuperRoleDef().getEntityAlias().isCaseDependent()
|
with("ifConditionThen", g.getSuperRoleDef().getEntityAlias().isCaseDependent()
|
||||||
// TODO.impl: .type needs to be dynamically generated
|
// TODO.impl: .type needs to be dynamically generated
|
||||||
@ -130,8 +130,9 @@ public class InsertTriggerGenerator {
|
|||||||
with("rawSuperTableWithSchema", g.getSuperRoleDef().getEntityAlias().getRawTableNameWithSchema()),
|
with("rawSuperTableWithSchema", g.getSuperRoleDef().getEntityAlias().getRawTableNameWithSchema()),
|
||||||
with("rawSuperTableShortName", g.getSuperRoleDef().getEntityAlias().getRawTableShortName()),
|
with("rawSuperTableShortName", g.getSuperRoleDef().getEntityAlias().getRawTableShortName()),
|
||||||
with("rawSuperTable", g.getSuperRoleDef().getEntityAlias().getRawTableName()),
|
with("rawSuperTable", g.getSuperRoleDef().getEntityAlias().getRawTableName()),
|
||||||
with("rawSuperTableSchemaName", g.getSuperRoleDef().getEntityAlias().getRawTableSchemaPrefix()),
|
|
||||||
with("rawSubTable", g.getPermDef().getEntityAlias().getRawTableNameWithSchema()),
|
with("rawSubTable", g.getPermDef().getEntityAlias().getRawTableNameWithSchema()),
|
||||||
|
with("rawSubTableSchemaPrefix", g.getPermDef().getEntityAlias().getRawTableSchemaPrefix()),
|
||||||
|
with("rawSubTableName", g.getPermDef().getEntityAlias().getRawTableName()),
|
||||||
with("rawSubTableShortName", g.getPermDef().getEntityAlias().getRawTableShortName()));
|
with("rawSubTableShortName", g.getPermDef().getEntityAlias().getRawTableShortName()));
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -154,15 +155,16 @@ public class InsertTriggerGenerator {
|
|||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
begin
|
begin
|
||||||
raise exception '[403] insert into ${rawSubTable} values(%) not allowed regardless of current subject, no insert permissions granted at all', NEW;
|
raise exception '[403] insert into ${rawSubTableWithSchema} values(%) not allowed regardless of current subject, no insert permissions granted at all', NEW;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
create trigger ${rawSubTable}_insert_permission_check_tg
|
create trigger ${rawSubTable}_insert_permission_check_tg
|
||||||
before insert on ${rawSubTable}
|
before insert on ${rawSubTable}
|
||||||
for each row
|
for each row
|
||||||
execute procedure ${rawSubTable}_insert_permission_missing_tf();
|
execute procedure ${rawSubTableWithSchema}_insert_permission_missing_tf();
|
||||||
""",
|
""",
|
||||||
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()));
|
with("rawSubTableWithSchema", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()),
|
||||||
|
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
|
||||||
|
|
||||||
plPgSql.writeLn("--//");
|
plPgSql.writeLn("--//");
|
||||||
}
|
}
|
||||||
@ -183,7 +185,7 @@ public class InsertTriggerGenerator {
|
|||||||
private void generateInsertPermissionsCheckHeader(final StringWriter plPgSql) {
|
private void generateInsertPermissionsCheckHeader(final StringWriter plPgSql) {
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset InsertTriggerGenerator:${rawSubTable}-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
|
--changeset InsertTriggerGenerator:${liquibaseTagPrefix}-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -196,6 +198,7 @@ public class InsertTriggerGenerator {
|
|||||||
superObjectUuid uuid;
|
superObjectUuid uuid;
|
||||||
begin
|
begin
|
||||||
""",
|
""",
|
||||||
|
with("liquibaseTagPrefix", liquibaseTagPrefix),
|
||||||
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()));
|
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()));
|
||||||
plPgSql.chopEmptyLines();
|
plPgSql.chopEmptyLines();
|
||||||
}
|
}
|
||||||
@ -210,7 +213,7 @@ public class InsertTriggerGenerator {
|
|||||||
if (g.getSuperRoleDef().isGlobal(GUEST)) {
|
if (g.getSuperRoleDef().isGlobal(GUEST)) {
|
||||||
plPgSql.writeLn(
|
plPgSql.writeLn(
|
||||||
"""
|
"""
|
||||||
-- check INSERT INSERT permission for rbac.global anyone
|
-- check INSERT permission for rbac.global anyone
|
||||||
if ${caseCondition}true then
|
if ${caseCondition}true then
|
||||||
return NEW;
|
return NEW;
|
||||||
end if;
|
end if;
|
||||||
@ -219,7 +222,7 @@ public class InsertTriggerGenerator {
|
|||||||
} else if (g.getSuperRoleDef().isGlobal(ADMIN)) {
|
} else if (g.getSuperRoleDef().isGlobal(ADMIN)) {
|
||||||
plPgSql.writeLn(
|
plPgSql.writeLn(
|
||||||
"""
|
"""
|
||||||
-- check INSERT INSERT if rbac.global ADMIN
|
-- check INSERT permission if rbac.global ADMIN
|
||||||
if ${caseCondition}rbac.isGlobalAdmin() then
|
if ${caseCondition}rbac.isGlobalAdmin() then
|
||||||
return NEW;
|
return NEW;
|
||||||
end if;
|
end if;
|
||||||
@ -258,17 +261,18 @@ public class InsertTriggerGenerator {
|
|||||||
private void generateInsertPermissionsChecksFooter(final StringWriter plPgSql) {
|
private void generateInsertPermissionsChecksFooter(final StringWriter plPgSql) {
|
||||||
plPgSql.writeLn();
|
plPgSql.writeLn();
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
raise exception '[403] insert into ${rawSubTable} values(%) not allowed for current subjects % (%)',
|
raise exception '[403] insert into ${rawSubTableWithSchema} values(%) not allowed for current subjects % (%)',
|
||||||
NEW, base.currentSubjects(), rbac.currentSubjectOrAssumedRolesUuids();
|
NEW, base.currentSubjects(), rbac.currentSubjectOrAssumedRolesUuids();
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
create trigger ${rawSubTable}_insert_permission_check_tg
|
create trigger ${rawSubTable}_insert_permission_check_tg
|
||||||
before insert on ${rawSubTable}
|
before insert on ${rawSubTableWithSchema}
|
||||||
for each row
|
for each row
|
||||||
execute procedure ${rawSubTable}_insert_permission_check_tf();
|
execute procedure ${rawSubTableWithSchema}_insert_permission_check_tf();
|
||||||
--//
|
--//
|
||||||
""",
|
""",
|
||||||
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()));
|
with("rawSubTableWithSchema", rbacDef.getRootEntityAlias().getRawTableNameWithSchema()),
|
||||||
|
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toStringList(final Set<RbacView.CaseDef> cases) {
|
private String toStringList(final Set<RbacView.CaseDef> cases) {
|
||||||
@ -321,7 +325,7 @@ public class InsertTriggerGenerator {
|
|||||||
|
|
||||||
|
|
||||||
private String toRoleDescriptor(final RbacView.RbacRoleDefinition roleDef, final String ref) {
|
private String toRoleDescriptor(final RbacView.RbacRoleDefinition roleDef, final String ref) {
|
||||||
final var functionName = toVar(roleDef);
|
final var functionName = roleDef.descriptorFunctionName();
|
||||||
if (roleDef.getEntityAlias().isGlobal()) {
|
if (roleDef.getEntityAlias().isGlobal()) {
|
||||||
return functionName + "()";
|
return functionName + "()";
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,11 @@ public class RbacRoleDescriptorsGenerator {
|
|||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset RbacRoleDescriptorsGenerator:${liquibaseTagPrefix}-rbac-ROLE-DESCRIPTORS endDelimiter:--//
|
--changeset RbacRoleDescriptorsGenerator:${liquibaseTagPrefix}-rbac-ROLE-DESCRIPTORS endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
call rbac.generateRbacRoleDescriptors('${simpleEntityVarName}', '${rawTableName}');
|
call rbac.generateRbacRoleDescriptors('${rawTableName}');
|
||||||
--//
|
--//
|
||||||
|
|
||||||
""",
|
""",
|
||||||
with("liquibaseTagPrefix", liquibaseTagPrefix),
|
with("liquibaseTagPrefix", liquibaseTagPrefix),
|
||||||
with("simpleEntityVarName", simpleEntityVarName),
|
|
||||||
with("rawTableName", rawTableName));
|
with("rawTableName", rawTableName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,6 @@ import jakarta.persistence.Version;
|
|||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@ -30,6 +29,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinit
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.Part.AUTO_FETCH;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.Part.AUTO_FETCH;
|
||||||
import static org.apache.commons.collections4.SetUtils.hashSet;
|
import static org.apache.commons.collections4.SetUtils.hashSet;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ -90,11 +90,11 @@ public class RbacView {
|
|||||||
* @param <E>
|
* @param <E>
|
||||||
* a JPA entity class extending RbacObject
|
* a JPA entity class extending RbacObject
|
||||||
*/
|
*/
|
||||||
public static <E extends BaseEntity> RbacView rbacViewFor(final String alias, final Class<E> entityClass) {
|
public static <E extends BaseEntity<?>> RbacView rbacViewFor(final String alias, final Class<E> entityClass) {
|
||||||
return new RbacView(alias, entityClass);
|
return new RbacView(alias, entityClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
RbacView(final String alias, final Class<? extends BaseEntity> entityClass) {
|
RbacView(final String alias, final Class<? extends BaseEntity<?>> entityClass) {
|
||||||
rootEntityAlias = new EntityAlias(alias, entityClass);
|
rootEntityAlias = new EntityAlias(alias, entityClass);
|
||||||
entityAliases.put(alias, rootEntityAlias);
|
entityAliases.put(alias, rootEntityAlias);
|
||||||
new RbacSubjectReference(CREATOR);
|
new RbacSubjectReference(CREATOR);
|
||||||
@ -121,7 +121,7 @@ public class RbacView {
|
|||||||
* <p>An identity view is a view which maps an objectUuid to an idName.
|
* <p>An identity view is a view which maps an objectUuid to an idName.
|
||||||
* The idName should be a human-readable representation of the row, but as short as possible.
|
* The idName should be a human-readable representation of the row, but as short as possible.
|
||||||
* The idName must only consist of letters (A-Z, a-z), digits (0-9), dash (-), dot (.) and unserscore '_'.
|
* The idName must only consist of letters (A-Z, a-z), digits (0-9), dash (-), dot (.) and unserscore '_'.
|
||||||
* It's used to create the object-specific-role-names like test_customer#abc:ADMIN - here 'abc' is the idName.
|
* It's used to create the object-specific-role-names like rbactest.customer#abc:ADMIN - here 'abc' is the idName.
|
||||||
* The idName not necessarily unique in a table, but it should be avoided.
|
* The idName not necessarily unique in a table, but it should be avoided.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
@ -287,9 +287,9 @@ public class RbacView {
|
|||||||
* @param <EC>
|
* @param <EC>
|
||||||
* a JPA entity class extending RbacObject
|
* a JPA entity class extending RbacObject
|
||||||
*/
|
*/
|
||||||
public <EC extends BaseEntity> RbacView importRootEntityAliasProxy(
|
public <EC extends BaseEntity<?>> RbacView importRootEntityAliasProxy(
|
||||||
final String aliasName,
|
final String aliasName,
|
||||||
final Class<? extends BaseEntity> entityClass,
|
final Class<? extends BaseEntity<?>> entityClass,
|
||||||
final ColumnValue forCase,
|
final ColumnValue forCase,
|
||||||
final SQL fetchSql,
|
final SQL fetchSql,
|
||||||
final Column dependsOnColum) {
|
final Column dependsOnColum) {
|
||||||
@ -313,7 +313,7 @@ public class RbacView {
|
|||||||
* a JPA entity class extending RbacObject
|
* a JPA entity class extending RbacObject
|
||||||
*/
|
*/
|
||||||
public RbacView importSubEntityAlias(
|
public RbacView importSubEntityAlias(
|
||||||
final String aliasName, final Class<? extends BaseEntity> entityClass,
|
final String aliasName, final Class<? extends BaseEntity<?>> entityClass,
|
||||||
final SQL fetchSql, final Column dependsOnColum) {
|
final SQL fetchSql, final Column dependsOnColum) {
|
||||||
importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, true, NOT_NULL);
|
importEntityAliasImpl(aliasName, entityClass, usingDefaultCase(), fetchSql, dependsOnColum, true, NOT_NULL);
|
||||||
return this;
|
return this;
|
||||||
@ -350,14 +350,14 @@ public class RbacView {
|
|||||||
* a JPA entity class extending RbacObject
|
* a JPA entity class extending RbacObject
|
||||||
*/
|
*/
|
||||||
public RbacView importEntityAlias(
|
public RbacView importEntityAlias(
|
||||||
final String aliasName, final Class<? extends BaseEntity> entityClass, final ColumnValue usingCase,
|
final String aliasName, final Class<? extends BaseEntity<?>> entityClass, final ColumnValue usingCase,
|
||||||
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
|
final Column dependsOnColum, final SQL fetchSql, final Nullable nullable) {
|
||||||
importEntityAliasImpl(aliasName, entityClass, usingCase, fetchSql, dependsOnColum, false, nullable);
|
importEntityAliasImpl(aliasName, entityClass, usingCase, fetchSql, dependsOnColum, false, nullable);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private EntityAlias importEntityAliasImpl(
|
private EntityAlias importEntityAliasImpl(
|
||||||
final String aliasName, final Class<? extends BaseEntity> entityClass, final ColumnValue usingCase,
|
final String aliasName, final Class<? extends BaseEntity<?>> entityClass, final ColumnValue usingCase,
|
||||||
final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity, final Nullable nullable) {
|
final SQL fetchSql, final Column dependsOnColum, boolean asSubEntity, final Nullable nullable) {
|
||||||
|
|
||||||
final var entityAlias = ofNullable(entityAliases.get(aliasName))
|
final var entityAlias = ofNullable(entityAliases.get(aliasName))
|
||||||
@ -831,6 +831,10 @@ public class RbacView {
|
|||||||
public boolean isGlobal(final Role role) {
|
public boolean isGlobal(final Role role) {
|
||||||
return entityAlias.isGlobal() && this.role == role;
|
return entityAlias.isGlobal() && this.role == role;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String descriptorFunctionName() {
|
||||||
|
return entityAlias.getRawTableNameWithSchema() + "_" + capitalize(role.name());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RbacSubjectReference findUserRef(final RbacSubjectReference.UserRole userRole) {
|
public RbacSubjectReference findUserRef(final RbacSubjectReference.UserRole userRole) {
|
||||||
@ -911,13 +915,13 @@ public class RbacView {
|
|||||||
return distinctGrantDef;
|
return distinctGrantDef;
|
||||||
}
|
}
|
||||||
|
|
||||||
record EntityAlias(String aliasName, Class<? extends BaseEntity> entityClass, ColumnValue usingCase, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) {
|
record EntityAlias(String aliasName, Class<? extends BaseEntity<?>> entityClass, ColumnValue usingCase, SQL fetchSql, Column dependsOnColum, boolean isSubEntity, Nullable nullable) {
|
||||||
|
|
||||||
public EntityAlias(final String aliasName) {
|
public EntityAlias(final String aliasName) {
|
||||||
this(aliasName, null, null, null, null, false, null);
|
this(aliasName, null, null, null, null, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntityAlias(final String aliasName, final Class<? extends BaseEntity> entityClass) {
|
public EntityAlias(final String aliasName, final Class<? extends BaseEntity<?>> entityClass) {
|
||||||
this(aliasName, entityClass, null, null, null, false, null);
|
this(aliasName, entityClass, null, null, null, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -964,7 +968,7 @@ public class RbacView {
|
|||||||
if ( aliasName.equals("rbac.global")) {
|
if ( aliasName.equals("rbac.global")) {
|
||||||
return "rbac.global"; // TODO: maybe we should introduce a GlobalEntity class?
|
return "rbac.global"; // TODO: maybe we should introduce a GlobalEntity class?
|
||||||
}
|
}
|
||||||
return withoutRvSuffix(entityClass.getAnnotation(Table.class).name());
|
return qualifiedRealTableName(entityClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getRawTableSchemaPrefix() {
|
String getRawTableSchemaPrefix() {
|
||||||
@ -983,14 +987,12 @@ public class RbacView {
|
|||||||
|
|
||||||
String getRawTableShortName() {
|
String getRawTableShortName() {
|
||||||
// TODO.impl: some combined function and trigger names are too long
|
// TODO.impl: some combined function and trigger names are too long
|
||||||
// maybe we should shorten the table name e.g. hs_office_coopsharestransaction -> hsof.coopsharetx
|
// maybe we should shorten the table name e.g. hs_office.coopsharetx -> hsof.coopsharetx
|
||||||
// this is just a workaround:
|
// this is just a workaround:
|
||||||
return getRawTableName()
|
return getRawTableName()
|
||||||
.replace("hs_office_", "hsof_")
|
.replace("hs_office.", "hsof.")
|
||||||
.replace("hs_booking_", "hsbk_")
|
.replace("hs_booking.", "hsbk_")
|
||||||
.replace("hs_hosting_", "hsho_")
|
.replace("hs_hosting.", "hsho_");
|
||||||
.replace("coopsharestransaction", "coopsharetx")
|
|
||||||
.replace("coopassetstransaction", "coopassettx");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String dependsOnColumName() {
|
String dependsOnColumName() {
|
||||||
@ -1010,8 +1012,12 @@ public class RbacView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String withoutRvSuffix(final String tableName) {
|
public static String qualifiedRealTableName(final Class<? extends BaseEntity<?>> entityClass) {
|
||||||
return tableName.substring(0, tableName.length() - "_rv".length());
|
final var tableAnnotation = entityClass.getAnnotation(Table.class);
|
||||||
|
final var schema = tableAnnotation.schema();
|
||||||
|
final var tableName = tableAnnotation.name();
|
||||||
|
final var realTableName = tableName.substring(0, tableName.length() - "_rv".length());
|
||||||
|
return (schema.isEmpty() ? "" : (schema + ".")) + realTableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Role {
|
public enum Role {
|
||||||
@ -1270,13 +1276,14 @@ public class RbacView {
|
|||||||
|
|
||||||
public static Set<Class<? extends BaseEntity>> findRbacEntityClasses(String packageName) {
|
public static Set<Class<? extends BaseEntity>> findRbacEntityClasses(String packageName) {
|
||||||
final var reflections = new Reflections(packageName, TypeAnnotationsScanner.class);
|
final var reflections = new Reflections(packageName, TypeAnnotationsScanner.class);
|
||||||
return reflections.getTypesAnnotatedWith(Entity.class).stream()
|
final Set<Class<? extends BaseEntity>> rbacEntityClasses = reflections.getTypesAnnotatedWith(Entity.class).stream()
|
||||||
.filter(c -> stream(c.getInterfaces()).anyMatch(i -> i== BaseEntity.class))
|
.filter(BaseEntity.class::isAssignableFrom)
|
||||||
.filter(c -> stream(c.getDeclaredMethods())
|
.filter(c -> stream(c.getDeclaredMethods())
|
||||||
.anyMatch(m -> m.getName().equals("rbac") && Modifier.isStatic(m.getModifiers()))
|
.anyMatch(m -> m.getName().equals("rbac") && isStatic(m.getModifiers()))
|
||||||
)
|
)
|
||||||
.map(RbacView::castToSubclassOfBaseEntity)
|
.map(RbacView::castToSubclassOfBaseEntity)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
return rbacEntityClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -17,7 +17,7 @@ public class RbacViewPostgresGenerator {
|
|||||||
|
|
||||||
public RbacViewPostgresGenerator(final RbacView forRbacDef) {
|
public RbacViewPostgresGenerator(final RbacView forRbacDef) {
|
||||||
rbacDef = forRbacDef;
|
rbacDef = forRbacDef;
|
||||||
liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableNameWithSchema().replace("_", "-");
|
liqibaseTagPrefix = rbacDef.getRootEntityAlias().getRawTableNameWithSchema().replace("_", "-").replace(".", "-");
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
--liquibase formatted sql
|
--liquibase formatted sql
|
||||||
-- This code generated was by ${generator}, do not amend manually.
|
-- This code generated was by ${generator}, do not amend manually.
|
||||||
|
@ -20,7 +20,6 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacGrantDefinit
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
||||||
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
import static net.hostsharing.hsadminng.rbac.generator.StringWriter.with;
|
||||||
import static org.apache.commons.lang3.StringUtils.capitalize;
|
import static org.apache.commons.lang3.StringUtils.capitalize;
|
||||||
import static org.apache.commons.lang3.StringUtils.uncapitalize;
|
|
||||||
|
|
||||||
class RolesGrantsAndPermissionsGenerator {
|
class RolesGrantsAndPermissionsGenerator {
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
private final String liquibaseTagPrefix;
|
private final String liquibaseTagPrefix;
|
||||||
private final String simpleEntityName;
|
private final String simpleEntityName;
|
||||||
private final String simpleEntityVarName;
|
private final String simpleEntityVarName;
|
||||||
private final String rawTableName;
|
private final String qualifiedRawTableName;
|
||||||
|
|
||||||
RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
RolesGrantsAndPermissionsGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
|
||||||
this.rbacDef = rbacDef;
|
this.rbacDef = rbacDef;
|
||||||
@ -40,7 +39,7 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
|
|
||||||
simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
|
||||||
simpleEntityName = capitalize(simpleEntityVarName);
|
simpleEntityName = capitalize(simpleEntityVarName);
|
||||||
rawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
qualifiedRawTableName = rbacDef.getRootEntityAlias().getRawTableNameWithSchema();
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateTo(final StringWriter plPgSql) {
|
void generateTo(final StringWriter plPgSql) {
|
||||||
@ -66,13 +65,12 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create or replace procedure buildRbacSystemFor${simpleEntityName}(
|
create or replace procedure ${rawTableQualifiedName}_build_rbac_system(
|
||||||
NEW ${rawTableName}
|
NEW ${rawTableQualifiedName}
|
||||||
)
|
)
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
"""
|
"""
|
||||||
.replace("${simpleEntityName}", simpleEntityName)
|
.replace("${rawTableQualifiedName}", qualifiedRawTableName));
|
||||||
.replace("${rawTableName}", rawTableName));
|
|
||||||
|
|
||||||
plPgSql.writeLn("declare");
|
plPgSql.writeLn("declare");
|
||||||
plPgSql.indented(() -> {
|
plPgSql.indented(() -> {
|
||||||
@ -106,21 +104,21 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create or replace procedure updateRbacRulesFor${simpleEntityName}(
|
create or replace procedure ${rawTableQualifiedName}_update_rbac_system(
|
||||||
OLD ${rawTableName},
|
OLD ${rawTableQualifiedName},
|
||||||
NEW ${rawTableName}
|
NEW ${rawTableQualifiedName}
|
||||||
)
|
)
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
begin
|
begin
|
||||||
|
|
||||||
if ${updateConditions} then
|
if ${updateConditions} then
|
||||||
delete from rbac.grants g where g.grantedbytriggerof = OLD.uuid;
|
delete from rbac.grants g where g.grantedbytriggerof = OLD.uuid;
|
||||||
call buildRbacSystemFor${simpleEntityName}(NEW);
|
call ${rawTableQualifiedName}_build_rbac_system(NEW);
|
||||||
end if;
|
end if;
|
||||||
end; $$;
|
end; $$;
|
||||||
""",
|
""",
|
||||||
with("simpleEntityName", simpleEntityName),
|
with("simpleEntityName", simpleEntityName),
|
||||||
with("rawTableName", rawTableName),
|
with("rawTableQualifiedName", qualifiedRawTableName),
|
||||||
with("updateConditions", updateConditions));
|
with("updateConditions", updateConditions));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,16 +128,15 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create or replace procedure updateRbacRulesFor${simpleEntityName}(
|
create or replace procedure ${rawTableQualifiedName}_update_rbac_system(
|
||||||
OLD ${rawTableName},
|
OLD ${rawTableQualifiedName},
|
||||||
NEW ${rawTableName}
|
NEW ${rawTableQualifiedName}
|
||||||
)
|
)
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
|
|
||||||
declare
|
declare
|
||||||
"""
|
""",
|
||||||
.replace("${simpleEntityName}", simpleEntityName)
|
with("rawTableQualifiedName", qualifiedRawTableName));
|
||||||
.replace("${rawTableName}", rawTableName));
|
|
||||||
|
|
||||||
plPgSql.chopEmptyLines();
|
plPgSql.chopEmptyLines();
|
||||||
plPgSql.indented(() -> {
|
plPgSql.indented(() -> {
|
||||||
@ -364,11 +361,10 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
System.out.println("null");
|
System.out.println("null");
|
||||||
}
|
}
|
||||||
if (roleDef.getEntityAlias().isGlobal()) {
|
if (roleDef.getEntityAlias().isGlobal()) {
|
||||||
return "rbac.globalAdmin()";
|
return "rbac.global_ADMIN()";
|
||||||
}
|
}
|
||||||
final String entityRefVar = entityRefVar(rootRefVar, roleDef.getEntityAlias());
|
final String entityRefVar = entityRefVar(rootRefVar, roleDef.getEntityAlias());
|
||||||
return roleDef.getEntityAlias().simpleName() + capitalize(roleDef.getRole().name())
|
return roleDef.descriptorFunctionName() + "(" + entityRefVar + ")";
|
||||||
+ "(" + entityRefVar + ")";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String entityRefVar(
|
private String entityRefVar(
|
||||||
@ -391,8 +387,8 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
plPgSql.writeLn();
|
plPgSql.writeLn();
|
||||||
plPgSql.writeLn("perform rbac.defineRoleWithGrants(");
|
plPgSql.writeLn("perform rbac.defineRoleWithGrants(");
|
||||||
plPgSql.indented(() -> {
|
plPgSql.indented(() -> {
|
||||||
plPgSql.writeLn("${simpleVarName)${roleSuffix}(NEW),"
|
plPgSql.writeLn("${qualifiedRawTableName)_${roleSuffix}(NEW),"
|
||||||
.replace("${simpleVarName)", simpleEntityVarName)
|
.replace("${qualifiedRawTableName)", qualifiedRawTableName)
|
||||||
.replace("${roleSuffix}", capitalize(role.name())));
|
.replace("${roleSuffix}", capitalize(role.name())));
|
||||||
|
|
||||||
generatePermissionsForRole(plPgSql, role);
|
generatePermissionsForRole(plPgSql, role);
|
||||||
@ -514,25 +510,25 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
|
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
/*
|
/*
|
||||||
AFTER INSERT TRIGGER to create the role+grant structure for a new ${rawTableName} row.
|
AFTER INSERT TRIGGER to create the role+grant structure for a new ${rawTableQualifiedName} row.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create or replace function insertTriggerFor${simpleEntityName}_tf()
|
create or replace function ${rawTableQualifiedName}_build_rbac_system_after_insert_tf()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $$
|
strict as $$
|
||||||
begin
|
begin
|
||||||
call buildRbacSystemFor${simpleEntityName}(NEW);
|
call ${rawTableQualifiedName}_build_rbac_system(NEW);
|
||||||
return NEW;
|
return NEW;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
create trigger insertTriggerFor${simpleEntityName}_tg
|
create trigger build_rbac_system_after_insert_tg
|
||||||
after insert on ${rawTableName}
|
after insert on ${rawTableQualifiedName}
|
||||||
for each row
|
for each row
|
||||||
execute procedure insertTriggerFor${simpleEntityName}_tf();
|
execute procedure ${rawTableQualifiedName}_build_rbac_system_after_insert_tf();
|
||||||
"""
|
"""
|
||||||
.replace("${simpleEntityName}", simpleEntityName)
|
.replace("${schemaPrefix}", schemaPrefix(qualifiedRawTableName))
|
||||||
.replace("${rawTableName}", rawTableName)
|
.replace("${rawTableQualifiedName}", qualifiedRawTableName)
|
||||||
);
|
);
|
||||||
|
|
||||||
generateFooter(plPgSql);
|
generateFooter(plPgSql);
|
||||||
@ -549,30 +545,35 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
|
|
||||||
plPgSql.writeLn("""
|
plPgSql.writeLn("""
|
||||||
/*
|
/*
|
||||||
AFTER INSERT TRIGGER to re-wire the grant structure for a new ${rawTableName} row.
|
AFTER UPDATE TRIGGER to re-wire the grant structure for a new ${rawTableQualifiedName} row.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create or replace function updateTriggerFor${simpleEntityName}_tf()
|
create or replace function ${rawTableQualifiedName}_update_rbac_system_after_update_tf()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $$
|
strict as $$
|
||||||
begin
|
begin
|
||||||
call updateRbacRulesFor${simpleEntityName}(OLD, NEW);
|
call ${rawTableQualifiedName}_update_rbac_system(OLD, NEW);
|
||||||
return NEW;
|
return NEW;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
create trigger updateTriggerFor${simpleEntityName}_tg
|
create trigger update_rbac_system_after_update_tg
|
||||||
after update on ${rawTableName}
|
after update on ${rawTableQualifiedName}
|
||||||
for each row
|
for each row
|
||||||
execute procedure updateTriggerFor${simpleEntityName}_tf();
|
execute procedure ${rawTableQualifiedName}_update_rbac_system_after_update_tf();
|
||||||
"""
|
"""
|
||||||
.replace("${simpleEntityName}", simpleEntityName)
|
.replace("${rawTableQualifiedName}", qualifiedRawTableName)
|
||||||
.replace("${rawTableName}", rawTableName)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
generateFooter(plPgSql);
|
generateFooter(plPgSql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String schemaPrefix(final String qualifiedIdentifier) {
|
||||||
|
return qualifiedIdentifier.contains(".")
|
||||||
|
? qualifiedIdentifier.split("\\.")[0] + "."
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
private static void generateFooter(final StringWriter plPgSql) {
|
private static void generateFooter(final StringWriter plPgSql) {
|
||||||
plPgSql.writeLn("--//");
|
plPgSql.writeLn("--//");
|
||||||
plPgSql.writeLn();
|
plPgSql.writeLn();
|
||||||
@ -590,16 +591,12 @@ class RolesGrantsAndPermissionsGenerator {
|
|||||||
final RbacView.RbacRoleDefinition roleDef,
|
final RbacView.RbacRoleDefinition roleDef,
|
||||||
final boolean assumed) {
|
final boolean assumed) {
|
||||||
final var assumedArg = assumed ? "" : ", rbac.unassumed()";
|
final var assumedArg = assumed ? "" : ", rbac.unassumed()";
|
||||||
return toRoleRef(roleDef) +
|
return roleDef.descriptorFunctionName() +
|
||||||
(roleDef.getEntityAlias().isGlobal() ? ( assumed ? "()" : "(rbac.unassumed())")
|
(roleDef.getEntityAlias().isGlobal() ? ( assumed ? "()" : "(rbac.unassumed())")
|
||||||
: rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) ? ("(" + triggerRef.name() + ")")
|
: rbacDef.isRootEntityAlias(roleDef.getEntityAlias()) ? ("(" + triggerRef.name() + ")")
|
||||||
: "(" + toTriggerReference(triggerRef, roleDef.getEntityAlias()) + assumedArg + ")");
|
: "(" + toTriggerReference(triggerRef, roleDef.getEntityAlias()) + assumedArg + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String toRoleRef(final RbacView.RbacRoleDefinition roleDef) {
|
|
||||||
return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().name());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String toTriggerReference(
|
private static String toTriggerReference(
|
||||||
final PostgresTriggerReference triggerRef,
|
final PostgresTriggerReference triggerRef,
|
||||||
final RbacView.EntityAlias entityAlias) {
|
final RbacView.EntityAlias entityAlias) {
|
||||||
|
@ -19,9 +19,11 @@ public class StringWriter {
|
|||||||
writeLn();
|
writeLn();
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeLn(final String text, final VarDef... varDefs) {
|
String writeLn(final String text, final VarDef... varDefs) {
|
||||||
string.append( indented( new VarReplacer(varDefs).apply(text) ));
|
final var insertText = indented(new VarReplacer(varDefs).apply(text));
|
||||||
|
string.append(insertText);
|
||||||
writeLn();
|
writeLn();
|
||||||
|
return insertText;
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeLn() {
|
void writeLn() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.grant;
|
package net.hostsharing.hsadminng.rbac.grant;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacGrantsApi;
|
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacGrantsApi;
|
||||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacGrantResource;
|
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacGrantResource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -22,7 +22,7 @@ public class RbacGrantController implements RbacGrantsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RbacGrantRepository rbacGrantRepository;
|
private RbacGrantRepository rbacGrantRepository;
|
||||||
|
@ -79,10 +79,10 @@ public class RbacGrantsDiagramService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( !g.getDescendantIdName().startsWith("role:rbac.global")) {
|
if ( !g.getDescendantIdName().startsWith("role:rbac.global")) {
|
||||||
if (!includes.contains(TEST_ENTITIES) && g.getDescendantIdName().contains(":test_")) {
|
if (!includes.contains(TEST_ENTITIES) && g.getDescendantIdName().contains(":rbactest.")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!includes.contains(NON_TEST_ENTITIES) && !g.getDescendantIdName().contains(":test_")) {
|
if (!includes.contains(NON_TEST_ENTITIES) && !g.getDescendantIdName().contains(":rbactest.")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.role;
|
package net.hostsharing.hsadminng.rbac.role;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacRolesApi;
|
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacRolesApi;
|
||||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacRoleResource;
|
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacRoleResource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -18,7 +18,7 @@ public class RbacRoleController implements RbacRolesApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RbacRoleRepository rbacRoleRepository;
|
private RbacRoleRepository rbacRoleRepository;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.subject;
|
package net.hostsharing.hsadminng.rbac.subject;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacSubjectsApi;
|
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacSubjectsApi;
|
||||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectPermissionResource;
|
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectPermissionResource;
|
||||||
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectResource;
|
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectResource;
|
||||||
@ -21,7 +21,7 @@ public class RbacSubjectController implements RbacSubjectsApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RbacSubjectRepository rbacSubjectRepository;
|
private RbacSubjectRepository rbacSubjectRepository;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.test.cust;
|
package net.hostsharing.hsadminng.rbac.test.cust;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.test.generated.api.v1.api.TestCustomersApi;
|
import net.hostsharing.hsadminng.test.generated.api.v1.api.TestCustomersApi;
|
||||||
import net.hostsharing.hsadminng.test.generated.api.v1.model.TestCustomerResource;
|
import net.hostsharing.hsadminng.test.generated.api.v1.model.TestCustomerResource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -21,7 +21,7 @@ public class TestCustomerController implements TestCustomersApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TestCustomerRepository testCustomerRepository;
|
private TestCustomerRepository testCustomerRepository;
|
||||||
|
@ -20,7 +20,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "test_customer_rv")
|
@Table(schema = "rbactest", name = "customer_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@ -62,6 +62,6 @@ public class TestCustomerEntity implements BaseEntity<TestCustomerEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
rbac().generateWithBaseFileName("2-test/201-test-customer/2013-test-customer-rbac");
|
rbac().generateWithBaseFileName("2-rbactest/201-rbactest-customer/2013-rbactest-customer-rbac");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetc
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "test_domain_rv")
|
@Table(schema = "rbactest", name = "domain_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@ -68,6 +68,6 @@ public class TestDomainEntity implements BaseEntity<TestDomainEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
rbac().generateWithBaseFileName("2-test/203-test-domain/2033-test-domain-rbac");
|
rbac().generateWithBaseFileName("2-rbactest/203-rbactest-domain/2033-rbactest-domain-rbac");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.rbac.test.pac;
|
package net.hostsharing.hsadminng.rbac.test.pac;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
import net.hostsharing.hsadminng.mapper.StandardMapper;
|
||||||
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.test.generated.api.v1.api.TestPackagesApi;
|
import net.hostsharing.hsadminng.test.generated.api.v1.api.TestPackagesApi;
|
||||||
@ -21,7 +21,7 @@ public class TestPackageController implements TestPackagesApi {
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Mapper mapper;
|
private StandardMapper mapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TestPackageRepository testPackageRepository;
|
private TestPackageRepository testPackageRepository;
|
||||||
|
@ -22,7 +22,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.*;
|
|||||||
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "test_package_rv")
|
@Table(schema = "rbactest", name = "package_rv")
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@ -69,6 +69,6 @@ public class TestPackageEntity implements BaseEntity<TestPackageEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
rbac().generateWithBaseFileName("2-test/202-test-package/2023-test-package-rbac");
|
rbac().generateWithBaseFileName("2-rbactest/202-rbactest-package/2023-rbactest-package-rbac");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ components:
|
|||||||
- CLOUD_SERVER
|
- CLOUD_SERVER
|
||||||
- MANAGED_SERVER
|
- MANAGED_SERVER
|
||||||
- MANAGED_WEBSPACE
|
- MANAGED_WEBSPACE
|
||||||
|
- DOMAIN_SETUP
|
||||||
|
|
||||||
HsBookingItem:
|
HsBookingItem:
|
||||||
type: object
|
type: object
|
||||||
|
@ -6,15 +6,31 @@
|
|||||||
--changeset michael.hoennig:table-columns-function endDelimiter:--//
|
--changeset michael.hoennig:table-columns-function endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
create or replace function base.tableColumnNames( tableName text )
|
create or replace function base.tableColumnNames( ofTableName text )
|
||||||
returns text
|
returns text
|
||||||
stable
|
stable
|
||||||
language 'plpgsql' as $$
|
language 'plpgsql' as $$
|
||||||
declare columns text[];
|
declare
|
||||||
|
tableName text;
|
||||||
|
tableSchema text;
|
||||||
|
columns text[];
|
||||||
begin
|
begin
|
||||||
|
tableSchema := CASE
|
||||||
|
WHEN position('.' in ofTableName) > 0 THEN split_part(ofTableName, '.', 1)
|
||||||
|
ELSE 'public'
|
||||||
|
END;
|
||||||
|
|
||||||
|
tableName := CASE
|
||||||
|
WHEN position('.' in ofTableName) > 0 THEN split_part(ofTableName, '.', 2)
|
||||||
|
ELSE ofTableName
|
||||||
|
END;
|
||||||
|
|
||||||
columns := (select array(select column_name::text
|
columns := (select array(select column_name::text
|
||||||
from information_schema.columns
|
from information_schema.columns
|
||||||
where table_name = tableName));
|
where table_name = tableName
|
||||||
|
and table_schema = tableSchema));
|
||||||
|
assert cardinality(columns) > 0, 'cannot determine columns of table ' || ofTableName ||
|
||||||
|
'("' || tableSchema || '"."' || tableName || '")';
|
||||||
return array_to_string(columns, ', ');
|
return array_to_string(columns, ', ');
|
||||||
end; $$
|
end; $$
|
||||||
--//
|
--//
|
||||||
|
@ -127,6 +127,7 @@ begin
|
|||||||
end; $$;
|
end; $$;
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset michael.hoennig:context-base.ASSUMED-ROLES endDelimiter:--//
|
--changeset michael.hoennig:context-base.ASSUMED-ROLES endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
@ -167,45 +168,6 @@ begin
|
|||||||
return cleanIdentifier;
|
return cleanIdentifier;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
create or replace function base.findObjectUuidByIdName(objectTable varchar, objectIdName varchar)
|
|
||||||
returns uuid
|
|
||||||
returns null on null input
|
|
||||||
language plpgsql as $$
|
|
||||||
declare
|
|
||||||
sql varchar;
|
|
||||||
uuid uuid;
|
|
||||||
begin
|
|
||||||
objectTable := base.pureIdentifier(objectTable);
|
|
||||||
objectIdName := base.pureIdentifier(objectIdName);
|
|
||||||
sql := format('select * from %sUuidByIdName(%L);', objectTable, objectIdName);
|
|
||||||
begin
|
|
||||||
execute sql into uuid;
|
|
||||||
exception
|
|
||||||
when others then
|
|
||||||
raise exception 'function %UuidByIdName(...) not found, add identity view support for table %', objectTable, objectTable;
|
|
||||||
end;
|
|
||||||
return uuid;
|
|
||||||
end ; $$;
|
|
||||||
|
|
||||||
create or replace function base.findIdNameByObjectUuid(objectTable varchar, objectUuid uuid)
|
|
||||||
returns varchar
|
|
||||||
returns null on null input
|
|
||||||
language plpgsql as $$
|
|
||||||
declare
|
|
||||||
sql varchar;
|
|
||||||
idName varchar;
|
|
||||||
begin
|
|
||||||
objectTable := base.pureIdentifier(objectTable);
|
|
||||||
sql := format('select * from %sIdNameByUuid(%L::uuid);', objectTable, objectUuid);
|
|
||||||
begin
|
|
||||||
execute sql into idName;
|
|
||||||
exception
|
|
||||||
when others then
|
|
||||||
raise exception 'function %IdNameByUuid(...) not found, add identity view support for table %', objectTable, objectTable;
|
|
||||||
end;
|
|
||||||
return idName;
|
|
||||||
end ; $$;
|
|
||||||
|
|
||||||
create or replace function base.currentSubjects()
|
create or replace function base.currentSubjects()
|
||||||
returns varchar(1023)[]
|
returns varchar(1023)[]
|
||||||
stable -- leakproof
|
stable -- leakproof
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
--liquibase formatted sql
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset michael.hoennig:base-COMBINE-TABLE-SCHEMA-AND-NAME endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
create or replace function base.combine_table_schema_and_name(tableSchema name, tableName name)
|
||||||
|
returns text
|
||||||
|
language plpgsql as $$
|
||||||
|
begin
|
||||||
|
assert LEFT(tableSchema, 1) <> '"', 'tableSchema must not start with "';
|
||||||
|
assert LEFT(tableName, 1) <> '"', 'tableName must not start with "';
|
||||||
|
|
||||||
|
if tableSchema is null or tableSchema = 'public' or tableSchema = '' then
|
||||||
|
return tableName::text;
|
||||||
|
else
|
||||||
|
return tableSchema::text || '.' || tableName::text;
|
||||||
|
end if;
|
||||||
|
end; $$;
|
||||||
|
--//
|
@ -77,9 +77,11 @@ create or replace function base.tx_journal_trigger()
|
|||||||
declare
|
declare
|
||||||
curTask text;
|
curTask text;
|
||||||
curTxId xid8;
|
curTxId xid8;
|
||||||
|
tableSchemaAndName text;
|
||||||
begin
|
begin
|
||||||
curTask := base.currentTask();
|
curTask := base.currentTask();
|
||||||
curTxId := pg_current_xact_id();
|
curTxId := pg_current_xact_id();
|
||||||
|
tableSchemaAndName := base.combine_table_schema_and_name(tg_table_schema, tg_table_name);
|
||||||
|
|
||||||
insert
|
insert
|
||||||
into base.tx_context (txId, txTimestamp, currentSubject, assumedRoles, currentTask, currentRequest)
|
into base.tx_context (txId, txTimestamp, currentSubject, assumedRoles, currentTask, currentRequest)
|
||||||
@ -90,20 +92,20 @@ begin
|
|||||||
case tg_op
|
case tg_op
|
||||||
when 'INSERT' then insert
|
when 'INSERT' then insert
|
||||||
into base.tx_journal
|
into base.tx_journal
|
||||||
values (curTxId,
|
values (curTxId, tableSchemaAndName,
|
||||||
tg_table_name, new.uuid, tg_op::base.tx_operation,
|
new.uuid, tg_op::base.tx_operation,
|
||||||
to_jsonb(new));
|
to_jsonb(new));
|
||||||
when 'UPDATE' then insert
|
when 'UPDATE' then insert
|
||||||
into base.tx_journal
|
into base.tx_journal
|
||||||
values (curTxId,
|
values (curTxId, tableSchemaAndName,
|
||||||
tg_table_name, old.uuid, tg_op::base.tx_operation,
|
old.uuid, tg_op::base.tx_operation,
|
||||||
base.jsonb_changes_delta(to_jsonb(old), to_jsonb(new)));
|
base.jsonb_changes_delta(to_jsonb(old), to_jsonb(new)));
|
||||||
when 'DELETE' then insert
|
when 'DELETE' then insert
|
||||||
into base.tx_journal
|
into base.tx_journal
|
||||||
values (curTxId,
|
values (curTxId,tableSchemaAndName,
|
||||||
tg_table_name, old.uuid, 'DELETE'::base.tx_operation,
|
old.uuid, 'DELETE'::base.tx_operation,
|
||||||
null::jsonb);
|
null::jsonb);
|
||||||
else raise exception 'Trigger op % not supported for %.', tg_op, tg_table_name;
|
else raise exception 'Trigger op % not supported for %.', tg_op, tableSchemaAndName;
|
||||||
end case;
|
end case;
|
||||||
return null;
|
return null;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
@ -63,7 +63,6 @@ begin
|
|||||||
if (currentSubject is null or currentSubject = '') then
|
if (currentSubject is null or currentSubject = '') then
|
||||||
raise exception 'hsadminng.currentSubject must be defined, please use "SET LOCAL ...;"';
|
raise exception 'hsadminng.currentSubject must be defined, please use "SET LOCAL ...;"';
|
||||||
end if;
|
end if;
|
||||||
raise notice 'currentSubject: %', currentSubject;
|
|
||||||
|
|
||||||
-- determine task
|
-- determine task
|
||||||
currentTask = current_setting('hsadminng.currentTask');
|
currentTask = current_setting('hsadminng.currentTask');
|
||||||
@ -81,8 +80,9 @@ begin
|
|||||||
"alive" := false;
|
"alive" := false;
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
sql := format('INSERT INTO %3$I_ex VALUES (DEFAULT, pg_current_xact_id(), %1$L, %2$L, $1.*)', TG_OP, alive, TG_TABLE_NAME);
|
sql := format('INSERT INTO %3$s_ex VALUES (DEFAULT, pg_current_xact_id(), %1$L, %2$L, $1.*)',
|
||||||
raise notice 'sql: %', sql;
|
TG_OP, alive, base.combine_table_schema_and_name(tg_table_schema, tg_table_name)::name);
|
||||||
|
-- raise exception 'generated-SQL: %', sql;
|
||||||
execute sql using "row";
|
execute sql using "row";
|
||||||
|
|
||||||
return "row";
|
return "row";
|
||||||
@ -117,12 +117,12 @@ begin
|
|||||||
' EXCLUDING CONSTRAINTS' ||
|
' EXCLUDING CONSTRAINTS' ||
|
||||||
' EXCLUDING STATISTICS' ||
|
' EXCLUDING STATISTICS' ||
|
||||||
')';
|
')';
|
||||||
raise notice 'sql: %', createHistTableSql;
|
-- raise notice 'sql: %', createHistTableSql;
|
||||||
execute createHistTableSql;
|
execute createHistTableSql;
|
||||||
|
|
||||||
-- create the historical view
|
-- create the historical view
|
||||||
viewName = quote_ident(format('%s_hv', baseTable));
|
viewName = baseTable || '_hv';
|
||||||
exVersionsTable = quote_ident(format('%s_ex', baseTable));
|
exVersionsTable = baseTable || '_ex';
|
||||||
baseCols = (select string_agg(quote_ident(column_name), ', ')
|
baseCols = (select string_agg(quote_ident(column_name), ', ')
|
||||||
from information_schema.columns
|
from information_schema.columns
|
||||||
where table_schema = 'public'
|
where table_schema = 'public'
|
||||||
@ -146,15 +146,14 @@ begin
|
|||||||
' )' ||
|
' )' ||
|
||||||
')',
|
')',
|
||||||
viewName, baseCols, exVersionsTable
|
viewName, baseCols, exVersionsTable
|
||||||
);
|
);
|
||||||
raise notice 'sql: %', createViewSQL;
|
-- raise notice 'generated-sql: %', createViewSQL;
|
||||||
execute createViewSQL;
|
execute createViewSQL;
|
||||||
|
|
||||||
-- "-9-" to put the trigger execution after any alphabetically lesser tx-triggers
|
-- "-9-" to put the trigger execution after any alphabetically lesser tx-triggers
|
||||||
createTriggerSQL = 'CREATE TRIGGER tx_9_historicize_tg' ||
|
createTriggerSQL = 'CREATE TRIGGER tx_9_historicize_tg' ||
|
||||||
' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable ||
|
' AFTER INSERT OR DELETE OR UPDATE ON ' || baseTable ||
|
||||||
' FOR EACH ROW EXECUTE PROCEDURE base.tx_historicize_tf()';
|
' FOR EACH ROW EXECUTE PROCEDURE base.tx_historicize_tf()';
|
||||||
raise notice 'sql: %', createTriggerSQL;
|
|
||||||
execute createTriggerSQL;
|
execute createTriggerSQL;
|
||||||
|
|
||||||
end; $$;
|
end; $$;
|
||||||
|
@ -3,9 +3,7 @@
|
|||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset michael.hoennig:rbac-base-REFERENCE endDelimiter:--//
|
--changeset michael.hoennig:rbac-base-REFERENCE endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
/*
|
|
||||||
|
|
||||||
*/
|
|
||||||
create type rbac.ReferenceType as enum ('rbac.subject', 'rbac.role', 'rbac.permission');
|
create type rbac.ReferenceType as enum ('rbac.subject', 'rbac.role', 'rbac.permission');
|
||||||
|
|
||||||
create table rbac.reference
|
create table rbac.reference
|
||||||
@ -120,18 +118,20 @@ create or replace function rbac.insert_related_object()
|
|||||||
strict as $$
|
strict as $$
|
||||||
declare
|
declare
|
||||||
objectUuid uuid;
|
objectUuid uuid;
|
||||||
|
tableSchemaAndName text;
|
||||||
begin
|
begin
|
||||||
|
tableSchemaAndName := base.combine_table_schema_and_name(TG_TABLE_SCHEMA, TG_TABLE_NAME);
|
||||||
if TG_OP = 'INSERT' then
|
if TG_OP = 'INSERT' then
|
||||||
if NEW.uuid is null then
|
if NEW.uuid is null then
|
||||||
insert
|
insert
|
||||||
into rbac.object (objectTable)
|
into rbac.object (objectTable)
|
||||||
values (TG_TABLE_NAME)
|
values (tableSchemaAndName)
|
||||||
returning uuid into objectUuid;
|
returning uuid into objectUuid;
|
||||||
NEW.uuid = objectUuid;
|
NEW.uuid = objectUuid;
|
||||||
else
|
else
|
||||||
insert
|
insert
|
||||||
into rbac.object (uuid, objectTable)
|
into rbac.object (uuid, objectTable)
|
||||||
values (NEW.uuid, TG_TABLE_NAME)
|
values (NEW.uuid, tableSchemaAndName)
|
||||||
returning uuid into objectUuid;
|
returning uuid into objectUuid;
|
||||||
end if;
|
end if;
|
||||||
return NEW;
|
return NEW;
|
||||||
@ -233,6 +233,50 @@ $$;
|
|||||||
--//
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset michael.hoennig:rbac-base-IDNAME-FUNCTIONS endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
create or replace function rbac.findObjectUuidByIdName(objectTable varchar, objectIdName varchar)
|
||||||
|
returns uuid
|
||||||
|
returns null on null input
|
||||||
|
language plpgsql as $$
|
||||||
|
declare
|
||||||
|
sql varchar;
|
||||||
|
uuid uuid;
|
||||||
|
begin
|
||||||
|
objectTable := base.pureIdentifier(objectTable);
|
||||||
|
objectIdName := base.pureIdentifier(objectIdName);
|
||||||
|
sql := format('select * from %s_uuid_by_id_name(%L);', objectTable, objectIdName);
|
||||||
|
begin
|
||||||
|
execute sql into uuid;
|
||||||
|
exception
|
||||||
|
when others then
|
||||||
|
raise exception 'function %_uuid_by_id_name(...) not found, add identity view support for table %', objectTable, objectTable;
|
||||||
|
end;
|
||||||
|
return uuid;
|
||||||
|
end ; $$;
|
||||||
|
|
||||||
|
create or replace function rbac.findIdNameByObjectUuid(objectTable varchar, objectUuid uuid)
|
||||||
|
returns varchar
|
||||||
|
returns null on null input
|
||||||
|
language plpgsql as $$
|
||||||
|
declare
|
||||||
|
sql varchar;
|
||||||
|
idName varchar;
|
||||||
|
begin
|
||||||
|
objectTable := base.pureIdentifier(objectTable);
|
||||||
|
sql := format('select * from %s_id_name_by_uuid(%L::uuid);', objectTable, objectUuid);
|
||||||
|
begin
|
||||||
|
execute sql into idName;
|
||||||
|
exception
|
||||||
|
when others then
|
||||||
|
raise exception 'function %_id_name_by_uuid(...) not found, add identity view support for table %', objectTable, objectTable;
|
||||||
|
end;
|
||||||
|
return idName;
|
||||||
|
end ; $$;
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset michael.hoennig:rbac-base-ROLE-FUNCTIONS endDelimiter:--//
|
--changeset michael.hoennig:rbac-base-ROLE-FUNCTIONS endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
@ -262,7 +306,7 @@ begin
|
|||||||
objectTableFromRoleIdName = split_part(roleParts, '#', 1);
|
objectTableFromRoleIdName = split_part(roleParts, '#', 1);
|
||||||
objectNameFromRoleIdName = split_part(roleParts, '#', 2);
|
objectNameFromRoleIdName = split_part(roleParts, '#', 2);
|
||||||
roleTypeFromRoleIdName = split_part(roleParts, '#', 3);
|
roleTypeFromRoleIdName = split_part(roleParts, '#', 3);
|
||||||
objectUuidOfRole = base.findObjectUuidByIdName(objectTableFromRoleIdName, objectNameFromRoleIdName);
|
objectUuidOfRole = rbac.findObjectUuidByIdName(objectTableFromRoleIdName, objectNameFromRoleIdName);
|
||||||
|
|
||||||
select uuid
|
select uuid
|
||||||
from rbac.role
|
from rbac.role
|
||||||
@ -384,7 +428,7 @@ create index on rbac.permission (objectUuid, op);
|
|||||||
create index on rbac.permission (opTableName, op);
|
create index on rbac.permission (opTableName, op);
|
||||||
|
|
||||||
ALTER TABLE rbac.permission
|
ALTER TABLE rbac.permission
|
||||||
ADD CONSTRAINT RbacPermission_uc UNIQUE NULLS NOT DISTINCT (objectUuid, op, opTableName);
|
ADD CONSTRAINT unique_including_null_values UNIQUE NULLS NOT DISTINCT (objectUuid, op, opTableName);
|
||||||
|
|
||||||
call base.create_journal('rbac.permission');
|
call base.create_journal('rbac.permission');
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ begin
|
|||||||
objectNameToAssume = split_part(roleNameParts, '#', 2);
|
objectNameToAssume = split_part(roleNameParts, '#', 2);
|
||||||
roleTypeToAssume = split_part(roleNameParts, '#', 3);
|
roleTypeToAssume = split_part(roleNameParts, '#', 3);
|
||||||
|
|
||||||
objectUuidToAssume = base.findObjectUuidByIdName(objectTableToAssume, objectNameToAssume);
|
objectUuidToAssume = rbac.findObjectUuidByIdName(objectTableToAssume, objectNameToAssume);
|
||||||
if objectUuidToAssume is null then
|
if objectUuidToAssume is null then
|
||||||
raise exception '[401] object % cannot be found in table % (from roleNameParts=%)', objectNameToAssume, objectTableToAssume, roleNameParts;
|
raise exception '[401] object % cannot be found in table % (from roleNameParts=%)', objectNameToAssume, objectTableToAssume, roleNameParts;
|
||||||
end if;
|
end if;
|
||||||
|
@ -13,7 +13,7 @@ select (objectTable || '#' || objectIdName || ':' || roleType) as roleIdName, *
|
|||||||
-- @formatter:off
|
-- @formatter:off
|
||||||
from (
|
from (
|
||||||
select r.*,
|
select r.*,
|
||||||
o.objectTable, base.findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
|
o.objectTable, rbac.findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
|
||||||
from rbac.role as r
|
from rbac.role as r
|
||||||
join rbac.object as o on o.uuid = r.objectuuid
|
join rbac.object as o on o.uuid = r.objectuuid
|
||||||
) as unordered
|
) as unordered
|
||||||
@ -34,7 +34,7 @@ select *
|
|||||||
-- @formatter:off
|
-- @formatter:off
|
||||||
from (
|
from (
|
||||||
select r.*, o.objectTable,
|
select r.*, o.objectTable,
|
||||||
base.findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
|
rbac.findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
|
||||||
from rbac.role as r
|
from rbac.role as r
|
||||||
join rbac.object as o on o.uuid = r.objectuuid
|
join rbac.object as o on o.uuid = r.objectuuid
|
||||||
where rbac.isGranted(rbac.currentSubjectOrAssumedRolesUuids(), r.uuid)
|
where rbac.isGranted(rbac.currentSubjectOrAssumedRolesUuids(), r.uuid)
|
||||||
@ -57,7 +57,7 @@ create or replace view rbac.grants_ev as
|
|||||||
-- @formatter:off
|
-- @formatter:off
|
||||||
select x.grantUuid as uuid,
|
select x.grantUuid as uuid,
|
||||||
x.grantedByTriggerOf as grantedByTriggerOf,
|
x.grantedByTriggerOf as grantedByTriggerOf,
|
||||||
go.objectTable || '#' || base.findIdNameByObjectUuid(go.objectTable, go.uuid) || ':' || r.roletype as grantedByRoleIdName,
|
go.objectTable || '#' || rbac.findIdNameByObjectUuid(go.objectTable, go.uuid) || ':' || r.roletype as grantedByRoleIdName,
|
||||||
x.ascendingIdName as ascendantIdName,
|
x.ascendingIdName as ascendantIdName,
|
||||||
x.descendingIdName as descendantIdName,
|
x.descendingIdName as descendantIdName,
|
||||||
x.grantedByRoleUuid,
|
x.grantedByRoleUuid,
|
||||||
@ -72,15 +72,15 @@ create or replace view rbac.grants_ev as
|
|||||||
|
|
||||||
coalesce(
|
coalesce(
|
||||||
'user:' || au.name,
|
'user:' || au.name,
|
||||||
'role:' || aro.objectTable || '#' || base.findIdNameByObjectUuid(aro.objectTable, aro.uuid) || ':' || ar.roletype
|
'role:' || aro.objectTable || '#' || rbac.findIdNameByObjectUuid(aro.objectTable, aro.uuid) || ':' || ar.roletype
|
||||||
) as ascendingIdName,
|
) as ascendingIdName,
|
||||||
aro.objectTable, aro.uuid,
|
aro.objectTable, aro.uuid,
|
||||||
( case
|
( case
|
||||||
when dro is not null
|
when dro is not null
|
||||||
then ('role:' || dro.objectTable || '#' || base.findIdNameByObjectUuid(dro.objectTable, dro.uuid) || ':' || dr.roletype)
|
then ('role:' || dro.objectTable || '#' || rbac.findIdNameByObjectUuid(dro.objectTable, dro.uuid) || ':' || dr.roletype)
|
||||||
when dp.op = 'INSERT'
|
when dp.op = 'INSERT'
|
||||||
then 'perm:' || dpo.objecttable || '#' || base.findIdNameByObjectUuid(dpo.objectTable, dpo.uuid) || ':' || dp.op || '>' || dp.opTableName
|
then 'perm:' || dpo.objecttable || '#' || rbac.findIdNameByObjectUuid(dpo.objectTable, dpo.uuid) || ':' || dp.op || '>' || dp.opTableName
|
||||||
else 'perm:' || dpo.objecttable || '#' || base.findIdNameByObjectUuid(dpo.objectTable, dpo.uuid) || ':' || dp.op
|
else 'perm:' || dpo.objecttable || '#' || rbac.findIdNameByObjectUuid(dpo.objectTable, dpo.uuid) || ':' || dp.op
|
||||||
end
|
end
|
||||||
) as descendingIdName,
|
) as descendingIdName,
|
||||||
dro.objectTable, dro.uuid,
|
dro.objectTable, dro.uuid,
|
||||||
@ -114,14 +114,14 @@ create or replace view rbac.grants_ev as
|
|||||||
*/
|
*/
|
||||||
create or replace view rbac.grants_rv as
|
create or replace view rbac.grants_rv as
|
||||||
-- @formatter:off
|
-- @formatter:off
|
||||||
select o.objectTable || '#' || base.findIdNameByObjectUuid(o.objectTable, o.uuid) || ':' || r.roletype as grantedByRoleIdName,
|
select o.objectTable || '#' || rbac.findIdNameByObjectUuid(o.objectTable, o.uuid) || ':' || r.roletype as grantedByRoleIdName,
|
||||||
g.objectTable || '#' || g.objectIdName || ':' || g.roletype as grantedRoleIdName, g.userName, g.assumed,
|
g.objectTable || '#' || g.objectIdName || ':' || g.roletype as grantedRoleIdName, g.userName, g.assumed,
|
||||||
g.grantedByRoleUuid, g.descendantUuid as grantedRoleUuid, g.ascendantUuid as subjectUuid,
|
g.grantedByRoleUuid, g.descendantUuid as grantedRoleUuid, g.ascendantUuid as subjectUuid,
|
||||||
g.objectTable, g.objectUuid, g.objectIdName, g.roleType as grantedRoleType
|
g.objectTable, g.objectUuid, g.objectIdName, g.roleType as grantedRoleType
|
||||||
from (
|
from (
|
||||||
select g.grantedbyroleuuid, g.ascendantuuid, g.descendantuuid, g.assumed,
|
select g.grantedbyroleuuid, g.ascendantuuid, g.descendantuuid, g.assumed,
|
||||||
u.name as userName, o.objecttable, r.objectuuid, r.roletype,
|
u.name as userName, o.objecttable, r.objectuuid, r.roletype,
|
||||||
base.findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
|
rbac.findIdNameByObjectUuid(o.objectTable, o.uuid) as objectIdName
|
||||||
from rbac.grants as g
|
from rbac.grants as g
|
||||||
join rbac.role as r on r.uuid = g.descendantUuid
|
join rbac.role as r on r.uuid = g.descendantUuid
|
||||||
join rbac.object o on o.uuid = r.objectuuid
|
join rbac.object o on o.uuid = r.objectuuid
|
||||||
@ -363,10 +363,10 @@ begin
|
|||||||
xp.permissionObjectTable, xp.permissionObjectIdName, xp.permissionObjectUuid
|
xp.permissionObjectTable, xp.permissionObjectIdName, xp.permissionObjectUuid
|
||||||
from (select
|
from (select
|
||||||
r.uuid as roleUuid, r.roletype, ro.objectTable as roleObjectTable,
|
r.uuid as roleUuid, r.roletype, ro.objectTable as roleObjectTable,
|
||||||
base.findIdNameByObjectUuid(ro.objectTable, ro.uuid) as roleObjectIdName,
|
rbac.findIdNameByObjectUuid(ro.objectTable, ro.uuid) as roleObjectIdName,
|
||||||
p.uuid as permissionUuid, p.op, p.opTableName,
|
p.uuid as permissionUuid, p.op, p.opTableName,
|
||||||
po.objecttable as permissionObjectTable,
|
po.objecttable as permissionObjectTable,
|
||||||
base.findIdNameByObjectUuid(po.objectTable, po.uuid) as permissionObjectIdName,
|
rbac.findIdNameByObjectUuid(po.objectTable, po.uuid) as permissionObjectIdName,
|
||||||
po.uuid as permissionObjectUuid
|
po.uuid as permissionObjectUuid
|
||||||
from rbac.queryPermissionsGrantedToSubjectId( targetSubjectUuid) as p
|
from rbac.queryPermissionsGrantedToSubjectId( targetSubjectUuid) as p
|
||||||
join rbac.grants as g on g.descendantUuid = p.uuid
|
join rbac.grants as g on g.descendantUuid = p.uuid
|
||||||
|
@ -8,26 +8,40 @@
|
|||||||
create or replace procedure rbac.generateRelatedRbacObject(targetTable varchar)
|
create or replace procedure rbac.generateRelatedRbacObject(targetTable varchar)
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
declare
|
declare
|
||||||
|
targetTableName text;
|
||||||
|
targetSchemaPrefix text;
|
||||||
createInsertTriggerSQL text;
|
createInsertTriggerSQL text;
|
||||||
createDeleteTriggerSQL text;
|
createDeleteTriggerSQL text;
|
||||||
begin
|
begin
|
||||||
|
if POSITION('.' IN targetTable) > 0 then
|
||||||
|
targetSchemaPrefix := SPLIT_PART(targetTable, '.', 1) || '.';
|
||||||
|
targetTableName := SPLIT_PART(targetTable, '.', 2);
|
||||||
|
else
|
||||||
|
targetSchemaPrefix := '';
|
||||||
|
targetTableName := targetTable;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if targetSchemaPrefix = '' and targetTableName = 'customer' then
|
||||||
|
raise exception 'missing targetShemaPrefix: %', targetTable;
|
||||||
|
end if;
|
||||||
|
|
||||||
createInsertTriggerSQL = format($sql$
|
createInsertTriggerSQL = format($sql$
|
||||||
create trigger createRbacObjectFor_%s_Trigger
|
create trigger createRbacObjectFor_%s_insert_tg_1058_25
|
||||||
before insert on %s
|
before insert on %s%s
|
||||||
for each row
|
for each row
|
||||||
execute procedure rbac.insert_related_object();
|
execute procedure rbac.insert_related_object();
|
||||||
$sql$, targetTable, targetTable);
|
$sql$, targetTableName, targetSchemaPrefix, targetTableName);
|
||||||
execute createInsertTriggerSQL;
|
execute createInsertTriggerSQL;
|
||||||
|
|
||||||
createDeleteTriggerSQL = format($sql$
|
createDeleteTriggerSQL = format($sql$
|
||||||
create trigger delete_related_rbac_rules_for_%s_tg
|
create trigger createRbacObjectFor_%s_delete_tg_1058_35
|
||||||
after delete
|
after delete on %s%s
|
||||||
on %s
|
|
||||||
for each row
|
for each row
|
||||||
execute procedure rbac.delete_related_rbac_rules_tf();
|
execute procedure rbac.delete_related_rbac_rules_tf();
|
||||||
$sql$, targetTable, targetTable);
|
$sql$, targetTableName, targetSchemaPrefix, targetTableName);
|
||||||
execute createDeleteTriggerSQL;
|
execute createDeleteTriggerSQL;
|
||||||
end; $$;
|
end;
|
||||||
|
$$;
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
|
||||||
@ -35,62 +49,62 @@ end; $$;
|
|||||||
--changeset michael.hoennig:rbac-generators-ROLE-DESCRIPTORS endDelimiter:--//
|
--changeset michael.hoennig:rbac-generators-ROLE-DESCRIPTORS endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
create procedure rbac.generateRbacRoleDescriptors(prefix text, targetTable text)
|
create procedure rbac.generateRbacRoleDescriptors(targetTable text)
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
declare
|
declare
|
||||||
sql text;
|
sql text;
|
||||||
begin
|
begin
|
||||||
sql = format($sql$
|
sql = format($sql$
|
||||||
create or replace function %1$sOwner(entity %2$s, assumed boolean = true)
|
create or replace function %1$s_OWNER(entity %1$s, assumed boolean = true)
|
||||||
returns rbac.RoleDescriptor
|
returns rbac.RoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return rbac.roleDescriptorOf('%2$s', entity.uuid, 'OWNER', assumed);
|
return rbac.roleDescriptorOf('%1$s', entity.uuid, 'OWNER', assumed);
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
create or replace function %1$sAdmin(entity %2$s, assumed boolean = true)
|
create or replace function %1$s_ADMIN(entity %1$s, assumed boolean = true)
|
||||||
returns rbac.RoleDescriptor
|
returns rbac.RoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return rbac.roleDescriptorOf('%2$s', entity.uuid, 'ADMIN', assumed);
|
return rbac.roleDescriptorOf('%1$s', entity.uuid, 'ADMIN', assumed);
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
create or replace function %1$sAgent(entity %2$s, assumed boolean = true)
|
create or replace function %1$s_AGENT(entity %1$s, assumed boolean = true)
|
||||||
returns rbac.RoleDescriptor
|
returns rbac.RoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return rbac.roleDescriptorOf('%2$s', entity.uuid, 'AGENT', assumed);
|
return rbac.roleDescriptorOf('%1$s', entity.uuid, 'AGENT', assumed);
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
create or replace function %1$sTenant(entity %2$s, assumed boolean = true)
|
create or replace function %1$s_TENANT(entity %1$s, assumed boolean = true)
|
||||||
returns rbac.RoleDescriptor
|
returns rbac.RoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return rbac.roleDescriptorOf('%2$s', entity.uuid, 'TENANT', assumed);
|
return rbac.roleDescriptorOf('%1$s', entity.uuid, 'TENANT', assumed);
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
-- TODO: remove guest role
|
-- TODO: remove guest role
|
||||||
create or replace function %1$sGuest(entity %2$s, assumed boolean = true)
|
create or replace function %1$s_GUEST(entity %1$s, assumed boolean = true)
|
||||||
returns rbac.RoleDescriptor
|
returns rbac.RoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return rbac.roleDescriptorOf('%2$s', entity.uuid, 'GUEST', assumed);
|
return rbac.roleDescriptorOf('%1$s', entity.uuid, 'GUEST', assumed);
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
create or replace function %1$sReferrer(entity %2$s)
|
create or replace function %1$s_REFERRER(entity %1$s)
|
||||||
returns rbac.RoleDescriptor
|
returns rbac.RoleDescriptor
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
begin
|
begin
|
||||||
return rbac.roleDescriptorOf('%2$s', entity.uuid, 'REFERRER');
|
return rbac.roleDescriptorOf('%1$s', entity.uuid, 'REFERRER');
|
||||||
end; $f$;
|
end; $f$;
|
||||||
|
|
||||||
$sql$, prefix, targetTable);
|
$sql$, targetTable);
|
||||||
execute sql;
|
execute sql;
|
||||||
end; $$;
|
end; $$;
|
||||||
--//
|
--//
|
||||||
@ -116,7 +130,7 @@ begin
|
|||||||
|
|
||||||
-- creates a function which maps an idName to the objectUuid
|
-- creates a function which maps an idName to the objectUuid
|
||||||
sql = format($sql$
|
sql = format($sql$
|
||||||
create or replace function %1$sUuidByIdName(givenIdName varchar)
|
create or replace function %1$s_uuid_by_id_name(givenIdName varchar)
|
||||||
returns uuid
|
returns uuid
|
||||||
language plpgsql as $f$
|
language plpgsql as $f$
|
||||||
declare
|
declare
|
||||||
@ -130,7 +144,7 @@ begin
|
|||||||
|
|
||||||
-- creates a function which maps an objectUuid to the related idName
|
-- creates a function which maps an objectUuid to the related idName
|
||||||
sql = format($sql$
|
sql = format($sql$
|
||||||
create or replace function %1$sIdNameByUuid(givenUuid uuid)
|
create or replace function %1$s_id_name_by_uuid(givenUuid uuid)
|
||||||
returns varchar
|
returns varchar
|
||||||
language sql
|
language sql
|
||||||
strict as $f$
|
strict as $f$
|
||||||
@ -176,7 +190,7 @@ begin
|
|||||||
*/
|
*/
|
||||||
sql := format($sql$
|
sql := format($sql$
|
||||||
create or replace view %1$s_rv as
|
create or replace view %1$s_rv as
|
||||||
with accessible_%1$s_uuids as (
|
with accessible_uuids as (
|
||||||
with recursive
|
with recursive
|
||||||
recursive_grants as
|
recursive_grants as
|
||||||
(select distinct rbac.grants.descendantuuid,
|
(select distinct rbac.grants.descendantuuid,
|
||||||
@ -209,7 +223,7 @@ begin
|
|||||||
)
|
)
|
||||||
select target.*
|
select target.*
|
||||||
from %1$s as target
|
from %1$s as target
|
||||||
where target.uuid in (select * from accessible_%1$s_uuids)
|
where target.uuid in (select * from accessible_uuids)
|
||||||
order by %2$s;
|
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};
|
||||||
@ -219,9 +233,9 @@ begin
|
|||||||
/**
|
/**
|
||||||
Instead of insert trigger function for the restricted view.
|
Instead of insert trigger function for the restricted view.
|
||||||
*/
|
*/
|
||||||
newColumns := 'new.' || replace(columnNames, ',', ', new.');
|
newColumns := 'new.' || replace(columnNames, ', ', ', new.');
|
||||||
sql := format($sql$
|
sql := format($sql$
|
||||||
create or replace function %1$sInsert()
|
create function %1$s_instead_of_insert_tf()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql as $f$
|
language plpgsql as $f$
|
||||||
declare
|
declare
|
||||||
@ -240,11 +254,11 @@ begin
|
|||||||
Creates an instead of insert trigger for the restricted view.
|
Creates an instead of insert trigger for the restricted view.
|
||||||
*/
|
*/
|
||||||
sql := format($sql$
|
sql := format($sql$
|
||||||
create trigger %1$sInsert_tg
|
create trigger instead_of_insert_tg
|
||||||
instead of insert
|
instead of insert
|
||||||
on %1$s_rv
|
on %1$s_rv
|
||||||
for each row
|
for each row
|
||||||
execute function %1$sInsert();
|
execute function %1$s_instead_of_insert_tf();
|
||||||
$sql$, targetTable);
|
$sql$, targetTable);
|
||||||
execute sql;
|
execute sql;
|
||||||
|
|
||||||
@ -252,7 +266,7 @@ begin
|
|||||||
Instead of delete trigger function for the restricted view.
|
Instead of delete trigger function for the restricted view.
|
||||||
*/
|
*/
|
||||||
sql := format($sql$
|
sql := format($sql$
|
||||||
create or replace function %1$sDelete()
|
create function %1$s_instead_of_delete_tf()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql as $f$
|
language plpgsql as $f$
|
||||||
begin
|
begin
|
||||||
@ -269,11 +283,11 @@ begin
|
|||||||
Creates an instead of delete trigger for the restricted view.
|
Creates an instead of delete trigger for the restricted view.
|
||||||
*/
|
*/
|
||||||
sql := format($sql$
|
sql := format($sql$
|
||||||
create trigger %1$sDelete_tg
|
create trigger instead_of_delete_tg
|
||||||
instead of delete
|
instead of delete
|
||||||
on %1$s_rv
|
on %1$s_rv
|
||||||
for each row
|
for each row
|
||||||
execute function %1$sDelete();
|
execute function %1$s_instead_of_delete_tf();
|
||||||
$sql$, targetTable);
|
$sql$, targetTable);
|
||||||
execute sql;
|
execute sql;
|
||||||
|
|
||||||
@ -283,7 +297,7 @@ begin
|
|||||||
*/
|
*/
|
||||||
if columnUpdates is not null then
|
if columnUpdates is not null then
|
||||||
sql := format($sql$
|
sql := format($sql$
|
||||||
create or replace function %1$sUpdate()
|
create function %1$s_instead_of_update_tf()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql as $f$
|
language plpgsql as $f$
|
||||||
begin
|
begin
|
||||||
@ -302,11 +316,11 @@ begin
|
|||||||
Creates an instead of delete trigger for the restricted view.
|
Creates an instead of delete trigger for the restricted view.
|
||||||
*/
|
*/
|
||||||
sql = format($sql$
|
sql = format($sql$
|
||||||
create trigger %1$sUpdate_tg
|
create trigger instead_of_update_tg
|
||||||
instead of update
|
instead of update
|
||||||
on %1$s_rv
|
on %1$s_rv
|
||||||
for each row
|
for each row
|
||||||
execute function %1$sUpdate();
|
execute function %1$s_instead_of_update_tf();
|
||||||
$sql$, targetTable);
|
$sql$, targetTable);
|
||||||
execute sql;
|
execute sql;
|
||||||
end if;
|
end if;
|
||||||
|
@ -30,7 +30,7 @@ create or replace function rbac.isGlobalAdmin()
|
|||||||
returns boolean
|
returns boolean
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
begin
|
begin
|
||||||
return rbac.isGranted(rbac.currentSubjectOrAssumedRolesUuids(), rbac.findRoleId(rbac.globalAdmin()));
|
return rbac.isGranted(rbac.currentSubjectOrAssumedRolesUuids(), rbac.findRoleId(rbac.global_ADMIN()));
|
||||||
end; $$;
|
end; $$;
|
||||||
--//
|
--//
|
||||||
|
|
||||||
@ -66,21 +66,21 @@ grant all privileges on rbac.global_iv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNA
|
|||||||
/*
|
/*
|
||||||
Returns the objectUuid for a given identifying name (in this case the idName).
|
Returns the objectUuid for a given identifying name (in this case the idName).
|
||||||
*/
|
*/
|
||||||
create or replace function rbac.globalUuidByIdName(idName varchar)
|
create or replace function rbac.global_uuid_by_id_name(idName varchar)
|
||||||
returns uuid
|
returns uuid
|
||||||
language sql
|
language sql
|
||||||
strict as $$
|
strict as $$
|
||||||
select uuid from rbac.global_iv iv where iv.idName = globalUuidByIdName.idName;
|
select uuid from rbac.global_iv iv where iv.idName = global_uuid_by_id_name.idName;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Returns the identifying name for a given objectUuid (in this case the idName).
|
Returns the identifying name for a given objectUuid (in this case the idName).
|
||||||
*/
|
*/
|
||||||
create or replace function rbac.globalIdNameByUuid(uuid uuid)
|
create or replace function rbac.global_id_name_by_uuid(uuid uuid)
|
||||||
returns varchar
|
returns varchar
|
||||||
language sql
|
language sql
|
||||||
strict as $$
|
strict as $$
|
||||||
select idName from rbac.global_iv iv where iv.uuid = globalIdNameByUuid.uuid;
|
select idName from rbac.global_iv iv where iv.uuid = global_id_name_by_uuid.uuid;
|
||||||
$$;
|
$$;
|
||||||
--//
|
--//
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ commit;
|
|||||||
/*
|
/*
|
||||||
A rbac.Global administrator role.
|
A rbac.Global administrator role.
|
||||||
*/
|
*/
|
||||||
create or replace function rbac.globalAdmin(assumed boolean = true)
|
create or replace function rbac.global_ADMIN(assumed boolean = true)
|
||||||
returns rbac.RoleDescriptor
|
returns rbac.RoleDescriptor
|
||||||
returns null on null input
|
returns null on null input
|
||||||
stable -- leakproof
|
stable -- leakproof
|
||||||
@ -119,7 +119,7 @@ $$;
|
|||||||
|
|
||||||
begin transaction;
|
begin transaction;
|
||||||
call base.defineContext('creating role:rbac.global#global:ADMIN', null, null, null);
|
call base.defineContext('creating role:rbac.global#global:ADMIN', null, null, null);
|
||||||
select rbac.createRole(rbac.globalAdmin());
|
select rbac.createRole(rbac.global_ADMIN());
|
||||||
commit;
|
commit;
|
||||||
--//
|
--//
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ do language plpgsql $$
|
|||||||
begin
|
begin
|
||||||
call base.defineContext('creating fake test-realm admin users', null, null, null);
|
call base.defineContext('creating fake test-realm admin users', null, null, null);
|
||||||
|
|
||||||
admins = rbac.findRoleId(rbac.globalAdmin());
|
admins = rbac.findRoleId(rbac.global_ADMIN());
|
||||||
call rbac.grantRoleToSubjectUnchecked(admins, admins, rbac.create_subject('superuser-alex@hostsharing.net'));
|
call rbac.grantRoleToSubjectUnchecked(admins, admins, rbac.create_subject('superuser-alex@hostsharing.net'));
|
||||||
call rbac.grantRoleToSubjectUnchecked(admins, admins, rbac.create_subject('superuser-fran@hostsharing.net'));
|
call rbac.grantRoleToSubjectUnchecked(admins, admins, rbac.create_subject('superuser-fran@hostsharing.net'));
|
||||||
perform rbac.create_subject('selfregistered-user-drew@hostsharing.org');
|
perform rbac.create_subject('selfregistered-user-drew@hostsharing.org');
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
--liquibase formatted sql
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset michael.hoennig:rbactest-SCHEMA endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
CREATE SCHEMA rbactest; -- just 'test' does not work, databasechangelog gets emptied or deleted
|
||||||
|
--//
|
@ -4,7 +4,7 @@
|
|||||||
--changeset michael.hoennig:test-customer-MAIN-TABLE endDelimiter:--//
|
--changeset michael.hoennig:test-customer-MAIN-TABLE endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
create table if not exists test_customer
|
create table if not exists rbactest.customer
|
||||||
(
|
(
|
||||||
uuid uuid unique references rbac.object (uuid),
|
uuid uuid unique references rbac.object (uuid),
|
||||||
version int not null default 0,
|
version int not null default 0,
|
@ -3,29 +3,29 @@
|
|||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset RbacObjectGenerator:test-customer-rbac-OBJECT endDelimiter:--//
|
--changeset RbacObjectGenerator:rbactest-customer-rbac-OBJECT endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
call rbac.generateRelatedRbacObject('test_customer');
|
call rbac.generateRelatedRbacObject('rbactest.customer');
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset RbacRoleDescriptorsGenerator:test-customer-rbac-ROLE-DESCRIPTORS endDelimiter:--//
|
--changeset RbacRoleDescriptorsGenerator:rbactest-customer-rbac-ROLE-DESCRIPTORS endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
call rbac.generateRbacRoleDescriptors('testCustomer', 'test_customer');
|
call rbac.generateRbacRoleDescriptors('rbactest.customer');
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset RolesGrantsAndPermissionsGenerator:test-customer-rbac-insert-trigger endDelimiter:--//
|
--changeset RolesGrantsAndPermissionsGenerator:rbactest-customer-rbac-insert-trigger endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create or replace procedure buildRbacSystemForTestCustomer(
|
create or replace procedure rbactest.customer_build_rbac_system(
|
||||||
NEW test_customer
|
NEW rbactest.customer
|
||||||
)
|
)
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
|
|
||||||
@ -35,129 +35,129 @@ begin
|
|||||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||||
|
|
||||||
perform rbac.defineRoleWithGrants(
|
perform rbac.defineRoleWithGrants(
|
||||||
testCustomerOWNER(NEW),
|
rbactest.customer_OWNER(NEW),
|
||||||
permissions => array['DELETE'],
|
permissions => array['DELETE'],
|
||||||
incomingSuperRoles => array[rbac.globalADMIN(rbac.unassumed())],
|
incomingSuperRoles => array[rbac.global_ADMIN(rbac.unassumed())],
|
||||||
subjectUuids => array[rbac.currentSubjectUuid()]
|
subjectUuids => array[rbac.currentSubjectUuid()]
|
||||||
);
|
);
|
||||||
|
|
||||||
perform rbac.defineRoleWithGrants(
|
perform rbac.defineRoleWithGrants(
|
||||||
testCustomerADMIN(NEW),
|
rbactest.customer_ADMIN(NEW),
|
||||||
permissions => array['UPDATE'],
|
permissions => array['UPDATE'],
|
||||||
incomingSuperRoles => array[testCustomerOWNER(NEW)]
|
incomingSuperRoles => array[rbactest.customer_OWNER(NEW)]
|
||||||
);
|
);
|
||||||
|
|
||||||
perform rbac.defineRoleWithGrants(
|
perform rbac.defineRoleWithGrants(
|
||||||
testCustomerTENANT(NEW),
|
rbactest.customer_TENANT(NEW),
|
||||||
permissions => array['SELECT'],
|
permissions => array['SELECT'],
|
||||||
incomingSuperRoles => array[testCustomerADMIN(NEW)]
|
incomingSuperRoles => array[rbactest.customer_ADMIN(NEW)]
|
||||||
);
|
);
|
||||||
|
|
||||||
call rbac.leaveTriggerForObjectUuid(NEW.uuid);
|
call rbac.leaveTriggerForObjectUuid(NEW.uuid);
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
AFTER INSERT TRIGGER to create the role+grant structure for a new test_customer row.
|
AFTER INSERT TRIGGER to create the role+grant structure for a new rbactest.customer row.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
create or replace function insertTriggerForTestCustomer_tf()
|
create or replace function rbactest.customer_build_rbac_system_after_insert_tf()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $$
|
strict as $$
|
||||||
begin
|
begin
|
||||||
call buildRbacSystemForTestCustomer(NEW);
|
call rbactest.customer_build_rbac_system(NEW);
|
||||||
return NEW;
|
return NEW;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
create trigger insertTriggerForTestCustomer_tg
|
create trigger build_rbac_system_after_insert_tg
|
||||||
after insert on test_customer
|
after insert on rbactest.customer
|
||||||
for each row
|
for each row
|
||||||
execute procedure insertTriggerForTestCustomer_tf();
|
execute procedure rbactest.customer_build_rbac_system_after_insert_tf();
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset InsertTriggerGenerator:test-customer-rbac-GRANTING-INSERT-PERMISSION endDelimiter:--//
|
--changeset InsertTriggerGenerator:rbactest-customer-rbac-GRANTING-INSERT-PERMISSION endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
-- granting INSERT permission to rbac.global ----------------------------
|
-- granting INSERT permission to rbac.global ----------------------------
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Grants INSERT INTO test_customer permissions to specified role of pre-existing rbac.global rows.
|
Grants INSERT INTO rbactest.customer permissions to specified role of pre-existing rbac.global rows.
|
||||||
*/
|
*/
|
||||||
do language plpgsql $$
|
do language plpgsql $$
|
||||||
declare
|
declare
|
||||||
row rbac.global;
|
row rbac.global;
|
||||||
begin
|
begin
|
||||||
call base.defineContext('create INSERT INTO test_customer permissions for pre-exising rbac.global rows');
|
call base.defineContext('create INSERT INTO rbactest.customer permissions for pre-exising rbac.global rows');
|
||||||
|
|
||||||
FOR row IN SELECT * FROM rbac.global
|
FOR row IN SELECT * FROM rbac.global
|
||||||
-- unconditional for all rows in that table
|
-- unconditional for all rows in that table
|
||||||
LOOP
|
LOOP
|
||||||
call rbac.grantPermissionToRole(
|
call rbac.grantPermissionToRole(
|
||||||
rbac.createPermission(row.uuid, 'INSERT', 'test_customer'),
|
rbac.createPermission(row.uuid, 'INSERT', 'rbactest.customer'),
|
||||||
rbac.globalADMIN());
|
rbac.global_ADMIN());
|
||||||
END LOOP;
|
END LOOP;
|
||||||
end;
|
end;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Grants test_customer INSERT permission to specified role of new global rows.
|
Grants rbactest.customer INSERT permission to specified role of new global rows.
|
||||||
*/
|
*/
|
||||||
create or replace function rbac.new_test_customer_grants_insert_to_global_tf()
|
create or replace function rbactest.customer_grants_insert_to_global_tf()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql
|
language plpgsql
|
||||||
strict as $$
|
strict as $$
|
||||||
begin
|
begin
|
||||||
-- unconditional for all rows in that table
|
-- unconditional for all rows in that table
|
||||||
call rbac.grantPermissionToRole(
|
call rbac.grantPermissionToRole(
|
||||||
rbac.createPermission(NEW.uuid, 'INSERT', 'test_customer'),
|
rbac.createPermission(NEW.uuid, 'INSERT', 'rbactest.customer'),
|
||||||
rbac.globalADMIN());
|
rbac.global_ADMIN());
|
||||||
-- end.
|
-- end.
|
||||||
return NEW;
|
return NEW;
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
|
-- ..._z_... is to put it at the end of after insert triggers, to make sure the roles exist
|
||||||
create trigger z_new_test_customer_grants_after_insert_tg
|
create trigger customer_z_grants_after_insert_tg
|
||||||
after insert on rbac.global
|
after insert on rbac.global
|
||||||
for each row
|
for each row
|
||||||
execute procedure rbac.new_test_customer_grants_insert_to_global_tf();
|
execute procedure rbactest.customer_grants_insert_to_global_tf();
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset InsertTriggerGenerator:test_customer-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
|
--changeset InsertTriggerGenerator:rbactest-customer-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Checks if the user respectively the assumed roles are allowed to insert a row to test_customer.
|
Checks if the user respectively the assumed roles are allowed to insert a row to rbactest.customer.
|
||||||
*/
|
*/
|
||||||
create or replace function test_customer_insert_permission_check_tf()
|
create or replace function rbactest.customer_insert_permission_check_tf()
|
||||||
returns trigger
|
returns trigger
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
declare
|
declare
|
||||||
superObjectUuid uuid;
|
superObjectUuid uuid;
|
||||||
begin
|
begin
|
||||||
-- check INSERT INSERT if rbac.global ADMIN
|
-- check INSERT permission if rbac.global ADMIN
|
||||||
if rbac.isGlobalAdmin() then
|
if rbac.isGlobalAdmin() then
|
||||||
return NEW;
|
return NEW;
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
raise exception '[403] insert into test_customer values(%) not allowed for current subjects % (%)',
|
raise exception '[403] insert into rbactest.customer values(%) not allowed for current subjects % (%)',
|
||||||
NEW, base.currentSubjects(), rbac.currentSubjectOrAssumedRolesUuids();
|
NEW, base.currentSubjects(), rbac.currentSubjectOrAssumedRolesUuids();
|
||||||
end; $$;
|
end; $$;
|
||||||
|
|
||||||
create trigger test_customer_insert_permission_check_tg
|
create trigger customer_insert_permission_check_tg
|
||||||
before insert on test_customer
|
before insert on rbactest.customer
|
||||||
for each row
|
for each row
|
||||||
execute procedure test_customer_insert_permission_check_tf();
|
execute procedure rbactest.customer_insert_permission_check_tf();
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset RbacIdentityViewGenerator:test-customer-rbac-IDENTITY-VIEW endDelimiter:--//
|
--changeset RbacIdentityViewGenerator:rbactest-customer-rbac-IDENTITY-VIEW endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
call rbac.generateRbacIdentityViewFromProjection('test_customer',
|
call rbac.generateRbacIdentityViewFromProjection('rbactest.customer',
|
||||||
$idName$
|
$idName$
|
||||||
prefix
|
prefix
|
||||||
$idName$);
|
$idName$);
|
||||||
@ -165,9 +165,9 @@ call rbac.generateRbacIdentityViewFromProjection('test_customer',
|
|||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
--changeset RbacRestrictedViewGenerator:test-customer-rbac-RESTRICTED-VIEW endDelimiter:--//
|
--changeset RbacRestrictedViewGenerator:rbactest-customer-rbac-RESTRICTED-VIEW endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
call rbac.generateRbacRestrictedView('test_customer',
|
call rbac.generateRbacRestrictedView('rbactest.customer',
|
||||||
$orderBy$
|
$orderBy$
|
||||||
reference
|
reference
|
||||||
$orderBy$,
|
$orderBy$,
|
@ -7,7 +7,7 @@
|
|||||||
/*
|
/*
|
||||||
Generates a customer reference number for a given test data counter.
|
Generates a customer reference number for a given test data counter.
|
||||||
*/
|
*/
|
||||||
create or replace function testCustomerReference(customerCount integer)
|
create or replace function rbactest.customer_create_test_data(customerCount integer)
|
||||||
returns integer
|
returns integer
|
||||||
returns null on null input
|
returns null on null input
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
@ -19,7 +19,7 @@ end; $$;
|
|||||||
/*
|
/*
|
||||||
Creates a single customer test record with dist.
|
Creates a single customer test record with dist.
|
||||||
*/
|
*/
|
||||||
create or replace procedure createTestCustomerTestData(
|
create or replace procedure rbactest.customer_create_test_data(
|
||||||
custReference integer,
|
custReference integer,
|
||||||
custPrefix varchar
|
custPrefix varchar
|
||||||
)
|
)
|
||||||
@ -28,21 +28,21 @@ declare
|
|||||||
custRowId uuid;
|
custRowId uuid;
|
||||||
custAdminName varchar;
|
custAdminName varchar;
|
||||||
custAdminUuid uuid;
|
custAdminUuid uuid;
|
||||||
newCust test_customer;
|
newCust rbactest.customer;
|
||||||
begin
|
begin
|
||||||
custRowId = uuid_generate_v4();
|
custRowId = uuid_generate_v4();
|
||||||
custAdminName = 'customer-admin@' || custPrefix || '.example.com';
|
custAdminName = 'customer-admin@' || custPrefix || '.example.com';
|
||||||
custAdminUuid = rbac.create_subject(custAdminName);
|
custAdminUuid = rbac.create_subject(custAdminName);
|
||||||
|
|
||||||
insert
|
insert
|
||||||
into test_customer (reference, prefix, adminUserName)
|
into rbactest.customer (reference, prefix, adminUserName)
|
||||||
values (custReference, custPrefix, custAdminName);
|
values (custReference, custPrefix, custAdminName);
|
||||||
|
|
||||||
select * into newCust
|
select * into newCust
|
||||||
from test_customer where reference=custReference;
|
from rbactest.customer where reference=custReference;
|
||||||
call rbac.grantRoleToSubject(
|
call rbac.grantRoleToSubject(
|
||||||
rbac.getRoleId(testCustomerOwner(newCust)),
|
rbac.getRoleId(rbactest.customer_OWNER(newCust)),
|
||||||
rbac.getRoleId(testCustomerAdmin(newCust)),
|
rbac.getRoleId(rbactest.customer_ADMIN(newCust)),
|
||||||
custAdminUuid,
|
custAdminUuid,
|
||||||
true);
|
true);
|
||||||
end; $$;
|
end; $$;
|
||||||
@ -51,7 +51,7 @@ end; $$;
|
|||||||
/*
|
/*
|
||||||
Creates a range of test customers for mass data generation.
|
Creates a range of test customers for mass data generation.
|
||||||
*/
|
*/
|
||||||
create or replace procedure createTestCustomerTestData(
|
create or replace procedure rbactest.customer_create_test_data(
|
||||||
startCount integer, -- count of auto generated rows before the run
|
startCount integer, -- count of auto generated rows before the run
|
||||||
endCount integer -- count of auto generated rows after the run
|
endCount integer -- count of auto generated rows after the run
|
||||||
)
|
)
|
||||||
@ -59,7 +59,7 @@ create or replace procedure createTestCustomerTestData(
|
|||||||
begin
|
begin
|
||||||
for t in startCount..endCount
|
for t in startCount..endCount
|
||||||
loop
|
loop
|
||||||
call createTestCustomerTestData(testCustomerReference(t), base.intToVarChar(t, 3));
|
call rbactest.customer_create_test_data(rbactest.testCustomerReference(t), base.intToVarChar(t, 3));
|
||||||
commit;
|
commit;
|
||||||
end loop;
|
end loop;
|
||||||
end; $$;
|
end; $$;
|
||||||
@ -74,9 +74,9 @@ do language plpgsql $$
|
|||||||
begin
|
begin
|
||||||
call base.defineContext('creating RBAC test customer', null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
|
call base.defineContext('creating RBAC test customer', null, 'superuser-alex@hostsharing.net', 'rbac.global#global:ADMIN');
|
||||||
|
|
||||||
call createTestCustomerTestData(99901, 'xxx');
|
call rbactest.customer_create_test_data(99901, 'xxx');
|
||||||
call createTestCustomerTestData(99902, 'yyy');
|
call rbactest.customer_create_test_data(99902, 'yyy');
|
||||||
call createTestCustomerTestData(99903, 'zzz');
|
call rbactest.customer_create_test_data(99903, 'zzz');
|
||||||
end;
|
end;
|
||||||
$$;
|
$$;
|
||||||
--//
|
--//
|
@ -4,11 +4,11 @@
|
|||||||
--changeset michael.hoennig:test-package-MAIN-TABLE endDelimiter:--//
|
--changeset michael.hoennig:test-package-MAIN-TABLE endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
create table if not exists test_package
|
create table if not exists rbactest.package
|
||||||
(
|
(
|
||||||
uuid uuid unique references rbac.object (uuid),
|
uuid uuid unique references rbac.object (uuid),
|
||||||
version int not null default 0,
|
version int not null default 0,
|
||||||
customerUuid uuid references test_customer (uuid),
|
customerUuid uuid references rbactest.customer (uuid),
|
||||||
name varchar(5),
|
name varchar(5),
|
||||||
description varchar(96)
|
description varchar(96)
|
||||||
);
|
);
|
@ -0,0 +1,245 @@
|
|||||||
|
--liquibase formatted sql
|
||||||
|
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RbacObjectGenerator:rbactest-package-rbac-OBJECT endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
call rbac.generateRelatedRbacObject('rbactest.package');
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RbacRoleDescriptorsGenerator:rbactest-package-rbac-ROLE-DESCRIPTORS endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
call rbac.generateRbacRoleDescriptors('rbactest.package');
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RolesGrantsAndPermissionsGenerator:rbactest-package-rbac-insert-trigger endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace procedure rbactest.package_build_rbac_system(
|
||||||
|
NEW rbactest.package
|
||||||
|
)
|
||||||
|
language plpgsql as $$
|
||||||
|
|
||||||
|
declare
|
||||||
|
newCustomer rbactest.customer;
|
||||||
|
|
||||||
|
begin
|
||||||
|
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||||
|
|
||||||
|
SELECT * FROM rbactest.customer WHERE uuid = NEW.customerUuid INTO newCustomer;
|
||||||
|
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
|
||||||
|
|
||||||
|
|
||||||
|
perform rbac.defineRoleWithGrants(
|
||||||
|
rbactest.package_OWNER(NEW),
|
||||||
|
permissions => array['DELETE', 'UPDATE'],
|
||||||
|
incomingSuperRoles => array[rbactest.customer_ADMIN(newCustomer)]
|
||||||
|
);
|
||||||
|
|
||||||
|
perform rbac.defineRoleWithGrants(
|
||||||
|
rbactest.package_ADMIN(NEW),
|
||||||
|
incomingSuperRoles => array[rbactest.package_OWNER(NEW)]
|
||||||
|
);
|
||||||
|
|
||||||
|
perform rbac.defineRoleWithGrants(
|
||||||
|
rbactest.package_TENANT(NEW),
|
||||||
|
permissions => array['SELECT'],
|
||||||
|
incomingSuperRoles => array[rbactest.package_ADMIN(NEW)],
|
||||||
|
outgoingSubRoles => array[rbactest.customer_TENANT(newCustomer)]
|
||||||
|
);
|
||||||
|
|
||||||
|
call rbac.leaveTriggerForObjectUuid(NEW.uuid);
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
/*
|
||||||
|
AFTER INSERT TRIGGER to create the role+grant structure for a new rbactest.package row.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace function rbactest.package_build_rbac_system_after_insert_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql
|
||||||
|
strict as $$
|
||||||
|
begin
|
||||||
|
call rbactest.package_build_rbac_system(NEW);
|
||||||
|
return NEW;
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
create trigger build_rbac_system_after_insert_tg
|
||||||
|
after insert on rbactest.package
|
||||||
|
for each row
|
||||||
|
execute procedure rbactest.package_build_rbac_system_after_insert_tf();
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RolesGrantsAndPermissionsGenerator:rbactest-package-rbac-update-trigger endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace procedure rbactest.package_update_rbac_system(
|
||||||
|
OLD rbactest.package,
|
||||||
|
NEW rbactest.package
|
||||||
|
)
|
||||||
|
language plpgsql as $$
|
||||||
|
|
||||||
|
declare
|
||||||
|
oldCustomer rbactest.customer;
|
||||||
|
newCustomer rbactest.customer;
|
||||||
|
|
||||||
|
begin
|
||||||
|
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||||
|
|
||||||
|
SELECT * FROM rbactest.customer WHERE uuid = OLD.customerUuid INTO oldCustomer;
|
||||||
|
assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s', OLD.customerUuid);
|
||||||
|
|
||||||
|
SELECT * FROM rbactest.customer WHERE uuid = NEW.customerUuid INTO newCustomer;
|
||||||
|
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
|
||||||
|
|
||||||
|
|
||||||
|
if NEW.customerUuid <> OLD.customerUuid then
|
||||||
|
|
||||||
|
call rbac.revokeRoleFromRole(rbactest.package_OWNER(OLD), rbactest.customer_ADMIN(oldCustomer));
|
||||||
|
call rbac.grantRoleToRole(rbactest.package_OWNER(NEW), rbactest.customer_ADMIN(newCustomer));
|
||||||
|
|
||||||
|
call rbac.revokeRoleFromRole(rbactest.customer_TENANT(oldCustomer), rbactest.package_TENANT(OLD));
|
||||||
|
call rbac.grantRoleToRole(rbactest.customer_TENANT(newCustomer), rbactest.package_TENANT(NEW));
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
call rbac.leaveTriggerForObjectUuid(NEW.uuid);
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
/*
|
||||||
|
AFTER UPDATE TRIGGER to re-wire the grant structure for a new rbactest.package row.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace function rbactest.package_update_rbac_system_after_update_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql
|
||||||
|
strict as $$
|
||||||
|
begin
|
||||||
|
call rbactest.package_update_rbac_system(OLD, NEW);
|
||||||
|
return NEW;
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
create trigger update_rbac_system_after_update_tg
|
||||||
|
after update on rbactest.package
|
||||||
|
for each row
|
||||||
|
execute procedure rbactest.package_update_rbac_system_after_update_tf();
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset InsertTriggerGenerator:rbactest-package-rbac-GRANTING-INSERT-PERMISSION endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- granting INSERT permission to rbactest.customer ----------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
Grants INSERT INTO rbactest.package permissions to specified role of pre-existing rbactest.customer rows.
|
||||||
|
*/
|
||||||
|
do language plpgsql $$
|
||||||
|
declare
|
||||||
|
row rbactest.customer;
|
||||||
|
begin
|
||||||
|
call base.defineContext('create INSERT INTO rbactest.package permissions for pre-exising rbactest.customer rows');
|
||||||
|
|
||||||
|
FOR row IN SELECT * FROM rbactest.customer
|
||||||
|
-- unconditional for all rows in that table
|
||||||
|
LOOP
|
||||||
|
call rbac.grantPermissionToRole(
|
||||||
|
rbac.createPermission(row.uuid, 'INSERT', 'rbactest.package'),
|
||||||
|
rbactest.customer_ADMIN(row));
|
||||||
|
END LOOP;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Grants rbactest.package INSERT permission to specified role of new customer rows.
|
||||||
|
*/
|
||||||
|
create or replace function rbactest.package_grants_insert_to_customer_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql
|
||||||
|
strict as $$
|
||||||
|
begin
|
||||||
|
-- unconditional for all rows in that table
|
||||||
|
call rbac.grantPermissionToRole(
|
||||||
|
rbac.createPermission(NEW.uuid, 'INSERT', 'rbactest.package'),
|
||||||
|
rbactest.customer_ADMIN(NEW));
|
||||||
|
-- end.
|
||||||
|
return NEW;
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
-- ..._z_... is to put it at the end of after insert triggers, to make sure the roles exist
|
||||||
|
create trigger package_z_grants_after_insert_tg
|
||||||
|
after insert on rbactest.customer
|
||||||
|
for each row
|
||||||
|
execute procedure rbactest.package_grants_insert_to_customer_tf();
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset InsertTriggerGenerator:rbactest-package-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if the user respectively the assumed roles are allowed to insert a row to rbactest.package.
|
||||||
|
*/
|
||||||
|
create or replace function rbactest.package_insert_permission_check_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql as $$
|
||||||
|
declare
|
||||||
|
superObjectUuid uuid;
|
||||||
|
begin
|
||||||
|
-- check INSERT permission via direct foreign key: NEW.customerUuid
|
||||||
|
if rbac.hasInsertPermission(NEW.customerUuid, 'rbactest.package') then
|
||||||
|
return NEW;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
raise exception '[403] insert into rbactest.package values(%) not allowed for current subjects % (%)',
|
||||||
|
NEW, base.currentSubjects(), rbac.currentSubjectOrAssumedRolesUuids();
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
create trigger package_insert_permission_check_tg
|
||||||
|
before insert on rbactest.package
|
||||||
|
for each row
|
||||||
|
execute procedure rbactest.package_insert_permission_check_tf();
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RbacIdentityViewGenerator:rbactest-package-rbac-IDENTITY-VIEW endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
call rbac.generateRbacIdentityViewFromProjection('rbactest.package',
|
||||||
|
$idName$
|
||||||
|
name
|
||||||
|
$idName$);
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RbacRestrictedViewGenerator:rbactest-package-rbac-RESTRICTED-VIEW endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
call rbac.generateRbacRestrictedView('rbactest.package',
|
||||||
|
$orderBy$
|
||||||
|
name
|
||||||
|
$orderBy$,
|
||||||
|
$updates$
|
||||||
|
version = new.version,
|
||||||
|
customerUuid = new.customerUuid,
|
||||||
|
description = new.description
|
||||||
|
$updates$);
|
||||||
|
--//
|
||||||
|
|
@ -6,32 +6,32 @@
|
|||||||
/*
|
/*
|
||||||
Creates the given number of test packages for the given customer.
|
Creates the given number of test packages for the given customer.
|
||||||
*/
|
*/
|
||||||
create or replace procedure createPackageTestData(customerPrefix varchar, pacCount int)
|
create or replace procedure rbactest.package_create_test_data(customerPrefix varchar, pacCount int)
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
declare
|
declare
|
||||||
cust test_customer;
|
cust rbactest.customer;
|
||||||
custAdminUser varchar;
|
custAdminUser varchar;
|
||||||
custAdminRole varchar;
|
custAdminRole varchar;
|
||||||
pacName varchar;
|
pacName varchar;
|
||||||
pac test_package;
|
pac rbactest.package;
|
||||||
begin
|
begin
|
||||||
select * from test_customer where test_customer.prefix = customerPrefix into cust;
|
select * from rbactest.customer where rbactest.customer.prefix = customerPrefix into cust;
|
||||||
|
|
||||||
for t in 0..(pacCount-1)
|
for t in 0..(pacCount-1)
|
||||||
loop
|
loop
|
||||||
pacName = cust.prefix || to_char(t, 'fm00');
|
pacName = cust.prefix || to_char(t, 'fm00');
|
||||||
custAdminUser = 'customer-admin@' || cust.prefix || '.example.com';
|
custAdminUser = 'customer-admin@' || cust.prefix || '.example.com';
|
||||||
custAdminRole = 'test_customer#' || cust.prefix || ':ADMIN';
|
custAdminRole = 'rbactest.customer#' || cust.prefix || ':ADMIN';
|
||||||
call base.defineContext('creating RBAC test package', null, 'superuser-fran@hostsharing.net', custAdminRole);
|
call base.defineContext('creating RBAC test package', null, 'superuser-fran@hostsharing.net', custAdminRole);
|
||||||
|
|
||||||
insert
|
insert
|
||||||
into test_package (customerUuid, name, description)
|
into rbactest.package (customerUuid, name, description)
|
||||||
values (cust.uuid, pacName, 'Here you can add your own description of package ' || pacName || '.')
|
values (cust.uuid, pacName, 'Here you can add your own description of package ' || pacName || '.')
|
||||||
returning * into pac;
|
returning * into pac;
|
||||||
|
|
||||||
call rbac.grantRoleToSubject(
|
call rbac.grantRoleToSubject(
|
||||||
rbac.getRoleId(testCustomerAdmin(cust)),
|
rbac.getRoleId(rbactest.customer_ADMIN(cust)),
|
||||||
rbac.findRoleId(testPackageAdmin(pac)),
|
rbac.findRoleId(rbactest.package_ADMIN(pac)),
|
||||||
rbac.create_subject('pac-admin-' || pacName || '@' || cust.prefix || '.example.com'),
|
rbac.create_subject('pac-admin-' || pacName || '@' || cust.prefix || '.example.com'),
|
||||||
true);
|
true);
|
||||||
|
|
||||||
@ -41,15 +41,15 @@ end; $$;
|
|||||||
/*
|
/*
|
||||||
Creates a range of test packages for mass data generation.
|
Creates a range of test packages for mass data generation.
|
||||||
*/
|
*/
|
||||||
create or replace procedure createPackageTestData()
|
create or replace procedure rbactest.package_create_test_data()
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
declare
|
declare
|
||||||
cust test_customer;
|
cust rbactest.customer;
|
||||||
begin
|
begin
|
||||||
for cust in (select * from test_customer)
|
for cust in (select * from rbactest.customer)
|
||||||
loop
|
loop
|
||||||
continue when cust.reference >= 90000; -- reserved for functional testing
|
continue when cust.reference >= 90000; -- reserved for functional testing
|
||||||
call createPackageTestData(cust.prefix, 3);
|
call rbactest.package_create_test_data(cust.prefix, 3);
|
||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
@ -64,9 +64,9 @@ $$;
|
|||||||
|
|
||||||
do language plpgsql $$
|
do language plpgsql $$
|
||||||
begin
|
begin
|
||||||
call createPackageTestData('xxx', 3);
|
call rbactest.package_create_test_data('xxx', 3);
|
||||||
call createPackageTestData('yyy', 3);
|
call rbactest.package_create_test_data('yyy', 3);
|
||||||
call createPackageTestData('zzz', 3);
|
call rbactest.package_create_test_data('zzz', 3);
|
||||||
end;
|
end;
|
||||||
$$;
|
$$;
|
||||||
--//
|
--//
|
@ -4,10 +4,10 @@
|
|||||||
--changeset michael.hoennig:test-domain-MAIN-TABLE endDelimiter:--//
|
--changeset michael.hoennig:test-domain-MAIN-TABLE endDelimiter:--//
|
||||||
-- ----------------------------------------------------------------------------
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
create table if not exists test_domain
|
create table if not exists rbactest.domain
|
||||||
(
|
(
|
||||||
uuid uuid unique references rbac.object (uuid),
|
uuid uuid unique references rbac.object (uuid),
|
||||||
packageUuid uuid references test_package (uuid),
|
packageUuid uuid references rbactest.package (uuid),
|
||||||
name character varying(253),
|
name character varying(253),
|
||||||
description character varying(96)
|
description character varying(96)
|
||||||
);
|
);
|
@ -0,0 +1,244 @@
|
|||||||
|
--liquibase formatted sql
|
||||||
|
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RbacObjectGenerator:rbactest-domain-rbac-OBJECT endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
call rbac.generateRelatedRbacObject('rbactest.domain');
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RbacRoleDescriptorsGenerator:rbactest-domain-rbac-ROLE-DESCRIPTORS endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
call rbac.generateRbacRoleDescriptors('rbactest.domain');
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RolesGrantsAndPermissionsGenerator:rbactest-domain-rbac-insert-trigger endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace procedure rbactest.domain_build_rbac_system(
|
||||||
|
NEW rbactest.domain
|
||||||
|
)
|
||||||
|
language plpgsql as $$
|
||||||
|
|
||||||
|
declare
|
||||||
|
newPackage rbactest.package;
|
||||||
|
|
||||||
|
begin
|
||||||
|
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||||
|
|
||||||
|
SELECT * FROM rbactest.package WHERE uuid = NEW.packageUuid INTO newPackage;
|
||||||
|
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
|
||||||
|
|
||||||
|
|
||||||
|
perform rbac.defineRoleWithGrants(
|
||||||
|
rbactest.domain_OWNER(NEW),
|
||||||
|
permissions => array['DELETE', 'UPDATE'],
|
||||||
|
incomingSuperRoles => array[rbactest.package_ADMIN(newPackage)],
|
||||||
|
outgoingSubRoles => array[rbactest.package_TENANT(newPackage)]
|
||||||
|
);
|
||||||
|
|
||||||
|
perform rbac.defineRoleWithGrants(
|
||||||
|
rbactest.domain_ADMIN(NEW),
|
||||||
|
permissions => array['SELECT'],
|
||||||
|
incomingSuperRoles => array[rbactest.domain_OWNER(NEW)],
|
||||||
|
outgoingSubRoles => array[rbactest.package_TENANT(newPackage)]
|
||||||
|
);
|
||||||
|
|
||||||
|
call rbac.leaveTriggerForObjectUuid(NEW.uuid);
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
/*
|
||||||
|
AFTER INSERT TRIGGER to create the role+grant structure for a new rbactest.domain row.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace function rbactest.domain_build_rbac_system_after_insert_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql
|
||||||
|
strict as $$
|
||||||
|
begin
|
||||||
|
call rbactest.domain_build_rbac_system(NEW);
|
||||||
|
return NEW;
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
create trigger build_rbac_system_after_insert_tg
|
||||||
|
after insert on rbactest.domain
|
||||||
|
for each row
|
||||||
|
execute procedure rbactest.domain_build_rbac_system_after_insert_tf();
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RolesGrantsAndPermissionsGenerator:rbactest-domain-rbac-update-trigger endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace procedure rbactest.domain_update_rbac_system(
|
||||||
|
OLD rbactest.domain,
|
||||||
|
NEW rbactest.domain
|
||||||
|
)
|
||||||
|
language plpgsql as $$
|
||||||
|
|
||||||
|
declare
|
||||||
|
oldPackage rbactest.package;
|
||||||
|
newPackage rbactest.package;
|
||||||
|
|
||||||
|
begin
|
||||||
|
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
||||||
|
|
||||||
|
SELECT * FROM rbactest.package WHERE uuid = OLD.packageUuid INTO oldPackage;
|
||||||
|
assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s', OLD.packageUuid);
|
||||||
|
|
||||||
|
SELECT * FROM rbactest.package WHERE uuid = NEW.packageUuid INTO newPackage;
|
||||||
|
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
|
||||||
|
|
||||||
|
|
||||||
|
if NEW.packageUuid <> OLD.packageUuid then
|
||||||
|
|
||||||
|
call rbac.revokeRoleFromRole(rbactest.domain_OWNER(OLD), rbactest.package_ADMIN(oldPackage));
|
||||||
|
call rbac.grantRoleToRole(rbactest.domain_OWNER(NEW), rbactest.package_ADMIN(newPackage));
|
||||||
|
|
||||||
|
call rbac.revokeRoleFromRole(rbactest.package_TENANT(oldPackage), rbactest.domain_OWNER(OLD));
|
||||||
|
call rbac.grantRoleToRole(rbactest.package_TENANT(newPackage), rbactest.domain_OWNER(NEW));
|
||||||
|
|
||||||
|
call rbac.revokeRoleFromRole(rbactest.package_TENANT(oldPackage), rbactest.domain_ADMIN(OLD));
|
||||||
|
call rbac.grantRoleToRole(rbactest.package_TENANT(newPackage), rbactest.domain_ADMIN(NEW));
|
||||||
|
|
||||||
|
end if;
|
||||||
|
|
||||||
|
call rbac.leaveTriggerForObjectUuid(NEW.uuid);
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
/*
|
||||||
|
AFTER UPDATE TRIGGER to re-wire the grant structure for a new rbactest.domain row.
|
||||||
|
*/
|
||||||
|
|
||||||
|
create or replace function rbactest.domain_update_rbac_system_after_update_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql
|
||||||
|
strict as $$
|
||||||
|
begin
|
||||||
|
call rbactest.domain_update_rbac_system(OLD, NEW);
|
||||||
|
return NEW;
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
create trigger update_rbac_system_after_update_tg
|
||||||
|
after update on rbactest.domain
|
||||||
|
for each row
|
||||||
|
execute procedure rbactest.domain_update_rbac_system_after_update_tf();
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset InsertTriggerGenerator:rbactest-domain-rbac-GRANTING-INSERT-PERMISSION endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- granting INSERT permission to rbactest.package ----------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
Grants INSERT INTO rbactest.domain permissions to specified role of pre-existing rbactest.package rows.
|
||||||
|
*/
|
||||||
|
do language plpgsql $$
|
||||||
|
declare
|
||||||
|
row rbactest.package;
|
||||||
|
begin
|
||||||
|
call base.defineContext('create INSERT INTO rbactest.domain permissions for pre-exising rbactest.package rows');
|
||||||
|
|
||||||
|
FOR row IN SELECT * FROM rbactest.package
|
||||||
|
-- unconditional for all rows in that table
|
||||||
|
LOOP
|
||||||
|
call rbac.grantPermissionToRole(
|
||||||
|
rbac.createPermission(row.uuid, 'INSERT', 'rbactest.domain'),
|
||||||
|
rbactest.package_ADMIN(row));
|
||||||
|
END LOOP;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Grants rbactest.domain INSERT permission to specified role of new package rows.
|
||||||
|
*/
|
||||||
|
create or replace function rbactest.domain_grants_insert_to_package_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql
|
||||||
|
strict as $$
|
||||||
|
begin
|
||||||
|
-- unconditional for all rows in that table
|
||||||
|
call rbac.grantPermissionToRole(
|
||||||
|
rbac.createPermission(NEW.uuid, 'INSERT', 'rbactest.domain'),
|
||||||
|
rbactest.package_ADMIN(NEW));
|
||||||
|
-- end.
|
||||||
|
return NEW;
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
-- ..._z_... is to put it at the end of after insert triggers, to make sure the roles exist
|
||||||
|
create trigger domain_z_grants_after_insert_tg
|
||||||
|
after insert on rbactest.package
|
||||||
|
for each row
|
||||||
|
execute procedure rbactest.domain_grants_insert_to_package_tf();
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset InsertTriggerGenerator:rbactest-domain-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if the user respectively the assumed roles are allowed to insert a row to rbactest.domain.
|
||||||
|
*/
|
||||||
|
create or replace function rbactest.domain_insert_permission_check_tf()
|
||||||
|
returns trigger
|
||||||
|
language plpgsql as $$
|
||||||
|
declare
|
||||||
|
superObjectUuid uuid;
|
||||||
|
begin
|
||||||
|
-- check INSERT permission via direct foreign key: NEW.packageUuid
|
||||||
|
if rbac.hasInsertPermission(NEW.packageUuid, 'rbactest.domain') then
|
||||||
|
return NEW;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
raise exception '[403] insert into rbactest.domain values(%) not allowed for current subjects % (%)',
|
||||||
|
NEW, base.currentSubjects(), rbac.currentSubjectOrAssumedRolesUuids();
|
||||||
|
end; $$;
|
||||||
|
|
||||||
|
create trigger domain_insert_permission_check_tg
|
||||||
|
before insert on rbactest.domain
|
||||||
|
for each row
|
||||||
|
execute procedure rbactest.domain_insert_permission_check_tf();
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RbacIdentityViewGenerator:rbactest-domain-rbac-IDENTITY-VIEW endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
call rbac.generateRbacIdentityViewFromProjection('rbactest.domain',
|
||||||
|
$idName$
|
||||||
|
name
|
||||||
|
$idName$);
|
||||||
|
--//
|
||||||
|
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
--changeset RbacRestrictedViewGenerator:rbactest-domain-rbac-RESTRICTED-VIEW endDelimiter:--//
|
||||||
|
-- ----------------------------------------------------------------------------
|
||||||
|
call rbac.generateRbacRestrictedView('rbactest.domain',
|
||||||
|
$orderBy$
|
||||||
|
name
|
||||||
|
$orderBy$,
|
||||||
|
$updates$
|
||||||
|
version = new.version,
|
||||||
|
packageUuid = new.packageUuid,
|
||||||
|
description = new.description
|
||||||
|
$updates$);
|
||||||
|
--//
|
||||||
|
|
@ -6,15 +6,15 @@
|
|||||||
/*
|
/*
|
||||||
Creates the given count of test unix users for a single package.
|
Creates the given count of test unix users for a single package.
|
||||||
*/
|
*/
|
||||||
create or replace procedure createdomainTestData( packageName varchar, domainCount int )
|
create or replace procedure rbactest.domain_create_test_data( packageName varchar, domainCount int )
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
declare
|
declare
|
||||||
pac record;
|
pac record;
|
||||||
pacAdmin varchar;
|
pacAdmin varchar;
|
||||||
begin
|
begin
|
||||||
select p.uuid, p.name, c.prefix as custPrefix
|
select p.uuid, p.name, c.prefix as custPrefix
|
||||||
from test_package p
|
from rbactest.package p
|
||||||
join test_customer c on p.customeruuid = c.uuid
|
join rbactest.customer c on p.customeruuid = c.uuid
|
||||||
where p.name = packageName
|
where p.name = packageName
|
||||||
into pac;
|
into pac;
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ begin
|
|||||||
call base.defineContext('creating RBAC test domain', null, pacAdmin, null);
|
call base.defineContext('creating RBAC test domain', null, pacAdmin, null);
|
||||||
|
|
||||||
insert
|
insert
|
||||||
into test_domain (name, packageUuid)
|
into rbactest.domain (name, packageUuid)
|
||||||
values (pac.name || '-' || base.intToVarChar(t, 4), pac.uuid);
|
values (pac.name || '-' || base.intToVarChar(t, 4), pac.uuid);
|
||||||
end loop;
|
end loop;
|
||||||
end; $$;
|
end; $$;
|
||||||
@ -32,20 +32,18 @@ end; $$;
|
|||||||
/*
|
/*
|
||||||
Creates a range of unix users for mass data generation.
|
Creates a range of unix users for mass data generation.
|
||||||
*/
|
*/
|
||||||
create or replace procedure createdomainTestData( domainPerPackage integer )
|
create or replace procedure rbactest.domain_create_test_data( domainPerPackage integer )
|
||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
declare
|
declare
|
||||||
pac record;
|
pac record;
|
||||||
pacAdmin varchar;
|
|
||||||
currentTask varchar;
|
|
||||||
begin
|
begin
|
||||||
for pac in
|
for pac in
|
||||||
(select p.uuid, p.name
|
(select p.uuid, p.name
|
||||||
from test_package p
|
from rbactest.package p
|
||||||
join test_customer c on p.customeruuid = c.uuid
|
join rbactest.customer c on p.customeruuid = c.uuid
|
||||||
where c.reference < 90000) -- reserved for functional testing
|
where c.reference < 90000) -- reserved for functional testing
|
||||||
loop
|
loop
|
||||||
call createdomainTestData(pac.name, 2);
|
call rbactest.domain_create_test_data(pac.name, 2);
|
||||||
commit;
|
commit;
|
||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
@ -59,17 +57,17 @@ end; $$;
|
|||||||
|
|
||||||
do language plpgsql $$
|
do language plpgsql $$
|
||||||
begin
|
begin
|
||||||
call createdomainTestData('xxx00', 2);
|
call rbactest.domain_create_test_data('xxx00', 2);
|
||||||
call createdomainTestData('xxx01', 2);
|
call rbactest.domain_create_test_data('xxx01', 2);
|
||||||
call createdomainTestData('xxx02', 2);
|
call rbactest.domain_create_test_data('xxx02', 2);
|
||||||
|
|
||||||
call createdomainTestData('yyy00', 2);
|
call rbactest.domain_create_test_data('yyy00', 2);
|
||||||
call createdomainTestData('yyy01', 2);
|
call rbactest.domain_create_test_data('yyy01', 2);
|
||||||
call createdomainTestData('yyy02', 2);
|
call rbactest.domain_create_test_data('yyy02', 2);
|
||||||
|
|
||||||
call createdomainTestData('zzz00', 2);
|
call rbactest.domain_create_test_data('zzz00', 2);
|
||||||
call createdomainTestData('zzz01', 2);
|
call rbactest.domain_create_test_data('zzz01', 2);
|
||||||
call createdomainTestData('zzz02', 2);
|
call rbactest.domain_create_test_data('zzz02', 2);
|
||||||
end;
|
end;
|
||||||
$$;
|
$$;
|
||||||
--//
|
--//
|
@ -1,245 +0,0 @@
|
|||||||
--liquibase formatted sql
|
|
||||||
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RbacObjectGenerator:test-package-rbac-OBJECT endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
call rbac.generateRelatedRbacObject('test_package');
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RbacRoleDescriptorsGenerator:test-package-rbac-ROLE-DESCRIPTORS endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
call rbac.generateRbacRoleDescriptors('testPackage', 'test_package');
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RolesGrantsAndPermissionsGenerator:test-package-rbac-insert-trigger endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/*
|
|
||||||
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
|
||||||
*/
|
|
||||||
|
|
||||||
create or replace procedure buildRbacSystemForTestPackage(
|
|
||||||
NEW test_package
|
|
||||||
)
|
|
||||||
language plpgsql as $$
|
|
||||||
|
|
||||||
declare
|
|
||||||
newCustomer test_customer;
|
|
||||||
|
|
||||||
begin
|
|
||||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
|
||||||
|
|
||||||
SELECT * FROM test_customer WHERE uuid = NEW.customerUuid INTO newCustomer;
|
|
||||||
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
|
|
||||||
|
|
||||||
|
|
||||||
perform rbac.defineRoleWithGrants(
|
|
||||||
testPackageOWNER(NEW),
|
|
||||||
permissions => array['DELETE', 'UPDATE'],
|
|
||||||
incomingSuperRoles => array[testCustomerADMIN(newCustomer)]
|
|
||||||
);
|
|
||||||
|
|
||||||
perform rbac.defineRoleWithGrants(
|
|
||||||
testPackageADMIN(NEW),
|
|
||||||
incomingSuperRoles => array[testPackageOWNER(NEW)]
|
|
||||||
);
|
|
||||||
|
|
||||||
perform rbac.defineRoleWithGrants(
|
|
||||||
testPackageTENANT(NEW),
|
|
||||||
permissions => array['SELECT'],
|
|
||||||
incomingSuperRoles => array[testPackageADMIN(NEW)],
|
|
||||||
outgoingSubRoles => array[testCustomerTENANT(newCustomer)]
|
|
||||||
);
|
|
||||||
|
|
||||||
call rbac.leaveTriggerForObjectUuid(NEW.uuid);
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
/*
|
|
||||||
AFTER INSERT TRIGGER to create the role+grant structure for a new test_package row.
|
|
||||||
*/
|
|
||||||
|
|
||||||
create or replace function insertTriggerForTestPackage_tf()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql
|
|
||||||
strict as $$
|
|
||||||
begin
|
|
||||||
call buildRbacSystemForTestPackage(NEW);
|
|
||||||
return NEW;
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
create trigger insertTriggerForTestPackage_tg
|
|
||||||
after insert on test_package
|
|
||||||
for each row
|
|
||||||
execute procedure insertTriggerForTestPackage_tf();
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RolesGrantsAndPermissionsGenerator:test-package-rbac-update-trigger endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/*
|
|
||||||
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|
|
||||||
*/
|
|
||||||
|
|
||||||
create or replace procedure updateRbacRulesForTestPackage(
|
|
||||||
OLD test_package,
|
|
||||||
NEW test_package
|
|
||||||
)
|
|
||||||
language plpgsql as $$
|
|
||||||
|
|
||||||
declare
|
|
||||||
oldCustomer test_customer;
|
|
||||||
newCustomer test_customer;
|
|
||||||
|
|
||||||
begin
|
|
||||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
|
||||||
|
|
||||||
SELECT * FROM test_customer WHERE uuid = OLD.customerUuid INTO oldCustomer;
|
|
||||||
assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s', OLD.customerUuid);
|
|
||||||
|
|
||||||
SELECT * FROM test_customer WHERE uuid = NEW.customerUuid INTO newCustomer;
|
|
||||||
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
|
|
||||||
|
|
||||||
|
|
||||||
if NEW.customerUuid <> OLD.customerUuid then
|
|
||||||
|
|
||||||
call rbac.revokeRoleFromRole(testPackageOWNER(OLD), testCustomerADMIN(oldCustomer));
|
|
||||||
call rbac.grantRoleToRole(testPackageOWNER(NEW), testCustomerADMIN(newCustomer));
|
|
||||||
|
|
||||||
call rbac.revokeRoleFromRole(testCustomerTENANT(oldCustomer), testPackageTENANT(OLD));
|
|
||||||
call rbac.grantRoleToRole(testCustomerTENANT(newCustomer), testPackageTENANT(NEW));
|
|
||||||
|
|
||||||
end if;
|
|
||||||
|
|
||||||
call rbac.leaveTriggerForObjectUuid(NEW.uuid);
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
/*
|
|
||||||
AFTER INSERT TRIGGER to re-wire the grant structure for a new test_package row.
|
|
||||||
*/
|
|
||||||
|
|
||||||
create or replace function updateTriggerForTestPackage_tf()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql
|
|
||||||
strict as $$
|
|
||||||
begin
|
|
||||||
call updateRbacRulesForTestPackage(OLD, NEW);
|
|
||||||
return NEW;
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
create trigger updateTriggerForTestPackage_tg
|
|
||||||
after update on test_package
|
|
||||||
for each row
|
|
||||||
execute procedure updateTriggerForTestPackage_tf();
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset InsertTriggerGenerator:test-package-rbac-GRANTING-INSERT-PERMISSION endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-- granting INSERT permission to test_customer ----------------------------
|
|
||||||
|
|
||||||
/*
|
|
||||||
Grants INSERT INTO test_package permissions to specified role of pre-existing test_customer rows.
|
|
||||||
*/
|
|
||||||
do language plpgsql $$
|
|
||||||
declare
|
|
||||||
row test_customer;
|
|
||||||
begin
|
|
||||||
call base.defineContext('create INSERT INTO test_package permissions for pre-exising test_customer rows');
|
|
||||||
|
|
||||||
FOR row IN SELECT * FROM test_customer
|
|
||||||
-- unconditional for all rows in that table
|
|
||||||
LOOP
|
|
||||||
call rbac.grantPermissionToRole(
|
|
||||||
rbac.createPermission(row.uuid, 'INSERT', 'test_package'),
|
|
||||||
testCustomerADMIN(row));
|
|
||||||
END LOOP;
|
|
||||||
end;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Grants test_package INSERT permission to specified role of new test_customer rows.
|
|
||||||
*/
|
|
||||||
create or replace function new_test_package_grants_insert_to_test_customer_tf()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql
|
|
||||||
strict as $$
|
|
||||||
begin
|
|
||||||
-- unconditional for all rows in that table
|
|
||||||
call rbac.grantPermissionToRole(
|
|
||||||
rbac.createPermission(NEW.uuid, 'INSERT', 'test_package'),
|
|
||||||
testCustomerADMIN(NEW));
|
|
||||||
-- end.
|
|
||||||
return NEW;
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
|
|
||||||
create trigger z_new_test_package_grants_after_insert_tg
|
|
||||||
after insert on test_customer
|
|
||||||
for each row
|
|
||||||
execute procedure new_test_package_grants_insert_to_test_customer_tf();
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset InsertTriggerGenerator:test_package-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
Checks if the user respectively the assumed roles are allowed to insert a row to test_package.
|
|
||||||
*/
|
|
||||||
create or replace function test_package_insert_permission_check_tf()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql as $$
|
|
||||||
declare
|
|
||||||
superObjectUuid uuid;
|
|
||||||
begin
|
|
||||||
-- check INSERT permission via direct foreign key: NEW.customerUuid
|
|
||||||
if rbac.hasInsertPermission(NEW.customerUuid, 'test_package') then
|
|
||||||
return NEW;
|
|
||||||
end if;
|
|
||||||
|
|
||||||
raise exception '[403] insert into test_package values(%) not allowed for current subjects % (%)',
|
|
||||||
NEW, base.currentSubjects(), rbac.currentSubjectOrAssumedRolesUuids();
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
create trigger test_package_insert_permission_check_tg
|
|
||||||
before insert on test_package
|
|
||||||
for each row
|
|
||||||
execute procedure test_package_insert_permission_check_tf();
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RbacIdentityViewGenerator:test-package-rbac-IDENTITY-VIEW endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
call rbac.generateRbacIdentityViewFromProjection('test_package',
|
|
||||||
$idName$
|
|
||||||
name
|
|
||||||
$idName$);
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RbacRestrictedViewGenerator:test-package-rbac-RESTRICTED-VIEW endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
call rbac.generateRbacRestrictedView('test_package',
|
|
||||||
$orderBy$
|
|
||||||
name
|
|
||||||
$orderBy$,
|
|
||||||
$updates$
|
|
||||||
version = new.version,
|
|
||||||
customerUuid = new.customerUuid,
|
|
||||||
description = new.description
|
|
||||||
$updates$);
|
|
||||||
--//
|
|
||||||
|
|
@ -1,244 +0,0 @@
|
|||||||
--liquibase formatted sql
|
|
||||||
-- This code generated was by RbacViewPostgresGenerator, do not amend manually.
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RbacObjectGenerator:test-domain-rbac-OBJECT endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
call rbac.generateRelatedRbacObject('test_domain');
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RbacRoleDescriptorsGenerator:test-domain-rbac-ROLE-DESCRIPTORS endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
call rbac.generateRbacRoleDescriptors('testDomain', 'test_domain');
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RolesGrantsAndPermissionsGenerator:test-domain-rbac-insert-trigger endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/*
|
|
||||||
Creates the roles, grants and permission for the AFTER INSERT TRIGGER.
|
|
||||||
*/
|
|
||||||
|
|
||||||
create or replace procedure buildRbacSystemForTestDomain(
|
|
||||||
NEW test_domain
|
|
||||||
)
|
|
||||||
language plpgsql as $$
|
|
||||||
|
|
||||||
declare
|
|
||||||
newPackage test_package;
|
|
||||||
|
|
||||||
begin
|
|
||||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
|
||||||
|
|
||||||
SELECT * FROM test_package WHERE uuid = NEW.packageUuid INTO newPackage;
|
|
||||||
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
|
|
||||||
|
|
||||||
|
|
||||||
perform rbac.defineRoleWithGrants(
|
|
||||||
testDomainOWNER(NEW),
|
|
||||||
permissions => array['DELETE', 'UPDATE'],
|
|
||||||
incomingSuperRoles => array[testPackageADMIN(newPackage)],
|
|
||||||
outgoingSubRoles => array[testPackageTENANT(newPackage)]
|
|
||||||
);
|
|
||||||
|
|
||||||
perform rbac.defineRoleWithGrants(
|
|
||||||
testDomainADMIN(NEW),
|
|
||||||
permissions => array['SELECT'],
|
|
||||||
incomingSuperRoles => array[testDomainOWNER(NEW)],
|
|
||||||
outgoingSubRoles => array[testPackageTENANT(newPackage)]
|
|
||||||
);
|
|
||||||
|
|
||||||
call rbac.leaveTriggerForObjectUuid(NEW.uuid);
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
/*
|
|
||||||
AFTER INSERT TRIGGER to create the role+grant structure for a new test_domain row.
|
|
||||||
*/
|
|
||||||
|
|
||||||
create or replace function insertTriggerForTestDomain_tf()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql
|
|
||||||
strict as $$
|
|
||||||
begin
|
|
||||||
call buildRbacSystemForTestDomain(NEW);
|
|
||||||
return NEW;
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
create trigger insertTriggerForTestDomain_tg
|
|
||||||
after insert on test_domain
|
|
||||||
for each row
|
|
||||||
execute procedure insertTriggerForTestDomain_tf();
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RolesGrantsAndPermissionsGenerator:test-domain-rbac-update-trigger endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/*
|
|
||||||
Called from the AFTER UPDATE TRIGGER to re-wire the grants.
|
|
||||||
*/
|
|
||||||
|
|
||||||
create or replace procedure updateRbacRulesForTestDomain(
|
|
||||||
OLD test_domain,
|
|
||||||
NEW test_domain
|
|
||||||
)
|
|
||||||
language plpgsql as $$
|
|
||||||
|
|
||||||
declare
|
|
||||||
oldPackage test_package;
|
|
||||||
newPackage test_package;
|
|
||||||
|
|
||||||
begin
|
|
||||||
call rbac.enterTriggerForObjectUuid(NEW.uuid);
|
|
||||||
|
|
||||||
SELECT * FROM test_package WHERE uuid = OLD.packageUuid INTO oldPackage;
|
|
||||||
assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s', OLD.packageUuid);
|
|
||||||
|
|
||||||
SELECT * FROM test_package WHERE uuid = NEW.packageUuid INTO newPackage;
|
|
||||||
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
|
|
||||||
|
|
||||||
|
|
||||||
if NEW.packageUuid <> OLD.packageUuid then
|
|
||||||
|
|
||||||
call rbac.revokeRoleFromRole(testDomainOWNER(OLD), testPackageADMIN(oldPackage));
|
|
||||||
call rbac.grantRoleToRole(testDomainOWNER(NEW), testPackageADMIN(newPackage));
|
|
||||||
|
|
||||||
call rbac.revokeRoleFromRole(testPackageTENANT(oldPackage), testDomainOWNER(OLD));
|
|
||||||
call rbac.grantRoleToRole(testPackageTENANT(newPackage), testDomainOWNER(NEW));
|
|
||||||
|
|
||||||
call rbac.revokeRoleFromRole(testPackageTENANT(oldPackage), testDomainADMIN(OLD));
|
|
||||||
call rbac.grantRoleToRole(testPackageTENANT(newPackage), testDomainADMIN(NEW));
|
|
||||||
|
|
||||||
end if;
|
|
||||||
|
|
||||||
call rbac.leaveTriggerForObjectUuid(NEW.uuid);
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
/*
|
|
||||||
AFTER INSERT TRIGGER to re-wire the grant structure for a new test_domain row.
|
|
||||||
*/
|
|
||||||
|
|
||||||
create or replace function updateTriggerForTestDomain_tf()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql
|
|
||||||
strict as $$
|
|
||||||
begin
|
|
||||||
call updateRbacRulesForTestDomain(OLD, NEW);
|
|
||||||
return NEW;
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
create trigger updateTriggerForTestDomain_tg
|
|
||||||
after update on test_domain
|
|
||||||
for each row
|
|
||||||
execute procedure updateTriggerForTestDomain_tf();
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset InsertTriggerGenerator:test-domain-rbac-GRANTING-INSERT-PERMISSION endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-- granting INSERT permission to test_package ----------------------------
|
|
||||||
|
|
||||||
/*
|
|
||||||
Grants INSERT INTO test_domain permissions to specified role of pre-existing test_package rows.
|
|
||||||
*/
|
|
||||||
do language plpgsql $$
|
|
||||||
declare
|
|
||||||
row test_package;
|
|
||||||
begin
|
|
||||||
call base.defineContext('create INSERT INTO test_domain permissions for pre-exising test_package rows');
|
|
||||||
|
|
||||||
FOR row IN SELECT * FROM test_package
|
|
||||||
-- unconditional for all rows in that table
|
|
||||||
LOOP
|
|
||||||
call rbac.grantPermissionToRole(
|
|
||||||
rbac.createPermission(row.uuid, 'INSERT', 'test_domain'),
|
|
||||||
testPackageADMIN(row));
|
|
||||||
END LOOP;
|
|
||||||
end;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
/**
|
|
||||||
Grants test_domain INSERT permission to specified role of new test_package rows.
|
|
||||||
*/
|
|
||||||
create or replace function new_test_domain_grants_insert_to_test_package_tf()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql
|
|
||||||
strict as $$
|
|
||||||
begin
|
|
||||||
-- unconditional for all rows in that table
|
|
||||||
call rbac.grantPermissionToRole(
|
|
||||||
rbac.createPermission(NEW.uuid, 'INSERT', 'test_domain'),
|
|
||||||
testPackageADMIN(NEW));
|
|
||||||
-- end.
|
|
||||||
return NEW;
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
-- z_... is to put it at the end of after insert triggers, to make sure the roles exist
|
|
||||||
create trigger z_new_test_domain_grants_after_insert_tg
|
|
||||||
after insert on test_package
|
|
||||||
for each row
|
|
||||||
execute procedure new_test_domain_grants_insert_to_test_package_tf();
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset InsertTriggerGenerator:test_domain-rbac-CHECKING-INSERT-PERMISSION endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
Checks if the user respectively the assumed roles are allowed to insert a row to test_domain.
|
|
||||||
*/
|
|
||||||
create or replace function test_domain_insert_permission_check_tf()
|
|
||||||
returns trigger
|
|
||||||
language plpgsql as $$
|
|
||||||
declare
|
|
||||||
superObjectUuid uuid;
|
|
||||||
begin
|
|
||||||
-- check INSERT permission via direct foreign key: NEW.packageUuid
|
|
||||||
if rbac.hasInsertPermission(NEW.packageUuid, 'test_domain') then
|
|
||||||
return NEW;
|
|
||||||
end if;
|
|
||||||
|
|
||||||
raise exception '[403] insert into test_domain values(%) not allowed for current subjects % (%)',
|
|
||||||
NEW, base.currentSubjects(), rbac.currentSubjectOrAssumedRolesUuids();
|
|
||||||
end; $$;
|
|
||||||
|
|
||||||
create trigger test_domain_insert_permission_check_tg
|
|
||||||
before insert on test_domain
|
|
||||||
for each row
|
|
||||||
execute procedure test_domain_insert_permission_check_tf();
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RbacIdentityViewGenerator:test-domain-rbac-IDENTITY-VIEW endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
call rbac.generateRbacIdentityViewFromProjection('test_domain',
|
|
||||||
$idName$
|
|
||||||
name
|
|
||||||
$idName$);
|
|
||||||
--//
|
|
||||||
|
|
||||||
|
|
||||||
-- ============================================================================
|
|
||||||
--changeset RbacRestrictedViewGenerator:test-domain-rbac-RESTRICTED-VIEW endDelimiter:--//
|
|
||||||
-- ----------------------------------------------------------------------------
|
|
||||||
call rbac.generateRbacRestrictedView('test_domain',
|
|
||||||
$orderBy$
|
|
||||||
name
|
|
||||||
$orderBy$,
|
|
||||||
$updates$
|
|
||||||
version = new.version,
|
|
||||||
packageUuid = new.packageUuid,
|
|
||||||
description = new.description
|
|
||||||
$updates$);
|
|
||||||
--//
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user