2022-07-22 13:31:37 +02:00
|
|
|
|
|
|
|
-- ========================================================
|
|
|
|
-- RBAC
|
|
|
|
-- --------------------------------------------------------
|
|
|
|
|
|
|
|
SET SESSION SESSION AUTHORIZATION DEFAULT;
|
|
|
|
|
|
|
|
-- https://arctype.com/blog/postgres-uuid/#creating-a-uuid-primary-key-using-uuid-osp-postgresql-example
|
|
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
|
|
|
|
|
|
DROP TABLE IF EXISTS "RbacPermission";
|
|
|
|
DROP TABLE IF EXISTS "RbacGrants";
|
|
|
|
DROP TABLE IF EXISTS "RbacUser";
|
|
|
|
DROP TABLE IF EXISTS RbacReference CASCADE;
|
|
|
|
DROP TYPE IF EXISTS RbacOp CASCADE;
|
|
|
|
DROP TYPE IF EXISTS ReferenceType CASCADE;
|
|
|
|
|
|
|
|
CREATE TYPE ReferenceType AS ENUM ('RbacUser', 'RbacRole', 'RbacPermission');
|
|
|
|
|
|
|
|
CREATE TABLE RbacReference
|
|
|
|
(
|
|
|
|
uuid uuid UNIQUE DEFAULT uuid_generate_v4(),
|
|
|
|
type ReferenceType not null
|
|
|
|
);
|
|
|
|
|
|
|
|
CREATE TABLE RbacUser
|
|
|
|
(
|
|
|
|
uuid uuid primary key references RbacReference (uuid) ON DELETE CASCADE,
|
|
|
|
name varchar(63) not null unique
|
|
|
|
);
|
|
|
|
|
|
|
|
CREATE TABLE RbacRole
|
|
|
|
(
|
|
|
|
uuid uuid primary key references RbacReference (uuid) ON DELETE CASCADE,
|
|
|
|
name varchar(63) not null unique
|
|
|
|
);
|
|
|
|
|
|
|
|
CREATE TABLE RbacGrants
|
|
|
|
(
|
|
|
|
ascendantUuid uuid references RbacReference (uuid) ON DELETE CASCADE,
|
|
|
|
descendantUuid uuid references RbacReference (uuid) ON DELETE CASCADE,
|
2022-07-22 16:52:49 +02:00
|
|
|
follow boolean not null default true,
|
2022-07-22 13:31:37 +02:00
|
|
|
primary key (ascendantUuid, descendantUuid)
|
|
|
|
);
|
|
|
|
CREATE INDEX ON RbacGrants (ascendantUuid);
|
|
|
|
CREATE INDEX ON RbacGrants (descendantUuid);
|
|
|
|
|
|
|
|
DROP DOMAIN IF EXISTS RbacOp CASCADE;
|
|
|
|
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]+$'
|
|
|
|
);
|
|
|
|
|
|
|
|
DROP TABLE IF EXISTS RbacObject;
|
|
|
|
CREATE TABLE RbacObject
|
|
|
|
(
|
|
|
|
uuid uuid UNIQUE DEFAULT uuid_generate_v4(),
|
|
|
|
objectTable varchar(64) not null,
|
|
|
|
unique (objectTable, uuid)
|
|
|
|
);
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION createRbacObject()
|
|
|
|
RETURNS trigger
|
|
|
|
LANGUAGE plpgsql STRICT AS $$
|
|
|
|
DECLARE
|
|
|
|
objectUuid uuid;
|
|
|
|
BEGIN
|
|
|
|
IF TG_OP = 'INSERT' THEN
|
|
|
|
INSERT INTO RbacObject (objectTable) VALUES (TG_TABLE_NAME) RETURNING uuid INTO objectUuid;
|
|
|
|
NEW.uuid = objectUuid;
|
|
|
|
RETURN NEW;
|
|
|
|
ELSE
|
|
|
|
RAISE EXCEPTION 'invalid usage of TRIGGER AFTER INSERT';
|
|
|
|
END IF;
|
|
|
|
END; $$;
|
|
|
|
|
|
|
|
|
|
|
|
DROP TABLE IF EXISTS RbacPermission;
|
|
|
|
CREATE TABLE RbacPermission
|
|
|
|
( uuid uuid primary key references RbacReference (uuid) ON DELETE CASCADE,
|
|
|
|
objectUuid uuid not null,
|
|
|
|
op RbacOp not null,
|
|
|
|
unique (objectUuid, op)
|
|
|
|
);
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION hasPermission(forObjectUuid uuid, forOp RbacOp)
|
|
|
|
RETURNS bool
|
|
|
|
LANGUAGE sql AS $$
|
|
|
|
SELECT EXISTS (
|
|
|
|
SELECT op
|
|
|
|
FROM RbacPermission p
|
|
|
|
WHERE p.objectUuid=forObjectUuid AND p.op in ('*', forOp)
|
|
|
|
);
|
|
|
|
$$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION createRbacUser(userName varchar)
|
|
|
|
RETURNS uuid
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
declare
|
|
|
|
objectId uuid;
|
|
|
|
BEGIN
|
|
|
|
INSERT INTO RbacReference (type) VALUES ('RbacUser') RETURNING uuid INTO objectId;
|
|
|
|
INSERT INTO RbacUser (uuid, name) VALUES (objectid, userName);
|
|
|
|
return objectId;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION findRbacUser(userName varchar) -- TODO: rename to ...Id
|
|
|
|
RETURNS uuid
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE sql AS $$
|
|
|
|
SELECT uuid FROM RbacUser WHERE name = userName
|
|
|
|
$$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION getRbacUserId(userName varchar, whenNotExists RbacWhenNotExists)
|
|
|
|
RETURNS uuid
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
|
|
|
userUuid uuid;
|
|
|
|
BEGIN
|
|
|
|
userUuid = findRbacUser(userName);
|
|
|
|
IF ( userUuid IS NULL ) THEN
|
|
|
|
IF ( whenNotExists = 'fail') THEN
|
|
|
|
RAISE EXCEPTION 'RbacUser with name="%" not found', userName;
|
|
|
|
END IF;
|
|
|
|
IF ( whenNotExists = 'create') THEN
|
|
|
|
userUuid = createRbacUser(userName);
|
|
|
|
END IF;
|
|
|
|
END IF;
|
|
|
|
return userUuid;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION createRole(roleName varchar)
|
|
|
|
RETURNS uuid
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
declare
|
|
|
|
referenceId uuid;
|
|
|
|
BEGIN
|
|
|
|
INSERT INTO RbacReference (type) VALUES ('RbacRole') RETURNING uuid INTO referenceId;
|
|
|
|
INSERT INTO RbacRole (uuid, name) VALUES (referenceId, roleName);
|
|
|
|
IF (referenceId IS NULL) THEN
|
|
|
|
RAISE EXCEPTION 'referenceId for roleName "%" is unexpectedly null', roleName;
|
|
|
|
end if;
|
|
|
|
return referenceId;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
|
|
CREATE OR REPLACE PROCEDURE deleteRole(roleUUid uuid)
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
BEGIN
|
|
|
|
DELETE FROM RbacRole WHERE uuid=roleUUid;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION findRoleId(roleName varchar)
|
|
|
|
RETURNS uuid
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE sql AS $$
|
|
|
|
SELECT uuid FROM RbacRole WHERE name = roleName
|
|
|
|
$$;
|
|
|
|
|
|
|
|
CREATE TYPE RbacWhenNotExists AS ENUM ('fail', 'create');
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION getRoleId(roleName varchar, whenNotExists RbacWhenNotExists)
|
|
|
|
RETURNS uuid
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
|
|
|
roleUuid uuid;
|
|
|
|
BEGIN
|
|
|
|
roleUuid = findRoleId(roleName);
|
|
|
|
IF ( roleUuid IS NULL ) THEN
|
|
|
|
IF ( whenNotExists = 'fail') THEN
|
|
|
|
RAISE EXCEPTION 'RbacRole with name="%" not found', roleName;
|
|
|
|
END IF;
|
|
|
|
IF ( whenNotExists = 'create') THEN
|
|
|
|
roleUuid = createRole(roleName);
|
|
|
|
END IF;
|
|
|
|
END IF;
|
|
|
|
return roleUuid;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
|
|
|
|
-- select getRoleId('hostmaster', 'create');
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION createPermissions(forObjectUuid uuid, permitOps RbacOp[])
|
|
|
|
RETURNS uuid[]
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
|
|
|
refId uuid;
|
|
|
|
permissionIds uuid[] = ARRAY[]::uuid[];
|
|
|
|
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
|
|
|
|
refId = (SELECT uuid FROM RbacPermission WHERE objectUuid=forObjectUuid AND op=permitOps[i]);
|
|
|
|
IF (refId IS NULL) THEN
|
|
|
|
INSERT INTO RbacReference ("type") VALUES ('RbacPermission') RETURNING uuid INTO refId;
|
|
|
|
INSERT INTO RbacPermission (uuid, objectUuid, op) VALUES (refId, forObjectUuid, permitOps[i]);
|
|
|
|
END IF;
|
|
|
|
permissionIds = permissionIds || refId;
|
|
|
|
END LOOP;
|
|
|
|
return permissionIds;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION findPermissionId(forObjectTable varchar, forObjectUuid uuid, forOp RbacOp)
|
|
|
|
RETURNS uuid
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
STABLE LEAKPROOF
|
|
|
|
LANGUAGE sql AS $$
|
|
|
|
SELECT uuid FROM RbacPermission p
|
|
|
|
WHERE p.objectUuid=forObjectUuid AND p.op in ('*', forOp)
|
|
|
|
$$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION assertReferenceType(argument varchar, referenceId uuid, expectedType ReferenceType)
|
|
|
|
RETURNS ReferenceType
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
|
|
|
actualType ReferenceType;
|
|
|
|
BEGIN
|
|
|
|
actualType = (SELECT type FROM RbacReference WHERE uuid=referenceId);
|
|
|
|
IF ( actualType <> expectedType ) THEN
|
|
|
|
RAISE EXCEPTION '% must reference a %, but got a %', argument, expectedType, actualType;
|
|
|
|
end if;
|
|
|
|
RETURN expectedType;
|
|
|
|
END; $$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE PROCEDURE grantPermissionsToRole(roleUuid uuid, permissionIds uuid[])
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
BEGIN
|
|
|
|
FOR i IN array_lower(permissionIds, 1)..array_upper(permissionIds, 1) LOOP
|
|
|
|
perform assertReferenceType('roleId (ascendant)', roleUuid, 'RbacRole');
|
|
|
|
perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission');
|
|
|
|
|
|
|
|
-- INSERT INTO RbacGrants (ascendantUuid, descendantUuid, apply) VALUES (roleId, permissionIds[i], true); -- assumeV1
|
|
|
|
INSERT INTO RbacGrants (ascendantUuid, descendantUuid) VALUES (roleUuid, permissionIds[i]);
|
|
|
|
END LOOP;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
|
2022-07-22 16:52:49 +02:00
|
|
|
CREATE OR REPLACE PROCEDURE grantRoleToRole(subRoleId uuid, superRoleId uuid, doFollow bool = true )
|
2022-07-22 13:31:37 +02:00
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
BEGIN
|
|
|
|
perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole');
|
|
|
|
perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole');
|
|
|
|
|
|
|
|
IF ( isGranted(subRoleId, superRoleId) ) THEN
|
|
|
|
RAISE EXCEPTION 'Cyclic role grant detected between % and %', subRoleId, superRoleId;
|
|
|
|
END IF;
|
|
|
|
|
|
|
|
-- INSERT INTO RbacGrants (ascendantUuid, descendantUuid, apply) VALUES (superRoleId, subRoleId, doapply); -- assumeV1
|
2022-07-22 16:52:49 +02:00
|
|
|
INSERT INTO RbacGrants (ascendantUuid, descendantUuid, follow)VALUES (superRoleId, subRoleId, doFollow)
|
|
|
|
ON CONFLICT DO NOTHING ; -- TODO: remove?
|
2022-07-22 13:31:37 +02:00
|
|
|
END; $$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE PROCEDURE revokeRoleFromRole(subRoleId uuid, superRoleId uuid)
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
BEGIN
|
|
|
|
perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole');
|
|
|
|
perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole');
|
|
|
|
|
|
|
|
IF ( isGranted(subRoleId, superRoleId) ) THEN
|
|
|
|
DELETE FROM RbacGrants WHERE ascendantUuid=superRoleId AND descendantUuid=subRoleId;
|
|
|
|
END IF;
|
|
|
|
END; $$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE PROCEDURE grantRoleToUser(roleId uuid, userId uuid)
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
BEGIN
|
|
|
|
perform assertReferenceType('roleId (ascendant)', roleId, 'RbacRole');
|
|
|
|
perform assertReferenceType('userId (descendant)', userId, 'RbacUser');
|
|
|
|
|
|
|
|
-- INSERT INTO RbacGrants (ascendantUuid, descendantUuid, apply) VALUES (userId, roleId, true); -- assumeV1
|
|
|
|
INSERT INTO RbacGrants (ascendantUuid, descendantUuid) VALUES (userId, roleId)
|
|
|
|
ON CONFLICT DO NOTHING ; -- TODO: remove
|
|
|
|
END; $$;
|
|
|
|
|
|
|
|
abort;
|
|
|
|
set local session authorization default;
|
|
|
|
|
2022-07-22 16:52:49 +02:00
|
|
|
CREATE OR REPLACE FUNCTION nextLevel(level integer, maxDepth integer)
|
|
|
|
RETURNS INTEGER
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
BEGIN
|
|
|
|
IF (level > maxDepth) THEN
|
|
|
|
RAISE WARNING 'Role assignment depth exceeded %/%.', level, maxDepth;
|
|
|
|
END IF;
|
|
|
|
RETURN level+1;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
2022-07-22 13:31:37 +02:00
|
|
|
CREATE OR REPLACE FUNCTION queryAccessibleObjectUuidsOfSubjectIds(
|
|
|
|
requiredOp RbacOp,
|
2022-07-22 16:52:49 +02:00
|
|
|
forObjectTable varchar, -- TODO: test perforamance in joins!
|
2022-07-22 13:31:37 +02:00
|
|
|
subjectIds uuid[],
|
|
|
|
maxObjects integer = 16000)
|
|
|
|
RETURNS SETOF uuid
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
2022-07-22 16:52:49 +02:00
|
|
|
foundRows bigint;
|
2022-07-22 13:31:37 +02:00
|
|
|
BEGIN
|
2022-07-22 16:52:49 +02:00
|
|
|
RETURN QUERY SELECT DISTINCT perm.objectUuid
|
2022-07-22 13:31:37 +02:00
|
|
|
FROM (
|
|
|
|
WITH RECURSIVE grants AS (
|
|
|
|
SELECT descendantUuid, ascendantUuid, 1 AS level
|
|
|
|
FROM RbacGrants
|
2022-07-22 16:52:49 +02:00
|
|
|
WHERE follow AND ascendantUuid = ANY(subjectIds)
|
|
|
|
UNION DISTINCT
|
|
|
|
SELECT "grant".descendantUuid, "grant".ascendantUuid, level+1 AS level
|
2022-07-22 13:31:37 +02:00
|
|
|
FROM RbacGrants "grant"
|
|
|
|
INNER JOIN grants recur ON recur.descendantUuid = "grant".ascendantUuid
|
2022-07-22 16:52:49 +02:00
|
|
|
WHERE follow
|
2022-07-22 13:31:37 +02:00
|
|
|
) SELECT descendantUuid
|
|
|
|
FROM grants
|
|
|
|
) as granted
|
2022-07-22 16:52:49 +02:00
|
|
|
JOIN RbacPermission perm
|
|
|
|
ON granted.descendantUuid=perm.uuid AND perm.op IN ('*', requiredOp)
|
|
|
|
JOIN RbacObject obj ON obj.uuid=perm.objectUuid AND obj.objectTable=forObjectTable;
|
2022-07-22 13:31:37 +02:00
|
|
|
|
|
|
|
foundRows = lastRowCount();
|
|
|
|
IF foundRows > maxObjects THEN
|
|
|
|
RAISE EXCEPTION 'Too many accessible objects, limit is %, found %.', maxObjects, foundRows
|
|
|
|
USING
|
2022-07-22 16:52:49 +02:00
|
|
|
ERRCODE = 'P0003',
|
2022-07-22 13:31:37 +02:00
|
|
|
HINT = 'Please assume a sub-role and try again.';
|
|
|
|
END IF;
|
|
|
|
END;
|
|
|
|
$$;
|
|
|
|
|
|
|
|
abort;
|
|
|
|
set local session authorization restricted;
|
|
|
|
begin transaction;
|
2022-07-22 16:52:49 +02:00
|
|
|
set local statement_timeout TO '5s';
|
2022-07-22 13:31:37 +02:00
|
|
|
select count(*)
|
2022-07-22 16:52:49 +02:00
|
|
|
from queryAccessibleObjectUuidsOfSubjectIds('view', 'customer', ARRAY[findRbacUser('mike@hostsharing.net')], 10000);
|
2022-07-22 13:31:37 +02:00
|
|
|
end transaction;
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
abort;
|
|
|
|
set local session authorization default;
|
|
|
|
CREATE OR REPLACE FUNCTION queryRequiredPermissionsOfSubjectIds(requiredOp RbacOp, subjectIds uuid[])
|
|
|
|
RETURNS SETOF RbacPermission
|
|
|
|
STRICT
|
|
|
|
LANGUAGE sql AS $$
|
|
|
|
SELECT DISTINCT *
|
|
|
|
FROM RbacPermission
|
|
|
|
WHERE op = '*' OR op = requiredOp
|
|
|
|
AND uuid IN (
|
|
|
|
WITH RECURSIVE grants AS (
|
|
|
|
SELECT DISTINCT
|
|
|
|
descendantUuid,
|
|
|
|
ascendantUuid
|
|
|
|
FROM RbacGrants
|
|
|
|
WHERE
|
|
|
|
ascendantUuid = ANY(subjectIds)
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
|
|
"grant".descendantUuid,
|
|
|
|
"grant".ascendantUuid
|
|
|
|
FROM RbacGrants "grant"
|
|
|
|
INNER JOIN grants recur ON recur.descendantUuid = "grant".ascendantUuid
|
|
|
|
) SELECT
|
|
|
|
descendantUuid
|
|
|
|
FROM grants
|
|
|
|
);
|
|
|
|
$$;
|
|
|
|
|
|
|
|
abort;
|
|
|
|
set local session authorization restricted;
|
|
|
|
begin transaction;
|
|
|
|
-- set local statement_timeout TO '5s';
|
|
|
|
set local statement_timeout TO '5min';
|
|
|
|
select count(*) from queryRequiredPermissionsOfSubjectIds('view', ARRAY[findRbacUser('mike@hostsharing.net')]);
|
|
|
|
end transaction;
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
abort;
|
|
|
|
set local session authorization default;
|
|
|
|
CREATE OR REPLACE FUNCTION queryAllPermissionsOfSubjectIds(subjectIds uuid[])
|
|
|
|
RETURNS SETOF RbacPermission
|
|
|
|
STRICT
|
|
|
|
LANGUAGE sql AS $$
|
|
|
|
SELECT DISTINCT * FROM RbacPermission WHERE uuid IN (
|
|
|
|
WITH RECURSIVE grants AS (
|
|
|
|
SELECT DISTINCT
|
|
|
|
descendantUuid,
|
|
|
|
ascendantUuid
|
|
|
|
FROM RbacGrants
|
|
|
|
WHERE
|
|
|
|
ascendantUuid = ANY(subjectIds)
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
|
|
"grant".descendantUuid,
|
|
|
|
"grant".ascendantUuid
|
|
|
|
FROM RbacGrants "grant"
|
|
|
|
INNER JOIN grants recur ON recur.descendantUuid = "grant".ascendantUuid
|
|
|
|
) SELECT
|
|
|
|
descendantUuid
|
|
|
|
FROM grants
|
|
|
|
);
|
|
|
|
$$;
|
|
|
|
|
|
|
|
abort;
|
|
|
|
set local session authorization restricted;
|
|
|
|
begin transaction;
|
|
|
|
set local statement_timeout TO '5s';
|
|
|
|
select count(*) from queryAllPermissionsOfSubjectIds(ARRAY[findRbacUser('mike@hostsharing.net')]);
|
|
|
|
end transaction;
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION queryAllPermissionsOfSubjectId(subjectId uuid) -- TODO: remove?
|
|
|
|
RETURNS SETOF RbacPermission
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE sql AS $$
|
|
|
|
SELECT * FROM RbacPermission WHERE uuid IN (
|
|
|
|
WITH RECURSIVE grants AS (
|
|
|
|
SELECT
|
|
|
|
descendantUuid,
|
|
|
|
ascendantUuid
|
|
|
|
FROM
|
|
|
|
RbacGrants
|
|
|
|
WHERE
|
|
|
|
ascendantUuid = subjectId
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
|
|
"grant".descendantUuid,
|
|
|
|
"grant".ascendantUuid
|
|
|
|
FROM RbacGrants "grant"
|
|
|
|
INNER JOIN grants recur ON recur.descendantUuid = "grant".ascendantUuid
|
|
|
|
) SELECT
|
|
|
|
descendantUuid
|
|
|
|
FROM
|
|
|
|
grants
|
|
|
|
);
|
|
|
|
$$;
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION queryAllRbacUsersWithPermissionsFor(objectId uuid)
|
|
|
|
RETURNS SETOF RbacUser
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE sql AS $$
|
|
|
|
SELECT * FROM RbacUser WHERE uuid IN (
|
|
|
|
WITH RECURSIVE grants AS (
|
|
|
|
SELECT
|
|
|
|
descendantUuid,
|
|
|
|
ascendantUuid
|
|
|
|
FROM
|
|
|
|
RbacGrants
|
|
|
|
WHERE
|
|
|
|
descendantUuid = objectId
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
|
|
"grant".descendantUuid,
|
|
|
|
"grant".ascendantUuid
|
|
|
|
FROM
|
|
|
|
RbacGrants "grant"
|
|
|
|
INNER JOIN grants recur ON recur.ascendantUuid = "grant".descendantUuid
|
|
|
|
) SELECT
|
|
|
|
ascendantUuid
|
|
|
|
FROM
|
|
|
|
grants
|
|
|
|
);
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION findGrantees(grantedId uuid)
|
|
|
|
RETURNS SETOF RbacReference
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE sql AS $$
|
|
|
|
SELECT reference.*
|
|
|
|
FROM (
|
|
|
|
WITH RECURSIVE grants AS (
|
|
|
|
SELECT
|
|
|
|
descendantUuid,
|
|
|
|
ascendantUuid
|
|
|
|
FROM
|
|
|
|
RbacGrants
|
|
|
|
WHERE
|
|
|
|
descendantUuid = grantedId
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
|
|
"grant".descendantUuid,
|
|
|
|
"grant".ascendantUuid
|
|
|
|
FROM
|
|
|
|
RbacGrants "grant"
|
|
|
|
INNER JOIN grants recur ON recur.ascendantUuid = "grant".descendantUuid
|
|
|
|
) SELECT
|
|
|
|
ascendantUuid
|
|
|
|
FROM
|
|
|
|
grants
|
|
|
|
) as grantee
|
|
|
|
JOIN RbacReference reference ON reference.uuid=grantee.ascendantUuid;
|
|
|
|
$$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION isGranted(granteeId uuid, grantedId uuid)
|
|
|
|
RETURNS bool
|
|
|
|
RETURNS NULL ON NULL INPUT
|
|
|
|
LANGUAGE sql AS $$
|
2022-07-22 16:52:49 +02:00
|
|
|
SELECT granteeId=grantedId OR granteeId IN (
|
|
|
|
WITH RECURSIVE grants AS (
|
|
|
|
SELECT descendantUuid, ascendantUuid
|
|
|
|
FROM RbacGrants
|
|
|
|
WHERE descendantUuid = grantedId
|
|
|
|
UNION ALL
|
|
|
|
SELECT "grant".descendantUuid, "grant".ascendantUuid
|
|
|
|
FROM RbacGrants "grant"
|
2022-07-22 13:31:37 +02:00
|
|
|
INNER JOIN grants recur ON recur.ascendantUuid = "grant".descendantUuid
|
2022-07-22 16:52:49 +02:00
|
|
|
) SELECT
|
|
|
|
ascendantUuid
|
|
|
|
FROM
|
|
|
|
grants
|
|
|
|
);
|
2022-07-22 13:31:37 +02:00
|
|
|
$$;
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION isPermissionGrantedToSubject(permissionId uuid, subjectId uuid)
|
|
|
|
RETURNS BOOL
|
|
|
|
STABLE LEAKPROOF
|
|
|
|
LANGUAGE sql AS $$
|
|
|
|
SELECT EXISTS (
|
|
|
|
SELECT * FROM RbacUser WHERE uuid IN (
|
|
|
|
WITH RECURSIVE grants AS (
|
|
|
|
SELECT
|
|
|
|
descendantUuid,
|
|
|
|
ascendantUuid
|
|
|
|
FROM
|
|
|
|
RbacGrants g
|
|
|
|
WHERE
|
|
|
|
g.descendantUuid = permissionId
|
|
|
|
UNION ALL
|
|
|
|
SELECT
|
|
|
|
g.descendantUuid,
|
|
|
|
g.ascendantUuid
|
|
|
|
FROM
|
|
|
|
RbacGrants g
|
|
|
|
INNER JOIN grants recur ON recur.ascendantUuid = g.descendantUuid
|
|
|
|
) SELECT
|
|
|
|
ascendantUuid
|
|
|
|
FROM
|
|
|
|
grants
|
|
|
|
WHERE ascendantUuid=subjectId
|
|
|
|
)
|
|
|
|
);
|
|
|
|
$$;
|
|
|
|
|
|
|
|
SET SESSION AUTHORIZATION DEFAULT;
|
|
|
|
CREATE ROLE admin;
|
|
|
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO admin;
|
|
|
|
CREATE ROLE restricted;
|
|
|
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO restricted;
|
|
|
|
|
|
|
|
|
|
|
|
-- ========================================================
|
|
|
|
-- Current User
|
|
|
|
-- --------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION currentUser()
|
|
|
|
RETURNS varchar(63)
|
|
|
|
STABLE LEAKPROOF
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
|
|
|
currentUser VARCHAR(63);
|
|
|
|
BEGIN
|
|
|
|
BEGIN
|
|
|
|
currentUser := current_setting('hsadminng.currentUser');
|
|
|
|
EXCEPTION WHEN OTHERS THEN
|
|
|
|
currentUser := NULL;
|
|
|
|
END;
|
|
|
|
IF (currentUser IS NULL OR currentUser = '') THEN
|
|
|
|
RAISE EXCEPTION 'hsadminng.currentUser must be defined, please use "SET LOCAL ...;"';
|
|
|
|
END IF;
|
|
|
|
RETURN currentUser;
|
|
|
|
END; $$;
|
|
|
|
|
|
|
|
SET SESSION AUTHORIZATION DEFAULT;
|
|
|
|
CREATE OR REPLACE FUNCTION currentUserId()
|
|
|
|
RETURNS uuid
|
|
|
|
STABLE LEAKPROOF
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
|
|
|
currentUser VARCHAR(63);
|
|
|
|
currentUserId uuid;
|
|
|
|
BEGIN
|
|
|
|
currentUser := currentUser();
|
|
|
|
currentUserId = (SELECT uuid FROM RbacUser WHERE name = currentUser);
|
|
|
|
RETURN currentUserId;
|
|
|
|
END; $$;
|
|
|
|
|
|
|
|
|
|
|
|
CREATE OR REPLACE FUNCTION assumedRoles()
|
|
|
|
RETURNS varchar(63)[]
|
|
|
|
STABLE LEAKPROOF
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
|
|
|
currentSubject VARCHAR(63);
|
|
|
|
BEGIN
|
|
|
|
BEGIN
|
|
|
|
currentSubject := current_setting('hsadminng.assumedRoles');
|
2022-07-22 16:52:49 +02:00
|
|
|
EXCEPTION WHEN OTHERS THEN
|
|
|
|
RETURN ARRAY[]::varchar[];
|
|
|
|
END;
|
|
|
|
IF (currentSubject = '') THEN
|
|
|
|
RETURN ARRAY[]::varchar[];
|
|
|
|
END IF;
|
|
|
|
RETURN string_to_array(currentSubject, ';');
|
|
|
|
END; $$;
|
2022-07-22 13:31:37 +02:00
|
|
|
|
|
|
|
|
2022-07-22 16:52:49 +02:00
|
|
|
ROLLBACK;
|
2022-07-22 13:31:37 +02:00
|
|
|
SET SESSION AUTHORIZATION DEFAULT;
|
|
|
|
CREATE OR REPLACE FUNCTION currentSubjectIds()
|
|
|
|
RETURNS uuid[]
|
|
|
|
STABLE LEAKPROOF
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
|
|
|
assumedRoles VARCHAR(63)[];
|
|
|
|
currentUserId uuid;
|
|
|
|
assumedRoleIds uuid[];
|
|
|
|
assumedRoleId uuid;
|
|
|
|
BEGIN
|
|
|
|
currentUserId := currentUserId();
|
|
|
|
assumedRoles := assumedRoles();
|
2022-07-22 16:52:49 +02:00
|
|
|
IF ( CARDINALITY(assumedRoles) = 0 ) THEN
|
|
|
|
RETURN ARRAY[currentUserId];
|
2022-07-22 13:31:37 +02:00
|
|
|
END IF;
|
|
|
|
|
|
|
|
RAISE NOTICE 'assuming roles: %', assumedRoles;
|
|
|
|
|
|
|
|
SELECT ARRAY_AGG(uuid) FROM RbacRole WHERE name = ANY(assumedRoles) INTO assumedRoleIds;
|
2022-07-22 16:52:49 +02:00
|
|
|
IF assumedRoleIds IS NOT NULL THEN
|
|
|
|
FOREACH assumedRoleId IN ARRAY assumedRoleIds LOOP
|
|
|
|
IF ( NOT isGranted(currentUserId, assumedRoleId) ) THEN
|
|
|
|
RAISE EXCEPTION 'user % has no permission to assume role %', currentUser(), assumedRoleId;
|
|
|
|
END IF;
|
|
|
|
END LOOP;
|
|
|
|
END IF;
|
2022-07-22 13:31:37 +02:00
|
|
|
RETURN assumedRoleIds;
|
|
|
|
END; $$;
|
2022-07-22 16:52:49 +02:00
|
|
|
|
|
|
|
rollback;
|
|
|
|
set session authorization default;
|
|
|
|
CREATE OR REPLACE FUNCTION maxGrantDepth()
|
|
|
|
RETURNS integer
|
|
|
|
STABLE LEAKPROOF
|
|
|
|
LANGUAGE plpgsql AS $$
|
|
|
|
DECLARE
|
|
|
|
maxGrantDepth VARCHAR(63);
|
|
|
|
BEGIN
|
|
|
|
BEGIN
|
|
|
|
maxGrantDepth := current_setting('hsadminng.maxGrantDepth');
|
|
|
|
EXCEPTION WHEN OTHERS THEN
|
|
|
|
maxGrantDepth := NULL;
|
|
|
|
END;
|
|
|
|
RETURN coalesce(maxGrantDepth, '8')::integer;
|
|
|
|
END; $$;
|