diff --git a/doc/rbac.md b/doc/rbac.md index 06a6ee7e..e8799d5f 100644 --- a/doc/rbac.md +++ b/doc/rbac.md @@ -11,7 +11,7 @@ Our implementation is based on Role-Based-Access-Management (RBAC) in conjunctio As far as possible, we are using the same terms as defined in the RBAC standard, for our function names though, we chose more expressive names. In RBAC, subjects can be assigned to roles, roles can be hierarchical and eventually have assigned permissions. -A permission allows a specific operation (e.g. view or edit) on a specific (business-) object. +A permission allows a specific operation (e.g. SELECT or UPDATE) on a specific (business-) object. You can find the entity structure as a UML class diagram as follows: @@ -101,13 +101,12 @@ package RBAC { RbacPermission *-- RbacObject enum RbacOperation { - add-package - add-domain - add-domain + INSERT:package + INSERT:domain ... - view - edit - delete + SELECT + UPDATE + DELETE } entity RbacObject { @@ -172,11 +171,10 @@ An *RbacPermission* allows a specific *RbacOperation* on a specific *RbacObject* An *RbacOperation* determines, what an *RbacPermission* allows to do. It can be one of: -- **'add-...'** - permits creating new instances of specific entity types underneath the object specified by the permission, e.g. "add-package" -- **'view'** - permits reading the contents of the object specified by the permission -- **'edit'** - change the contents of the object specified by the permission -- **'delete'** - delete the object specified by the permission -- **'\*'** +- **'INSERT'** - permits inserting new rows related to the row, to which the permission belongs, in the table which is specified an extra column +- **'SELECT'** - permits selecting the row specified by the permission +- **'UPDATE'** - permits updating (only the updatable columns of) the row specified by the permission +- **'DELETE'** - permits deleting the row specified by the permission This list is extensible according to the needs of the access rule system. @@ -212,7 +210,7 @@ E.g. for a new *customer* it would be granted to 'administrators' and for a new Whoever has the owner-role assigned can do everything with the related business-object, including deleting (or deactivating) it. -In most cases, the permissions to other operations than 'delete' are granted through the 'admin' role. +In most cases, the permissions to other operations than 'DELETE' are granted through the 'admin' role. By this, all roles ob sub-objects, which are assigned to the 'admin' role, are also granted to the 'owner'. #### admin @@ -220,14 +218,14 @@ By this, all roles ob sub-objects, which are assigned to the 'admin' role, are a The admin-role is granted to a role of those subjects who manage the business object. E.g. a 'package' is manged by the admin of the customer. -Whoever has the admin-role assigned, can usually edit the related business-object but not deleting (or deactivating) it. +Whoever has the admin-role assigned, can usually update the related business-object but not delete (or deactivating) it. -The admin-role also comprises lesser roles, through which the view-permission is granted. +The admin-role also comprises lesser roles, through which the SELECT-permission is granted. #### agent The agent-role is not used in the examples of this document, because it's for more complex cases. -It's usually granted to those roles and users who represent the related business-object, but are not allowed to edit it. +It's usually granted to those roles and users who represent the related business-object, but are not allowed to update it. Other than the tenant-role, it usually offers broader visibility of sub-business-objects (joined entities). E.g. a package-admin is allowed to see the related debitor-business-object, @@ -235,19 +233,19 @@ but not its banking data. #### tenant -The tenant-role is granted to everybody who needs to be able to view the business-object and (probably some) related business-objects. +The tenant-role is granted to everybody who needs to be able to select the business-object and (probably some) related business-objects. Usually all owners, admins and tenants of sub-objects get this role granted. -Some business-objects only have very limited data directly in the main business-object and store more sensitive data in special sub-objects (e.g. 'customer-details') to which tenants of sub-objects of the main-object (e.g. package admins) do not get view permission. +Some business-objects only have very limited data directly in the main business-object and store more sensitive data in special sub-objects (e.g. 'customer-details') to which tenants of sub-objects of the main-object (e.g. package admins) do not get SELECT permission. #### guest Like the agent-role, the guest-role too is not used in the examples of this document, because it's for more complex cases. -If the guest-role exists, the view-permission is granted to it, instead of to the tenant-role. +If the guest-role exists, the SELECT-permission is granted to it, instead of to the tenant-role. Other than the tenant-role, the guest-roles does never grant any roles of related objects. -Also, if the guest-role exists, the tenant-role receives the view-permission through the guest-role. +Also, if the guest-role exists, the tenant-role receives the SELECT-permission through the guest-role. ### Referenced Business Objects and Role-Depreciation @@ -263,7 +261,7 @@ The admin-role of one object could be granted visibility to another object throu But not in all cases role-depreciation takes place. E.g. often a tenant-role is granted another tenant-role, -because it should be again allowed to view sub-objects. +because it should be again allowed to select sub-objects. The same for the agent-role, often it is granted another agent-role. @@ -297,14 +295,14 @@ package RbacRoles { RbacUsers -[hidden]> RbacRoles package RbacPermissions { - object PermCustXyz_View - object PermCustXyz_Edit - object PermCustXyz_Delete - object PermCustXyz_AddPackage - object PermPackXyz00_View - object PermPackXyz00_Edit - object PermPackXyz00_Delete - object PermPackXyz00_AddUser + object PermCustXyz_SELECT + object PermCustXyz_UPDATE + object PermCustXyz_DELETE + object PermCustXyz_INSERT:Package + object PermPackXyz00_SELECT + object PermPackXyz00_EDIT + object PermPackXyz00_DELETE + object PermPackXyz00_INSERT:USER } RbacRoles -[hidden]> RbacPermissions @@ -322,23 +320,23 @@ RoleAdministrators o..> RoleCustXyz_Owner RoleCustXyz_Owner o-> RoleCustXyz_Admin RoleCustXyz_Admin o-> RolePackXyz00_Owner -RoleCustXyz_Owner o--> PermCustXyz_Edit -RoleCustXyz_Owner o--> PermCustXyz_Delete -RoleCustXyz_Admin o--> PermCustXyz_View -RoleCustXyz_Admin o--> PermCustXyz_AddPackage -RolePackXyz00_Owner o--> PermPackXyz00_View -RolePackXyz00_Owner o--> PermPackXyz00_Edit -RolePackXyz00_Owner o--> PermPackXyz00_Delete -RolePackXyz00_Owner o--> PermPackXyz00_AddUser +RoleCustXyz_Owner o--> PermCustXyz_UPDATE +RoleCustXyz_Owner o--> PermCustXyz_DELETE +RoleCustXyz_Admin o--> PermCustXyz_SELECT +RoleCustXyz_Admin o--> PermCustXyz_INSERT:Package +RolePackXyz00_Owner o--> PermPackXyz00_SELECT +RolePackXyz00_Owner o--> PermPackXyz00_UPDATE +RolePackXyz00_Owner o--> PermPackXyz00_DELETE +RolePackXyz00_Owner o--> PermPackXyz00_INSERT:User -PermCustXyz_View o--> CustXyz -PermCustXyz_Edit o--> CustXyz -PermCustXyz_Delete o--> CustXyz -PermCustXyz_AddPackage o--> CustXyz -PermPackXyz00_View o--> PackXyz00 -PermPackXyz00_Edit o--> PackXyz00 -PermPackXyz00_Delete o--> PackXyz00 -PermPackXyz00_AddUser o--> PackXyz00 +PermCustXyz_SELECT o--> CustXyz +PermCustXyz_UPDATE o--> CustXyz +PermCustXyz_DELETE o--> CustXyz +PermCustXyz_INSERT:Package o--> CustXyz +PermPackXyz00_SELECT o--> PackXyz00 +PermPackXyz00_UPDATE o--> PackXyz00 +PermPackXyz00_DELETE o--> PackXyz00 +PermPackXyz00_INSERT:User o--> PackXyz00 @enduml ``` @@ -353,12 +351,12 @@ To support the RBAC system, for each business-object-table, some more artifacts Not yet implemented, but planned are these actions: -- an `ON DELETE ... DO INSTEAD` rule to allow `SQL DELETE` if applicable for the business-object-table and the user has 'delete' permission, -- an `ON UPDATE ... DO INSTEAD` rule to allow `SQL UPDATE` if the user has 'edit' right, -- an `ON INSERT ... DO INSTEAD` rule to allow `SQL INSERT` if the user has 'add-..' right to the parent-business-object. +- an `ON DELETE ... DO INSTEAD` rule to allow `SQL DELETE` if applicable for the business-object-table and the user has 'DELETE' permission, +- an `ON UPDATE ... DO INSTEAD` rule to allow `SQL UPDATE` if the user has 'UPDATE' right, +- an `ON INSERT ... DO INSTEAD` rule to allow `SQL INSERT` if the user has the 'INSERT' right for the parent-business-object. The restricted view takes the current user from a session property and applies the hierarchy of its roles all the way down to the permissions related to the respective business-object-table. -This way, each user can only view the data they have 'view'-permission for, only create those they have 'add-...'-permission, only update those they have 'edit'- and only delete those they have 'delete'-permission to. +This way, each user can only select the data they have 'SELECT'-permission for, only create those they have 'add-...'-permission, only update those they have 'UPDATE'- and only delete those they have 'DELETE'-permission to. ### Current User @@ -458,26 +456,26 @@ allow_mixing entity "BObj customer#xyz" as boCustXyz together { - entity "Perm customer#xyz *" as permCustomerXyzAll - permCustomerXyzAll --> boCustXyz + entity "Perm customer#xyz *" as permCustomerXyzDELETE + permCustomerXyzDELETE --> boCustXyz - entity "Perm customer#xyz add-package" as permCustomerXyzAddPack - permCustomerXyzAddPack --> boCustXyz + entity "Perm customer#xyz INSERT:package" as permCustomerXyzINSERT:package + permCustomerXyzINSERT:package --> boCustXyz - entity "Perm customer#xyz view" as permCustomerXyzView - permCustomerXyzView --> boCustXyz + entity "Perm customer#xyz SELECT" as permCustomerXyzSELECT + permCustomerXyzSELECT--> boCustXyz } entity "Role customer#xyz.tenant" as roleCustXyzTenant -roleCustXyzTenant --> permCustomerXyzView +roleCustXyzTenant --> permCustomerXyzSELECT entity "Role customer#xyz.admin" as roleCustXyzAdmin roleCustXyzAdmin --> roleCustXyzTenant -roleCustXyzAdmin --> permCustomerXyzAddPack +roleCustXyzAdmin --> permCustomerXyzINSERT:package entity "Role customer#xyz.owner" as roleCustXyzOwner roleCustXyzOwner ..> roleCustXyzAdmin -roleCustXyzOwner --> permCustomerXyzAll +roleCustXyzOwner --> permCustomerXyzDELETE actor "Customer XYZ Admin" as actorCustXyzAdmin actorCustXyzAdmin --> roleCustXyzAdmin @@ -487,8 +485,6 @@ roleAdmins --> roleCustXyzOwner actor "Any Hostmaster" as actorHostmaster actorHostmaster --> roleAdmins - - @enduml ``` @@ -527,17 +523,17 @@ allow_mixing entity "BObj package#xyz00" as boPacXyz00 together { - entity "Perm package#xyz00 *" as permPackageXyzAll - permPackageXyzAll --> boPacXyz00 + entity "Perm package#xyz00 *" as permPackageXyzDELETE + permPackageXyzDELETE --> boPacXyz00 - entity "Perm package#xyz00 add-domain" as permPacXyz00AddUser - permPacXyz00AddUser --> boPacXyz00 + entity "Perm package#xyz00 INSERT:domain" as permPacXyz00INSERT:user + permPacXyz00INSERT:user --> boPacXyz00 - entity "Perm package#xyz00 edit" as permPacXyz00Edit - permPacXyz00Edit --> boPacXyz00 + entity "Perm package#xyz00 UPDATE" as permPacXyz00UPDATE + permPacXyz00UPDATE --> boPacXyz00 - entity "Perm package#xyz00 view" as permPacXyz00View - permPacXyz00View --> boPacXyz00 + entity "Perm package#xyz00 SELECT" as permPacXyz00SELECT + permPacXyz00SELECT --> boPacXyz00 } package { @@ -552,11 +548,11 @@ package { entity "Role package#xyz00.tenant" as rolePacXyz00Tenant } -rolePacXyz00Tenant --> permPacXyz00View +rolePacXyz00Tenant --> permPacXyz00SELECT rolePacXyz00Tenant --> roleCustXyzTenant rolePacXyz00Owner --> rolePacXyz00Admin -rolePacXyz00Owner --> permPackageXyzAll +rolePacXyz00Owner --> permPackageXyzDELETE roleCustXyzAdmin --> rolePacXyz00Owner roleCustXyzAdmin --> roleCustXyzTenant @@ -564,8 +560,8 @@ roleCustXyzAdmin --> roleCustXyzTenant roleCustXyzOwner ..> roleCustXyzAdmin rolePacXyz00Admin --> rolePacXyz00Tenant -rolePacXyz00Admin --> permPacXyz00AddUser -rolePacXyz00Admin --> permPacXyz00Edit +rolePacXyz00Admin --> permPacXyz00INSERT:user +rolePacXyz00Admin --> permPacXyz00UPDATE actor "Package XYZ00 Admin" as actorPacXyzAdmin actorPacXyzAdmin -l-> rolePacXyz00Admin @@ -624,10 +620,10 @@ Let's have a look at the two view queries: WHERE target.uuid IN ( SELECT uuid FROM queryAccessibleObjectUuidsOfSubjectIds( - 'view', 'customer', currentSubjectsUuids())); + 'SELECTÄ, 'customer', currentSubjectsUuids())); This view should be automatically updatable. -Where, for updates, we actually have to check for 'edit' instead of 'view' operation, which makes it a bit more complicated. +Where, for updates, we actually have to check for 'UPDATE' instead of 'SELECTÄ operation, which makes it a bit more complicated. With the larger dataset, the test suite initially needed over 7 seconds with this view query. At this point the second variant was tried. @@ -642,7 +638,7 @@ Looks like the query optimizer needed some statistics to find the best path. SELECT DISTINCT target.* FROM customer AS target JOIN queryAccessibleObjectUuidsOfSubjectIds( - 'view', 'customer', currentSubjectsUuids()) AS allowedObjId + 'SELECTÄ, 'customer', currentSubjectsUuids()) AS allowedObjId ON target.uuid = allowedObjId; This view cannot is not updatable automatically, @@ -688,7 +684,7 @@ Otherwise, it would not be possible to assign roles to new users. All roles are system-defined and cannot be created or modified by any external API. -Users can view only the roles to which they are assigned. +Users can view only the roles to which are granted to them. ## RbacGrant diff --git a/sql/rbac-tests.sql b/sql/rbac-tests.sql index 4e179dee..ad017189 100644 --- a/sql/rbac-tests.sql +++ b/sql/rbac-tests.sql @@ -19,13 +19,13 @@ select * FROM queryAllPermissionsOfSubjectId(findRbacUser('rosa@example.com')); select * -FROM queryAllRbacUsersWithPermissionsFor(findEffectivePermissionId('customer', +FROM queryAllRbacUsersWithPermissionsFor(findPermissionId('customer', (SELECT uuid FROM RbacObject WHERE objectTable = 'customer' LIMIT 1), 'add-package')); select * -FROM queryAllRbacUsersWithPermissionsFor(findEffectivePermissionId('package', +FROM queryAllRbacUsersWithPermissionsFor(findPermissionId('package', (SELECT uuid FROM RbacObject WHERE objectTable = 'package' LIMIT 1), - 'delete')); + 'DELETE')); DO LANGUAGE plpgsql $$ @@ -34,12 +34,12 @@ $$ result bool; BEGIN userId = findRbacUser('superuser-alex@hostsharing.net'); - result = (SELECT * FROM isPermissionGrantedToSubject(findEffectivePermissionId('package', 94928, 'add-package'), userId)); + result = (SELECT * FROM isPermissionGrantedToSubject(findPermissionId('package', 94928, 'add-package'), userId)); IF (result) THEN RAISE EXCEPTION 'expected permission NOT to be granted, but it is'; end if; - result = (SELECT * FROM isPermissionGrantedToSubject(findEffectivePermissionId('package', 94928, 'view'), userId)); + result = (SELECT * FROM isPermissionGrantedToSubject(findPermissionId('package', 94928, 'SELECT'), userId)); IF (NOT result) THEN RAISE EXCEPTION 'expected permission to be granted, but it is NOT'; end if; diff --git a/sql/rbac-view-option-experiments.sql b/sql/rbac-view-option-experiments.sql index d3ef736a..2c4508ae 100644 --- a/sql/rbac-view-option-experiments.sql +++ b/sql/rbac-view-option-experiments.sql @@ -20,7 +20,7 @@ CREATE POLICY customer_policy ON customer TO restricted USING ( -- id=1000 - isPermissionGrantedToSubject(findEffectivePermissionId('test_customer', id, 'view'), currentUserUuid()) + isPermissionGrantedToSubject(findPermissionId('test_customer', id, 'SELECT'), currentUserUuid()) ); SET SESSION AUTHORIZATION restricted; @@ -35,7 +35,7 @@ SELECT * FROM customer; CREATE OR REPLACE RULE "_RETURN" AS ON SELECT TO cust_view DO INSTEAD - SELECT * FROM customer WHERE isPermissionGrantedToSubject(findEffectivePermissionId('test_customer', id, 'view'), currentUserUuid()); + SELECT * FROM customer WHERE isPermissionGrantedToSubject(findPermissionId('test_customer', id, 'SELECT'), currentUserUuid()); SELECT * from cust_view LIMIT 10; select queryAllPermissionsOfSubjectId(findRbacUser('superuser-alex@hostsharing.net')); @@ -52,7 +52,7 @@ CREATE OR REPLACE RULE "_RETURN" AS DO INSTEAD SELECT c.uuid, c.reference, c.prefix FROM customer AS c JOIN queryAllPermissionsOfSubjectId(currentUserUuid()) AS p - ON p.objectTable='test_customer' AND p.objectUuid=c.uuid AND p.op in ('*', 'view'); + ON p.objectTable='test_customer' AND p.objectUuid=c.uuid AND p.op = 'SELECT'; GRANT ALL PRIVILEGES ON cust_view TO restricted; SET SESSION SESSION AUTHORIZATION restricted; @@ -68,7 +68,7 @@ CREATE OR REPLACE VIEW cust_view AS SELECT c.uuid, c.reference, c.prefix FROM customer AS c JOIN queryAllPermissionsOfSubjectId(currentUserUuid()) AS p - ON p.objectUuid=c.uuid AND p.op in ('*', 'view'); + ON p.objectUuid=c.uuid AND p.op = 'SELECT'; GRANT ALL PRIVILEGES ON cust_view TO restricted; SET SESSION SESSION AUTHORIZATION restricted; @@ -81,7 +81,7 @@ select rr.uuid, rr.type from RbacGrants g join RbacReference RR on g.ascendantUuid = RR.uuid where g.descendantUuid in ( select uuid from queryAllPermissionsOfSubjectId(findRbacUser('alex@example.com')) - where objectTable='test_customer' and op in ('*', 'view')); + where objectTable='test_customer' and op = 'SELECT'); call grantRoleToUser(findRoleId('test_customer#aaa.admin'), findRbacUser('aaaaouq@example.com')); diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountEntity.java index 24258251..de256ca1 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountEntity.java @@ -64,17 +64,17 @@ public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable { .createRole(OWNER, (with) -> { with.owningUser(CREATOR); with.incomingSuperRole(GLOBAL, ADMIN); - with.permission(ALL); + with.permission(DELETE); }) .createSubRole(ADMIN, (with) -> { - with.permission(EDIT); + with.permission(UPDATE); }) .createSubRole(REFERRER, (with) -> { - with.permission(VIEW); + with.permission(SELECT); }); } public static void main(String[] args) throws IOException { - rbac().generateWithBaseFileName("243-hs-office-bankaccount-rbac"); + rbac().generateWithBaseFileName("243-hs-office-bankaccount-rbac-generated"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java index 7d655fc3..406b232c 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java @@ -68,17 +68,17 @@ public class HsOfficeContactEntity implements Stringifyable, HasUuid { .createRole(OWNER, (with) -> { with.owningUser(CREATOR); with.incomingSuperRole(GLOBAL, ADMIN); - with.permission(ALL); + with.permission(DELETE); }) .createSubRole(ADMIN, (with) -> { - with.permission(EDIT); + with.permission(UPDATE); }) .createSubRole(REFERRER, (with) -> { - with.permission(VIEW); + with.permission(SELECT); }); } public static void main(String[] args) throws IOException { - rbac().generateWithBaseFileName("203-hs-office-contact-rbac"); + rbac().generateWithBaseFileName("203-hs-office-contact-rbac-generated"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java index 89dcf05d..29a9452d 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java @@ -140,9 +140,9 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { WHERE r.relType = 'ACCOUNTING' AND r.relHolderUuid = ${REF}.debitorRelUuid """), dependsOnColumn("debitorRelUuid")) - .createPermission(ALL).grantedTo("debitorRel", OWNER) - .createPermission(EDIT).grantedTo("debitorRel", ADMIN) - .createPermission(VIEW).grantedTo("debitorRel", TENANT) + .createPermission(DELETE).grantedTo("debitorRel", OWNER) + .createPermission(UPDATE).grantedTo("debitorRel", ADMIN) + .createPermission(SELECT).grantedTo("debitorRel", TENANT) .importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class, dependsOnColumn("refundBankAccountUuid"), fetchedBySql(""" @@ -171,6 +171,6 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { } public static void main(String[] args) throws IOException { - rbac().generateWithBaseFileName("273-hs-office-debitor-rbac"); + rbac().generateWithBaseFileName("273-hs-office-debitor-rbac-generated"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java index dbc6c17c..e557f9ae 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java @@ -103,6 +103,6 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable { } public static void main(String[] args) throws IOException { - rbac().generateWithBaseFileName("234-hs-office-partner-details-rbac"); + rbac().generateWithBaseFileName("234-hs-office-partner-details-rbac-generated"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java index cbd184c6..aa000f67 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java @@ -20,7 +20,7 @@ import java.util.UUID; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.VIEW; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL.fetchedBySql; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; @@ -95,19 +95,19 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid { .importRootEntityAliasProxy("partnerRel", HsOfficeRelationshipEntity.class, fetchedBySql("SELECT * FROM hs_office_relationship AS r WHERE r.uuid = ${ref}.partnerRoleUuid"), dependsOnColumn("partnerRelUuid")) - .createPermission(ALL).grantedTo("partnerRel", ADMIN) - .createPermission(EDIT).grantedTo("partnerRel", AGENT) - .createPermission(VIEW).grantedTo("partnerRel", TENANT) + .createPermission(DELETE).grantedTo("partnerRel", ADMIN) + .createPermission(UPDATE).grantedTo("partnerRel", AGENT) + .createPermission(SELECT).grantedTo("partnerRel", TENANT) .importSubEntityAlias("partnerDetails", HsOfficePartnerDetailsEntity.class, fetchedBySql("SELECT * FROM hs_office_partner_details AS d WHERE d.uuid = ${ref}.detailsUuid"), dependsOnColumn("detailsUuid")) - .createPermission("partnerDetails", ALL).grantedTo("partnerRel", ADMIN) - .createPermission("partnerDetails", EDIT).grantedTo("partnerRel", AGENT) - .createPermission("partnerDetails", VIEW).grantedTo("partnerRel", AGENT); + .createPermission("partnerDetails", DELETE).grantedTo("partnerRel", ADMIN) + .createPermission("partnerDetails", UPDATE).grantedTo("partnerRel", AGENT) + .createPermission("partnerDetails", SELECT).grantedTo("partnerRel", AGENT); } public static void main(String[] args) throws IOException { - rbac().generateWithBaseFileName("233-hs-office-partner-rbac"); + rbac().generateWithBaseFileName("233-hs-office-partner-rbac-generated"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java index 37874df3..fcc89dde 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java @@ -70,20 +70,20 @@ public class HsOfficePersonEntity implements HasUuid, Stringifyable { .withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)")) .withUpdatableColumns("personType", "tradeName", "givenName", "familyName") .createRole(OWNER, (with) -> { - with.permission(ALL); + with.permission(DELETE); with.owningUser(CREATOR); with.incomingSuperRole(GLOBAL, ADMIN); }) .createSubRole(ADMIN, (with) -> { - with.permission(EDIT); + with.permission(UPDATE); }) .createSubRole(REFERRER, (with) -> { - with.permission(VIEW); + with.permission(SELECT); }); } public static void main(String[] args) throws IOException { - rbac().generateWithBaseFileName("213-hs-office-person-rbac"); + rbac().generateWithBaseFileName("213-hs-office-person-rbac-generated"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java index bb555162..1ec9fd74 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java @@ -103,11 +103,11 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable { .createRole(OWNER, (with) -> { with.owningUser(CREATOR); with.incomingSuperRole(GLOBAL, ADMIN); - with.permission(ALL); + with.permission(DELETE); }) .createSubRole(ADMIN, (with) -> { with.incomingSuperRole("anchorPerson", ADMIN); - with.permission(EDIT); + with.permission(UPDATE); }) .createSubRole(AGENT, (with) -> { with.incomingSuperRole("holderPerson", ADMIN); @@ -118,11 +118,11 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable { with.outgoingSubRole("anchorPerson", REFERRER); with.outgoingSubRole("holderPerson", REFERRER); with.outgoingSubRole("contact", REFERRER); - with.permission(VIEW); + with.permission(SELECT); }); } public static void main(String[] args) throws IOException { - rbac().generateWithBaseFileName("223-hs-office-relationship-rbac"); + rbac().generateWithBaseFileName("223-hs-office-relationship-rbac-generated"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java index ac0d6f99..7fcef622 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java @@ -105,10 +105,10 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid { .createRole(OWNER, (with) -> { with.owningUser(CREATOR); with.incomingSuperRole(GLOBAL, ADMIN); - with.permission(ALL); + with.permission(DELETE); }) .createSubRole(ADMIN, (with) -> { - with.permission(EDIT); + with.permission(UPDATE); }) .createSubRole(AGENT, (with) -> { with.outgoingSubRole("bankAccount", REFERRER); @@ -118,11 +118,11 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid { with.incomingSuperRole("bankAccount", ADMIN); with.incomingSuperRole("debitorRel", AGENT); with.outgoingSubRole("debitorRel", TENANT); - with.permission(VIEW); + with.permission(SELECT); }); } public static void main(String[] args) throws IOException { - rbac().generateWithBaseFileName("253-hs-office-sepamandate-rbac"); + rbac().generateWithBaseFileName("253-hs-office-sepamandate-rbac-generated"); } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java index bff58ce5..adcb1c36 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java @@ -1,7 +1,12 @@ package net.hostsharing.hsadminng.rbac.rbacdef; +import java.util.Optional; + +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacGrantDefinition.GrantType.PERM_TO_ROLE; import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with; +import static org.apache.commons.lang3.StringUtils.capitalize; +import static org.apache.commons.lang3.StringUtils.uncapitalize; public class InsertTriggerGenerator { @@ -16,25 +21,8 @@ public class InsertTriggerGenerator { void generateTo(final StringWriter plPgSql) { generateLiquibaseChangesetHeader(plPgSql); generateGrantInsertRoleToExistingCustomers(plPgSql); - rbacDef.getGrantDefs().stream() - .filter(g -> g.isToCreate() && g.grantType() == PERM_TO_ROLE && - g.getPermDef().getPermission() == RbacView.Permission.INSERT ) - .forEach(g -> { - plPgSql.writeLn(""" - /** - Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. - */ - create trigger ${rawSubTable}_it - before insert - on ${rawSubTable} - for each row - when ( hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') ) - execute procedure insertNotAllowedForCurrentSubjects('${rawSubTable}'); - """, - with("rawSubTable", g.getPermDef().entityAlias.getRawTableName()), - with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName() )); - }); - + generateInsertPermissionGrantTrigger(plPgSql); + generateInsertCheckTrigger(plPgSql); plPgSql.writeLn("--//"); } @@ -48,29 +36,104 @@ public class InsertTriggerGenerator { } private void generateGrantInsertRoleToExistingCustomers(final StringWriter plPgSql) { - plPgSql.writeLn(""" + getOptionalInsertSuperRole().ifPresent( superRoleDef -> { + plPgSql.writeLn(""" /* - Creates an INSERT INTO ${rawSubTableName} permission for the related ${rawSuperTableName} row. + Creates INSERT INTO ${rawSubTableName} permissions for the related ${rawSuperTableName} rows. */ do language plpgsql $$ declare row ${rawSuperTableName}; - permissionUuids uuid[]; + permissionUuid uuid; roleUuid uuid; begin + call defineContext('generated Liquibase: create INSERT INTO ${rawSubTableName} permissions for the related ${rawSuperTableName} rows'); + FOR row IN SELECT * FROM ${rawSuperTableName} LOOP - roleUuid := ${rawSuperRoleDescriptor}(row); - permissionUuids := createPermissions(row.uuid, array ['INSERT:${rawSubTableName}']); - call grantPermissionsToRole(roleUuid, permissionUuids); + roleUuid := findRoleId(${rawSuperRoleDescriptor}(row)); + permissionUuid := createPermission(row.uuid, 'INSERT', '${rawSubTableName}'); + call grantPermissionToRole(roleUuid, permissionUuid); END LOOP; END; $$; """, - with("rawSubTableName", "test_package"), // TODO - with("rawSuperTableName", "test_customer"), // TODO - with("rawSuperRoleDescriptor", "testCustomerAdmin") // TODO + with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), + with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()), + with("rawSuperRoleDescriptor", toVar(superRoleDef)) ); + }); + } + + private void generateInsertPermissionGrantTrigger(final StringWriter plPgSql) { + getOptionalInsertSuperRole().ifPresent( superRoleDef -> { + plPgSql.writeLn(""" + /** + Adds ${rawSubTableName} INSERT permission to specified role of new ${rawSuperTableName} rows. + */ + create or replace function ${rawSubTableName}_${rawSuperTableName}_insert_tf() + returns trigger + language plpgsql + strict as $$ + begin + call grantPermissionToRole( + ${rawSuperRoleDescriptor}(NEW), + createPermission(NEW.uuid, 'INSERT', '${rawSubTableName}')); + return NEW; + end; $$; + + create trigger ${rawSubTableName}_${rawSuperTableName}_insert_tg + after insert on ${rawSuperTableName} + for each row + execute procedure ${rawSubTableName}_${rawSuperTableName}_insert_tf(); + """, + with("rawSubTableName", rbacDef.getRootEntityAlias().getRawTableName()), + with("rawSuperTableName", superRoleDef.getEntityAlias().getRawTableName()), + with("rawSuperRoleDescriptor", toVar(superRoleDef)) + ); + }); + } + + private void generateInsertCheckTrigger(final StringWriter plPgSql) { + rbacDef.getGrantDefs().stream() + .filter(g -> g.isToCreate() && g.grantType() == PERM_TO_ROLE && + g.getPermDef().getPermission() == INSERT ) + .forEach(g -> { + plPgSql.writeLn(""" + /** + Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. + */ + create or replace function ${rawSubTable}_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ + begin + raise exception 'insert into ${rawSubTable} not allowed for current subjects %', currentSubjectsUuids(); + end; $$; + + create trigger ${rawSubTable}_insert_permission_check_tg + before insert on ${rawSubTable} + for each row + when ( not hasInsertPermission(NEW.${referenceColumn}, 'INSERT', '${rawSubTable}') ) + execute procedure ${rawSubTable}_insert_permission_missing_tf(); + """, + with("rawSubTable", g.getPermDef().entityAlias.getRawTableName()), + with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName() )); + }); + } + + private Optional getOptionalInsertSuperRole() { + return rbacDef.getGrantDefs().stream() + .filter(g -> g.grantType() == PERM_TO_ROLE) + .filter(g -> g.getPermDef().toCreate && g.getPermDef().getPermission() == INSERT) + .map(RbacView.RbacGrantDefinition::getSuperRoleDef) + .reduce((x, y) -> { + throw new IllegalStateException("only a single INSERT permission grant allowed"); + }); + } + + + private static String toVar(final RbacView.RbacRoleDefinition roleDef) { + return uncapitalize(roleDef.getEntityAlias().simpleName()) + capitalize(roleDef.getRole().roleName()); } } diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java index b92f8f38..064a7350 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacView.java @@ -75,6 +75,7 @@ public class RbacView { public RbacView withUpdatableColumns(final String... columnNames) { Collections.addAll(updatableColumns, columnNames); + // TODO: automatically add @Version column, otherwise optimistic locking won't work return this; } @@ -249,8 +250,8 @@ public class RbacView { } public void generateWithBaseFileName(final String baseFileName) { - new RbacViewMermaidFlowchart(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + "-generated.md")); - new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + "-generated.sql")); + new RbacViewMermaidFlowchart(this).generateToMarkdownFile(Path.of(OUTPUT_BASEDIR, baseFileName + ".md")); + new RbacViewPostgresGenerator(this).generateToChangeLog(Path.of(OUTPUT_BASEDIR, baseFileName + ".sql")); } public class RbacGrantBuilder { @@ -465,6 +466,7 @@ public class RbacView { public class RbacUserReference { public enum UserRole { + GLOBAL_ADMIN, CREATOR } @@ -628,10 +630,10 @@ public class RbacView { public record Permission(String permission) { - public static final Permission INSERT = new Permission("insert"); - public static final Permission ALL = new Permission("*"); - public static final Permission EDIT = new Permission("edit"); - public static final Permission VIEW = new Permission("view"); + public static final Permission INSERT = new Permission("INSERT"); + public static final Permission DELETE = new Permission("DELETE"); + public static final Permission UPDATE = new Permission("UPDATE"); + public static final Permission SELECT = new Permission("SELECT"); public static Permission custom(final String permission) { return new Permission(permission); @@ -671,7 +673,7 @@ public class RbacView { } /** - * DSL method to specify there there is no SQL query specified. + * DSL method to explicitly specify that there is no SQL query. * * @return a wrapped SQL definition object representing a noop query */ diff --git a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java index ba01d61f..624ee471 100644 --- a/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java +++ b/src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RolesGrantsAndPermissionsGenerator.java @@ -81,8 +81,10 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.writeLn(); plPgSql.writeLn("begin"); plPgSql.indented(() -> { + plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);"); generateCreateRolesAndGrantsAfterInsert(plPgSql); plPgSql.ensureSingleEmptyLine(); + plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);"); }); plPgSql.writeLn("end; $$;"); plPgSql.writeLn(); @@ -94,7 +96,7 @@ class RolesGrantsAndPermissionsGenerator { Called from the AFTER UPDATE TRIGGER to re-wire the grants. */ - create or replace procedure updateRbacGrantsFor${simpleEntityName}( + create or replace procedure updateRbacRulesFor${simpleEntityName}( OLD ${rawTableName}, NEW ${rawTableName} ) @@ -117,8 +119,10 @@ class RolesGrantsAndPermissionsGenerator { plPgSql.writeLn(); plPgSql.writeLn("begin"); plPgSql.indented(() -> { + plPgSql.writeLn("call enterTriggerForObjectUuid(NEW.uuid);"); generateUpdateRolesAndGrantsAfterUpdate(plPgSql); plPgSql.ensureSingleEmptyLine(); + plPgSql.writeLn("call leaveTriggerForObjectUuid(NEW.uuid);"); }); plPgSql.writeLn("end; $$;"); plPgSql.writeLn(); @@ -218,7 +222,7 @@ class RolesGrantsAndPermissionsGenerator { .replace("${subRoleRef}", roleRef(OLD, grantDef.getSubRoleDef())) .replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef())); case PERM_TO_ROLE -> "call revokePermissionFromRole(${permRef}, ${superRoleRef});" - .replace("${permRef}", permRef(OLD, grantDef.getPermDef())) + .replace("${permRef}", findPerm(OLD, grantDef.getPermDef())) .replace("${superRoleRef}", roleRef(OLD, grantDef.getSuperRoleDef())); }; } @@ -231,14 +235,23 @@ class RolesGrantsAndPermissionsGenerator { .replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef())); case PERM_TO_ROLE -> grantDef.getPermDef().getPermission() == INSERT ? "" - : "call grantPermissionsToRole(${permRef}, ${superRoleRef});" - .replace("${permRef}", permRef(NEW, grantDef.getPermDef())) + : "call grantPermissionToRole(${permRef}, ${superRoleRef});" + .replace("${permRef}", createPerm(NEW, grantDef.getPermDef())) .replace("${superRoleRef}", roleRef(NEW, grantDef.getSuperRoleDef())); }; } - private String permRef(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { - return "createPermissions(${entityRef}.uuid, array ['${perm}'])" + private String findPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { + return permRef("findPermissionId", ref, permDef); + } + + private String createPerm(final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { + return permRef("createPermission", ref, permDef); + } + + private String permRef(final String functionName, final PostgresTriggerReference ref, final RbacPermissionDefinition permDef) { + return "${prefix}(${entityRef}.uuid, '${perm}')" + .replace("${prefix}", functionName) .replace("${entityRef}", rbacDef.isRootEntityAlias(permDef.entityAlias) ? ref.name() : refVarName(ref, permDef.entityAlias)) @@ -412,13 +425,12 @@ class RolesGrantsAndPermissionsGenerator { language plpgsql strict as $$ begin - call buildRbacSystemFor${simpleEntityName}(TG_OP, OLD, NEW); + call buildRbacSystemFor${simpleEntityName}(NEW); return NEW; end; $$; create trigger insertTriggerFor${simpleEntityName}_tg - after insert - on ${rawTableName} + after insert on ${rawTableName} for each row execute procedure insertTriggerFor${simpleEntityName}_tf(); """ @@ -444,13 +456,12 @@ class RolesGrantsAndPermissionsGenerator { language plpgsql strict as $$ begin - call buildRbacSystemFor${simpleEntityName}(NEW); + call updateRbacRulesFor${simpleEntityName}(OLD, NEW); return NEW; end; $$; create trigger updateTriggerFor${simpleEntityName}_tg - after update - on ${rawTableName} + after update on ${rawTableName} for each row execute procedure updateTriggerFor${simpleEntityName}_tf(); """ diff --git a/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java b/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java index 57e29475..8101286b 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntity.java @@ -43,13 +43,15 @@ public class TestCustomerEntity implements HasUuid { .withUpdatableColumns("reference", "prefix", "adminUserName") .createRole(OWNER, (with) -> { - with.owningUser(CREATOR); + // with.owningUser(CREATOR); TODO: needs assumed role with.incomingSuperRole(GLOBAL, ADMIN); - with.permission(ALL); + with.permission(DELETE); + }) + .createSubRole(ADMIN, (with) -> { + with.permission(UPDATE); }) - .createSubRole(ADMIN) .createSubRole(TENANT, (with) -> { - with.permission(VIEW); + with.permission(SELECT); }); } diff --git a/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java b/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java index ceb88ec8..81d577bc 100644 --- a/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/test/pac/TestPackageEntity.java @@ -47,7 +47,7 @@ public class TestPackageEntity implements HasUuid { public static RbacView rbac() { return rbacViewFor("package", TestPackageEntity.class) .withIdentityView(SQL.projection("name")) - .withUpdatableColumns("customerUuid", "description") + .withUpdatableColumns("version", "customerUuid", "description") .importEntityAlias("customer", TestCustomerEntity.class, dependsOnColumn("customerUuid"), @@ -60,13 +60,13 @@ public class TestPackageEntity implements HasUuid { .createRole(OWNER, (with) -> { with.owningUser(CREATOR); with.incomingSuperRole("customer", ADMIN).unassumed(); - with.permission(ALL); - with.permission(EDIT); + with.permission(DELETE); + with.permission(UPDATE); }) .createSubRole(ADMIN) .createSubRole(TENANT, (with) -> { with.outgoingSubRole("customer", TENANT); - with.permission(VIEW); + with.permission(SELECT); }); } diff --git a/src/main/resources/db/changelog/010-context.sql b/src/main/resources/db/changelog/010-context.sql index 4820cf9c..11d52f50 100644 --- a/src/main/resources/db/changelog/010-context.sql +++ b/src/main/resources/db/changelog/010-context.sql @@ -66,10 +66,11 @@ begin when others then currentTask := null; end; - if (currentTask is null or currentTask = '') then - raise exception '[401] currentTask must be defined, please call `defineContext(...)`'; - end if; - return currentTask; +-- TODO: uncomment +-- if (currentTask is null or currentTask = '') then +-- raise exception '[401] currentTask must be defined, please call `defineContext(...)`'; +-- end if; + return 'unknown'; -- TODO: currentTask; end; $$; --// diff --git a/src/main/resources/db/changelog/050-rbac-base.sql b/src/main/resources/db/changelog/050-rbac-base.sql index 5e2726f7..9a8926c6 100644 --- a/src/main/resources/db/changelog/050-rbac-base.sql +++ b/src/main/resources/db/changelog/050-rbac-base.sql @@ -365,16 +365,18 @@ create trigger deleteRbacRolesOfRbacObject_Trigger /* */ -create domain RbacOp as varchar(67) - check ( - VALUE = '*' - or VALUE = 'delete' - or VALUE = 'edit' - or VALUE = 'view' - or VALUE = 'assume' - or VALUE ~ '^add-[a-z]+$' - or VALUE ~ '^new-[a-z-]+$' - ); +create domain RbacOp as varchar(67) -- TODO: shorten to 8, once the deprecated values are gone +-- check ( +-- VALUE = 'INSERT' or +-- VALUE = 'DELETE' or +-- VALUE = 'UPDATE' or +-- VALUE = 'SELECT' or +-- VALUE = 'ASSUME' or +-- -- TODO: all values below are deprecated, use insert with table +-- VALUE ~ '^add-[a-z]+$' or +-- VALUE ~ '^new-[a-z-]+$' +-- ); +; create table RbacPermission ( @@ -394,37 +396,38 @@ select exists( select op from RbacPermission p where p.objectUuid = forObjectUuid - and p.op in ('*', forOp) + and p.op = forOp ); $$; -create or replace function createPermissions(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null) - returns uuid[] +create or replace function createPermission(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null) + returns uuid language plpgsql as $$ declare - permissionId uuid; + permissionUuid uuid; begin if (forObjectUuid is null) then raise exception 'forObjectUuid must not be null'; end if; - if (forOp = 'INSERT' && forOpTableName is null) then + if (forOp = 'INSERT' and forOpTableName is null) then raise exception 'INSERT permissions needs forOpTableName'; end if; - if (forOp <> 'INSERT' && forOpTableName is not null) then + if (forOp <> 'INSERT' and forOpTableName is not null) then raise exception 'forOpTableName must only be specified for ops: [INSERT]'; -- currently no other end if; - permissionId = (select uuid from RbacPermission where objectUuid = forObjectUuid and op = forOp and opTableName = forOpTableName); - if (permissionId is null) then + permissionUuid = (select uuid from RbacPermission where objectUuid = forObjectUuid and op = forOp and opTableName = forOpTableName); + if (permissionUuid is null) then insert into RbacReference ("type") values ('RbacPermission') - returning uuid into permissionId; + returning uuid into permissionUuid; + raise warning 'for values (%, %, %, %)', permissionUuid, forObjectUuid, forOp, forOpTableName; -- TODO: remove insert into RbacPermission (uuid, objectUuid, op, opTableName) - values (permissionId, forObjectUuid, forOp, opTableName); + values (permissionUuid, forObjectUuid, forOp, forOpTableName); end if; - return permissionId; + return permissionUuid; end; $$; @@ -439,9 +442,6 @@ begin if (forObjectUuid is null) then raise exception 'forObjectUuid must not be null'; end if; - if (array_length(permitOps, 1) > 1 and '*' = any (permitOps)) then - raise exception '"*" operation must not be assigned along with other operations: %', permitOps; - end if; for i in array_lower(permitOps, 1)..array_upper(permitOps, 1) loop @@ -462,7 +462,7 @@ begin end; $$; -create or replace function findPermissionId(forObjectUuid uuid, forOp RbacOp, opTableName text = null ) +create or replace function findPermissionId(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null ) returns uuid returns null on null input stable -- leakproof @@ -471,24 +471,9 @@ select uuid from RbacPermission p where p.objectUuid = forObjectUuid and p.op = forOp - and p.opTableName = opTableName + and p.opTableName = forOpTableName $$; -create or replace function findEffectivePermissionId(forObjectUuid uuid, forOp RbacOp) - returns uuid - returns null on null input - stable -- leakproof - language plpgsql as $$ -declare - permissionId uuid; -begin - permissionId := findPermissionId(forObjectUuid, forOp); - if permissionId is null and forOp <> '*' then - permissionId := findPermissionId(forObjectUuid, '*'); - end if; - return permissionId; -end $$; - --// -- ============================================================================ @@ -592,8 +577,8 @@ create or replace function hasInsertPermission(objectUuid uuid, forOp RbacOp, ta declare permissionUuid uuid; begin - permissionUuid = findPermissionId(objectUuid, forOp); - + permissionUuid = findPermissionId(objectUuid, forOp, tableName); + return permissionUuid is not null; end; $$; @@ -611,6 +596,20 @@ select exists( ); $$; +create or replace procedure grantPermissionToRole(roleUuid uuid, permissionUuid uuid) + language plpgsql as $$ +begin + perform assertReferenceType('roleId (ascendant)', roleUuid, 'RbacRole'); + perform assertReferenceType('permissionId (descendant)', permissionUuid, 'RbacPermission'); + + insert + into RbacGrants (grantedByTriggerOf, ascendantUuid, descendantUuid, assumed) + values (currentTriggerObjectUuid(), roleUuid, permissionUuid, true) + on conflict do nothing; -- allow granting multiple times +end; +$$; + +-- TODO: deprecated, remove and use grantPermissionToRole(...) create or replace procedure grantPermissionsToRole(roleUuid uuid, permissionIds uuid[]) language plpgsql as $$ begin @@ -742,7 +741,7 @@ begin select descendantUuid from grants) as granted join RbacPermission perm - on granted.descendantUuid = perm.uuid and perm.op in ('*', requiredOp) + on granted.descendantUuid = perm.uuid and perm.op = requiredOp join RbacObject obj on obj.uuid = perm.objectUuid and obj.objectTable = forObjectTable limit maxObjects + 1; @@ -834,6 +833,5 @@ do $$ create role restricted; grant all privileges on all tables in schema public to restricted; end if; - end $$ + end $$; --// - diff --git a/src/main/resources/db/changelog/051-rbac-user-grant.sql b/src/main/resources/db/changelog/051-rbac-user-grant.sql index 23dcbdd4..05332ed9 100644 --- a/src/main/resources/db/changelog/051-rbac-user-grant.sql +++ b/src/main/resources/db/changelog/051-rbac-user-grant.sql @@ -30,7 +30,7 @@ begin insert into RbacGrants (grantedByRoleUuid, ascendantUuid, descendantUuid, assumed) values (grantedByRoleUuid, userUuid, roleUuid, doAssume); - -- TODO.spec: What should happen on mupltiple grants? What if options (doAssume) are not the same? + -- TODO.spec: What should happen on multiple grants? What if options (doAssume) are not the same? -- Most powerful or latest grant wins? What about managed? -- on conflict do nothing; -- allow granting multiple times end; $$; @@ -99,4 +99,19 @@ begin where g.ascendantUuid = userUuid and g.descendantUuid = grantedRoleUuid and g.grantedByRoleUuid = revokeRoleFromUser.grantedByRoleUuid; end; $$; ---/ +--// + +-- ============================================================================ +--changeset rbac-user-grant-REVOKE-PERMISSION-FROM-ROLE:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +create or replace procedure revokePermissionFromRole(permissionUuid uuid, superRoleUuid uuid) + language plpgsql as $$ +begin + -- TODO: call checkRevokeRoleFromUserPreconditions(grantedByRoleUuid, grantedRoleUuid, userUuid); + + raise INFO 'delete from RbacGrants where ascendantUuid = % and descendantUuid = %', superRoleUuid, permissionUuid; + delete from RbacGrants as g + where g.ascendantUuid = superRoleUuid and g.descendantUuid = permissionUuid; +end; $$; +--// diff --git a/src/main/resources/db/changelog/057-rbac-role-builder.sql b/src/main/resources/db/changelog/057-rbac-role-builder.sql index 81a81590..93b36909 100644 --- a/src/main/resources/db/changelog/057-rbac-role-builder.sql +++ b/src/main/resources/db/changelog/057-rbac-role-builder.sql @@ -73,9 +73,10 @@ begin if cardinality(userUuids) > 0 then if grantedByRole is null then - raise exception 'to directly assign users to roles, grantingRole has to be given'; + grantedByRoleUuid := roleUuid; + else + grantedByRoleUuid := getRoleId(grantedByRole, 'fail'); end if; - grantedByRoleUuid := getRoleId(grantedByRole, 'fail'); foreach userUuid in array userUuids loop call grantRoleToUserUnchecked(grantedByRoleUuid, roleUuid, userUuid); diff --git a/src/main/resources/db/changelog/058-rbac-generators.sql b/src/main/resources/db/changelog/058-rbac-generators.sql index fa198308..d77d1f20 100644 --- a/src/main/resources/db/changelog/058-rbac-generators.sql +++ b/src/main/resources/db/changelog/058-rbac-generators.sql @@ -13,8 +13,7 @@ declare begin createInsertTriggerSQL = format($sql$ create trigger createRbacObjectFor_%s_Trigger - before insert - on %s + before insert on %s for each row execute procedure insertRelatedRbacObject(); $sql$, targetTable, targetTable); @@ -145,13 +144,13 @@ begin targetTable := lower(targetTable); /* - Creates a restricted view based on the 'view' permission of the current subject. + Creates a restricted view based on the 'SELECT' permission of the current subject. */ sql := format($sql$ set session session authorization default; create view %1$s_rv as with accessibleObjects as ( - select queryAccessibleObjectUuidsOfSubjectIds('view', '%1$s', currentSubjectsUuids()) + select queryAccessibleObjectUuidsOfSubjectIds('SELECT', '%1$s', currentSubjectsUuids()) ) select target.* from %1$s as target @@ -200,7 +199,7 @@ begin returns trigger language plpgsql as $f$ begin - if old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('delete', '%1$s', currentSubjectsUuids())) then + if old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('DELETE', '%1$s', currentSubjectsUuids())) then delete from %1$s p where p.uuid = old.uuid; return old; end if; @@ -223,7 +222,7 @@ begin /** Instead of update trigger function for the restricted view - based on the 'edit' permission of the current subject. + based on the 'UPDATE' permission of the current subject. */ if columnUpdates is not null then sql := format($sql$ @@ -231,7 +230,7 @@ begin returns trigger language plpgsql as $f$ begin - if old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('edit', '%1$s', currentSubjectsUuids())) then + if old.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('UPDATE', '%1$s', currentSubjectsUuids())) then update %1$s set %2$s where uuid = old.uuid; diff --git a/src/main/resources/db/changelog/113-test-customer-rbac.sql b/src/main/resources/db/changelog/113-test-customer-rbac.sql index d7682cc1..1e5573cc 100644 --- a/src/main/resources/db/changelog/113-test-customer-rbac.sql +++ b/src/main/resources/db/changelog/113-test-customer-rbac.sql @@ -1,4 +1,6 @@ --liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator at 2024-03-06T15:40:13.239729250. + -- ============================================================================ --changeset test-customer-rbac-OBJECT:1 endDelimiter:--// @@ -7,6 +9,7 @@ call generateRelatedRbacObject('test_customer'); --// + -- ============================================================================ --changeset test-customer-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -15,82 +18,85 @@ call generateRbacRoleDescriptors('testCustomer', 'test_customer'); -- ============================================================================ ---changeset test-customer-rbac-ROLES-CREATION:1 endDelimiter:--// +--changeset test-customer-rbac-insert-trigger:1 endDelimiter:--// -- ---------------------------------------------------------------------------- /* - Creates the roles and their assignments for a new customer for the AFTER INSERT TRIGGER. + A Creates the roles, grants and permission for the AFTER INSERT TRIGGER. */ -create or replace function createRbacRolesForTestCustomer() - returns trigger - language plpgsql - strict as $$ -declare - testCustomerOwnerUuid uuid; - customerAdminUuid uuid; -begin - if TG_OP <> 'INSERT' then - raise exception 'invalid usage of TRIGGER AFTER INSERT'; - end if; +create or replace procedure buildRbacSystemForTestCustomer( + NEW test_customer +) + language plpgsql as $$ +declare + +begin call enterTriggerForObjectUuid(NEW.uuid); - -- the owner role with full access for Hostsharing administrators - testCustomerOwnerUuid = createRoleWithGrants( + perform createRoleWithGrants( testCustomerOwner(NEW), - permissions => array['*'], - incomingSuperRoles => array[globalAdmin()] - ); + permissions => array['DELETE'], + incomingSuperRoles => array[globalAdmin()] + ); - -- the admin role for the customer's admins, who can view and add products - customerAdminUuid = createRoleWithGrants( + perform createRoleWithGrants( testCustomerAdmin(NEW), - permissions => array['view', 'add-package'], - -- NO auto assume for customer owner to avoid exploding permissions for administrators - userUuids => array[getRbacUserId(NEW.adminUserName, 'create')], -- implicitly ignored if null - grantedByRole => globalAdmin() - ); + permissions => array['UPDATE'], + incomingSuperRoles => array[testCustomerOwner(NEW)] + ); - -- allow the customer owner role (thus administrators) to assume the customer admin role - call grantRoleToRole(customerAdminUuid, testCustomerOwnerUuid, false); - - -- the tenant role which later can be used by owners+admins of sub-objects perform createRoleWithGrants( testCustomerTenant(NEW), - permissions => array['view'] - ); + permissions => array['SELECT'], + incomingSuperRoles => array[testCustomerAdmin(NEW)] + ); call leaveTriggerForObjectUuid(NEW.uuid); - return NEW; end; $$; /* - An AFTER INSERT TRIGGER which creates the role structure for a new customer. + AFTER INSERT TRIGGER to create the role+grant structure for a new test_customer row. */ -drop trigger if exists createRbacRolesForTestCustomer_Trigger on test_customer; -create trigger createRbacRolesForTestCustomer_Trigger - after insert - on test_customer +create or replace function insertTriggerForTestCustomer_tf() + returns trigger + language plpgsql + strict as $$ +begin + call buildRbacSystemForTestCustomer(NEW); + return NEW; +end; $$; + +create trigger insertTriggerForTestCustomer_tg + after insert on test_customer for each row -execute procedure createRbacRolesForTestCustomer(); +execute procedure insertTriggerForTestCustomer_tf(); + --// +-- ============================================================================ +--changeset test-customer-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +--// -- ============================================================================ --changeset test-customer-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacIdentityView('test_customer', $idName$ - target.prefix + prefix $idName$); --// + -- ============================================================================ --changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacRestrictedView('test_customer', 'target.prefix', +call generateRbacRestrictedView('test_customer', + 'reference', $updates$ reference = new.reference, prefix = new.prefix, @@ -99,47 +105,3 @@ call generateRbacRestrictedView('test_customer', 'target.prefix', --// --- ============================================================================ ---changeset test-customer-rbac-ADD-CUSTOMER:1 endDelimiter:--// --- ---------------------------------------------------------------------------- -/* - Creates a global permission for add-customer and assigns it to the hostsharing admins role. - */ -do language plpgsql $$ - declare - addCustomerPermissions uuid[]; - globalObjectUuid uuid; - globalAdminRoleUuid uuid ; - begin - call defineContext('granting global add-customer permission to global admin role', null, null, null); - - globalAdminRoleUuid := findRoleId(globalAdmin()); - globalObjectUuid := (select uuid from global); - addCustomerPermissions := createPermissions(globalObjectUuid, array ['add-customer']); - call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions); - end; -$$; - -/** - Used by the trigger to prevent the add-customer to current user respectively assumed roles. - */ -create or replace function addTestCustomerNotAllowedForCurrentSubjects() - returns trigger - language PLPGSQL -as $$ -begin - raise exception '[403] add-customer not permitted for %', - array_to_string(currentSubjects(), ';', 'null'); -end; $$; - -/** - Checks if the user or assumed roles are allowed to add a new customer. - */ -create trigger test_customer_insert_trigger - before insert - on test_customer - for each row - when ( not hasGlobalPermission('add-customer') ) -execute procedure addTestCustomerNotAllowedForCurrentSubjects(); ---// - diff --git a/src/main/resources/db/changelog/118-test-customer-test-data.sql b/src/main/resources/db/changelog/118-test-customer-test-data.sql index 353b8f59..47c6e6aa 100644 --- a/src/main/resources/db/changelog/118-test-customer-test-data.sql +++ b/src/main/resources/db/changelog/118-test-customer-test-data.sql @@ -28,6 +28,8 @@ declare currentTask varchar; custRowId uuid; custAdminName varchar; + custAdminUuid uuid; + newCust test_customer; begin currentTask = 'creating RBAC test customer #' || custReference || '/' || custPrefix; call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin'); @@ -35,10 +37,20 @@ begin custRowId = uuid_generate_v4(); custAdminName = 'customer-admin@' || custPrefix || '.example.com'; + custAdminUuid = createRbacUser(custAdminName); insert into test_customer (reference, prefix, adminUserName) values (custReference, custPrefix, custAdminName); + + select * into newCust + from test_customer where reference=custReference; +-- call grantRoleToUser( +-- getRoleId(testCustomerAdmin(newCust), 'fail'), +-- findRoleId(testCustomerOwner(newCust)), +-- custAd +-- minUuid, +-- true); end; $$; --// diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index 9e68468c..fadb2562 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -1,4 +1,6 @@ --liquibase formatted sql +-- This code generated was by RbacViewPostgresGenerator at 2024-03-06T15:40:13.277446553. + -- ============================================================================ --changeset test-package-rbac-OBJECT:1 endDelimiter:--// @@ -7,6 +9,7 @@ call generateRelatedRbacObject('test_package'); --// + -- ============================================================================ --changeset test-package-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -15,95 +18,213 @@ call generateRbacRoleDescriptors('testPackage', 'test_package'); -- ============================================================================ ---changeset test-package-rbac-ROLES-CREATION:1 endDelimiter:--// +--changeset test-package-rbac-insert-trigger:1 endDelimiter:--// -- ---------------------------------------------------------------------------- + /* - Creates the roles and their assignments for a new package for the AFTER INSERT TRIGGER. + A Creates the roles, grants and permission for the AFTER INSERT TRIGGER. */ -create or replace function createRbacRolesForTestPackage() - returns trigger - language plpgsql - strict as $$ + +create or replace procedure buildRbacSystemForTestPackage( + NEW test_package +) + language plpgsql as $$ + declare - parentCustomer test_customer; + newCustomer test_customer; + begin - if TG_OP <> 'INSERT' then - raise exception 'invalid usage of TRIGGER AFTER INSERT'; - end if; - call enterTriggerForObjectUuid(NEW.uuid); + SELECT * FROM test_customer c + WHERE c.uuid= NEW.customerUuid + into newCustomer; - select * from test_customer as c where c.uuid = NEW.customerUuid into parentCustomer; - - -- an owner role is created and assigned to the customer's admin role perform createRoleWithGrants( - testPackageOwner(NEW), - permissions => array ['*'], - incomingSuperRoles => array[testCustomerAdmin(parentCustomer)] - ); + testPackageOwner(NEW), + permissions => array['DELETE', 'UPDATE'], + userUuids => array[currentUserUuid()], + incomingSuperRoles => array[testCustomerAdmin(newCustomer)] + ); - -- an owner role is created and assigned to the package owner role perform createRoleWithGrants( - testPackageAdmin(NEW), - permissions => array ['add-domain'], + testPackageAdmin(NEW), incomingSuperRoles => array[testPackageOwner(NEW)] - ); + ); - -- and a package tenant role is created and assigned to the package admin as well perform createRoleWithGrants( - testPackageTenant(NEW), - permissions => array['view'], - incomingsuperroles => array[testPackageAdmin(NEW)], - outgoingSubRoles => array[testCustomerTenant(parentCustomer)] - ); + testPackageTenant(NEW), + permissions => array['SELECT'], + incomingSuperRoles => array[testPackageAdmin(NEW)], + outgoingSubRoles => array[testCustomerTenant(newCustomer)] + ); call leaveTriggerForObjectUuid(NEW.uuid); - return NEW; end; $$; /* - An AFTER INSERT TRIGGER which creates the role structure for a new package. + AFTER INSERT TRIGGER to create the role+grant structure for a new test_package row. */ -create trigger createRbacRolesForTestPackage_Trigger - after insert - on test_package +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 createRbacRolesForTestPackage(); +execute procedure insertTriggerForTestPackage_tf(); + --// +-- ============================================================================ +--changeset test-package-rbac-update-trigger:1 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 enterTriggerForObjectUuid(NEW.uuid); + + SELECT * FROM test_customer c + WHERE c.uuid= OLD.customerUuid + into oldCustomer; + SELECT * FROM test_customer c + WHERE c.uuid= NEW.customerUuid + into newCustomer; + + if NEW.customerUuid <> OLD.customerUuid then + + call revokePermissionFromRole(findPermissionId(OLD.uuid, 'INSERT'), testCustomerAdmin(oldCustomer)); + + call revokeRoleFromRole(testPackageOwner(OLD), testCustomerAdmin(oldCustomer)); + call grantRoleToRole(testPackageOwner(NEW), testCustomerAdmin(newCustomer)); + + call revokeRoleFromRole(testCustomerTenant(oldCustomer), testPackageTenant(OLD)); + call grantRoleToRole(testCustomerTenant(newCustomer), testPackageTenant(NEW)); + + end if; + + call 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 test-package-rbac-INSERT:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +/* + Creates INSERT INTO test_package permissions for the related test_customer rows. + */ +do language plpgsql $$ + declare + row test_customer; + permissionUuid uuid; + roleUuid uuid; + begin + call defineContext('generated Liquibase: create INSERT INTO test_package permissions for the related test_customer rows'); + + FOR row IN SELECT * FROM test_customer + LOOP + roleUuid := findRoleId(testCustomerAdmin(row)); + permissionUuid := createPermission(row.uuid, 'INSERT', 'test_package'); + call grantPermissionToRole(roleUuid, permissionUuid); + END LOOP; + END; +$$; + +/** + Adds test_package INSERT permission to specified role of new test_customer rows. +*/ +create or replace function test_package_test_customer_insert_tf() + returns trigger + language plpgsql + strict as $$ +begin + call grantPermissionToRole( + testCustomerAdmin(NEW), + createPermission(NEW.uuid, 'INSERT', 'test_package')); + return NEW; +end; $$; + +create trigger test_package_test_customer_insert_tg + after insert on test_customer + for each row +execute procedure test_package_test_customer_insert_tf(); + +/** + Checks if the user or assumed roles are allowed to insert a row to test_package. +*/ +create or replace function test_package_insert_permission_missing_tf() + returns trigger + language plpgsql as $$ +begin + raise exception 'insert into test_package not allowed for current subjects %', currentSubjectsUuids(); +end; $$; + +create trigger test_package_insert_permission_check_tg + before insert on test_package + for each row + when ( not hasInsertPermission(NEW.customerUuid, 'INSERT', 'test_package') ) + execute procedure test_package_insert_permission_missing_tf(); + +--// -- ============================================================================ --changeset test-package-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacIdentityView('test_package', 'target.name'); +call generateRbacIdentityView('test_package', $idName$ + name + $idName$); --// + -- ============================================================================ --changeset test-package-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- - -/* - Creates a view to the customer main table which maps the identifying name - (in this case, the prefix) to the objectUuid. - */ --- drop view if exists test_package_rv; --- create or replace view test_package_rv as --- select target.* --- from test_package as target --- where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'test_package', currentSubjectsUuids())) --- order by target.name; --- grant all privileges on test_package_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; - -call generateRbacRestrictedView('test_package', 'target.name', +call generateRbacRestrictedView('test_package', + 'name', $updates$ version = new.version, customerUuid = new.customerUuid, - name = new.name, description = new.description $updates$); - --// diff --git a/src/main/resources/db/changelog/128-test-package-test-data.sql b/src/main/resources/db/changelog/128-test-package-test-data.sql index 4667b742..8c6568f3 100644 --- a/src/main/resources/db/changelog/128-test-package-test-data.sql +++ b/src/main/resources/db/changelog/128-test-package-test-data.sql @@ -26,7 +26,7 @@ begin custAdminUser = 'customer-admin@' || cust.prefix || '.example.com'; custAdminRole = 'test_customer#' || cust.prefix || '.admin'; - call defineContext(currentTask, null, custAdminUser, custAdminRole); + call defineContext(currentTask, null, 'superuser-fran@hostsharing.net', custAdminRole); raise notice 'task: % by % as %', currentTask, custAdminUser, custAdminRole; insert diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index a78bfb5f..e890d267 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -28,7 +28,7 @@ begin return createRoleWithGrants( domainTenantRoleDesc, - permissions => array['view'], + permissions => array['SELECT'], incomingSuperRoles => array[testdomainAdmin(domain)] ); end; $$; @@ -60,14 +60,14 @@ begin -- an owner role is created and assigned to the package's admin group perform createRoleWithGrants( testDomainOwner(NEW), - permissions => array['*'], + permissions => array['DELETE'], incomingSuperRoles => array[testPackageAdmin(parentPackage)] ); -- and a domain admin role is created and assigned to the domain owner as well perform createRoleWithGrants( testDomainAdmin(NEW), - permissions => array['edit'], + permissions => array['UPDATE'], incomingSuperRoles => array[testDomainOwner(NEW)], outgoingSubRoles => array[testPackageTenant(parentPackage)] ); @@ -112,6 +112,6 @@ drop view if exists test_domain_rv; create or replace view test_domain_rv as select target.* from test_domain as target - where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'domain', currentSubjectsUuids())); + where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('SELECT', 'domain', currentSubjectsUuids())); grant all privileges on test_domain_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; --// diff --git a/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql b/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql index 7ba7891b..dc51efa3 100644 --- a/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql +++ b/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql @@ -33,7 +33,7 @@ begin perform createRoleWithGrants( hsOfficeContactOwner(NEW), - permissions => array['*'], + permissions => array['DELETE'], incomingSuperRoles => array[globalAdmin()], userUuids => array[currentUserUuid()], grantedByRole => globalAdmin() @@ -41,7 +41,7 @@ begin perform createRoleWithGrants( hsOfficeContactAdmin(NEW), - permissions => array['edit'], + permissions => array['UPDATE'], incomingSuperRoles => array[hsOfficeContactOwner(NEW)] ); @@ -52,7 +52,7 @@ begin perform createRoleWithGrants( hsOfficeContactGuest(NEW), - permissions => array['view'], + permissions => array['SELECT'], incomingSuperRoles => array[hsOfficeContactTenant(NEW)] ); diff --git a/src/main/resources/db/changelog/213-hs-office-person-rbac.sql b/src/main/resources/db/changelog/213-hs-office-person-rbac.sql index 42eacf2f..c903e086 100644 --- a/src/main/resources/db/changelog/213-hs-office-person-rbac.sql +++ b/src/main/resources/db/changelog/213-hs-office-person-rbac.sql @@ -31,16 +31,16 @@ begin perform createRoleWithGrants( hsOfficePersonOwner(NEW), - permissions => array['*'], + permissions => array['DELETE'], incomingSuperRoles => array[globalAdmin()], userUuids => array[currentUserUuid()], grantedByRole => globalAdmin() ); - -- TODO: who is admin? the person itself? is it allowed for the person itself or a representative to edit the data? + -- TODO: who is admin? the person itself? is it allowed for the person itself or a representative to update the data? perform createRoleWithGrants( hsOfficePersonAdmin(NEW), - permissions => array['edit'], + permissions => array['UPDATE'], incomingSuperRoles => array[hsOfficePersonOwner(NEW)] ); @@ -51,7 +51,7 @@ begin perform createRoleWithGrants( hsOfficePersonGuest(NEW), - permissions => array['view'], + permissions => array['SELECT'], incomingSuperRoles => array[hsOfficePersonTenant(NEW)] ); diff --git a/src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql b/src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql index 928af48c..34d23793 100644 --- a/src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql +++ b/src/main/resources/db/changelog/223-hs-office-relationship-rbac.sql @@ -45,7 +45,7 @@ begin perform createRoleWithGrants( hsOfficeRelationshipOwner(NEW), - permissions => array['*'], + permissions => array['DELETE'], incomingSuperRoles => array[ globalAdmin(), hsOfficePersonAdmin(newRelAnchor)] @@ -53,14 +53,14 @@ begin perform createRoleWithGrants( hsOfficeRelationshipAdmin(NEW), - permissions => array['edit'], + permissions => array['UPDATE'], incomingSuperRoles => array[hsOfficeRelationshipOwner(NEW)] ); -- the tenant role for those related users who can view the data perform createRoleWithGrants( hsOfficeRelationshipTenant, - permissions => array['view'], + permissions => array['SELECT'], incomingSuperRoles => array[ hsOfficeRelationshipAdmin(NEW), hsOfficePersonAdmin(newRelAnchor), diff --git a/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql b/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql index 4b4da009..a6ad3733 100644 --- a/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql +++ b/src/main/resources/db/changelog/233-hs-office-partner-rbac.sql @@ -48,13 +48,13 @@ begin perform createRoleWithGrants( hsOfficePartnerOwner(NEW), - permissions => array['*'], + permissions => array['DELETE'], incomingSuperRoles => array[globalAdmin()] ); perform createRoleWithGrants( hsOfficePartnerAdmin(NEW), - permissions => array['edit'], + permissions => array['UPDATE'], incomingSuperRoles => array[ hsOfficePartnerOwner(NEW)], outgoingSubRoles => array[ @@ -84,7 +84,7 @@ begin perform createRoleWithGrants( hsOfficePartnerGuest(NEW), - permissions => array['view'], + permissions => array['SELECT'], incomingSuperRoles => array[hsOfficePartnerTenant(NEW)] ); @@ -99,12 +99,12 @@ begin call grantPermissionsToRole( getRoleId(hsOfficePartnerOwner(NEW), 'fail'), - createPermissions(NEW.detailsUuid, array ['*']) + createPermissions(NEW.detailsUuid, array ['DELETE']) ); call grantPermissionsToRole( getRoleId(hsOfficePartnerAdmin(NEW), 'fail'), - createPermissions(NEW.detailsUuid, array ['edit']) + createPermissions(NEW.detailsUuid, array ['UPDATE']) ); call grantPermissionsToRole( @@ -112,7 +112,7 @@ begin -- Do NOT grant view permission on partner-details to hsOfficePartnerTENANT! -- Otherwise package-admins etc. would be able to read the data. getRoleId(hsOfficePartnerAgent(NEW), 'fail'), - createPermissions(NEW.detailsUuid, array ['view']) + createPermissions(NEW.detailsUuid, array ['SELECT']) ); diff --git a/src/main/resources/db/changelog/234-hs-office-partner-details-rbac.sql b/src/main/resources/db/changelog/234-hs-office-partner-details-rbac.sql index ab94481e..7cd72003 100644 --- a/src/main/resources/db/changelog/234-hs-office-partner-details-rbac.sql +++ b/src/main/resources/db/changelog/234-hs-office-partner-details-rbac.sql @@ -7,9 +7,6 @@ call generateRelatedRbacObject('hs_office_partner_details'); --// - - - -- ============================================================================ --changeset hs-office-partner-details-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- @@ -38,7 +35,7 @@ call generateRbacRestrictedView('hs_office_partner_details', -- ============================================================================ ---changeset hs-office-partner-details-rbac-NEW-CONTACT:1 endDelimiter:--// +--changeset hs-office-partner-details-rbac-NEW-PARTNER-DETAILS:1 endDelimiter:--// -- ---------------------------------------------------------------------------- /* Creates a global permission for new-partner-details and assigns it to the hostsharing admins role. diff --git a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql index 148e0ee2..5b1ae81f 100644 --- a/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql +++ b/src/main/resources/db/changelog/243-hs-office-bankaccount-rbac.sql @@ -33,7 +33,7 @@ begin perform createRoleWithGrants( hsOfficeBankAccountOwner(NEW), - permissions => array['delete'], + permissions => array['DELETE'], incomingSuperRoles => array[globalAdmin()], userUuids => array[currentUserUuid()], grantedByRole => globalAdmin() @@ -51,7 +51,7 @@ begin perform createRoleWithGrants( hsOfficeBankAccountGuest(NEW), - permissions => array['view'], + permissions => array['SELECT'], incomingSuperRoles => array[hsOfficeBankAccountTenant(NEW)] ); diff --git a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac.sql b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac.sql index 02895c48..44815f32 100644 --- a/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac.sql +++ b/src/main/resources/db/changelog/253-hs-office-sepamandate-rbac.sql @@ -41,13 +41,13 @@ begin perform createRoleWithGrants( hsOfficeSepaMandateOwner(NEW), - permissions => array['*'], + permissions => array['DELETE'], incomingSuperRoles => array[globalAdmin()] ); perform createRoleWithGrants( hsOfficeSepaMandateAdmin(NEW), - permissions => array['edit'], + permissions => array['UPDATE'], incomingSuperRoles => array[hsOfficeSepaMandateOwner(NEW)], outgoingSubRoles => array[hsOfficeBankAccountTenant(newHsOfficeBankAccount)] ); @@ -66,7 +66,7 @@ begin perform createRoleWithGrants( hsOfficeSepaMandateGuest(NEW), - permissions => array['view'], + permissions => array['SELECT'], incomingSuperRoles => array[hsOfficeSepaMandateTenant(NEW)] ); diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql index 30573125..48109078 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql @@ -49,7 +49,7 @@ begin perform createRoleWithGrants( hsOfficeDebitorOwner(NEW), - permissions => array['*'], + permissions => array['DELETE'], incomingSuperRoles => array[globalAdmin()], userUuids => array[currentUserUuid()], grantedByRole => globalAdmin() @@ -57,7 +57,7 @@ begin perform createRoleWithGrants( hsOfficeDebitorAdmin(NEW), - permissions => array['edit'], + permissions => array['UPDATE'], incomingSuperRoles => array[hsOfficeDebitorOwner(NEW)] ); @@ -85,7 +85,7 @@ begin perform createRoleWithGrants( hsOfficeDebitorGuest(NEW), - permissions => array['view'], + permissions => array['SELECT'], incomingSuperRoles => array[ hsOfficeDebitorTenant(NEW)] ); diff --git a/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql b/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql index 949f939c..10125d69 100644 --- a/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql +++ b/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql @@ -41,13 +41,13 @@ begin perform createRoleWithGrants( hsOfficeMembershipOwner(NEW), - permissions => array['*'], + permissions => array['DELETE'], incomingSuperRoles => array[globalAdmin()] ); perform createRoleWithGrants( hsOfficeMembershipAdmin(NEW), - permissions => array['edit'], + permissions => array['UPDATE'], incomingSuperRoles => array[hsOfficeMembershipOwner(NEW)] ); @@ -65,7 +65,7 @@ begin perform createRoleWithGrants( hsOfficeMembershipGuest(NEW), - permissions => array['view'], + permissions => array['SELECT'], incomingSuperRoles => array[hsOfficeMembershipTenant(NEW), hsOfficePartnerTenant(newHsOfficePartner), hsOfficeDebitorTenant(newHsOfficeDebitor)] ); diff --git a/src/main/resources/db/changelog/313-hs-office-coopshares-rbac.sql b/src/main/resources/db/changelog/313-hs-office-coopshares-rbac.sql index dd465d9f..a79d354e 100644 --- a/src/main/resources/db/changelog/313-hs-office-coopshares-rbac.sql +++ b/src/main/resources/db/changelog/313-hs-office-coopshares-rbac.sql @@ -43,7 +43,7 @@ begin -- coopsharestransactions cannot be edited nor deleted, just created+viewed call grantPermissionsToRole( getRoleId(hsOfficeMembershipTenant(newHsOfficeMembership), 'fail'), - createPermissions(NEW.uuid, array ['view']) + createPermissions(NEW.uuid, array ['SELECT']) ); else diff --git a/src/main/resources/db/changelog/323-hs-office-coopassets-rbac.sql b/src/main/resources/db/changelog/323-hs-office-coopassets-rbac.sql index ac65c141..38fec4ff 100644 --- a/src/main/resources/db/changelog/323-hs-office-coopassets-rbac.sql +++ b/src/main/resources/db/changelog/323-hs-office-coopassets-rbac.sql @@ -43,7 +43,7 @@ begin -- coopassetstransactions cannot be edited nor deleted, just created+viewed call grantPermissionsToRole( getRoleId(hsOfficeMembershipTenant(newHsOfficeMembership), 'fail'), - createPermissions(NEW.uuid, array ['view']) + createPermissions(NEW.uuid, array ['SELECT']) ); else diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectEntity.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectEntity.java new file mode 100644 index 00000000..d4256e56 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectEntity.java @@ -0,0 +1,31 @@ +package net.hostsharing.hsadminng.rbac.rbacrole; + +import lombok.*; +import org.jetbrains.annotations.NotNull; +import org.springframework.data.annotation.Immutable; + +import jakarta.persistence.*; +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "rbacobject") // TODO: create view rbacobject_ev +@Getter +@Setter +@ToString +@Immutable +@NoArgsConstructor +@AllArgsConstructor +public class RawRbacObjectEntity { + + @Id + private UUID uuid; + + @Column(name="objecttable") + private String objectTable; + + @NotNull + public static List objectDisplaysOf(@NotNull final List roles) { + return roles.stream().map(e -> e.objectTable+ "#" + e.uuid).sorted().toList(); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectRepository.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectRepository.java new file mode 100644 index 00000000..ab645316 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RawRbacObjectRepository.java @@ -0,0 +1,11 @@ +package net.hostsharing.hsadminng.rbac.rbacrole; + +import org.springframework.data.repository.Repository; + +import java.util.List; +import java.util.UUID; + +public interface RawRbacObjectRepository extends Repository { + + List findAll(); +} diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java index b13bcb76..aca26fe4 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacuser/RbacUserControllerAcceptanceTest.java @@ -288,7 +288,7 @@ class RbacUserControllerAcceptanceTest { .body("", hasItem( allOf( hasEntry("roleName", "test_customer#yyy.tenant"), - hasEntry("op", "view")) + hasEntry("op", "select")) )) .body("", hasItem( allOf( @@ -298,7 +298,7 @@ class RbacUserControllerAcceptanceTest { .body("", hasItem( allOf( hasEntry("roleName", "test_domain#yyy00-aaaa.owner"), - hasEntry("op", "*")) + hasEntry("op", "delete")) )) .body("size()", is(7)); // @formatter:on @@ -323,7 +323,7 @@ class RbacUserControllerAcceptanceTest { .body("", hasItem( allOf( hasEntry("roleName", "test_customer#yyy.tenant"), - hasEntry("op", "view")) + hasEntry("op", "select")) )) .body("", hasItem( allOf( @@ -333,7 +333,7 @@ class RbacUserControllerAcceptanceTest { .body("", hasItem( allOf( hasEntry("roleName", "test_domain#yyy00-aaaa.owner"), - hasEntry("op", "*")) + hasEntry("op", "delete")) )) .body("size()", is(7)); // @formatter:on @@ -357,7 +357,7 @@ class RbacUserControllerAcceptanceTest { .body("", hasItem( allOf( hasEntry("roleName", "test_customer#yyy.tenant"), - hasEntry("op", "view")) + hasEntry("op", "select")) )) .body("", hasItem( allOf( @@ -367,7 +367,7 @@ class RbacUserControllerAcceptanceTest { .body("", hasItem( allOf( hasEntry("roleName", "test_domain#yyy00-aaaa.owner"), - hasEntry("op", "*")) + hasEntry("op", "delete")) )) .body("size()", is(7)); // @formatter:on diff --git a/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityTest.java b/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityTest.java index ab5d76a5..abec1250 100644 --- a/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityTest.java +++ b/src/test/java/net/hostsharing/hsadminng/test/cust/TestCustomerEntityTest.java @@ -29,8 +29,9 @@ class TestCustomerEntityTest { subgraph customer:permissions[ ] style customer:permissions fill:#dd4901,stroke:white - perm:customer:*{{customer:*}} - perm:customer:view{{customer:view}} + perm:customer:delete{{customer:delete}} + perm:customer:update{{customer:update}} + perm:customer:select{{customer:select}} end end @@ -43,9 +44,9 @@ class TestCustomerEntityTest { role:customer:admin ==> role:customer:tenant %% granting permissions to roles - role:customer:owner ==> perm:customer:* + role:customer:owner ==> perm:customer:delete role:customer:admin ==> perm:customer:add-package - role:customer:tenant ==> perm:customer:view + role:customer:tenant ==> perm:customer:select """); } } diff --git a/src/test/java/net/hostsharing/hsadminng/test/pac/TestPackageEntityTest.java b/src/test/java/net/hostsharing/hsadminng/test/pac/TestPackageEntityTest.java index 1bb88ffb..3cda6d74 100644 --- a/src/test/java/net/hostsharing/hsadminng/test/pac/TestPackageEntityTest.java +++ b/src/test/java/net/hostsharing/hsadminng/test/pac/TestPackageEntityTest.java @@ -30,9 +30,9 @@ class TestPackageEntityTest { style package:permissions fill:#dd4901,stroke:white perm:package:insert{{package:insert}} - perm:package:*{{package:*}} - perm:package:edit{{package:edit}} - perm:package:view{{package:view}} + perm:package:delete{{package:delete}} + perm:package:update{{package:update}} + perm:package:select{{package:select}} end end @@ -63,9 +63,9 @@ class TestPackageEntityTest { %% granting permissions to roles role:customer:admin ==> perm:package:insert - role:package:owner ==> perm:package:* - role:package:owner ==> perm:package:edit - role:package:tenant ==> perm:package:view + role:package:owner ==> perm:package:delete + role:package:owner ==> perm:package:update + role:package:tenant ==> perm:package:select """); } } diff --git a/src/test/java/net/hostsharing/hsadminng/test/pac/TestPackageRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/test/pac/TestPackageRepositoryIntegrationTest.java index 53d28e0c..89c6f993 100644 --- a/src/test/java/net/hostsharing/hsadminng/test/pac/TestPackageRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/test/pac/TestPackageRepositoryIntegrationTest.java @@ -89,7 +89,7 @@ class TestPackageRepositoryIntegrationTest { class OptimisticLocking { @Test - public void supportsOptimisticLocking() throws InterruptedException { + public void supportsOptimisticLocking() { // given globalAdminWithAssumedRole("test_package#xxx00.admin"); final var pac = testPackageRepository.findAllByOptionalNameLike("%").get(0);