diff --git a/src/main/resources/db/changelog/20-hs-base.sql b/src/main/resources/db/changelog/20-hs-base.sql index dcd8a252..b81aa4ae 100644 --- a/src/main/resources/db/changelog/20-hs-base.sql +++ b/src/main/resources/db/changelog/20-hs-base.sql @@ -1,22 +1,21 @@ - - - -CREATE TABLE Hostsharing +create table Hostsharing ( - uuid uuid PRIMARY KEY REFERENCES RbacObject(uuid) + uuid uuid primary key references RbacObject (uuid) ); -CREATE UNIQUE INDEX Hostsharing_Singleton ON Hostsharing ((0)); +create unique index Hostsharing_Singleton on Hostsharing ((0)); -INSERT INTO RbacObject (objecttable) VALUES ('hostsharing'); -INSERT INTO Hostsharing (uuid) VALUES ((SELECT uuid FROM RbacObject WHERE objectTable='hostsharing')); +insert +into RbacObject (objecttable) values ('hostsharing'); +insert + into Hostsharing (uuid) values ((select uuid from RbacObject where objectTable = 'hostsharing')); -CREATE OR REPLACE FUNCTION hostsharingAdmin() - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - STABLE LEAKPROOF - LANGUAGE sql AS $$ -SELECT 'global', (SELECT uuid FROM RbacObject WHERE objectTable='hostsharing'), 'admin'::RbacRoleType; +create or replace function hostsharingAdmin() + returns RbacRoleDescriptor + returns null on null input + stable leakproof + language sql as $$ +select 'global', (select uuid from RbacObject where objectTable = 'hostsharing'), 'admin'::RbacRoleType; $$; -- create administrators role with two assigned users @@ -32,7 +31,7 @@ do language plpgsql $$ $$; -BEGIN TRANSACTION; -SET LOCAL hsadminng.currentUser = 'mike@hostsharing.net'; -select * from RbacUser where uuid=currentUserId(); -END TRANSACTION; +begin transaction; +set local hsadminng.currentUser = 'mike@hostsharing.net'; +select * from RbacUser where uuid = currentUserId(); +end transaction; diff --git a/src/main/resources/db/changelog/2022-07-28-002-int-to-var.sql b/src/main/resources/db/changelog/2022-07-28-002-int-to-var.sql index 7ef0efce..cccc037d 100644 --- a/src/main/resources/db/changelog/2022-07-28-002-int-to-var.sql +++ b/src/main/resources/db/changelog/2022-07-28-002-int-to-var.sql @@ -15,11 +15,11 @@ create or replace function intToVarChar(i integer, len integer) declare partial varchar; begin - select chr(ascii('a') + i%26) into partial; + select chr(ascii('a') + i % 26) into partial; if len > 1 then - return intToVarChar(i/26, len-1) || partial; + return intToVarChar(i / 26, len - 1) || partial; else return partial; end if; -END; $$; +end; $$; --// diff --git a/src/main/resources/db/changelog/2022-07-28-003-random-in-range.sql b/src/main/resources/db/changelog/2022-07-28-003-random-in-range.sql index 277984fc..41376770 100644 --- a/src/main/resources/db/changelog/2022-07-28-003-random-in-range.sql +++ b/src/main/resources/db/changelog/2022-07-28-003-random-in-range.sql @@ -12,9 +12,9 @@ create or replace function randomInRange(min integer, max integer) returns integer returns null on null input - language 'plpgsql' AS $$ + language 'plpgsql' as $$ begin - return floor(random() * (max-min + 1) + min); + return floor(random() * (max - min + 1) + min); end; $$; --// diff --git a/src/main/resources/db/changelog/2022-07-28-004-uuid-ossp-extension.sql b/src/main/resources/db/changelog/2022-07-28-004-uuid-ossp-extension.sql index 675b6ddb..45d6e799 100644 --- a/src/main/resources/db/changelog/2022-07-28-004-uuid-ossp-extension.sql +++ b/src/main/resources/db/changelog/2022-07-28-004-uuid-ossp-extension.sql @@ -5,5 +5,5 @@ /* Makes improved uuid generation available. */ -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +create extension if not exists "uuid-ossp"; --// diff --git a/src/main/resources/db/changelog/2022-07-28-005-rbac-base.sql b/src/main/resources/db/changelog/2022-07-28-005-rbac-base.sql index 83e954ee..1a91f0d0 100644 --- a/src/main/resources/db/changelog/2022-07-28-005-rbac-base.sql +++ b/src/main/resources/db/changelog/2022-07-28-005-rbac-base.sql @@ -4,26 +4,26 @@ /* */ -CREATE TYPE ReferenceType AS ENUM ('RbacUser', 'RbacRole', 'RbacPermission'); +create type ReferenceType as enum ('RbacUser', 'RbacRole', 'RbacPermission'); -CREATE TABLE RbacReference +create table RbacReference ( - uuid uuid UNIQUE DEFAULT uuid_generate_v4(), + uuid uuid unique default uuid_generate_v4(), type ReferenceType not null ); -CREATE OR REPLACE FUNCTION assertReferenceType(argument varchar, referenceId uuid, expectedType ReferenceType) - RETURNS ReferenceType - LANGUAGE plpgsql AS $$ -DECLARE +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; +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; $$; + return expectedType; +end; $$; --// @@ -31,52 +31,54 @@ END; $$; /* */ -CREATE TABLE RbacUser +create table RbacUser ( - uuid uuid primary key references RbacReference (uuid) ON DELETE CASCADE, + uuid uuid primary key references RbacReference (uuid) on delete cascade, name varchar(63) not null unique ); -CREATE OR REPLACE FUNCTION createRbacUser(userName varchar) - RETURNS uuid - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +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); +begin + insert + into RbacReference (type) values ('RbacUser') returning uuid into objectId; + insert + into RbacUser (uuid, name) values (objectid, userName); return objectId; -END; +end; $$; -CREATE OR REPLACE FUNCTION findRbacUserId(userName varchar) - RETURNS uuid - RETURNS NULL ON NULL INPUT - LANGUAGE sql AS $$ -SELECT uuid FROM RbacUser WHERE name = userName +create or replace function findRbacUserId(userName varchar) + returns uuid + returns null on null input + language sql as $$ +select uuid from RbacUser where name = userName $$; -CREATE TYPE RbacWhenNotExists AS ENUM ('fail', 'create'); +create type RbacWhenNotExists as enum ('fail', 'create'); -CREATE OR REPLACE FUNCTION getRbacUserId(userName varchar, whenNotExists RbacWhenNotExists) - RETURNS uuid - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ -DECLARE +create or replace function getRbacUserId(userName varchar, whenNotExists RbacWhenNotExists) + returns uuid + returns null on null input + language plpgsql as $$ +declare userUuid uuid; -BEGIN +begin userUuid = findRbacUserId(userName); - IF ( userUuid IS NULL ) THEN - IF ( whenNotExists = 'fail') THEN - RAISE EXCEPTION 'RbacUser with name="%" not found', userName; - END IF; - IF ( whenNotExists = 'create') THEN + 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; + end if; + end if; return userUuid; -END; +end; $$; --// @@ -85,27 +87,29 @@ $$; /* */ -CREATE TABLE RbacObject +create table RbacObject ( - uuid uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + uuid uuid primary key default uuid_generate_v4(), objectTable varchar(64) not null, unique (objectTable, uuid) ); -CREATE OR REPLACE FUNCTION createRbacObject() - RETURNS trigger - LANGUAGE plpgsql STRICT AS $$ -DECLARE +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; +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; $$; + return NEW; + else + raise exception 'invalid usage of TRIGGER AFTER INSERT'; + end if; +end; $$; --// @@ -115,100 +119,102 @@ END; $$; */ -CREATE TYPE RbacRoleType AS ENUM ('owner', 'admin', 'tenant'); +create type RbacRoleType as enum ('owner', 'admin', 'tenant'); -CREATE TABLE RbacRole +create table RbacRole ( - uuid uuid primary key references RbacReference (uuid) ON DELETE CASCADE, - objectUuid uuid references RbacObject(uuid) not null, - roleType RbacRoleType not null + uuid uuid primary key references RbacReference (uuid) on delete cascade, + objectUuid uuid references RbacObject (uuid) not null, + roleType RbacRoleType not null ); -CREATE TYPE RbacRoleDescriptor AS +create type RbacRoleDescriptor as ( - objectTable varchar(63), -- TODO: needed? remove? - objectUuid uuid, - roleType RbacRoleType + objectTable varchar(63), -- TODO: needed? remove? + objectUuid uuid, + roleType RbacRoleType ); -CREATE OR REPLACE FUNCTION roleDescriptor(objectTable varchar(63), objectUuid uuid, roleType RbacRoleType ) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT +create or replace function roleDescriptor(objectTable varchar(63), objectUuid uuid, roleType RbacRoleType) + returns RbacRoleDescriptor + returns null on null input -- STABLE LEAKPROOF - LANGUAGE sql AS $$ -SELECT objectTable, objectUuid, roleType::RbacRoleType; + language sql as $$ +select objectTable, objectUuid, roleType::RbacRoleType; $$; - -CREATE OR REPLACE FUNCTION createRole(roleDescriptor RbacRoleDescriptor) - RETURNS uuid - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function createRole(roleDescriptor RbacRoleDescriptor) + 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, objectUuid, roleType) VALUES (referenceId, roleDescriptor.objectUuid, roleDescriptor.roleType); +begin + insert + into RbacReference (type) values ('RbacRole') returning uuid into referenceId; + insert + into RbacRole (uuid, objectUuid, roleType) values (referenceId, roleDescriptor.objectUuid, roleDescriptor.roleType); return referenceId; -END; +end; $$; -CREATE OR REPLACE PROCEDURE deleteRole(roleUUid uuid) - LANGUAGE plpgsql AS $$ -BEGIN - DELETE FROM RbacRole WHERE uuid=roleUUid; -END; +create or replace procedure deleteRole(roleUUid uuid) + language plpgsql as $$ +begin + delete from RbacRole where uuid = roleUUid; +end; $$; -CREATE OR REPLACE FUNCTION findRoleId(roleDescriptor RbacRoleDescriptor) - RETURNS uuid - RETURNS NULL ON NULL INPUT - LANGUAGE sql AS $$ -SELECT uuid FROM RbacRole WHERE objectUuid = roleDescriptor.objectUuid AND roleType = roleDescriptor.roleType; +create or replace function findRoleId(roleDescriptor RbacRoleDescriptor) + returns uuid + returns null on null input + language sql as $$ +select uuid from RbacRole where objectUuid = roleDescriptor.objectUuid and roleType = roleDescriptor.roleType; $$; -CREATE OR REPLACE FUNCTION getRoleId(roleDescriptor RbacRoleDescriptor, whenNotExists RbacWhenNotExists) - RETURNS uuid - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ -DECLARE +create or replace function getRoleId(roleDescriptor RbacRoleDescriptor, whenNotExists RbacWhenNotExists) + returns uuid + returns null on null input + language plpgsql as $$ +declare roleUuid uuid; -BEGIN +begin roleUuid = findRoleId(roleDescriptor); - IF ( roleUuid IS NULL ) THEN - IF ( whenNotExists = 'fail') THEN - RAISE EXCEPTION 'RbacRole "%#%.%" not found', roleDescriptor.objectTable, roleDescriptor.objectUuid, roleDescriptor.roleType; - END IF; - IF ( whenNotExists = 'create') THEN + if (roleUuid is null) then + if (whenNotExists = 'fail') then + raise exception 'RbacRole "%#%.%" not found', roleDescriptor.objectTable, roleDescriptor.objectUuid, roleDescriptor.roleType; + end if; + if (whenNotExists = 'create') then roleUuid = createRole(roleDescriptor); - END IF; - END IF; + end if; + end if; return roleUuid; -END; +end; $$; --changeset rbac-base-permission:1 endDelimiter:--// /* */ -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]+$' +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 RbacPermission; -CREATE TABLE RbacPermission -( uuid uuid primary key references RbacReference (uuid) ON DELETE CASCADE, - objectUuid uuid not null references RbacObject, - op RbacOp not null, +create table RbacPermission +( + uuid uuid primary key references RbacReference (uuid) on delete cascade, + objectUuid uuid not null references RbacObject, + op RbacOp not null, unique (objectUuid, op) ); @@ -216,54 +222,60 @@ CREATE TABLE RbacPermission -- alter table rbacpermission add constraint rbacpermission_objectuuid_fkey foreign key (objectUuid) references rbacobject(uuid); -- alter table rbacpermission drop constraint rbacpermission_objectuuid; -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 createPermissions(forObjectUuid uuid, permitOps RbacOp[]) - RETURNS uuid[] - LANGUAGE plpgsql AS $$ -DECLARE - refId uuid; - permissionIds uuid[] = ARRAY[]::uuid[]; -BEGIN - RAISE NOTICE 'createPermission for: % %', forObjectUuid, permitOps; - 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 - RAISE NOTICE 'createPermission: % %', forObjectUuid, permitOps[i]; - INSERT INTO RbacReference ("type") VALUES ('RbacPermission') RETURNING uuid INTO refId; - INSERT INTO RbacPermission (uuid, objectUuid, op) VALUES (refId, forObjectUuid, permitOps[i]); - END IF; - RAISE NOTICE 'addPermission: %', refId; - permissionIds = permissionIds || refId; - END LOOP; - - RAISE NOTICE 'createPermissions returning: %', permissionIds; - return permissionIds; -END; +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 findPermissionId(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 createPermissions(forObjectUuid uuid, permitOps RbacOp[]) + returns uuid[] + language plpgsql as $$ +declare + refId uuid; + permissionIds uuid[] = array []::uuid[]; +begin + raise notice 'createPermission for: % %', forObjectUuid, permitOps; + 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 + raise notice 'createPermission: % %', forObjectUuid, permitOps[i]; + insert + into RbacReference ("type") values ('RbacPermission') returning uuid into refId; + insert + into RbacPermission (uuid, objectUuid, op) values (refId, forObjectUuid, permitOps[i]); + end if; + raise notice 'addPermission: %', refId; + permissionIds = permissionIds || refId; + end loop; + + raise notice 'createPermissions returning: %', permissionIds; + return permissionIds; +end; +$$; + +create or replace function findPermissionId(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) $$; --// @@ -272,192 +284,174 @@ $$; /* */ -CREATE TABLE RbacGrants +create table RbacGrants ( - ascendantUuid uuid references RbacReference (uuid) ON DELETE CASCADE, - descendantUuid uuid references RbacReference (uuid) ON DELETE CASCADE, - follow boolean not null default true, + ascendantUuid uuid references RbacReference (uuid) on delete cascade, + descendantUuid uuid references RbacReference (uuid) on delete cascade, + follow boolean not null default true, primary key (ascendantUuid, descendantUuid) ); -CREATE INDEX ON RbacGrants (ascendantUuid); -CREATE INDEX ON RbacGrants (descendantUuid); +create index on RbacGrants (ascendantUuid); +create index on RbacGrants (descendantUuid); --// -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 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 $$ -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" - INNER JOIN grants recur ON recur.ascendantUuid = "grant".descendantUuid - ) SELECT - ascendantUuid - FROM - grants -); +create or replace function isGranted(granteeId uuid, grantedId uuid) + returns bool + returns null on null input + language sql as $$ +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" + inner join grants recur on recur.ascendantUuid = "grant".descendantUuid) + select ascendantUuid + from grants); $$; -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 - ) +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) ); $$; -CREATE OR REPLACE PROCEDURE grantPermissionsToRole(roleUuid uuid, permissionIds uuid[]) - LANGUAGE plpgsql AS $$ -BEGIN - RAISE NOTICE 'grantPermissionsToRole: % -> %', roleUuid, permissionIds; - 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'); +create or replace procedure grantPermissionsToRole(roleUuid uuid, permissionIds uuid[]) + language plpgsql as $$ +begin + raise notice 'grantPermissionsToRole: % -> %', roleUuid, permissionIds; + 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; + -- INSERT INTO RbacGrants (ascendantUuid, descendantUuid, apply) VALUES (roleId, permissionIds[i], true); -- assumeV1 + insert + into RbacGrants (ascendantUuid, descendantUuid) values (roleUuid, permissionIds[i]); + end loop; +end; $$; -CREATE OR REPLACE PROCEDURE grantRoleToRole(subRoleId uuid, superRoleId uuid, doFollow bool = true ) - LANGUAGE plpgsql AS $$ -BEGIN +create or replace procedure grantRoleToRole(subRoleId uuid, superRoleId uuid, doFollow bool = true) + language plpgsql as $$ +begin perform assertReferenceType('superRoleId (ascendant)', superRoleId, 'RbacRole'); - perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole'); + perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole'); - IF ( isGranted(subRoleId, superRoleId) ) THEN - RAISE EXCEPTION 'Cyclic role grant detected between % and %', subRoleId, superRoleId; - END IF; + 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 - INSERT INTO RbacGrants (ascendantUuid, descendantUuid, follow) VALUES (superRoleId, subRoleId, doFollow) - ON CONFLICT DO NOTHING ; -- TODO: remove? -END; $$; + insert + into RbacGrants (ascendantUuid, descendantUuid, follow) + values (superRoleId, subRoleId, doFollow) + on conflict do nothing; -- TODO: remove? +end; $$; -CREATE OR REPLACE PROCEDURE revokeRoleFromRole(subRoleId uuid, superRoleId uuid) - LANGUAGE plpgsql AS $$ -BEGIN +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'); + perform assertReferenceType('subRoleId (descendant)', subRoleId, 'RbacRole'); - IF ( isGranted(subRoleId, superRoleId) ) THEN - DELETE FROM RbacGrants WHERE ascendantUuid=superRoleId AND descendantUuid=subRoleId; - END IF; -END; $$; + 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 +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'); + 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; $$; + insert + into RbacGrants (ascendantUuid, descendantUuid) + values (userId, roleId) + on conflict do nothing; -- TODO: remove? +end; $$; --// --changeset rbac-base-query-accessible-object-uuids:1 endDelimiter:--// /* */ -CREATE OR REPLACE FUNCTION queryAccessibleObjectUuidsOfSubjectIds( - requiredOp RbacOp, - forObjectTable varchar, -- reduces the result set, but is not really faster when used in restricted view - subjectIds uuid[], - maxObjects integer = 8000) - RETURNS SETOF uuid - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ - DECLARE - foundRows bigint; - BEGIN - RETURN QUERY SELECT DISTINCT perm.objectUuid - FROM ( - WITH RECURSIVE grants AS ( - SELECT descendantUuid, ascendantUuid, 1 AS level - FROM RbacGrants - WHERE follow AND ascendantUuid = ANY(subjectIds) - UNION DISTINCT - SELECT "grant".descendantUuid, "grant".ascendantUuid, level+1 AS level - FROM RbacGrants "grant" - INNER JOIN grants recur ON recur.descendantUuid = "grant".ascendantUuid - WHERE follow - ) SELECT descendantUuid - FROM grants - ) as granted - 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 - LIMIT maxObjects+1; +create or replace function queryAccessibleObjectUuidsOfSubjectIds( + requiredOp RbacOp, + forObjectTable varchar, -- reduces the result set, but is not really faster when used in restricted view + subjectIds uuid[], + maxObjects integer = 8000) + returns setof uuid + returns null on null input + language plpgsql as $$ +declare + foundRows bigint; +begin + return query select distinct perm.objectUuid + from (with recursive grants as (select descendantUuid, ascendantUuid, 1 as level + from RbacGrants + where follow + and ascendantUuid = any (subjectIds) + union + distinct + select "grant".descendantUuid, "grant".ascendantUuid, level + 1 as level + from RbacGrants "grant" + inner join grants recur on recur.descendantUuid = "grant".ascendantUuid + where follow) + select descendantUuid + from grants) as granted + 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 + limit maxObjects + 1; - foundRows = lastRowCount(); - IF foundRows > maxObjects THEN - RAISE EXCEPTION 'Too many accessible objects, limit is %, found %.', maxObjects, foundRows - USING - ERRCODE = 'P0003', - HINT = 'Please assume a sub-role and try again.'; - END IF; - END; + foundRows = lastRowCount(); + if foundRows > maxObjects then + raise exception 'Too many accessible objects, limit is %, found %.', maxObjects, foundRows + using + errcode = 'P0003', + hint = 'Please assume a sub-role and try again.'; + end if; +end; $$; --// @@ -466,31 +460,25 @@ $$; /* */ -CREATE OR REPLACE FUNCTION queryGrantedPermissionsOfSubjectIds(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 - ); +create or replace function queryGrantedPermissionsOfSubjectIds(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); $$; --// @@ -500,31 +488,23 @@ $$; */ -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 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); $$; --// @@ -533,36 +513,37 @@ $$; /* */ -CREATE OR REPLACE FUNCTION currentUser() - RETURNS varchar(63) - STABLE LEAKPROOF - LANGUAGE plpgsql AS $$ -DECLARE - currentUser VARCHAR(63); -BEGIN - BEGIN +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; $$; + 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; $$; -CREATE OR REPLACE FUNCTION currentUserId() - RETURNS uuid - STABLE LEAKPROOF - LANGUAGE plpgsql AS $$ -DECLARE - currentUser VARCHAR(63); +create or replace function currentUserId() + returns uuid + stable leakproof + language plpgsql as $$ +declare + currentUser varchar(63); currentUserId uuid; -BEGIN +begin currentUser := currentUser(); - currentUserId = (SELECT uuid FROM RbacUser WHERE name = currentUser); - RETURN currentUserId; -END; $$; + currentUserId = (select uuid from RbacUser where name = currentUser); + return currentUserId; +end; $$; --// @@ -571,86 +552,90 @@ END; $$; /* */ -CREATE OR REPLACE FUNCTION assumedRoles() - RETURNS varchar(63)[] - STABLE LEAKPROOF - LANGUAGE plpgsql AS $$ -DECLARE - currentSubject VARCHAR(63); -BEGIN - BEGIN +create or replace function assumedRoles() + returns varchar(63)[] + stable leakproof + language plpgsql as $$ +declare + currentSubject varchar(63); +begin + begin currentSubject := current_setting('hsadminng.assumedRoles'); - EXCEPTION WHEN OTHERS THEN - RETURN ARRAY[]::varchar[]; - END; - IF (currentSubject = '') THEN - RETURN ARRAY[]::varchar[]; - END IF; - RETURN string_to_array(currentSubject, ';'); - END; $$; + exception + when others then + return array []::varchar[]; + end; + if (currentSubject = '') then + return array []::varchar[]; + end if; + return string_to_array(currentSubject, ';'); +end; $$; -CREATE OR REPLACE FUNCTION pureIdentifier(rawIdentifier varchar) - RETURNS uuid - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function pureIdentifier(rawIdentifier varchar) + returns uuid + returns null on null input + language plpgsql as $$ begin return regexp_replace(rawIdentifier, '\W+', ''); end; $$; -CREATE OR REPLACE FUNCTION findUuidByIdName(objectTable varchar, objectIdName varchar) - RETURNS uuid - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ -DECLARE +create or replace function findUuidByIdName(objectTable varchar, objectIdName varchar) + returns uuid + returns null on null input + language plpgsql as $$ +declare sql varchar; -BEGIN +begin objectTable := pureIdentifier(objectTable); objectIdName := pureIdentifier(objectIdName); sql := objectTable || 'UuidByIdName(' || objectIdName || ');'; - EXECUTE sql; -END; $$; + execute sql; +end; $$; -CREATE OR REPLACE FUNCTION currentSubjectIds() - RETURNS uuid[] - STABLE LEAKPROOF - LANGUAGE plpgsql AS $$ -DECLARE - currentUserId uuid; - roleNames VARCHAR(63)[]; - roleName VARCHAR(63); - objectTableToAssume VARCHAR(63); - objectNameToAssume VARCHAR(63); - objectUuidToAssume uuid; - roleTypeToAssume RbacRoleType; - roleIdsToAssume uuid[]; - roleUuidToAssume uuid; -BEGIN +create or replace function currentSubjectIds() + returns uuid[] + stable leakproof + language plpgsql as $$ +declare + currentUserId uuid; + roleNames varchar(63)[]; + roleName varchar(63); + objectTableToAssume varchar(63); + objectNameToAssume varchar(63); + objectUuidToAssume uuid; + roleTypeToAssume RbacRoleType; + roleIdsToAssume uuid[]; + roleUuidToAssume uuid; +begin currentUserId := currentUserId(); roleNames := assumedRoles(); - IF ( CARDINALITY(roleNames) = 0 ) THEN - RETURN ARRAY[currentUserId]; - END IF; + if (cardinality(roleNames) = 0) then + return array [currentUserId]; + end if; - RAISE NOTICE 'assuming roles: %', roleNames; + raise notice 'assuming roles: %', roleNames; - FOREACH roleName IN ARRAY roleNames LOOP - roleName = overlay(roleName placing '#' from length(roleName) + 1 - strpos(reverse(roleName), '.')); - objectTableToAssume = split_part(roleName, '#', 1); - objectNameToAssume = split_part(roleName, '#', 2); - roleTypeToAssume = split_part(roleName, '#', 3); + foreach roleName in array roleNames + loop + roleName = overlay(roleName placing '#' from length(roleName) + 1 - strpos(reverse(roleName), '.')); + objectTableToAssume = split_part(roleName, '#', 1); + objectNameToAssume = split_part(roleName, '#', 2); + roleTypeToAssume = split_part(roleName, '#', 3); - objectUuidToAssume = findUuidByIdName(objectTableToAssume, objectNameToAssume); + objectUuidToAssume = findUuidByIdName(objectTableToAssume, objectNameToAssume); - -- TODO: either the result needs to be cached at least per transaction or we need to get rid of SELCT in a loop - SELECT uuid AS roleuuidToAssume - FROM RbacRole r - WHERE r.objectUuid=objectUuidToAssume AND r.roleType=roleTypeToAssume INTO roleUuidToAssume; - IF ( NOT isGranted(currentUserId, roleUuidToAssume) ) THEN - RAISE EXCEPTION 'user % has no permission to assume role %', currentUser(), roleUuidToAssume; - END IF; - roleIdsToAssume := roleIdsToAssume || roleUuidToAssume; - END LOOP; + -- TODO: either the result needs to be cached at least per transaction or we need to get rid of SELCT in a loop + select uuid as roleuuidToAssume + from RbacRole r + where r.objectUuid = objectUuidToAssume + and r.roleType = roleTypeToAssume + into roleUuidToAssume; + if (not isGranted(currentUserId, roleUuidToAssume)) then + raise exception 'user % has no permission to assume role %', currentUser(), roleUuidToAssume; + end if; + roleIdsToAssume := roleIdsToAssume || roleUuidToAssume; + end loop; - RETURN roleIdsToAssume; -END; $$; + return roleIdsToAssume; +end; $$; --// diff --git a/src/main/resources/db/changelog/2022-07-28-020-rbac-role-builder.sql b/src/main/resources/db/changelog/2022-07-28-020-rbac-role-builder.sql index 48127945..9079100b 100644 --- a/src/main/resources/db/changelog/2022-07-28-020-rbac-role-builder.sql +++ b/src/main/resources/db/changelog/2022-07-28-020-rbac-role-builder.sql @@ -9,17 +9,18 @@ */ -CREATE TYPE RbacPermissions AS +create type RbacPermissions as ( permissionUuids uuid[] ); -CREATE OR REPLACE FUNCTION grantingPermissions(forObjectUuid uuid, permitOps RbacOp[]) - RETURNS RbacPermissions - LANGUAGE plpgsql STRICT AS $$ -BEGIN - RETURN ROW(createPermissions(forObjectUuid, permitOps))::RbacPermissions; -END; $$; +create or replace function grantingPermissions(forObjectUuid uuid, permitOps RbacOp[]) + returns RbacPermissions + language plpgsql + strict as $$ +begin + return row (createPermissions(forObjectUuid, permitOps))::RbacPermissions; +end; $$; --// @@ -28,45 +29,50 @@ END; $$; /* */ -CREATE TYPE RbacSuperRoles AS +create type RbacSuperRoles as ( roleUuids uuid[] ); -CREATE OR REPLACE FUNCTION beneathRoles(roleDescriptors RbacRoleDescriptor[]) - RETURNS RbacSuperRoles - LANGUAGE plpgsql STRICT AS $$ -DECLARE +create or replace function beneathRoles(roleDescriptors RbacRoleDescriptor[]) + returns RbacSuperRoles + language plpgsql + strict as $$ +declare superRoleDescriptor RbacRoleDescriptor; - superRoleUuids uuid[] := ARRAY[]::uuid[]; -BEGIN - FOREACH superRoleDescriptor IN ARRAY roleDescriptors LOOP - superRoleUuids := superRoleUuids || getRoleId(superRoleDescriptor, 'fail'); - END LOOP; + superRoleUuids uuid[] := array []::uuid[]; +begin + foreach superRoleDescriptor in array roleDescriptors + loop + superRoleUuids := superRoleUuids || getRoleId(superRoleDescriptor, 'fail'); + end loop; - RETURN ROW(superRoleUuids)::RbacSuperRoles; -END; $$; + return row (superRoleUuids)::RbacSuperRoles; +end; $$; -CREATE OR REPLACE FUNCTION beneathRole(roleDescriptor RbacRoleDescriptor) - RETURNS RbacSuperRoles - LANGUAGE plpgsql STRICT AS $$ -BEGIN - RETURN beneathRoles(ARRAY[roleDescriptor]); -END; $$; +create or replace function beneathRole(roleDescriptor RbacRoleDescriptor) + returns RbacSuperRoles + language plpgsql + strict as $$ +begin + return beneathRoles(array [roleDescriptor]); +end; $$; -CREATE OR REPLACE FUNCTION beneathRole(roleUuid uuid) - RETURNS RbacSuperRoles - LANGUAGE plpgsql STRICT AS $$ -BEGIN - RETURN ROW(ARRAY[roleUuid]::uuid[])::RbacSuperRoles; -END; $$; +create or replace function beneathRole(roleUuid uuid) + returns RbacSuperRoles + language plpgsql + strict as $$ +begin + return row (array [roleUuid]::uuid[])::RbacSuperRoles; +end; $$; -CREATE OR REPLACE FUNCTION asTopLevelRole() - RETURNS RbacSuperRoles - LANGUAGE plpgsql STRICT AS $$ -BEGIN - RETURN ROW(ARRAY[]::uuid[])::RbacSuperRoles; -END; $$; +create or replace function asTopLevelRole() + returns RbacSuperRoles + language plpgsql + strict as $$ +begin + return row (array []::uuid[])::RbacSuperRoles; +end; $$; --// @@ -78,26 +84,28 @@ END; $$; /* */ -CREATE TYPE RbacSubRoles AS +create type RbacSubRoles as ( roleUuids uuid[] ); -- drop FUNCTION beingItselfA(roleUuid uuid) -CREATE OR REPLACE FUNCTION beingItselfA(roleUuid uuid) - RETURNS RbacSubRoles - LANGUAGE plpgsql STRICT AS $$ -BEGIN - RETURN ROW(ARRAY[roleUuid]::uuid[])::RbacSubRoles; -END; $$; +create or replace function beingItselfA(roleUuid uuid) + returns RbacSubRoles + language plpgsql + strict as $$ +begin + return row (array [roleUuid]::uuid[])::RbacSubRoles; +end; $$; -- drop FUNCTION beingItselfA(roleDescriptor RbacRoleDescriptor) -CREATE OR REPLACE FUNCTION beingItselfA(roleDescriptor RbacRoleDescriptor) - RETURNS RbacSubRoles - LANGUAGE plpgsql STRICT AS $$ -BEGIN - RETURN beingItselfA(getRoleId(roleDescriptor, 'fail')); -END; $$; +create or replace function beingItselfA(roleDescriptor RbacRoleDescriptor) + returns RbacSubRoles + language plpgsql + strict as $$ +begin + return beingItselfA(getRoleId(roleDescriptor, 'fail')); +end; $$; --// @@ -108,33 +116,35 @@ END; $$; /* */ -CREATE TYPE RbacUsers AS +create type RbacUsers as ( userUuids uuid[] ); -CREATE OR REPLACE FUNCTION withUsers(userNames varchar[]) - RETURNS RbacUsers - LANGUAGE plpgsql STRICT AS $$ -DECLARE - userName varchar; - userUuids uuid[] := ARRAY[]::uuid[]; -BEGIN - FOREACH userName IN ARRAY userNames LOOP - userUuids := userUuids || getRbacUserId(userName, 'fail'); - END LOOP; +create or replace function withUsers(userNames varchar[]) + returns RbacUsers + language plpgsql + strict as $$ +declare + userName varchar; + userUuids uuid[] := array []::uuid[]; +begin + foreach userName in array userNames + loop + userUuids := userUuids || getRbacUserId(userName, 'fail'); + end loop; - RETURN ROW(userUuids)::RbacUsers; -END; $$; + return row (userUuids)::RbacUsers; +end; $$; -CREATE OR REPLACE FUNCTION withUser(userName varchar, whenNotExists RbacWhenNotExists = 'fail') - RETURNS RbacUsers - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ -BEGIN - RETURN ROW(ARRAY[getRbacUserId(userName, whenNotExists )]); -END; $$; +create or replace function withUser(userName varchar, whenNotExists RbacWhenNotExists = 'fail') + returns RbacUsers + returns null on null input + language plpgsql as $$ +begin + return row (array [getRbacUserId(userName, whenNotExists)]); +end; $$; --// @@ -145,72 +155,75 @@ END; $$; /* */ -CREATE OR REPLACE FUNCTION createRole( +create or replace function createRole( roleDescriptor RbacRoleDescriptor, permissions RbacPermissions, superRoles RbacSuperRoles, subRoles RbacSubRoles = null, users RbacUsers = null ) - RETURNS uuid - CALLED ON NULL INPUT - LANGUAGE plpgsql AS $$ -DECLARE - roleUuid uuid; + returns uuid + called on null input + language plpgsql as $$ +declare + roleUuid uuid; superRoleUuid uuid; - subRoleUuid uuid; - userUuid uuid; -BEGIN - RAISE NOTICE 'will createRole for %', roleDescriptor; - RAISE NOTICE 'will createRole for % % %', roleDescriptor.objecttable, roleDescriptor.objectuuid, roleDescriptor.roletype; + subRoleUuid uuid; + userUuid uuid; +begin + raise notice 'will createRole for %', roleDescriptor; + raise notice 'will createRole for % % %', roleDescriptor.objecttable, roleDescriptor.objectuuid, roleDescriptor.roletype; roleUuid = createRole(roleDescriptor); call grantPermissionsToRole(roleUuid, permissions.permissionUuids); - IF superRoles IS NOT NULL THEN - FOREACH superRoleUuid IN ARRAY superRoles.roleuUids LOOP - call grantRoleToRole(roleUuid, superRoleUuid); - END LOOP; - END IF; + if superRoles is not null then + foreach superRoleUuid in array superRoles.roleuUids + loop + call grantRoleToRole(roleUuid, superRoleUuid); + end loop; + end if; - IF subRoles IS NOT NULL THEN - FOREACH subRoleUuid IN ARRAY subRoles.roleuUids LOOP - call grantRoleToRole(subRoleUuid, roleUuid); - END LOOP; - END IF; + if subRoles is not null then + foreach subRoleUuid in array subRoles.roleuUids + loop + call grantRoleToRole(subRoleUuid, roleUuid); + end loop; + end if; - IF users IS NOT NULL THEN - FOREACH userUuid IN ARRAY users.useruUids LOOP - call grantRoleToUser(roleUuid, userUuid); - END LOOP; - END IF; + if users is not null then + foreach userUuid in array users.useruUids + loop + call grantRoleToUser(roleUuid, userUuid); + end loop; + end if; - RETURN roleUuid; -END; $$; + return roleUuid; +end; $$; -CREATE OR REPLACE FUNCTION createRole( +create or replace function createRole( roleDescriptor RbacRoleDescriptor, permissions RbacPermissions, users RbacUsers = null ) - RETURNS uuid - CALLED ON NULL INPUT - LANGUAGE plpgsql AS $$ -BEGIN - RETURN createRole(roleDescriptor, permissions, null, null, users); -END; $$; + returns uuid + called on null input + language plpgsql as $$ +begin + return createRole(roleDescriptor, permissions, null, null, users); +end; $$; -CREATE OR REPLACE FUNCTION createRole( +create or replace function createRole( roleDescriptor RbacRoleDescriptor, permissions RbacPermissions, subRoles RbacSubRoles, users RbacUsers = null ) - RETURNS uuid - CALLED ON NULL INPUT - LANGUAGE plpgsql AS $$ -BEGIN - RETURN createRole(roleDescriptor, permissions, null, subRoles, users); -END; $$; + returns uuid + called on null input + language plpgsql as $$ +begin + return createRole(roleDescriptor, permissions, null, subRoles, users); +end; $$; --// diff --git a/src/main/resources/db/changelog/2022-07-28-030-rbac-statistics.sql b/src/main/resources/db/changelog/2022-07-28-030-rbac-statistics.sql index 94d8a2d3..1ef6283a 100644 --- a/src/main/resources/db/changelog/2022-07-28-030-rbac-statistics.sql +++ b/src/main/resources/db/changelog/2022-07-28-030-rbac-statistics.sql @@ -5,20 +5,24 @@ /* Creates a view which presents some statistics about the RBAC tables. */ -create view RbacStatisticsView AS - select no, to_char("count", '9 999 999 999') as "count", "table" - from ( - select 1 as no, count(*) as "count", 'login users' as "table" from RbacUser - union - select 2 as no, count(*) as "count", 'roles' as "table" from RbacRole - union - select 3 as no, count(*) as "count", 'permissions' as "table" from RbacPermission - union - select 4 as no, count(*) as "count", 'references' as "table" from RbacReference - union - select 5 as no, count(*) as "count", 'grants' as "table" from RbacGrants - union - select 6 as no, count(*) as "count", 'objects' as "table" from RbacObject - ) as totals +create view RbacStatisticsView as +select no, to_char("count", '9 999 999 999') as "count", "table" + from (select 1 as no, count(*) as "count", 'login users' as "table" + from RbacUser + union + select 2 as no, count(*) as "count", 'roles' as "table" + from RbacRole + union + select 3 as no, count(*) as "count", 'permissions' as "table" + from RbacPermission + union + select 4 as no, count(*) as "count", 'references' as "table" + from RbacReference + union + select 5 as no, count(*) as "count", 'grants' as "table" + from RbacGrants + union + select 6 as no, count(*) as "count", 'objects' as "table" + from RbacObject) as totals order by totals.no; --// diff --git a/src/main/resources/db/changelog/2022-07-28-051-hs-customer.sql b/src/main/resources/db/changelog/2022-07-28-051-hs-customer.sql new file mode 100644 index 00000000..831fadcf --- /dev/null +++ b/src/main/resources/db/changelog/2022-07-28-051-hs-customer.sql @@ -0,0 +1,183 @@ +-- ======================================================== +-- Customer example with RBAC +-- -------------------------------------------------------- + +set session session authorization default; + +create table if not exists customer +( + uuid uuid unique references RbacObject (uuid), + reference int not null unique check (reference between 10000 and 99999), + prefix character(3) unique, + adminUserName varchar(63) +); + +drop trigger if exists createRbacObjectForCustomer_Trigger on customer; +create trigger createRbacObjectForCustomer_Trigger + before insert + on customer + for each row +execute procedure createRbacObject(); + +create or replace function customerOwner(customer customer) + returns RbacRoleDescriptor + language plpgsql + strict as $$ +begin + return roleDescriptor('customer', customer.uuid, 'owner'); +end; $$; + +create or replace function customerAdmin(customer customer) + returns RbacRoleDescriptor + language plpgsql + strict as $$ +begin + return roleDescriptor('customer', customer.uuid, 'admin'); +end; $$; + +create or replace function customerTenant(customer customer) + returns RbacRoleDescriptor + language plpgsql + strict as $$ +begin + return roleDescriptor('customer', customer.uuid, 'tenant'); +end; $$; + + +create or replace function createRbacRulesForCustomer() + returns trigger + language plpgsql + strict as $$ +declare + customerOwnerUuid uuid; + customerAdminUuid uuid; +begin + if TG_OP <> 'INSERT' then + raise exception 'invalid usage of TRIGGER AFTER INSERT'; + end if; + + -- the owner role with full access for Hostsharing administrators + customerOwnerUuid = createRole( + customerOwner(NEW), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), + beneathRole(hostsharingAdmin()) + ); + + -- the admin role for the customer's admins, who can view and add products + customerAdminUuid = createRole( + customerAdmin(NEW), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view', 'add-package']), + -- NO auto follow for customer owner to avoid exploding permissions for administrators + withUser(NEW.adminUserName, 'create') -- implicitly ignored if null + ); + + -- allow the customer owner role (thus administrators) to assume the customer admin role + call grantRoleToRole(customerAdminUuid, customerOwnerUuid, false); + + -- the tenant role which later can be used by owners+admins of sub-objects + perform createRole( + customerTenant(NEW), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']) + ); + + return NEW; +end; $$; + +drop trigger if exists createRbacRulesForCustomer_Trigger on customer; +create trigger createRbacRulesForCustomer_Trigger + after insert + on customer + for each row +execute procedure createRbacRulesForCustomer(); + +create or replace function deleteRbacRulesForCustomer() + returns trigger + language plpgsql + strict as $$ +declare + objectTable varchar = 'customer'; +begin + if TG_OP = 'DELETE' then + + -- delete the owner role (for admininstrators) + call deleteRole(findRoleId(objectTable || '#' || NEW.prefix || '.owner')); + + -- delete the customer admin role + call deleteRole(findRoleId(objectTable || '#' || NEW.prefix || '.admin')); + else + raise exception 'invalid usage of TRIGGER BEFORE DELETE'; + end if; +end; $$; + +drop trigger if exists deleteRbacRulesForCustomer_Trigger on customer; +create trigger deleteRbacRulesForCustomer_Trigger + before delete + on customer + for each row +execute procedure deleteRbacRulesForCustomer(); + +-- create a restricted view to access the textual customer ids a idName +set session session authorization default; +-- ALTER TABLE customer ENABLE ROW LEVEL SECURITY; +drop view if exists customer_iv; +create or replace view customer_iv as +select distinct target.uuid, target.prefix as idName + from customer as target; +-- TODO: Is it ok that everybody has access to this information? +grant all privileges on customer_iv to restricted; + +create or replace function customerUuidByIdName(idName varchar) + returns uuid + language sql + strict as $$ +select uuid from customer_iv iv where iv.idName = customerUuidByIdName.idName; +$$; + +-- create RBAC restricted view +set session session authorization default; +-- ALTER TABLE customer ENABLE ROW LEVEL SECURITY; +drop view if exists customer_rv; +create or replace view customer_rv as +select distinct target.* + from customer as target + where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'customer', currentSubjectIds())); +grant all privileges on customer_rv to restricted; + + +-- generate Customer test data + +set session session authorization default; +do language plpgsql $$ + declare + currentTask varchar; + custReference integer; + custRowId uuid; + custPrefix varchar; + custAdminName varchar; + begin + set hsadminng.currentUser to ''; + + for t in 0..9 + loop + currentTask = 'creating RBAC test customer #' || t; + set local hsadminng.currentUser to 'mike@hostsharing.net'; + set local hsadminng.assumedRoles = ''; + set local hsadminng.currentTask to currentTask; + + -- When a new customer is created, + custReference = 10000 + t; + custRowId = uuid_generate_v4(); + custPrefix = intToVarChar(t, 3); + custAdminName = 'admin@' || custPrefix || '.example.com'; + + raise notice 'creating customer %:%', custReference, custPrefix; + insert + into customer (reference, prefix, adminUserName) + values (custReference, custPrefix, custAdminName); + + commit; + + end loop; + + end; +$$; diff --git a/src/main/resources/db/changelog/21-hs-customer.sql b/src/main/resources/db/changelog/21-hs-customer.sql deleted file mode 100644 index a386bc32..00000000 --- a/src/main/resources/db/changelog/21-hs-customer.sql +++ /dev/null @@ -1,169 +0,0 @@ - --- ======================================================== --- Customer example with RBAC --- -------------------------------------------------------- - -SET SESSION SESSION AUTHORIZATION DEFAULT ; - -CREATE TABLE IF NOT EXISTS customer ( - uuid uuid UNIQUE REFERENCES RbacObject(uuid), - reference int not null unique CHECK (reference BETWEEN 10000 AND 99999), - prefix character(3) unique, - adminUserName varchar(63) -); - -DROP TRIGGER IF EXISTS createRbacObjectForCustomer_Trigger ON customer; -CREATE TRIGGER createRbacObjectForCustomer_Trigger - BEFORE INSERT ON customer - FOR EACH ROW EXECUTE PROCEDURE createRbacObject(); - -CREATE OR REPLACE FUNCTION customerOwner(customer customer) - RETURNS RbacRoleDescriptor - LANGUAGE plpgsql STRICT AS $$ -begin - return roleDescriptor('customer', customer.uuid, 'owner'); -end; $$; - -CREATE OR REPLACE FUNCTION customerAdmin(customer customer) - RETURNS RbacRoleDescriptor - LANGUAGE plpgsql STRICT AS $$ -begin - return roleDescriptor('customer', customer.uuid, 'admin'); -end; $$; - -CREATE OR REPLACE FUNCTION customerTenant(customer customer) - RETURNS RbacRoleDescriptor - LANGUAGE plpgsql STRICT AS $$ -begin - return roleDescriptor('customer', customer.uuid, 'tenant'); -end; $$; - - -CREATE OR REPLACE FUNCTION createRbacRulesForCustomer() - RETURNS trigger - LANGUAGE plpgsql STRICT AS $$ -DECLARE - customerOwnerUuid uuid; - customerAdminUuid uuid; -BEGIN - IF TG_OP <> 'INSERT' THEN - RAISE EXCEPTION 'invalid usage of TRIGGER AFTER INSERT'; - END IF; - - -- the owner role with full access for Hostsharing administrators - customerOwnerUuid = createRole( - customerOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), - beneathRole(hostsharingAdmin()) - ); - - -- the admin role for the customer's admins, who can view and add products - customerAdminUuid = createRole( - customerAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['view', 'add-package']), - -- NO auto follow for customer owner to avoid exploding permissions for administrators - withUser(NEW.adminUserName, 'create') -- implicitly ignored if null - ); - - -- allow the customer owner role (thus administrators) to assume the customer admin role - call grantRoleToRole(customerAdminUuid, customerOwnerUuid, FALSE); - - -- the tenant role which later can be used by owners+admins of sub-objects - perform createRole( - customerTenant(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['view']) - ); - - RETURN NEW; -END; $$; - -DROP TRIGGER IF EXISTS createRbacRulesForCustomer_Trigger ON customer; -CREATE TRIGGER createRbacRulesForCustomer_Trigger - AFTER INSERT ON customer - FOR EACH ROW EXECUTE PROCEDURE createRbacRulesForCustomer(); - -CREATE OR REPLACE FUNCTION deleteRbacRulesForCustomer() - RETURNS trigger - LANGUAGE plpgsql STRICT AS $$ -DECLARE - objectTable varchar = 'customer'; -BEGIN - IF TG_OP = 'DELETE' THEN - - -- delete the owner role (for admininstrators) - call deleteRole(findRoleId(objectTable||'#'||NEW.prefix||'.owner')); - - -- delete the customer admin role - call deleteRole(findRoleId(objectTable||'#'||NEW.prefix||'.admin')); - ELSE - RAISE EXCEPTION 'invalid usage of TRIGGER BEFORE DELETE'; - END IF; -END; $$; - -DROP TRIGGER IF EXISTS deleteRbacRulesForCustomer_Trigger ON customer; -CREATE TRIGGER deleteRbacRulesForCustomer_Trigger - BEFORE DELETE ON customer - FOR EACH ROW EXECUTE PROCEDURE deleteRbacRulesForCustomer(); - --- create a restricted view to access the textual customer ids a idName -SET SESSION SESSION AUTHORIZATION DEFAULT; --- ALTER TABLE customer ENABLE ROW LEVEL SECURITY; -DROP VIEW IF EXISTS customer_iv; -CREATE OR REPLACE VIEW customer_iv AS -SELECT DISTINCT target.uuid, target.prefix as idName - FROM customer AS target; --- TODO: Is it ok that everybody has access to this information? -GRANT ALL PRIVILEGES ON customer_iv TO restricted; - -CREATE OR REPLACE FUNCTION customerUuidByIdName(idName varchar) - RETURNS uuid - LANGUAGE sql STRICT AS $$ - SELECT uuid FROM customer_iv iv WHERE iv.idName=customerUuidByIdName.idName; - $$; - --- create RBAC restricted view -SET SESSION SESSION AUTHORIZATION DEFAULT; --- ALTER TABLE customer ENABLE ROW LEVEL SECURITY; -DROP VIEW IF EXISTS customer_rv; -CREATE OR REPLACE VIEW customer_rv AS - SELECT DISTINCT target.* - FROM customer AS target - WHERE target.uuid IN (SELECT queryAccessibleObjectUuidsOfSubjectIds( 'view', 'customer', currentSubjectIds())); -GRANT ALL PRIVILEGES ON customer_rv TO restricted; - - --- generate Customer test data - -SET SESSION SESSION AUTHORIZATION DEFAULT; -DO LANGUAGE plpgsql $$ - DECLARE - currentTask varchar; - custReference integer; - custRowId uuid; - custPrefix varchar; - custAdminName varchar; - BEGIN - SET hsadminng.currentUser TO ''; - - FOR t IN 0..9 LOOP - currentTask = 'creating RBAC test customer #' || t; - SET LOCAL hsadminng.currentUser TO 'mike@hostsharing.net'; - SET LOCAL hsadminng.assumedRoles = ''; - SET LOCAL hsadminng.currentTask TO currentTask; - - -- When a new customer is created, - custReference = 10000 + t; - custRowId = uuid_generate_v4(); - custPrefix = intToVarChar(t, 3 ); - custAdminName = 'admin@' || custPrefix || '.example.com'; - - raise notice 'creating customer %:%', custReference, custPrefix; - insert into customer (reference, prefix, adminUserName) - VALUES (custReference, custPrefix, custAdminName); - - COMMIT; - - END LOOP; - - END; -$$; diff --git a/src/main/resources/db/changelog/22-hs-packages.sql b/src/main/resources/db/changelog/22-hs-packages.sql index 65eed84a..91d91cc9 100644 --- a/src/main/resources/db/changelog/22-hs-packages.sql +++ b/src/main/resources/db/changelog/22-hs-packages.sql @@ -1,149 +1,161 @@ - -- ======================================================== -- Package example with RBAC -- -------------------------------------------------------- -SET SESSION SESSION AUTHORIZATION DEFAULT ; +set session session authorization default; -CREATE TABLE IF NOT EXISTS package ( - uuid uuid UNIQUE REFERENCES RbacObject(uuid), - name character varying(5), - customerUuid uuid REFERENCES customer(uuid) +create table if not exists package +( + uuid uuid unique references RbacObject (uuid), + name character varying(5), + customerUuid uuid references customer (uuid) ); -CREATE OR REPLACE FUNCTION packageOwner(pac package) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function packageOwner(pac package) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ declare roleDesc RbacRoleDescriptor; begin return roleDescriptor('package', pac.uuid, 'admin'); end; $$; -CREATE OR REPLACE FUNCTION packageAdmin(pac package) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function packageAdmin(pac package) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ begin return roleDescriptor('package', pac.uuid, 'admin'); end; $$; -CREATE OR REPLACE FUNCTION packageTenant(pac package) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function packageTenant(pac package) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ begin return roleDescriptor('package', pac.uuid, 'tenant'); end; $$; -DROP TRIGGER IF EXISTS createRbacObjectForPackage_Trigger ON package; -CREATE TRIGGER createRbacObjectForPackage_Trigger - BEFORE INSERT ON package - FOR EACH ROW EXECUTE PROCEDURE createRbacObject(); +drop trigger if exists createRbacObjectForPackage_Trigger on package; +create trigger createRbacObjectForPackage_Trigger + before insert + on package + for each row +execute procedure createRbacObject(); -CREATE OR REPLACE FUNCTION createRbacRulesForPackage() - RETURNS trigger - LANGUAGE plpgsql STRICT AS $$ -DECLARE - parentCustomer customer; +create or replace function createRbacRulesForPackage() + returns trigger + language plpgsql + strict as $$ +declare + parentCustomer customer; packageOwnerRoleUuid uuid; packageAdminRoleUuid uuid; -BEGIN - IF TG_OP <> 'INSERT' THEN - RAISE EXCEPTION 'invalid usage of TRIGGER AFTER INSERT'; - END IF; +begin + if TG_OP <> 'INSERT' then + raise exception 'invalid usage of TRIGGER AFTER INSERT'; + end if; - SELECT * FROM customer AS c WHERE c.uuid=NEW.customerUuid INTO parentCustomer; + select * from customer as c where c.uuid = NEW.customerUuid into parentCustomer; -- an owner role is created and assigned to the customer's admin role packageOwnerRoleUuid = createRole( packageOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), beneathRole(customerAdmin(parentCustomer)) ); -- an owner role is created and assigned to the package owner role packageAdminRoleUuid = createRole( packageAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit', 'add-unixuser', 'add-domain']), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit', 'add-unixuser', 'add-domain']), beneathRole(packageOwnerRoleUuid) ); -- and a package tenant role is created and assigned to the package admin as well perform createRole( packageTenant(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY ['view']), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['view']), beneathRole(packageAdminRoleUuid), beingItselfA(customerTenant(parentCustomer)) ); - RETURN NEW; -END; $$; + return NEW; +end; $$; -DROP TRIGGER IF EXISTS createRbacRulesForPackage_Trigger ON package; -CREATE TRIGGER createRbacRulesForPackage_Trigger - AFTER INSERT ON package - FOR EACH ROW EXECUTE PROCEDURE createRbacRulesForPackage(); +drop trigger if exists createRbacRulesForPackage_Trigger on package; +create trigger createRbacRulesForPackage_Trigger + after insert + on package + for each row +execute procedure createRbacRulesForPackage(); -CREATE OR REPLACE FUNCTION deleteRbacRulesForPackage() - RETURNS trigger - LANGUAGE plpgsql STRICT AS $$ -BEGIN - IF TG_OP = 'DELETE' THEN +create or replace function deleteRbacRulesForPackage() + returns trigger + language plpgsql + strict as $$ +begin + if TG_OP = 'DELETE' then -- TODO - ELSE - RAISE EXCEPTION 'invalid usage of TRIGGER BEFORE DELETE'; - END IF; -END; $$; + else + raise exception 'invalid usage of TRIGGER BEFORE DELETE'; + end if; +end; $$; -DROP TRIGGER IF EXISTS deleteRbacRulesForPackage_Trigger ON customer; -CREATE TRIGGER deleteRbacRulesForPackage_Trigger - BEFORE DELETE ON customer - FOR EACH ROW EXECUTE PROCEDURE deleteRbacRulesForPackage(); +drop trigger if exists deleteRbacRulesForPackage_Trigger on customer; +create trigger deleteRbacRulesForPackage_Trigger + before delete + on customer + for each row +execute procedure deleteRbacRulesForPackage(); -- create RBAC-restricted view -SET SESSION SESSION AUTHORIZATION DEFAULT; +set session session authorization default; -- ALTER TABLE package ENABLE ROW LEVEL SECURITY; -DROP VIEW IF EXISTS package_rv; -CREATE OR REPLACE VIEW package_rv AS - SELECT DISTINCT target.* - FROM package AS target - WHERE target.uuid IN (SELECT queryAccessibleObjectUuidsOfSubjectIds( 'view', 'package', currentSubjectIds())); -GRANT ALL PRIVILEGES ON package_rv TO restricted; +drop view if exists package_rv; +create or replace view package_rv as +select distinct target.* + from package as target + where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'package', currentSubjectIds())); +grant all privileges on package_rv to restricted; -- generate Package test data -DO LANGUAGE plpgsql $$ - DECLARE - cust customer; - pacName varchar; +do language plpgsql $$ + declare + cust customer; + pacName varchar; currentTask varchar; - custAdmin varchar; - BEGIN - SET hsadminng.currentUser TO ''; + custAdmin varchar; + begin + set hsadminng.currentUser to ''; - FOR cust IN (SELECT * FROM customer) LOOP - -- CONTINUE WHEN cust.reference < 18000; + for cust in (select * from customer) + loop + -- CONTINUE WHEN cust.reference < 18000; - FOR t IN 0..randominrange(1, 2) LOOP - pacName = cust.prefix || TO_CHAR(t, 'fm00'); - currentTask = 'creating RBAC test package #'|| pacName || ' for customer ' || cust.prefix || ' #' || cust.uuid; - RAISE NOTICE 'task: %', currentTask; + for t in 0..randominrange(1, 2) + loop + pacName = cust.prefix || to_char(t, 'fm00'); + currentTask = 'creating RBAC test package #' || pacName || ' for customer ' || cust.prefix || ' #' || + cust.uuid; + raise notice 'task: %', currentTask; - custAdmin = 'admin@' || cust.prefix || '.example.com'; - SET LOCAL hsadminng.currentUser TO custAdmin; - SET LOCAL hsadminng.assumedRoles = ''; - SET LOCAL hsadminng.currentTask TO currentTask; + custAdmin = 'admin@' || cust.prefix || '.example.com'; + set local hsadminng.currentUser to custAdmin; + set local hsadminng.assumedRoles = ''; + set local hsadminng.currentTask to currentTask; - insert into package (name, customerUuid) - VALUES (pacName, cust.uuid); + insert + into package (name, customerUuid) + values (pacName, cust.uuid); - COMMIT; - END LOOP; - END LOOP; - END; + commit; + end loop; + end loop; + end; $$; diff --git a/src/main/resources/db/changelog/23-hs-unixuser.sql b/src/main/resources/db/changelog/23-hs-unixuser.sql index 00773135..98e640e0 100644 --- a/src/main/resources/db/changelog/23-hs-unixuser.sql +++ b/src/main/resources/db/changelog/23-hs-unixuser.sql @@ -1,152 +1,159 @@ - -- ======================================================== -- UnixUser example with RBAC -- -------------------------------------------------------- -SET SESSION SESSION AUTHORIZATION DEFAULT ; +set session session authorization default; -CREATE TABLE IF NOT EXISTS UnixUser ( - uuid uuid UNIQUE REFERENCES RbacObject(uuid), - name character varying(32), - comment character varying(96), - packageUuid uuid REFERENCES package(uuid) +create table if not exists UnixUser +( + uuid uuid unique references RbacObject (uuid), + name character varying(32), + comment character varying(96), + packageUuid uuid references package (uuid) ); -CREATE OR REPLACE FUNCTION unixUserOwner(uu UnixUser) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function unixUserOwner(uu UnixUser) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ begin return roleDescriptor('unixuser', uu.uuid, 'owner'); end; $$; -CREATE OR REPLACE FUNCTION unixUserAdmin(uu UnixUser) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function unixUserAdmin(uu UnixUser) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ begin return roleDescriptor('unixuser', uu.uuid, 'admin'); end; $$; -CREATE OR REPLACE FUNCTION unixUserTenant(uu UnixUser) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function unixUserTenant(uu UnixUser) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ begin return roleDescriptor('unixuser', uu.uuid, 'tenant'); end; $$; -CREATE OR REPLACE FUNCTION createUnixUserTenantRoleIfNotExists(unixUser UnixUser) - RETURNS uuid - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ -DECLARE +create or replace function createUnixUserTenantRoleIfNotExists(unixUser UnixUser) + returns uuid + returns null on null input + language plpgsql as $$ +declare unixUserTenantRoleDesc RbacRoleDescriptor; unixUserTenantRoleUuid uuid; -BEGIN +begin unixUserTenantRoleDesc = unixUserTenant(unixUser); unixUserTenantRoleUuid = findRoleId(unixUserTenantRoleDesc); - IF unixUserTenantRoleUuid IS NOT NULL THEN - RETURN unixUserTenantRoleUuid; - END IF; + if unixUserTenantRoleUuid is not null then + return unixUserTenantRoleUuid; + end if; - RETURN createRole( + return createRole( unixUserTenantRoleDesc, - grantingPermissions(forObjectUuid => unixUser.uuid, permitOps => ARRAY['view']), + grantingPermissions(forObjectUuid => unixUser.uuid, permitOps => array ['view']), beneathRole(unixUserAdmin(unixUser)) ); -END; $$; +end; $$; -DROP TRIGGER IF EXISTS createRbacObjectForUnixUser_Trigger ON UnixUser; -CREATE TRIGGER createRbacObjectForUnixUser_Trigger - BEFORE INSERT ON UnixUser - FOR EACH ROW EXECUTE PROCEDURE createRbacObject(); +drop trigger if exists createRbacObjectForUnixUser_Trigger on UnixUser; +create trigger createRbacObjectForUnixUser_Trigger + before insert + on UnixUser + for each row +execute procedure createRbacObject(); -CREATE OR REPLACE FUNCTION createRbacRulesForUnixUser() - RETURNS trigger - LANGUAGE plpgsql STRICT AS $$ -DECLARE - parentPackage package; +create or replace function createRbacRulesForUnixUser() + returns trigger + language plpgsql + strict as $$ +declare + parentPackage package; unixuserOwnerRoleId uuid; unixuserAdminRoleId uuid; -BEGIN - IF TG_OP <> 'INSERT' THEN - RAISE EXCEPTION 'invalid usage of TRIGGER AFTER INSERT'; - END IF; +begin + if TG_OP <> 'INSERT' then + raise exception 'invalid usage of TRIGGER AFTER INSERT'; + end if; - SELECT * FROM package WHERE uuid=NEW.packageUuid into parentPackage; + select * from package where uuid = NEW.packageUuid into parentPackage; -- an owner role is created and assigned to the package's admin group unixuserOwnerRoleId = createRole( unixUserOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), beneathRole(packageAdmin(parentPackage)) ); -- and a unixuser admin role is created and assigned to the unixuser owner as well unixuserAdminRoleId = createRole( unixUserAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit']), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), beneathRole(unixuserOwnerRoleId), beingItselfA(packageTenant(parentPackage)) ); -- a tenent role is only created on demand - RETURN NEW; -END; $$; + return NEW; +end; $$; -DROP TRIGGER IF EXISTS createRbacRulesForUnixUser_Trigger ON UnixUser; -CREATE TRIGGER createRbacRulesForUnixUser_Trigger - AFTER INSERT ON UnixUser - FOR EACH ROW EXECUTE PROCEDURE createRbacRulesForUnixUser(); +drop trigger if exists createRbacRulesForUnixUser_Trigger on UnixUser; +create trigger createRbacRulesForUnixUser_Trigger + after insert + on UnixUser + for each row +execute procedure createRbacRulesForUnixUser(); -- TODO: CREATE OR REPLACE FUNCTION deleteRbacRulesForUnixUser() -- create RBAC-restricted view -SET SESSION SESSION AUTHORIZATION DEFAULT; +set session session authorization default; -- ALTER TABLE unixuser ENABLE ROW LEVEL SECURITY; -DROP VIEW IF EXISTS unixuser_rv; -CREATE OR REPLACE VIEW unixuser_rv AS - SELECT DISTINCT target.* - FROM unixuser AS target - WHERE target.uuid IN (SELECT queryAccessibleObjectUuidsOfSubjectIds( 'view', 'unixuser', currentSubjectIds())); -GRANT ALL PRIVILEGES ON unixuser_rv TO restricted; +drop view if exists unixuser_rv; +create or replace view unixuser_rv as +select distinct target.* + from unixuser as target + where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'unixuser', currentSubjectIds())); +grant all privileges on unixuser_rv to restricted; -- generate UnixUser test data -DO LANGUAGE plpgsql $$ - DECLARE - pac record; - pacAdmin varchar; +do language plpgsql $$ + declare + pac record; + pacAdmin varchar; currentTask varchar; - BEGIN - SET hsadminng.currentUser TO ''; + begin + set hsadminng.currentUser to ''; - FOR pac IN ( - SELECT p.uuid, p.name - FROM package p - JOIN customer c ON p.customeruuid = c.uuid - -- WHERE c.reference >= 18000 - ) LOOP + for pac in (select p.uuid, p.name + from package p + join customer c on p.customeruuid = c.uuid + -- WHERE c.reference >= 18000 + ) + loop - FOR t IN 0..9 LOOP - currentTask = 'creating RBAC test unixuser #' || t || ' for package ' || pac.name|| ' #' || pac.uuid; - RAISE NOTICE 'task: %', currentTask; - pacAdmin = 'admin@' || pac.name || '.example.com'; - SET LOCAL hsadminng.currentUser TO 'mike@hostsharing.net'; -- TODO: use a package-admin - SET LOCAL hsadminng.assumedRoles = ''; - SET LOCAL hsadminng.currentTask TO currentTask; + for t in 0..9 + loop + currentTask = 'creating RBAC test unixuser #' || t || ' for package ' || pac.name || ' #' || pac.uuid; + raise notice 'task: %', currentTask; + pacAdmin = 'admin@' || pac.name || '.example.com'; + set local hsadminng.currentUser to 'mike@hostsharing.net'; -- TODO: use a package-admin + set local hsadminng.assumedRoles = ''; + set local hsadminng.currentTask to currentTask; - INSERT INTO unixuser (name, packageUuid) - VALUES (pac.name||'-'|| intToVarChar(t, 4), pac.uuid); + insert + into unixuser (name, packageUuid) + values (pac.name || '-' || intToVarChar(t, 4), pac.uuid); - COMMIT; - END LOOP; - END LOOP; + commit; + end loop; + end loop; - END; + end; $$; diff --git a/src/main/resources/db/changelog/24-hs-domain.sql b/src/main/resources/db/changelog/24-hs-domain.sql index 5435b35b..ac7d7205 100644 --- a/src/main/resources/db/changelog/24-hs-domain.sql +++ b/src/main/resources/db/changelog/24-hs-domain.sql @@ -1,144 +1,151 @@ - -- ======================================================== -- Domain example with RBAC -- -------------------------------------------------------- -SET SESSION SESSION AUTHORIZATION DEFAULT ; +set session session authorization default; -CREATE TABLE IF NOT EXISTS Domain ( - uuid uuid UNIQUE REFERENCES RbacObject(uuid), - name character varying(32), - unixUserUuid uuid REFERENCES unixuser(uuid) +create table if not exists Domain +( + uuid uuid unique references RbacObject (uuid), + name character varying(32), + unixUserUuid uuid references unixuser (uuid) ); -DROP TRIGGER IF EXISTS createRbacObjectForDomain_Trigger ON Domain; -CREATE TRIGGER createRbacObjectForDomain_Trigger - BEFORE INSERT ON Domain - FOR EACH ROW EXECUTE PROCEDURE createRbacObject(); +drop trigger if exists createRbacObjectForDomain_Trigger on Domain; +create trigger createRbacObjectForDomain_Trigger + before insert + on Domain + for each row +execute procedure createRbacObject(); -CREATE OR REPLACE FUNCTION domainOwner(dom Domain) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function domainOwner(dom Domain) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ begin return roleDescriptor('domain', dom.uuid, 'owner'); end; $$; -CREATE OR REPLACE FUNCTION domainAdmin(dom Domain) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function domainAdmin(dom Domain) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ begin return roleDescriptor('domain', dom.uuid, 'admin'); end; $$; -CREATE OR REPLACE FUNCTION domainTenant(dom Domain) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function domainTenant(dom Domain) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ begin return roleDescriptor('domain', dom.uuid, 'tenant'); end; $$; -CREATE OR REPLACE FUNCTION createRbacRulesForDomain() - RETURNS trigger - LANGUAGE plpgsql STRICT AS $$ -DECLARE - parentUser UnixUser; - parentPackage package; +create or replace function createRbacRulesForDomain() + returns trigger + language plpgsql + strict as $$ +declare + parentUser UnixUser; + parentPackage package; domainOwnerRoleUuid uuid; domainAdminRoleUuid uuid; -BEGIN - IF TG_OP <> 'INSERT' THEN - RAISE EXCEPTION 'invalid usage of TRIGGER AFTER INSERT'; - END IF; +begin + if TG_OP <> 'INSERT' then + raise exception 'invalid usage of TRIGGER AFTER INSERT'; + end if; - SELECT * FROM UnixUser WHERE uuid=NEW.unixUserUuid into parentUser; - SELECT * FROM Package WHERE uuid=parentUser.packageuuid into parentPackage; + select * from UnixUser where uuid = NEW.unixUserUuid into parentUser; + select * from Package where uuid = parentUser.packageuuid into parentPackage; -- a domain owner role is created and assigned to the unixuser's admin role domainOwnerRoleUuid = createRole( domainOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), beneathRole(packageAdmin(parentPackage)) ); -- a domain admin role is created and assigned to the domain's owner role domainAdminRoleUuid = createRole( domainAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit', 'add-emailaddress']), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit', 'add-emailaddress']), beneathRole(domainOwnerRoleUuid) ); -- and a domain tenant role is created and assigned to the domain's admiin role perform createRole( domainTenant(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), beneathRole(domainAdminRoleUuid), beingItselfA(createUnixUserTenantRoleIfNotExists(parentUser)) ); - RETURN NEW; -END; $$; + return NEW; +end; $$; -DROP TRIGGER IF EXISTS createRbacRulesForDomain_Trigger ON Domain; -CREATE TRIGGER createRbacRulesForDomain_Trigger - AFTER INSERT ON Domain - FOR EACH ROW EXECUTE PROCEDURE createRbacRulesForDomain(); +drop trigger if exists createRbacRulesForDomain_Trigger on Domain; +create trigger createRbacRulesForDomain_Trigger + after insert + on Domain + for each row +execute procedure createRbacRulesForDomain(); -- TODO: CREATE OR REPLACE FUNCTION deleteRbacRulesForDomain() -- create RBAC-restricted view -SET SESSION SESSION AUTHORIZATION DEFAULT; +set session session authorization default; -- ALTER TABLE Domain ENABLE ROW LEVEL SECURITY; -DROP VIEW IF EXISTS domain_rv; -CREATE OR REPLACE VIEW domain_rv AS - SELECT DISTINCT target.* - FROM Domain AS target - WHERE target.uuid IN (SELECT queryAccessibleObjectUuidsOfSubjectIds( 'view', 'domain', currentSubjectIds())); -GRANT ALL PRIVILEGES ON domain_rv TO restricted; +drop view if exists domain_rv; +create or replace view domain_rv as +select distinct target.* + from Domain as target + where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'domain', currentSubjectIds())); +grant all privileges on domain_rv to restricted; -- generate Domain test data -DO LANGUAGE plpgsql $$ - DECLARE - uu record; - pac package; - pacAdmin varchar; +do language plpgsql $$ + declare + uu record; + pac package; + pacAdmin varchar; currentTask varchar; - BEGIN - SET hsadminng.currentUser TO ''; + begin + set hsadminng.currentUser to ''; - FOR uu IN ( - SELECT u.uuid, u.name, u.packageuuid, c.reference - FROM unixuser u - JOIN package p ON u.packageuuid = p.uuid - JOIN customer c ON p.customeruuid = c.uuid - -- WHERE c.reference >= 18000 - ) LOOP - IF ( random() < 0.3 ) THEN - FOR t IN 0..1 LOOP - currentTask = 'creating RBAC test Domain #' || t || ' for UnixUser ' || uu.name|| ' #' || uu.uuid; - RAISE NOTICE 'task: %', currentTask; + for uu in (select u.uuid, u.name, u.packageuuid, c.reference + from unixuser u + join package p on u.packageuuid = p.uuid + join customer c on p.customeruuid = c.uuid + -- WHERE c.reference >= 18000 + ) + loop + if (random() < 0.3) then + for t in 0..1 + loop + currentTask = 'creating RBAC test Domain #' || t || ' for UnixUser ' || uu.name || ' #' || uu.uuid; + raise notice 'task: %', currentTask; - SELECT * FROM package WHERE uuid=uu.packageUuid INTO pac; - pacAdmin = 'admin@' || pac.name || '.example.com'; - SET LOCAL hsadminng.currentUser TO pacAdmin; - SET LOCAL hsadminng.assumedRoles = ''; - SET LOCAL hsadminng.currentTask TO currentTask; + select * from package where uuid = uu.packageUuid into pac; + pacAdmin = 'admin@' || pac.name || '.example.com'; + set local hsadminng.currentUser to pacAdmin; + set local hsadminng.assumedRoles = ''; + set local hsadminng.currentTask to currentTask; - INSERT INTO Domain (name, unixUserUuid) - VALUES ('dom-' || t || '.' || uu.name || '.example.org' , uu.uuid); + insert + into Domain (name, unixUserUuid) + values ('dom-' || t || '.' || uu.name || '.example.org', uu.uuid); - COMMIT; - END LOOP; - END IF; - END LOOP; + commit; + end loop; + end if; + end loop; - END; + end; $$; diff --git a/src/main/resources/db/changelog/25-hs-emailaddress.sql b/src/main/resources/db/changelog/25-hs-emailaddress.sql index 80cdb759..97b884a7 100644 --- a/src/main/resources/db/changelog/25-hs-emailaddress.sql +++ b/src/main/resources/db/changelog/25-hs-emailaddress.sql @@ -1,123 +1,131 @@ - -- ======================================================== -- EMailAddress example with RBAC -- -------------------------------------------------------- -SET SESSION SESSION AUTHORIZATION DEFAULT ; +set session session authorization default; -CREATE TABLE IF NOT EXISTS EMailAddress ( - uuid uuid UNIQUE REFERENCES RbacObject(uuid), - localPart character varying(64), - domainUuid uuid REFERENCES domain(uuid) +create table if not exists EMailAddress +( + uuid uuid unique references RbacObject (uuid), + localPart character varying(64), + domainUuid uuid references domain (uuid) ); -DROP TRIGGER IF EXISTS createRbacObjectForEMailAddress_Trigger ON EMailAddress; -CREATE TRIGGER createRbacObjectForEMailAddress_Trigger - BEFORE INSERT ON EMailAddress - FOR EACH ROW EXECUTE PROCEDURE createRbacObject(); +drop trigger if exists createRbacObjectForEMailAddress_Trigger on EMailAddress; +create trigger createRbacObjectForEMailAddress_Trigger + before insert + on EMailAddress + for each row +execute procedure createRbacObject(); -CREATE OR REPLACE FUNCTION emailAddressOwner(emAddr EMailAddress) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function emailAddressOwner(emAddr EMailAddress) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ begin return roleDescriptor('emailaddress', emAddr.uuid, 'owner'); end; $$; -CREATE OR REPLACE FUNCTION emailAddressAdmin(emAddr EMailAddress) - RETURNS RbacRoleDescriptor - RETURNS NULL ON NULL INPUT - LANGUAGE plpgsql AS $$ +create or replace function emailAddressAdmin(emAddr EMailAddress) + returns RbacRoleDescriptor + returns null on null input + language plpgsql as $$ begin return roleDescriptor('emailaddress', emAddr.uuid, 'admin'); end; $$; -CREATE OR REPLACE FUNCTION createRbacRulesForEMailAddress() - RETURNS trigger - LANGUAGE plpgsql STRICT AS $$ -DECLARE +create or replace function createRbacRulesForEMailAddress() + returns trigger + language plpgsql + strict as $$ +declare parentDomain Domain; eMailAddressOwnerRoleUuid uuid; -BEGIN - IF TG_OP <> 'INSERT' THEN - RAISE EXCEPTION 'invalid usage of TRIGGER AFTER INSERT'; - END IF; +begin + if TG_OP <> 'INSERT' then + raise exception 'invalid usage of TRIGGER AFTER INSERT'; + end if; - SELECT d.* - FROM domain d - LEFT JOIN unixuser u ON u.uuid = d.unixuseruuid - WHERE d.uuid=NEW.domainUuid INTO parentDomain; + select d.* + from domain d + left join unixuser u on u.uuid = d.unixuseruuid + where d.uuid = NEW.domainUuid + into parentDomain; -- an owner role is created and assigned to the domains's admin group eMailAddressOwnerRoleUuid = createRole( emailAddressOwner(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), - beneathRole(domainAdmin( parentDomain)) + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['*']), + beneathRole(domainAdmin(parentDomain)) ); -- and an admin role is created and assigned to the unixuser owner as well perform createRole( emailAddressAdmin(NEW), - grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit']), + grantingPermissions(forObjectUuid => NEW.uuid, permitOps => array ['edit']), beneathRole(eMailAddressOwnerRoleUuid), beingItselfA(domainTenant(parentDomain)) ); - RETURN NEW; -END; $$; + return NEW; +end; $$; -DROP TRIGGER IF EXISTS createRbacRulesForEMailAddress_Trigger ON EMailAddress; -CREATE TRIGGER createRbacRulesForEMailAddress_Trigger - AFTER INSERT ON EMailAddress - FOR EACH ROW EXECUTE PROCEDURE createRbacRulesForEMailAddress(); +drop trigger if exists createRbacRulesForEMailAddress_Trigger on EMailAddress; +create trigger createRbacRulesForEMailAddress_Trigger + after insert + on EMailAddress + for each row +execute procedure createRbacRulesForEMailAddress(); -- TODO: CREATE OR REPLACE FUNCTION deleteRbacRulesForEMailAddress() -- create RBAC-restricted view -SET SESSION SESSION AUTHORIZATION DEFAULT; +set session session authorization default; -- ALTER TABLE EMailAddress ENABLE ROW LEVEL SECURITY; -DROP VIEW IF EXISTS EMailAddress_rv; -CREATE OR REPLACE VIEW EMailAddress_rv AS - SELECT DISTINCT target.* - FROM EMailAddress AS target - WHERE target.uuid IN (SELECT queryAccessibleObjectUuidsOfSubjectIds( 'view', 'emailaddress', currentSubjectIds())); -GRANT ALL PRIVILEGES ON EMailAddress_rv TO restricted; +drop view if exists EMailAddress_rv; +create or replace view EMailAddress_rv as +select distinct target.* + from EMailAddress as target + where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'emailaddress', currentSubjectIds())); +grant all privileges on EMailAddress_rv to restricted; -- generate EMailAddress test data -DO LANGUAGE plpgsql $$ - DECLARE - dom record; - pacAdmin varchar; +do language plpgsql $$ + declare + dom record; + pacAdmin varchar; currentTask varchar; - BEGIN - SET hsadminng.currentUser TO ''; + begin + set hsadminng.currentUser to ''; - FOR dom IN ( - SELECT d.uuid, d.name, p.name as packageName - FROM domain d - JOIN unixuser u ON u.uuid = d.unixuseruuid - JOIN package p ON u.packageuuid = p.uuid - JOIN customer c ON p.customeruuid = c.uuid - -- WHERE c.reference >= 18000 - ) LOOP - FOR t IN 0..4 LOOP - currentTask = 'creating RBAC test EMailAddress #' || t || ' for Domain ' || dom.name; - RAISE NOTICE 'task: %', currentTask; + for dom in (select d.uuid, d.name, p.name as packageName + from domain d + join unixuser u on u.uuid = d.unixuseruuid + join package p on u.packageuuid = p.uuid + join customer c on p.customeruuid = c.uuid + -- WHERE c.reference >= 18000 + ) + loop + for t in 0..4 + loop + currentTask = 'creating RBAC test EMailAddress #' || t || ' for Domain ' || dom.name; + raise notice 'task: %', currentTask; - pacAdmin = 'admin@' || dom.packageName || '.example.com'; - SET LOCAL hsadminng.currentUser TO pacAdmin; - SET LOCAL hsadminng.assumedRoles = ''; - SET LOCAL hsadminng.currentTask TO currentTask; + pacAdmin = 'admin@' || dom.packageName || '.example.com'; + set local hsadminng.currentUser to pacAdmin; + set local hsadminng.assumedRoles = ''; + set local hsadminng.currentTask to currentTask; - INSERT INTO EMailAddress (localPart, domainUuid) - VALUES ('local' || t, dom.uuid); + insert + into EMailAddress (localPart, domainUuid) + values ('local' || t, dom.uuid); - COMMIT; - END LOOP; - END LOOP; - END; + commit; + end loop; + end loop; + end; $$; diff --git a/src/main/resources/db/changelog/29-hs-statistics.sql b/src/main/resources/db/changelog/29-hs-statistics.sql index d296a2d9..3dd43b5d 100644 --- a/src/main/resources/db/changelog/29-hs-statistics.sql +++ b/src/main/resources/db/changelog/29-hs-statistics.sql @@ -1,26 +1,28 @@ - -- ======================================================== -- Some Business Table Statistics -- -------------------------------------------------------- -DROP VIEW IF EXISTS "BusinessTableStatisticsV"; -CREATE VIEW "BusinessTableStatisticsV" AS -SELECT no, to_char("count", '999 999 999') as "count", to_char("required", '999 999 999') as "required", to_char("count"::float/"required"::float, '990.999') as "factor", "table" -FROM (select 1 as no, count(*) as "count", 7000 as "required", 'customers' as "table" - from customer - UNION - select 2 as no, count(*) as "count", 15000 as "required", 'packages' as "table" - from package - UNION - select 3 as no, count(*) as "count", 150000 as "required", 'unixuser' as "table" - from unixuser - UNION - select 4 as no, count(*) as "count", 100000 as "required", 'domain' as "table" - from domain - UNION - select 5 as no, count(*) as "count", 500000 as "required", 'emailaddress' as "table" - from emailaddress - ) totals -ORDER BY totals.no; +drop view if exists "BusinessTableStatisticsV"; +create view "BusinessTableStatisticsV" as +select no, + to_char("count", '999 999 999') as "count", + to_char("required", '999 999 999') as "required", + to_char("count"::float / "required"::float, '990.999') as "factor", + "table" + from (select 1 as no, count(*) as "count", 7000 as "required", 'customers' as "table" + from customer + union + select 2 as no, count(*) as "count", 15000 as "required", 'packages' as "table" + from package + union + select 3 as no, count(*) as "count", 150000 as "required", 'unixuser' as "table" + from unixuser + union + select 4 as no, count(*) as "count", 100000 as "required", 'domain' as "table" + from domain + union + select 5 as no, count(*) as "count", 500000 as "required", 'emailaddress' as "table" + from emailaddress) totals + order by totals.no; -SELECT * FROM "BusinessTableStatisticsV"; +select * from "BusinessTableStatisticsV";