RBAC Diagram+PostgreSQL Generator #21

Merged
hsh-michaelhoennig merged 54 commits from experimental-rbacview-generator into master 2024-03-11 12:30:44 +01:00
9 changed files with 47 additions and 37 deletions
Showing only changes of commit 20de9ba7a4 - Show all commits

View File

@ -171,10 +171,10 @@ An *RbacPermission* allows a specific *RbacOperation* on a specific *RbacObject*
An *RbacOperation* determines, <u>what</u> an *RbacPermission* allows to do. An *RbacOperation* determines, <u>what</u> an *RbacPermission* allows to do.
It can be one of: It can be one of:
- **'INSERT'** - permits inserting new rows related to the row, to which the permission belongs, in the table which is specified an extra column - **'INSERT'** - permits inserting new rows related to the row, to which the permission belongs, in the table which is specified an extra column, includes 'SELECT'
- **'SELECT'** - permits selecting the row specified by the permission - **'SELECT'** - permits selecting the row specified by the permission, is included in all other permissions
- **'UPDATE'** - permits updating (only the updatable columns of) the row specified by the permission - **'UPDATE'** - permits updating (only the updatable columns of) the row specified by the permission, includes 'SELECT'
- **'DELETE'** - permits deleting the row specified by the permission - **'DELETE'** - permits deleting the row specified by the permission, includes 'SELECT'
This list is extensible according to the needs of the access rule system. This list is extensible according to the needs of the access rule system.
@ -620,10 +620,10 @@ Let's have a look at the two view queries:
WHERE target.uuid IN ( WHERE target.uuid IN (
SELECT uuid SELECT uuid
FROM queryAccessibleObjectUuidsOfSubjectIds( FROM queryAccessibleObjectUuidsOfSubjectIds(
'SELECTÄ, 'customer', currentSubjectsUuids())); 'SELECT, 'customer', currentSubjectsUuids()));
This view should be automatically updatable. This view should be automatically updatable.
Where, for updates, we actually have to check for 'UPDATE' instead of 'SELECTÄ 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. With the larger dataset, the test suite initially needed over 7 seconds with this view query.
At this point the second variant was tried. At this point the second variant was tried.
@ -638,7 +638,7 @@ Looks like the query optimizer needed some statistics to find the best path.
SELECT DISTINCT target.* SELECT DISTINCT target.*
FROM customer AS target FROM customer AS target
JOIN queryAccessibleObjectUuidsOfSubjectIds( JOIN queryAccessibleObjectUuidsOfSubjectIds(
'SELECTÄ, 'customer', currentSubjectsUuids()) AS allowedObjId 'SELECT, 'customer', currentSubjectsUuids()) AS allowedObjId
ON target.uuid = allowedObjId; ON target.uuid = allowedObjId;
This view cannot is not updatable automatically, This view cannot is not updatable automatically,

View File

@ -20,7 +20,7 @@ CREATE POLICY customer_policy ON customer
TO restricted TO restricted
USING ( USING (
-- id=1000 -- id=1000
isPermissionGrantedToSubject(findPermissionId('test_customer', id, 'SELECT'), currentUserUuid()) isPermissionGrantedToSubject(findEffectivePermissionId('test_customer', id, 'SELECT'), currentUserUuid())
); );
SET SESSION AUTHORIZATION restricted; SET SESSION AUTHORIZATION restricted;
@ -35,7 +35,7 @@ SELECT * FROM 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 customer WHERE isPermissionGrantedToSubject(findPermissionId('test_customer', id, 'SELECT'), currentUserUuid()); SELECT * FROM customer WHERE isPermissionGrantedToSubject(findEffectivePermissionId('test_customer', id, 'SELECT'), currentUserUuid());
SELECT * from cust_view LIMIT 10; SELECT * from cust_view LIMIT 10;
select queryAllPermissionsOfSubjectId(findRbacUser('superuser-alex@hostsharing.net')); select queryAllPermissionsOfSubjectId(findRbacUser('superuser-alex@hostsharing.net'));
@ -52,7 +52,7 @@ CREATE OR REPLACE RULE "_RETURN" AS
DO INSTEAD DO INSTEAD
SELECT c.uuid, c.reference, c.prefix FROM customer AS c SELECT c.uuid, c.reference, c.prefix FROM customer AS c
JOIN queryAllPermissionsOfSubjectId(currentUserUuid()) AS p JOIN queryAllPermissionsOfSubjectId(currentUserUuid()) AS p
ON p.objectTable='test_customer' AND p.objectUuid=c.uuid AND p.op = 'SELECT'; ON p.objectTable='test_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;
@ -68,7 +68,7 @@ CREATE OR REPLACE VIEW cust_view AS
SELECT c.uuid, c.reference, c.prefix SELECT c.uuid, c.reference, c.prefix
FROM customer AS c FROM customer AS c
JOIN queryAllPermissionsOfSubjectId(currentUserUuid()) AS p JOIN queryAllPermissionsOfSubjectId(currentUserUuid()) AS p
ON p.objectUuid=c.uuid AND p.op = 'SELECT'; ON 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,7 +81,7 @@ select rr.uuid, rr.type from RbacGrants g
join RbacReference RR on g.ascendantUuid = RR.uuid join RbacReference RR on g.ascendantUuid = RR.uuid
where g.descendantUuid in ( where g.descendantUuid in (
select uuid from queryAllPermissionsOfSubjectId(findRbacUser('alex@example.com')) select uuid from queryAllPermissionsOfSubjectId(findRbacUser('alex@example.com'))
where objectTable='test_customer' and op = 'SELECT'); where objectTable='test_customer');
call grantRoleToUser(findRoleId('test_customer#aaa.admin'), findRbacUser('aaaaouq@example.com')); call grantRoleToUser(findRoleId('test_customer#aaa.admin'), findRbacUser('aaaaouq@example.com'));

View File

@ -14,7 +14,6 @@ import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
@ -43,7 +42,7 @@ public class TestCustomerEntity implements HasUuid {
.withUpdatableColumns("reference", "prefix", "adminUserName") .withUpdatableColumns("reference", "prefix", "adminUserName")
.createRole(OWNER, (with) -> { .createRole(OWNER, (with) -> {
// with.owningUser(CREATOR); TODO: needs assumed role // with.owningUser(CREATOR); FIXME: needs assumed role, was: getRbacUserId(NEW.adminUserName, 'create')
with.incomingSuperRole(GLOBAL, ADMIN); with.incomingSuperRole(GLOBAL, ADMIN);
with.permission(DELETE); with.permission(DELETE);
}) })

View File

@ -66,11 +66,11 @@ begin
when others then when others then
currentTask := null; currentTask := null;
end; end;
-- TODO: uncomment -- FIXME: uncomment
-- if (currentTask is null or currentTask = '') then -- if (currentTask is null or currentTask = '') then
-- raise exception '[401] currentTask must be defined, please call `defineContext(...)`'; -- raise exception '[401] currentTask must be defined, please call `defineContext(...)`';
-- end if; -- end if;
return 'unknown'; -- TODO: currentTask; return 'unknown'; -- FIXME: currentTask;
end; $$; end; $$;
--// --//

View File

@ -366,6 +366,7 @@ create trigger deleteRbacRolesOfRbacObject_Trigger
*/ */
create domain RbacOp as varchar(67) -- TODO: shorten to 8, once the deprecated values are gone create domain RbacOp as varchar(67) -- TODO: shorten to 8, once the deprecated values are gone
-- FIXME: uncomment check
-- check ( -- check (
-- VALUE = 'INSERT' or -- VALUE = 'INSERT' or
-- VALUE = 'DELETE' or -- VALUE = 'DELETE' or
@ -389,17 +390,6 @@ create table RbacPermission
call create_journal('RbacPermission'); call create_journal('RbacPermission');
create or replace function permissionExists(forObjectUuid uuid, forOp RbacOp)
returns bool
language sql as $$
select exists(
select op
from RbacPermission p
where p.objectUuid = forObjectUuid
and p.op = forOp
);
$$;
create or replace function createPermission(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null) create or replace function createPermission(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null)
returns uuid returns uuid
language plpgsql as $$ language plpgsql as $$
@ -462,7 +452,7 @@ begin
end; end;
$$; $$;
create or replace function findPermissionId(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null ) create or replace function findPermissionId(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null)
returns uuid returns uuid
returns null on null input returns null on null input
stable -- leakproof stable -- leakproof
@ -474,6 +464,17 @@ select uuid
and p.opTableName = forOpTableName and p.opTableName = forOpTableName
$$; $$;
create or replace function findEffectivePermissionId(forObjectUuid uuid, forOp RbacOp, forOpTableName text = null)
returns uuid
returns null on null input
stable -- leakproof
language sql as $$
select uuid
from RbacPermission p
where p.objectUuid = forObjectUuid
and (forOp = 'SELECT' or p.op = forOp) -- all other RbacOp include 'SELECT'
and p.opTableName = forOpTableName
$$;
--// --//
-- ============================================================================ -- ============================================================================
@ -748,7 +749,7 @@ begin
select descendantUuid select descendantUuid
from grants) as granted from grants) as granted
join RbacPermission perm join RbacPermission perm
on granted.descendantUuid = perm.uuid and perm.op = requiredOp on granted.descendantUuid = perm.uuid and (requiredOp = 'SELECT' or perm.op = requiredOp)
join RbacObject obj on obj.uuid = perm.objectUuid and obj.objectTable = forObjectTable join RbacObject obj on obj.uuid = perm.objectUuid and obj.objectTable = forObjectTable
limit maxObjects + 1; limit maxObjects + 1;

View File

@ -37,17 +37,28 @@ end; $$;
create or replace procedure grantRoleToUser(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid, doAssume boolean = true) create or replace procedure grantRoleToUser(grantedByRoleUuid uuid, grantedRoleUuid uuid, userUuid uuid, doAssume boolean = true)
language plpgsql as $$ language plpgsql as $$
declare
grantedByRoleIdName text;
grantedRoleIdName text;
begin begin
perform assertReferenceType('grantingRoleUuid', grantedByRoleUuid, 'RbacRole'); perform assertReferenceType('grantingRoleUuid', grantedByRoleUuid, 'RbacRole');
perform assertReferenceType('grantedRoleUuid (descendant)', grantedRoleUuid, 'RbacRole'); perform assertReferenceType('grantedRoleUuid (descendant)', grantedRoleUuid, 'RbacRole');
perform assertReferenceType('userUuid (ascendant)', userUuid, 'RbacUser'); perform assertReferenceType('userUuid (ascendant)', userUuid, 'RbacUser');
if NOT isGranted(currentSubjectsUuids(), grantedByRoleUuid) then assert grantedByRoleUuid is not null, 'grantedByRoleUuid must not be null';
raise exception '[403] Access to granted-by-role % forbidden for %', grantedByRoleUuid, currentSubjects(); assert grantedRoleUuid is not null, 'grantedRoleUuid must not be null';
end if; assert userUuid is not null, 'userUuid must not be null';
if NOT isGranted(currentSubjectsUuids(), grantedByRoleUuid) then
select roleIdName from rbacRole_ev where uuid=grantedByRoleUuid into grantedByRoleIdName;
raise exception '[403] Access to granted-by-role % (%) forbidden for % (%)',
grantedByRoleIdName, grantedByRoleUuid, currentSubjects(), currentSubjectsUuids();
end if;
if NOT isGranted(grantedByRoleUuid, grantedRoleUuid) then if NOT isGranted(grantedByRoleUuid, grantedRoleUuid) then
raise exception '[403] Access to granted role % forbidden for %', grantedRoleUuid, currentSubjects(); select roleIdName from rbacRole_ev where uuid=grantedByRoleUuid into grantedByRoleIdName;
select roleIdName from rbacRole_ev where uuid=grantedRoleUuid into grantedRoleIdName;
raise exception '[403] Access to granted role % (%) forbidden for % (%)',
grantedRoleIdName, grantedRoleUuid, grantedByRoleUuid, grantedByRoleIdName;
end if; end if;
insert insert

View File

@ -46,8 +46,8 @@ begin
select * into newCust select * into newCust
from test_customer where reference=custReference; from test_customer where reference=custReference;
call grantRoleToUser( call grantRoleToUser(
getRoleId(testCustomerOwner(newCust), 'fail'),
getRoleId(testCustomerAdmin(newCust), 'fail'), getRoleId(testCustomerAdmin(newCust), 'fail'),
findRoleId(testCustomerOwner(newCust)),
custAdminUuid, custAdminUuid,
true); true);
end; $$; end; $$;

View File

@ -16,7 +16,6 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import jakarta.persistence.PersistenceException; import jakarta.persistence.PersistenceException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;

View File

@ -4,8 +4,8 @@ spring:
platform: postgres platform: postgres
datasource: datasource:
url: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers url-tc: jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers
url-local: jdbc:postgresql://localhost:5432/postgres url: jdbc:postgresql://localhost:5432/postgres
username: postgres username: postgres
password: password password: password