RBAC Diagram+PostgreSQL Generator #21
14
doc/rbac.md
14
doc/rbac.md
@ -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,
|
||||||
|
@ -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'));
|
||||||
|
|
||||||
|
@ -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);
|
||||||
})
|
})
|
||||||
|
@ -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; $$;
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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; $$;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user