1
0
Files
hs.hsadmin.ng/src/main/resources/db/changelog/1-rbac/1050-rbac-base.sql
T
2025-09-12 11:37:55 +02:00

901 lines
31 KiB
PL/PgSQL

--liquibase formatted sql
-- ============================================================================
--changeset michael.hoennig:rbac-base-REFERENCE endDelimiter:--//
-- ----------------------------------------------------------------------------
create type rbac.ReferenceType as enum ('rbac.subject', 'rbac.role', 'rbac.permission');
create table rbac.reference
(
uuid uuid unique default uuid_generate_v4(),
type rbac.ReferenceType not null
);
create or replace function rbac.assertReferenceType(argument varchar, referenceId uuid, expectedType rbac.ReferenceType)
returns rbac.ReferenceType
language plpgsql as $$
declare
actualType rbac.ReferenceType;
begin
if referenceId is null then
raise exception '% must be a % and not null', argument, expectedType;
end if;
actualType = (select type from rbac.reference where uuid = referenceId);
if (actualType <> expectedType) then
raise exception '% must reference a %, but got a %', argument, expectedType, actualType;
end if;
return expectedType;
end; $$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-SUBJECT runOnChange:true validCheckSum:ANY endDelimiter:--//
-- ----------------------------------------------------------------------------
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM information_schema.tables
WHERE table_schema = 'rbac' AND table_name = 'subject') THEN
CREATE TABLE rbac.subject
(
uuid uuid primary key references rbac.reference (uuid) on delete cascade,
name varchar(63) not null unique
);
CALL base.create_journal('rbac.subject');
END IF;
END
$$;
create or replace function rbac.create_subject(subjectName varchar)
returns uuid
returns null on null input
language plpgsql as $$
declare
stableUuidNamespace uuid;
subjectUuid uuid;
begin
stableUuidNamespace := '6ba7b810-9dad-11d1-80b4-00c04fd430c8'::uuid;
subjectUuid := uuid_generate_v5(stableUuidNamespace, subjectName);
insert
into rbac.reference (uuid, type)
values (subjectUuid, 'rbac.subject');
insert
into rbac.subject (uuid, name)
values (subjectUuid, subjectName);
return subjectUuid;
end;
$$;
create or replace function rbac.create_subject(refUuid uuid, subjectName varchar)
returns uuid
called on null input
language plpgsql as $$
begin
insert
into rbac.reference as r (uuid, type)
values (coalesce(refUuid, uuid_generate_v4()), 'rbac.subject')
returning r.uuid into refUuid;
insert
into rbac.subject (uuid, name)
values (refUuid, subjectName);
return refUuid;
end;
$$;
create or replace function rbac.find_subject_id(subjectName varchar)
returns uuid
returns null on null input
language sql as $$
select uuid from rbac.subject where name = subjectName
$$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-OBJECT endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
*/
create table rbac.object
(
uuid uuid primary key default uuid_generate_v4(),
serialId serial, -- TODO.perf: only needed for reverse deletion of temp test data
objectTable varchar(64) not null,
unique (objectTable, uuid)
);
call base.create_journal('rbac.object');
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-GENERATE-RELATED-OBJECT endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Inserts related rbac.object for use in the BEFORE INSERT TRIGGERs on the business objects.
*/
create or replace function rbac.insert_related_object()
returns trigger
language plpgsql
strict as $$
declare
objectUuid uuid;
tableSchemaAndName text;
begin
tableSchemaAndName := base.combine_table_schema_and_name(TG_TABLE_SCHEMA, TG_TABLE_NAME);
if TG_OP = 'INSERT' then
if NEW.uuid is null then
insert
into rbac.object (objectTable)
values (tableSchemaAndName)
returning uuid into objectUuid;
NEW.uuid = objectUuid;
else
insert
into rbac.object (uuid, objectTable)
values (NEW.uuid, tableSchemaAndName)
returning uuid into objectUuid;
end if;
return NEW;
else
raise exception 'invalid usage of TRIGGER AFTER INSERT';
end if;
end; $$;
/*
Deletes related rbac.object for use in the BEFORE DELETE TRIGGERs on the business objects.
Through cascades all related rbac roles and grants are going to be deleted as well.
*/
create or replace function rbac.delete_related_rbac_rules_tf()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP = 'DELETE' then
delete from rbac.object where rbac.object.uuid = old.uuid;
else
raise exception 'invalid usage of TRIGGER BEFORE DELETE';
end if;
return old;
end; $$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-ROLE endDelimiter:--//
-- ----------------------------------------------------------------------------
create type rbac.RoleType as enum ('OWNER', 'ADMIN', 'AGENT', 'TENANT', 'GUEST', 'REFERRER');
create table rbac.role
(
uuid uuid primary key references rbac.reference (uuid) on delete cascade initially deferred, -- initially deferred
objectUuid uuid not null references rbac.object (uuid) initially deferred,
roleType rbac.RoleType not null,
unique (objectUuid, roleType)
);
call base.create_journal('rbac.role');
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-ROLE-DESCRIPTOR endDelimiter:--//
-- ----------------------------------------------------------------------------
create type rbac.RoleDescriptor as
(
objectTable varchar(63), -- for human readability and easier debugging
objectUuid uuid,
roleType rbac.RoleType,
assumed boolean
);
create or replace function rbac.assumed()
returns boolean
stable -- leakproof
language sql as $$
select true;
$$;
create or replace function rbac.unassumed()
returns boolean
stable -- leakproof
language sql as $$
select false;
$$;
create or replace function rbac.roleDescriptorOf(
objectTable varchar(63), objectUuid uuid, roleType rbac.RoleType,
assumed boolean = true) -- just for DSL readability, belongs actually to the grant
returns rbac.RoleDescriptor
returns null on null input
stable -- leakproof
language sql as $$
select objectTable, objectUuid, roleType::rbac.RoleType, assumed;
$$;
create or replace function rbac.createRole(roleDescriptor rbac.RoleDescriptor)
returns uuid
returns null on null input
language plpgsql as $$
declare
referenceId uuid;
begin
insert
into rbac.reference (type)
values ('rbac.role')
returning uuid into referenceId;
insert
into rbac.role (uuid, objectUuid, roleType)
values (referenceId, roleDescriptor.objectUuid, roleDescriptor.roleType);
return referenceId;
end;
$$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-IDNAME-FUNCTIONS endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace function rbac.findObjectUuidByIdName(objectTable varchar, objectIdName varchar)
returns uuid
returns null on null input
language plpgsql as $$
declare
sql varchar;
uuid uuid;
begin
objectTable := base.pureIdentifier(objectTable);
objectIdName := base.pureIdentifier(objectIdName);
sql := format('select * from %s_uuid_by_id_name(%L);', objectTable, objectIdName);
begin
execute sql into uuid;
exception
when others then
raise exception 'function %_uuid_by_id_name(''%'') failed: %, SQLSTATE: %. If the function itself could not be found, add identity view support to %\nSQL:%',
objectTable, objectIdName, SQLERRM, SQLSTATE, objectTable, sql;
end;
if uuid is null then
raise exception 'SQL returned null: %', sql;
else
return uuid;
end if;
end ; $$;
create or replace function rbac.findIdNameByObjectUuid(objectTable varchar, objectUuid uuid)
returns varchar
returns null on null input
language plpgsql as $$
declare
sql varchar;
idName varchar;
begin
objectTable := base.pureIdentifier(objectTable);
sql := format('select * from %s_id_name_by_uuid(%L::uuid);', objectTable, objectUuid);
begin
execute sql into idName;
exception
when others then
raise exception 'function %_id_name_by_uuid(''%'') failed: %, SQLSTATE: %. If the function itself could not be found, add identity view support to %',
objectTable, objectUuid, SQLERRM, SQLSTATE, objectTable;
end;
return idName;
end ; $$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-ROLE-FUNCTIONS endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace procedure rbac.deleteRole(roleUUid uuid)
language plpgsql as $$
begin
--raise exception '% deleting role uuid %', rbac.currentSubjectOrAssumedRolesUuids(), roleUUid;
delete from rbac.role where uuid = roleUUid;
end;
$$;
create or replace function rbac.findRoleId(roleIdName varchar)
returns uuid
returns null on null input
language plpgsql as $$
declare
roleParts text;
roleTypeFromRoleIdName rbac.RoleType;
objectNameFromRoleIdName text;
objectTableFromRoleIdName text;
objectUuidOfRole uuid;
roleUuid uuid;
begin
-- TODO.refa: extract function rbac.toRoleDescriptor(roleIdName varchar) + find other occurrences
roleParts = overlay(roleIdName placing '#' from length(roleIdName) + 1 - strpos(reverse(roleIdName), ':'));
objectTableFromRoleIdName = split_part(roleParts, '#', 1);
objectNameFromRoleIdName = split_part(roleParts, '#', 2);
roleTypeFromRoleIdName = split_part(roleParts, '#', 3);
objectUuidOfRole = rbac.findObjectUuidByIdName(objectTableFromRoleIdName, objectNameFromRoleIdName);
select uuid
from rbac.role
where objectUuid = objectUuidOfRole
and roleType = roleTypeFromRoleIdName
into roleUuid;
return roleUuid;
end; $$;
create or replace function rbac.findRoleId(roleDescriptor rbac.RoleDescriptor)
returns uuid
returns null on null input
language sql as $$
select uuid from rbac.role where objectUuid = roleDescriptor.objectUuid and roleType = roleDescriptor.roleType;
$$;
create or replace function rbac.getRoleId(roleDescriptor rbac.RoleDescriptor)
returns uuid
language plpgsql as $$
declare
roleUuid uuid;
begin
assert roleDescriptor is not null, 'roleDescriptor must not be null';
roleUuid := rbac.findRoleId(roleDescriptor);
if (roleUuid is null) then
raise exception 'rbac.role "%#%.%" not found', roleDescriptor.objectTable, roleDescriptor.objectUuid, roleDescriptor.roleType;
end if;
return roleUuid;
end;
$$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-BEFORE-DELETE-ROLE-TRIGGER endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
rbac.role BEFORE DELETE TRIGGER function which deletes all related roles.
*/
create or replace function rbac.delete_grants_of_role_tf()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP = 'DELETE' then
delete from rbac.grant g where old.uuid in (g.grantedbyroleuuid, g.ascendantuuid, g.descendantuuid);
else
raise exception 'invalid usage of TRIGGER BEFORE DELETE';
end if;
return old;
end; $$;
/*
Installs the rbac.role BEFORE DELETE TRIGGER.
*/
create trigger delete_grants_of_role_tg
before delete
on rbac.role
for each row
execute procedure rbac.delete_grants_of_role_tf();
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-BEFORE-DELETE-OBJECT-TRIGGER endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
rbac.object BEFORE DELETE TRIGGER function which deletes all related roles.
*/
create or replace function rbac.delete_roles_of_object_tf()
returns trigger
language plpgsql
strict as $$
begin
if TG_OP = 'DELETE' then
delete from rbac.permission p where p.objectuuid = old.uuid;
delete from rbac.role r where r.objectUuid = old.uuid;
else
raise exception 'invalid usage of TRIGGER BEFORE DELETE';
end if;
return old;
end; $$;
/*
Installs the rbac.role BEFORE DELETE TRIGGER.
*/
create trigger delete_roles_of_object_tg
before delete
on rbac.object
for each row
execute procedure rbac.delete_roles_of_object_tf();
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-PERMISSION endDelimiter:--//
-- ----------------------------------------------------------------------------
create domain rbac.RbacOp as varchar(6)
check (
VALUE = 'DELETE'
or VALUE = 'UPDATE'
or VALUE = 'SELECT'
or VALUE = 'INSERT'
or VALUE = 'ASSUME'
);
create table rbac.permission
(
uuid uuid primary key references rbac.reference (uuid) on delete cascade,
objectUuid uuid not null references rbac.object,
op rbac.RbacOp not null,
opTableName varchar(60)
);
-- TODO.perf: check if these indexes are really useful
create index on rbac.permission (objectUuid, op);
create index on rbac.permission (opTableName, op);
ALTER TABLE rbac.permission
ADD CONSTRAINT unique_including_null_values UNIQUE NULLS NOT DISTINCT (objectUuid, op, opTableName);
call base.create_journal('rbac.permission');
create or replace function rbac.createPermission(forObjectUuid uuid, forOp rbac.RbacOp, forOpTableName text = null)
returns uuid
language plpgsql as $$
declare
permissionUuid uuid;
begin
if (forObjectUuid is null) then
raise exception 'forObjectUuid must not be null';
end if;
if (forOp = 'INSERT' and forOpTableName is null) then
raise exception 'INSERT permissions needs forOpTableName';
end if;
if (forOp <> 'INSERT' and forOpTableName is not null) then
raise exception 'forOpTableName must only be specified for ops: [INSERT]'; -- currently no other
end if;
permissionUuid := (
select uuid from rbac.permission
where objectUuid = forObjectUuid
and op = forOp and opTableName is not distinct from forOpTableName);
if (permissionUuid is null) then
insert into rbac.reference ("type")
values ('rbac.permission')
returning uuid into permissionUuid;
begin
insert into rbac.permission (uuid, objectUuid, op, opTableName)
values (permissionUuid, forObjectUuid, forOp, forOpTableName);
exception
when others then
raise exception 'insert into rbac.permission (uuid, objectUuid, op, opTableName)
values (%, %, %, %);', permissionUuid, forObjectUuid, forOp, forOpTableName;
end;
end if;
return permissionUuid;
end; $$;
create or replace function rbac.findEffectivePermissionId(forObjectUuid uuid, forOp rbac.RbacOp, forOpTableName text = null)
returns uuid
returns null on null input
stable -- leakproof
language sql as $$
select uuid
from rbac.permission p
where p.objectUuid = forObjectUuid
and (forOp = 'SELECT' or p.op = forOp) -- all other rbac.RbacOp include 'SELECT'
and p.opTableName = forOpTableName
$$;
create or replace function rbac.findPermissionId(forObjectUuid uuid, forOp rbac.RbacOp, forOpTableName text = null)
returns uuid
returns null on null input
stable -- leakproof
language sql as $$
select uuid
from rbac.permission p
where p.objectUuid = forObjectUuid
and p.op = forOp
and p.opTableName = forOpTableName
$$;
create or replace function rbac.getPermissionId(forObjectUuid uuid, forOp rbac.RbacOp, forOpTableName text = null)
returns uuid
stable -- leakproof
language plpgsql as $$
declare
permissionUuid uuid;
begin
select uuid into permissionUuid
from rbac.permission p
where p.objectUuid = forObjectUuid
and p.op = forOp
and forOpTableName is null or p.opTableName = forOpTableName;
assert permissionUuid is not null,
format('permission %s %s for object UUID %s cannot be found', forOp, forOpTableName, forObjectUuid);
return permissionUuid;
end; $$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-duplicate-role-grant-exception endDelimiter:--//
-- ----------------------------------------------------------------------------
create or replace procedure rbac.raiseDuplicateRoleGrantException(subRoleId uuid, superRoleId uuid)
language plpgsql as $$
declare
subRoleIdName text;
superRoleIdName text;
begin
select roleIdName from rbac.role_ev where uuid=subRoleId into subRoleIdName;
select roleIdName from rbac.role_ev where uuid=superRoleId into superRoleIdName;
raise exception '[400] Duplicate role grant detected: role % (%) already granted to % (%)', subRoleId, subRoleIdName, superRoleId, superRoleIdName;
end;
$$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-GRANTS endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Table to store grants / role- or permission assignments to subjects or roles.
*/
create table rbac.grant
(
uuid uuid primary key default uuid_generate_v4(),
grantedByTriggerOf uuid references rbac.object (uuid) on delete cascade initially deferred ,
grantedByRoleUuid uuid references rbac.role (uuid),
ascendantUuid uuid references rbac.reference (uuid),
descendantUuid uuid references rbac.reference (uuid),
assumed boolean not null default true, -- auto assumed (true) vs. needs assumeRoles (false)
unique (ascendantUuid, descendantUuid),
constraint rbacGrant_createdBy check ( grantedByRoleUuid is null or grantedByTriggerOf is null) );
create index on rbac.grant (ascendantUuid);
create index on rbac.grant (descendantUuid);
call base.create_journal('rbac.grant');
create or replace function rbac.findGrantees(grantedId uuid)
returns setof rbac.reference
returns null on null input
language sql as $$
with recursive grants as (
select descendantUuid, ascendantUuid
from rbac.grant
where descendantUuid = grantedId
union all
select g.descendantUuid, g.ascendantUuid
from rbac.grant g
inner join grants on grants.ascendantUuid = g.descendantUuid
)
select ref.*
from grants
join rbac.reference ref on ref.uuid = grants.ascendantUuid;
$$;
create or replace function rbac.isGranted(granteeIds uuid[], grantedId uuid)
returns bool
returns null on null input
language sql as $$
with recursive grants as (
select descendantUuid, ascendantUuid
from rbac.grant
where descendantUuid = grantedId
union all
select "grant".descendantUuid, "grant".ascendantUuid
from rbac.grant "grant"
inner join grants recur on recur.ascendantUuid = "grant".descendantUuid
)
select exists (
select true
from grants
where ascendantUuid = any(granteeIds)
) or grantedId = any(granteeIds);
$$;
create or replace function rbac.isGranted(granteeId uuid, grantedId uuid)
returns bool
returns null on null input
language sql as $$
select * from rbac.isGranted(array[granteeId], grantedId);
$$;
create or replace function rbac.isPermissionGrantedToSubject(permissionId uuid, subjectId uuid)
returns BOOL
stable -- leakproof
language sql as $$
with recursive grants as (
select descendantUuid, ascendantUuid
from rbac.grant
where descendantUuid = permissionId
union all
select g.descendantUuid, g.ascendantUuid
from rbac.grant g
inner join grants on grants.ascendantUuid = g.descendantUuid
)
select exists(
select true
from grants
where ascendantUuid = subjectId
);
$$;
create or replace function rbac.hasInsertPermission(objectUuid uuid, tableName text )
returns BOOL
stable -- leakproof
language plpgsql as $$
declare
permissionUuid uuid;
begin
permissionUuid = rbac.findPermissionId(objectUuid, 'INSERT'::rbac.RbacOp, tableName);
return permissionUuid is not null;
end;
$$;
create or replace function rbac.hasGlobalRoleGranted(forAscendantUuid uuid)
returns bool
stable -- leakproof
language sql as $$
select exists(
select r.uuid
from rbac.grant as g
join rbac.role as r on r.uuid = g.descendantuuid
join rbac.object as o on o.uuid = r.objectuuid
where g.ascendantuuid = forAscendantUuid
and o.objecttable = 'rbac.global'
);
$$;
create or replace procedure rbac.grantPermissionToRole(permissionUuid uuid, roleUuid uuid)
language plpgsql as $$
begin
perform rbac.assertReferenceType('roleId (ascendant)', roleUuid, 'rbac.role');
perform rbac.assertReferenceType('permissionId (descendant)', permissionUuid, 'rbac.permission');
insert
into rbac.grant (grantedByTriggerOf, ascendantUuid, descendantUuid, assumed)
values (rbac.currentTriggerObjectUuid(), roleUuid, permissionUuid, true)
on conflict do nothing; -- allow granting multiple times
end;
$$;
create or replace procedure rbac.grantPermissionToRole(permissionUuid uuid, roleDesc rbac.RoleDescriptor)
language plpgsql as $$
begin
call rbac.grantPermissionToRole(permissionUuid, rbac.findRoleId(roleDesc));
end;
$$;
create or replace procedure rbac.grantRoleToRole(subRoleId uuid, superRoleId uuid, doAssume bool = true)
language plpgsql as $$
begin
perform rbac.assertReferenceType('superRoleId (ascendant)', superRoleId, 'rbac.role');
perform rbac.assertReferenceType('subRoleId (descendant)', subRoleId, 'rbac.role');
if rbac.isGranted(subRoleId, superRoleId) then
call rbac.raiseDuplicateRoleGrantException(subRoleId, superRoleId);
end if;
insert
into rbac.grant (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed)
values (rbac.currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume)
on conflict do nothing; -- allow granting multiple times
end; $$;
create or replace procedure rbac.grantRoleToRole(subRole rbac.RoleDescriptor, superRole rbac.RoleDescriptor, doAssume bool = true)
language plpgsql as $$
declare
superRoleId uuid;
subRoleId uuid;
begin
-- TODO.refa: maybe separate method rbac.grantRoleToRoleIfNotNull(...) for NULLABLE references
if superRole.objectUuid is null or subRole.objectuuid is null then
return;
end if;
superRoleId := rbac.findRoleId(superRole);
subRoleId := rbac.findRoleId(subRole);
perform rbac.assertReferenceType('superRoleId (ascendant)', superRoleId, 'rbac.role');
perform rbac.assertReferenceType('subRoleId (descendant)', subRoleId, 'rbac.role');
if rbac.isGranted(subRoleId, superRoleId) then
call rbac.raiseDuplicateRoleGrantException(subRoleId, superRoleId);
end if;
insert
into rbac.grant (grantedByTriggerOf, ascendantuuid, descendantUuid, assumed)
values (rbac.currentTriggerObjectUuid(), superRoleId, subRoleId, doAssume)
on conflict do nothing; -- allow granting multiple times
end; $$;
create or replace procedure rbac.revokeRoleFromRole(subRole rbac.RoleDescriptor, superRole rbac.RoleDescriptor)
language plpgsql as $$
declare
superRoleId uuid;
subRoleId uuid;
begin
superRoleId := rbac.findRoleId(superRole);
subRoleId := rbac.findRoleId(subRole);
perform rbac.assertReferenceType('superRoleId (ascendant)', superRoleId, 'rbac.role');
perform rbac.assertReferenceType('subRoleId (descendant)', subRoleId, 'rbac.role');
if (rbac.isGranted(superRoleId, subRoleId)) then
delete from rbac.grant where ascendantUuid = superRoleId and descendantUuid = subRoleId;
else
raise exception 'cannot revoke role % (%) from % (%) because it is not granted',
subRole, subRoleId, superRole, superRoleId;
end if;
end; $$;
create or replace procedure rbac.revokePermissionFromRole(permissionId UUID, superRole rbac.RoleDescriptor)
language plpgsql as $$
declare
superRoleId uuid;
permissionOp text;
objectTable text;
objectUuid uuid;
begin
superRoleId := rbac.findRoleId(superRole);
perform rbac.assertReferenceType('superRoleId (ascendant)', superRoleId, 'rbac.role');
perform rbac.assertReferenceType('permission (descendant)', permissionId, 'rbac.permission');
if (rbac.isGranted(superRoleId, permissionId)) then
delete from rbac.grant where ascendantUuid = superRoleId and descendantUuid = permissionId;
else
select p.op, o.objectTable, o.uuid
from rbac.grant g
join rbac.permission p on p.uuid=g.descendantUuid
join rbac.object o on o.uuid=p.objectUuid
where g.uuid=permissionId
into permissionOp, objectTable, objectUuid;
raise exception 'cannot revoke permission % (% on %#% (%) from % (%)) because it is not granted',
permissionId, permissionOp, objectTable, objectUuid, permissionId, superRole, superRoleId;
end if;
end; $$;
-- ============================================================================
--changeset michael.hoennig:rbac-base-QUERY-ACCESSIBLE-OBJECT-UUIDS runOnChange=true endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
*/
create or replace function rbac.queryAccessibleObjectUuidsOfSubjectIds(
requiredOp rbac.RbacOp,
forObjectTable varchar,
subjectIds uuid[],
maxObjects integer = 8000)
returns setof uuid
returns null on null input
language plpgsql as $$
declare
foundRows bigint;
begin
return query
WITH RECURSIVE grants AS (
SELECT descendantUuid, ascendantUuid, 1 AS level
FROM rbac.grant
WHERE assumed
AND ascendantUuid = any(subjectIds)
UNION ALL
SELECT g.descendantUuid, g.ascendantUuid, grants.level + 1 AS level
FROM rbac.grant g
INNER JOIN grants ON grants.descendantUuid = g.ascendantUuid
WHERE g.assumed
),
granted AS (
SELECT DISTINCT descendantUuid
FROM grants
)
SELECT DISTINCT perm.objectUuid
FROM granted
JOIN rbac.permission perm ON granted.descendantUuid = perm.uuid
JOIN rbac.object obj ON obj.uuid = perm.objectUuid
WHERE (requiredOp = 'SELECT' OR perm.op = requiredOp)
AND obj.objectTable = forObjectTable
LIMIT maxObjects+1;
foundRows = base.lastRowCount();
if foundRows > maxObjects then
raise exception '[400] Too many accessible objects, limit is %, found %.', maxObjects, foundRows
using
errcode = 'P0003',
hint = 'Please assume a sub-role and try again.';
end if;
end;
$$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-QUERY-GRANTED-PERMISSIONS endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Returns all permissions accessible to the given subject UUID (subject or role).
*/
create or replace function rbac.queryPermissionsGrantedToSubjectId(subjectId uuid)
returns setof rbac.permission
strict
language sql as $$
with recursive grants as (
select descendantUuid, ascendantUuid
from rbac.grant
where ascendantUuid = subjectId
union all
select g.descendantUuid, g.ascendantUuid
from rbac.grant g
inner join grants on grants.descendantUuid = g.ascendantUuid
)
select perm.*
from rbac.permission perm
where perm.uuid in (
select descendantUuid
from grants
);
$$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-QUERY-SUBJECTS-WITH-PERMISSION-FOR-OBJECT endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Returns all subject UUIDs which have any permission for the given object UUID.
*/
create or replace function rbac.queryAllRbacSubjectsWithPermissionsFor(objectId uuid)
returns setof rbac.subject
returns null on null input
language sql as $$
select *
from rbac.subject
where uuid in (
-- @formatter:off
with recursive grants as (
select descendantUuid, ascendantUuid
from rbac.grant
where descendantUuid = objectId
union all
select "grant".descendantUuid, "grant".ascendantUuid
from rbac.grant "grant"
inner join grants recur on recur.ascendantUuid = "grant".descendantUuid
)
-- @formatter:on
select ascendantUuid
from grants);
$$;
--//
-- ============================================================================
--changeset michael.hoennig:rbac-base-PGSQL-ROLES runOnChange:true validCheckSum:ANY context:!external-db endDelimiter:--//
-- ----------------------------------------------------------------------------
do $$
begin
if '${HSADMINNG_POSTGRES_ADMIN_USERNAME}'='admin' then
if not exists (select from pg_catalog.pg_roles where rolname = 'admin') then
create role admin;
end if;
grant all privileges on all tables in schema public to admin;
end if;
if '${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}'='restricted' then
if not exists (select from pg_catalog.pg_roles where rolname = 'restricted') then
create role restricted;
end if;
grant all privileges on all tables in schema public to restricted;
end if;
end $$;
--//