introduce referential integrity for role identification - part 1

This commit is contained in:
Michael Hoennig 2022-07-27 19:54:05 +02:00
parent 6f6320565c
commit 1dde6b2609
8 changed files with 246 additions and 191 deletions

View File

@ -29,10 +29,21 @@ CREATE TABLE RbacUser
name varchar(63) not null unique name varchar(63) not null unique
); );
-- DROP TABLE IF EXISTS RbacObject;
CREATE TABLE RbacObject
(
uuid uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
objectTable varchar(64) not null,
unique (objectTable, uuid)
);
CREATE TYPE RbacRoleType AS ENUM ('owner', 'admin', 'tenant');
CREATE TABLE RbacRole CREATE TABLE RbacRole
( (
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 objectUuid uuid references RbacObject(uuid) not null,
roleType RbacRoleType not null
); );
CREATE TABLE RbacGrants CREATE TABLE RbacGrants
@ -56,14 +67,6 @@ CREATE DOMAIN RbacOp AS VARCHAR(67)
OR VALUE ~ '^add-[a-z]+$' 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() CREATE OR REPLACE FUNCTION createRbacObject()
RETURNS trigger RETURNS trigger
LANGUAGE plpgsql STRICT AS $$ LANGUAGE plpgsql STRICT AS $$
@ -144,7 +147,51 @@ BEGIN
END; END;
$$; $$;
CREATE OR REPLACE FUNCTION createRole(roleName varchar) CREATE TYPE RbacRoleDescriptor AS
(
objectTable varchar(63), -- TODO: needed? remove?
objectUuid uuid,
roleType RbacRoleType
);
-- TODO: this ...
CREATE OR REPLACE FUNCTION roleDescriptor(objectTable varchar(63), objectUuid uuid, roleType RbacRoleType )
RETURNS RbacRoleDescriptor
RETURNS NULL ON NULL INPUT
STABLE LEAKPROOF
LANGUAGE plpgsql AS $$
BEGIN
RETURN ROW(objectTable, objectUuid, roleType);
END; $$;
CREATE FUNCTION new_emp() RETURNS emp AS $$
SELECT text 'None' AS name,
1000.0 AS salary,
25 AS age,
point '(2,2)' AS cubicle;
$$ LANGUAGE SQL;
DO LANGUAGE plpgsql $$
DECLARE
roleDesc RbacRoleDescriptor;
BEGIN
select * FROM roleDescriptor('global', gen_random_uuid(), 'admin') into roleDesc;
RAISE NOTICE 'result: % % %', roleDesc.objecttable, roleDesc.objectuuid, roleDesc.roletype;
END; $$;
-- TODO: ... or that?
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;
$$;
CREATE OR REPLACE FUNCTION createRole(roleDescriptor RbacRoleDescriptor)
RETURNS uuid RETURNS uuid
RETURNS NULL ON NULL INPUT RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$ LANGUAGE plpgsql AS $$
@ -152,10 +199,7 @@ declare
referenceId uuid; referenceId uuid;
BEGIN BEGIN
INSERT INTO RbacReference (type) VALUES ('RbacRole') RETURNING uuid INTO referenceId; INSERT INTO RbacReference (type) VALUES ('RbacRole') RETURNING uuid INTO referenceId;
INSERT INTO RbacRole (uuid, name) VALUES (referenceId, roleName); INSERT INTO RbacRole (uuid, objectUuid, roleType) VALUES (referenceId, roleDescriptor.objectUuid, roleDescriptor.roleType);
IF (referenceId IS NULL) THEN
RAISE EXCEPTION 'referenceId for roleName "%" is unexpectedly null', roleName;
end if;
return referenceId; return referenceId;
END; END;
$$; $$;
@ -168,27 +212,27 @@ BEGIN
END; END;
$$; $$;
CREATE OR REPLACE FUNCTION findRoleId(roleName varchar) CREATE OR REPLACE FUNCTION findRoleId(roleDescriptor RbacRoleDescriptor)
RETURNS uuid RETURNS uuid
RETURNS NULL ON NULL INPUT RETURNS NULL ON NULL INPUT
LANGUAGE sql AS $$ LANGUAGE sql AS $$
SELECT uuid FROM RbacRole WHERE name = roleName SELECT uuid FROM RbacRole WHERE objectUuid = roleDescriptor.objectUuid AND roleType = roleDescriptor.roleType;
$$; $$;
CREATE OR REPLACE FUNCTION getRoleId(roleName varchar, whenNotExists RbacWhenNotExists) CREATE OR REPLACE FUNCTION getRoleId(roleDescriptor RbacRoleDescriptor, whenNotExists RbacWhenNotExists)
RETURNS uuid RETURNS uuid
RETURNS NULL ON NULL INPUT RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$ LANGUAGE plpgsql AS $$
DECLARE DECLARE
roleUuid uuid; roleUuid uuid;
BEGIN BEGIN
roleUuid = findRoleId(roleName); roleUuid = findRoleId(roleDescriptor);
IF ( roleUuid IS NULL ) THEN IF ( roleUuid IS NULL ) THEN
IF ( whenNotExists = 'fail') THEN IF ( whenNotExists = 'fail') THEN
RAISE EXCEPTION 'RbacRole with name="%" not found', roleName; RAISE EXCEPTION 'RbacRole "%#%.%" not found', roleDescriptor.objectTable, roleDescriptor.objectUuid, roleDescriptor.roleType;
END IF; END IF;
IF ( whenNotExists = 'create') THEN IF ( whenNotExists = 'create') THEN
roleUuid = createRole(roleName); roleUuid = createRole(roleDescriptor);
END IF; END IF;
END IF; END IF;
return roleUuid; return roleUuid;
@ -204,6 +248,7 @@ DECLARE
refId uuid; refId uuid;
permissionIds uuid[] = ARRAY[]::uuid[]; permissionIds uuid[] = ARRAY[]::uuid[];
BEGIN BEGIN
RAISE NOTICE 'createPermission for: % %', forObjectUuid, permitOps;
IF ( forObjectUuid IS NULL ) THEN IF ( forObjectUuid IS NULL ) THEN
RAISE EXCEPTION 'forObjectUuid must not be null'; RAISE EXCEPTION 'forObjectUuid must not be null';
END IF; END IF;
@ -214,16 +259,20 @@ BEGIN
FOR i IN array_lower(permitOps, 1)..array_upper(permitOps, 1) LOOP FOR i IN array_lower(permitOps, 1)..array_upper(permitOps, 1) LOOP
refId = (SELECT uuid FROM RbacPermission WHERE objectUuid=forObjectUuid AND op=permitOps[i]); refId = (SELECT uuid FROM RbacPermission WHERE objectUuid=forObjectUuid AND op=permitOps[i]);
IF (refId IS NULL) THEN IF (refId IS NULL) THEN
RAISE NOTICE 'createPermission: % %', forObjectUuid, permitOps[i];
INSERT INTO RbacReference ("type") VALUES ('RbacPermission') RETURNING uuid INTO refId; INSERT INTO RbacReference ("type") VALUES ('RbacPermission') RETURNING uuid INTO refId;
INSERT INTO RbacPermission (uuid, objectUuid, op) VALUES (refId, forObjectUuid, permitOps[i]); INSERT INTO RbacPermission (uuid, objectUuid, op) VALUES (refId, forObjectUuid, permitOps[i]);
END IF; END IF;
RAISE NOTICE 'addPermission: %', refId;
permissionIds = permissionIds || refId; permissionIds = permissionIds || refId;
END LOOP; END LOOP;
RAISE NOTICE 'createPermissions returning: %', permissionIds;
return permissionIds; return permissionIds;
END; END;
$$; $$;
CREATE OR REPLACE FUNCTION findPermissionId(forObjectTable varchar, forObjectUuid uuid, forOp RbacOp) CREATE OR REPLACE FUNCTION findPermissionId(forObjectUuid uuid, forOp RbacOp)
RETURNS uuid RETURNS uuid
RETURNS NULL ON NULL INPUT RETURNS NULL ON NULL INPUT
STABLE LEAKPROOF STABLE LEAKPROOF
@ -248,6 +297,7 @@ END; $$;
CREATE OR REPLACE PROCEDURE grantPermissionsToRole(roleUuid uuid, permissionIds uuid[]) CREATE OR REPLACE PROCEDURE grantPermissionsToRole(roleUuid uuid, permissionIds uuid[])
LANGUAGE plpgsql AS $$ LANGUAGE plpgsql AS $$
BEGIN BEGIN
RAISE NOTICE 'grantPermissionsToRole: % -> %', roleUuid, permissionIds;
FOR i IN array_lower(permissionIds, 1)..array_upper(permissionIds, 1) LOOP FOR i IN array_lower(permissionIds, 1)..array_upper(permissionIds, 1) LOOP
perform assertReferenceType('roleId (ascendant)', roleUuid, 'RbacRole'); perform assertReferenceType('roleId (ascendant)', roleUuid, 'RbacRole');
perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission'); perform assertReferenceType('permissionId (descendant)', permissionIds[i], 'RbacPermission');
@ -295,21 +345,6 @@ BEGIN
ON CONFLICT DO NOTHING ; -- TODO: remove? ON CONFLICT DO NOTHING ; -- TODO: remove?
END; $$; END; $$;
abort;
set local session authorization default;
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;
$$;
abort; abort;
set local session authorization default; set local session authorization default;
@ -444,7 +479,7 @@ begin transaction;
end transaction; end transaction;
--- ---
/*
CREATE OR REPLACE FUNCTION queryAllPermissionsOfSubjectId(subjectId uuid) -- TODO: remove? CREATE OR REPLACE FUNCTION queryAllPermissionsOfSubjectId(subjectId uuid) -- TODO: remove?
RETURNS SETOF RbacPermission RETURNS SETOF RbacPermission
RETURNS NULL ON NULL INPUT RETURNS NULL ON NULL INPUT
@ -469,7 +504,7 @@ CREATE OR REPLACE FUNCTION queryAllPermissionsOfSubjectId(subjectId uuid) -- TOD
FROM FROM
grants grants
); );
$$; $$;*/
--- ---
@ -643,43 +678,39 @@ CREATE OR REPLACE FUNCTION currentSubjectIds()
STABLE LEAKPROOF STABLE LEAKPROOF
LANGUAGE plpgsql AS $$ LANGUAGE plpgsql AS $$
DECLARE DECLARE
assumedRoles VARCHAR(63)[]; currentUserId uuid;
currentUserId uuid; roleNames VARCHAR(63)[];
assumedRoleIds uuid[]; roleName VARCHAR(63);
assumedRoleId uuid; objectTableToAssume VARCHAR(63);
objectNameToAssume VARCHAR(63);
objectUuidToAssume uuid;
roleTypeToAssume RbacRoleType;
roleIdsToAssume uuid[];
roleUuidToAssume uuid;
BEGIN BEGIN
currentUserId := currentUserId(); currentUserId := currentUserId();
assumedRoles := assumedRoles(); roleNames := assumedRoles();
IF ( CARDINALITY(assumedRoles) = 0 ) THEN IF ( CARDINALITY(roleNames) = 0 ) THEN
RETURN ARRAY[currentUserId]; RETURN ARRAY[currentUserId];
END IF; END IF;
RAISE NOTICE 'assuming roles: %', assumedRoles; RAISE NOTICE 'assuming roles: %', roleNames;
SELECT ARRAY_AGG(uuid) FROM RbacRole WHERE name = ANY(assumedRoles) INTO assumedRoleIds; FOREACH roleName IN ARRAY roleNames LOOP
IF assumedRoleIds IS NOT NULL THEN roleName = overlay(roleName placing '#' from length(roleName) + 1 - strpos(reverse(roleName), '.'));
FOREACH assumedRoleId IN ARRAY assumedRoleIds LOOP objectTableToAssume = split_part(roleName, '#', 1);
IF ( NOT isGranted(currentUserId, assumedRoleId) ) THEN objectNameToAssume = split_part(roleName, '#', 2);
RAISE EXCEPTION 'user % has no permission to assume role %', currentUser(), assumedRoleId; roleTypeToAssume = split_part(roleName, '#', 3);
END IF;
END LOOP;
END IF;
RETURN assumedRoleIds;
END; $$;
rollback; -- TODO: either the result needs to be cached at least per transaction or we need to get rid of SELCT in a loop
set session authorization default; SELECT uuid AS roleuuidToAssume
CREATE OR REPLACE FUNCTION maxGrantDepth() FROM RbacRole r
RETURNS integer WHERE r.objectUuid=objectUuidToAssume AND r.roleType=roleTypeToAssume INTO roleUuidToAssume;
STABLE LEAKPROOF IF ( NOT isGranted(currentUserId, roleUuidToAssume) ) THEN
LANGUAGE plpgsql AS $$ RAISE EXCEPTION 'user % has no permission to assume role %', currentUser(), roleUuidToAssume;
DECLARE END IF;
maxGrantDepth VARCHAR(63); roleIdsToAssume := roleIdsToAssume || roleUuidToAssume;
BEGIN END LOOP;
BEGIN
maxGrantDepth := current_setting('hsadminng.maxGrantDepth'); RETURN roleIdsToAssume;
EXCEPTION WHEN OTHERS THEN
maxGrantDepth := NULL;
END;
RETURN coalesce(maxGrantDepth, '8')::integer;
END; $$; END; $$;

View File

@ -4,8 +4,6 @@
-- Role-Hierarcy helper functions -- Role-Hierarcy helper functions
-- -------------------------------------------------------- -- --------------------------------------------------------
CREATE TYPE RbacRoleType AS ENUM ('owner', 'admin', 'tenant');
-- PERMISSIONS -------------------------------------------- -- PERMISSIONS --------------------------------------------
-- drop type RbacPermissions; -- drop type RbacPermissions;
@ -29,27 +27,27 @@ CREATE TYPE RbacSuperRoles AS
roleUuids uuid[] roleUuids uuid[]
); );
-- drop function beneathRoles(roleName varchar); -- drop function beneathRoles(roleDescriptors RbacRoleDescriptor[])
CREATE OR REPLACE FUNCTION beneathRoles(roleNames varchar[]) CREATE OR REPLACE FUNCTION beneathRoles(roleDescriptors RbacRoleDescriptor[])
RETURNS RbacSuperRoles RETURNS RbacSuperRoles
LANGUAGE plpgsql STRICT AS $$ LANGUAGE plpgsql STRICT AS $$
DECLARE DECLARE
superRoleName varchar; superRoleDescriptor RbacRoleDescriptor;
superRoleUuids uuid[] := ARRAY[]::uuid[]; superRoleUuids uuid[] := ARRAY[]::uuid[];
BEGIN BEGIN
FOREACH superRoleName IN ARRAY roleNames LOOP FOREACH superRoleDescriptor IN ARRAY roleDescriptors LOOP
superRoleUuids := superRoleUuids || getRoleId(superRoleName, 'fail'); superRoleUuids := superRoleUuids || getRoleId(superRoleDescriptor, 'fail');
END LOOP; END LOOP;
RETURN ROW(superRoleUuids)::RbacSuperRoles; RETURN ROW(superRoleUuids)::RbacSuperRoles;
END; $$; END; $$;
-- drop function beneathRole(roleName varchar); -- drop function beneathRole(roleDescriptor RbacRoleDescriptor)
CREATE OR REPLACE FUNCTION beneathRole(roleName varchar) CREATE OR REPLACE FUNCTION beneathRole(roleDescriptor RbacRoleDescriptor)
RETURNS RbacSuperRoles RETURNS RbacSuperRoles
LANGUAGE plpgsql STRICT AS $$ LANGUAGE plpgsql STRICT AS $$
BEGIN BEGIN
RETURN beneathRoles(ARRAY[roleName]); RETURN beneathRoles(ARRAY[roleDescriptor]);
END; $$; END; $$;
-- drop function beneathRole(roleUuid uuid); -- drop function beneathRole(roleUuid uuid);
@ -83,12 +81,12 @@ BEGIN
RETURN ROW(ARRAY[roleUuid]::uuid[])::RbacSubRoles; RETURN ROW(ARRAY[roleUuid]::uuid[])::RbacSubRoles;
END; $$; END; $$;
-- drop FUNCTION beingItselfA(roleName varchar) -- drop FUNCTION beingItselfA(roleDescriptor RbacRoleDescriptor)
CREATE OR REPLACE FUNCTION beingItselfA(roleName varchar) CREATE OR REPLACE FUNCTION beingItselfA(roleDescriptor RbacRoleDescriptor)
RETURNS RbacSubRoles RETURNS RbacSubRoles
LANGUAGE plpgsql STRICT AS $$ LANGUAGE plpgsql STRICT AS $$
BEGIN BEGIN
RETURN beingItselfA(getRoleId(roleName, 'fail')); RETURN beingItselfA(getRoleId(roleDescriptor, 'fail'));
END; $$; END; $$;
-- USERS -------------------------------------------------- -- USERS --------------------------------------------------
@ -126,20 +124,11 @@ END; $$;
-- ROLE NAME BUILDER -------------------------------------- -- ROLE NAME BUILDER --------------------------------------
CREATE OR REPLACE FUNCTION roleName(objectTable varchar, objectName varchar, roleType RbacRoleType )
RETURNS varchar
RETURNS NULL ON NULL INPUT
STABLE LEAKPROOF
LANGUAGE plpgsql AS $$
BEGIN
RETURN objectTable || '#' || objectName || '.' || roleType;
END; $$;
-- CREATE ROLE MAIN FUNCTION ------------------------------ -- CREATE ROLE MAIN FUNCTION ------------------------------
CREATE OR REPLACE FUNCTION createRole( CREATE OR REPLACE FUNCTION createRole(
roleName varchar, roleDescriptor RbacRoleDescriptor,
permissions RbacPermissions, permissions RbacPermissions,
superRoles RbacSuperRoles, superRoles RbacSuperRoles,
subRoles RbacSubRoles = null, subRoles RbacSubRoles = null,
@ -154,8 +143,9 @@ DECLARE
subRoleUuid uuid; subRoleUuid uuid;
userUuid uuid; userUuid uuid;
BEGIN BEGIN
RAISE NOTICE 'creating role: %', roleName; RAISE NOTICE 'will createRole for %', roleDescriptor;
roleUuid = createRole(roleName); RAISE NOTICE 'will createRole for % % %', roleDescriptor.objecttable, roleDescriptor.objectuuid, roleDescriptor.roletype;
roleUuid = createRole(roleDescriptor);
call grantPermissionsToRole(roleUuid, permissions.permissionUuids); call grantPermissionsToRole(roleUuid, permissions.permissionUuids);
@ -181,7 +171,7 @@ BEGIN
END; $$; END; $$;
CREATE OR REPLACE FUNCTION createRole( CREATE OR REPLACE FUNCTION createRole(
roleName varchar, roleDescriptor RbacRoleDescriptor,
permissions RbacPermissions, permissions RbacPermissions,
users RbacUsers = null users RbacUsers = null
) )
@ -189,11 +179,11 @@ CREATE OR REPLACE FUNCTION createRole(
CALLED ON NULL INPUT CALLED ON NULL INPUT
LANGUAGE plpgsql AS $$ LANGUAGE plpgsql AS $$
BEGIN BEGIN
RETURN createRole(roleName, permissions, null, null, users); RETURN createRole(roleDescriptor, permissions, null, null, users);
END; $$; END; $$;
CREATE OR REPLACE FUNCTION createRole( CREATE OR REPLACE FUNCTION createRole(
roleName varchar, roleDescriptor RbacRoleDescriptor,
permissions RbacPermissions, permissions RbacPermissions,
subRoles RbacSubRoles, subRoles RbacSubRoles,
users RbacUsers = null users RbacUsers = null
@ -202,7 +192,7 @@ CREATE OR REPLACE FUNCTION createRole(
CALLED ON NULL INPUT CALLED ON NULL INPUT
LANGUAGE plpgsql AS $$ LANGUAGE plpgsql AS $$
BEGIN BEGIN
RETURN createRole(roleName, permissions, null, subRoles, users); RETURN createRole(roleDescriptor, permissions, null, subRoles, users);
END; $$; END; $$;

View File

@ -1,10 +1,30 @@
CREATE TABLE Hostsharing
(
uuid uuid PRIMARY KEY REFERENCES RbacObject(uuid)
);
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'));
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 -- create administrators role with two assigned users
do language plpgsql $$ do language plpgsql $$
declare declare
admins uuid ; admins uuid ;
begin begin
admins = createRole('administrators'); admins = createRole(hostsharingAdmin());
call grantRoleToUser(admins, createRbacUser('mike@hostsharing.net')); call grantRoleToUser(admins, createRbacUser('mike@hostsharing.net'));
call grantRoleToUser(admins, createRbacUser('sven@hostsharing.net')); call grantRoleToUser(admins, createRbacUser('sven@hostsharing.net'));
commit; commit;

View File

@ -17,25 +17,25 @@ CREATE TRIGGER createRbacObjectForCustomer_Trigger
BEFORE INSERT ON customer BEFORE INSERT ON customer
FOR EACH ROW EXECUTE PROCEDURE createRbacObject(); FOR EACH ROW EXECUTE PROCEDURE createRbacObject();
CREATE OR REPLACE FUNCTION customerOwner(customerName varchar) CREATE OR REPLACE FUNCTION customerOwner(customer customer)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ LANGUAGE plpgsql STRICT AS $$
begin begin
return roleName('customer', customerName, 'owner'); return roleDescriptor('customer', customer.uuid, 'owner');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION customerAdmin(customerName varchar) CREATE OR REPLACE FUNCTION customerAdmin(customer customer)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ LANGUAGE plpgsql STRICT AS $$
begin begin
return roleName('customer', customerName, 'admin'); return roleDescriptor('customer', customer.uuid, 'admin');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION customerTenant(customerName varchar) CREATE OR REPLACE FUNCTION customerTenant(customer customer)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ LANGUAGE plpgsql STRICT AS $$
begin begin
return roleName('customer', customerName, 'tenant'); return roleDescriptor('customer', customer.uuid, 'tenant');
end; $$; end; $$;
@ -52,14 +52,14 @@ BEGIN
-- the owner role with full access for Hostsharing administrators -- the owner role with full access for Hostsharing administrators
customerOwnerUuid = createRole( customerOwnerUuid = createRole(
customerOwner(NEW.prefix), customerOwner(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']),
beneathRole('administrators') beneathRole(hostsharingAdmin())
); );
-- the admin role for the customer's admins, who can view and add products -- the admin role for the customer's admins, who can view and add products
customerAdminUuid = createRole( customerAdminUuid = createRole(
customerAdmin(NEW.prefix), customerAdmin(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['view', 'add-package']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['view', 'add-package']),
-- NO auto follow for customer owner to avoid exploding permissions for administrators -- NO auto follow for customer owner to avoid exploding permissions for administrators
withUser(NEW.adminUserName, 'create') -- implicitly ignored if null withUser(NEW.adminUserName, 'create') -- implicitly ignored if null
@ -70,7 +70,7 @@ BEGIN
-- the tenant role which later can be used by owners+admins of sub-objects -- the tenant role which later can be used by owners+admins of sub-objects
perform createRole( perform createRole(
customerTenant(NEW.prefix), customerTenant(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['view']) grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['view'])
); );
@ -130,7 +130,7 @@ DO LANGUAGE plpgsql $$
BEGIN BEGIN
SET hsadminng.currentUser TO ''; SET hsadminng.currentUser TO '';
FOR t IN 0..6999 LOOP FOR t IN 0..9 LOOP
currentTask = 'creating RBAC test customer #' || t; currentTask = 'creating RBAC test customer #' || t;
SET LOCAL hsadminng.currentUser TO 'mike@hostsharing.net'; SET LOCAL hsadminng.currentUser TO 'mike@hostsharing.net';
SET LOCAL hsadminng.assumedRoles = ''; SET LOCAL hsadminng.assumedRoles = '';

View File

@ -11,25 +11,30 @@ CREATE TABLE IF NOT EXISTS package (
customerUuid uuid REFERENCES customer(uuid) customerUuid uuid REFERENCES customer(uuid)
); );
CREATE OR REPLACE FUNCTION packageOwner(packageName varchar) CREATE OR REPLACE FUNCTION packageOwner(pac package)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
declare
roleDesc RbacRoleDescriptor;
begin begin
return roleName('package', packageName, 'owner'); return roleDescriptor('package', pac.uuid, 'admin');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION packageAdmin(packageName varchar) CREATE OR REPLACE FUNCTION packageAdmin(pac package)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
begin begin
return roleName('package', packageName, 'admin'); return roleDescriptor('package', pac.uuid, 'admin');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION packageTenant(packageName varchar) CREATE OR REPLACE FUNCTION packageTenant(pac package)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
begin begin
return roleName('package', packageName, 'tenant'); return roleDescriptor('package', pac.uuid, 'tenant');
end; $$; end; $$;
@ -54,24 +59,24 @@ BEGIN
-- an owner role is created and assigned to the customer's admin role -- an owner role is created and assigned to the customer's admin role
packageOwnerRoleUuid = createRole( packageOwnerRoleUuid = createRole(
packageOwner(NEW.name), packageOwner(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']),
beneathRole(customerAdmin(parentCustomer.prefix)) beneathRole(customerAdmin(parentCustomer))
); );
-- an owner role is created and assigned to the package owner role -- an owner role is created and assigned to the package owner role
packageAdminRoleUuid = createRole( packageAdminRoleUuid = createRole(
packageAdmin(NEW.name), packageAdmin(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit', 'add-unixuser']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit', 'add-unixuser', 'add-domain']),
beneathRole(packageOwnerRoleUuid) beneathRole(packageOwnerRoleUuid)
); );
-- and a package tenant role is created and assigned to the package admin as well -- and a package tenant role is created and assigned to the package admin as well
perform createRole( perform createRole(
packageTenant(NEW.name), packageTenant(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY ['view']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY ['view']),
beneathRole(packageAdminRoleUuid), beneathRole(packageAdminRoleUuid),
beingItselfA(customerTenant(parentCustomer.prefix)) beingItselfA(customerTenant(parentCustomer))
); );
RETURN NEW; RETURN NEW;

View File

@ -12,25 +12,28 @@ CREATE TABLE IF NOT EXISTS UnixUser (
packageUuid uuid REFERENCES package(uuid) packageUuid uuid REFERENCES package(uuid)
); );
CREATE OR REPLACE FUNCTION unixUserOwner(unixUserName varchar) CREATE OR REPLACE FUNCTION unixUserOwner(uu UnixUser)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
begin begin
return roleName('unixuser', unixUserName, 'owner'); return roleDescriptor('unixuser', uu.uuid, 'owner');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION unixUserAdmin(unixUserName varchar) CREATE OR REPLACE FUNCTION unixUserAdmin(uu UnixUser)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
begin begin
return roleName('unixuser', unixUserName, 'admin'); return roleDescriptor('unixuser', uu.uuid, 'admin');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION unixUserTenant(unixUserName varchar) CREATE OR REPLACE FUNCTION unixUserTenant(uu UnixUser)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
begin begin
return roleName('unixuser', unixUserName, 'tenant'); return roleDescriptor('unixuser', uu.uuid, 'tenant');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION createUnixUserTenantRoleIfNotExists(unixUser UnixUser) CREATE OR REPLACE FUNCTION createUnixUserTenantRoleIfNotExists(unixUser UnixUser)
@ -38,19 +41,19 @@ CREATE OR REPLACE FUNCTION createUnixUserTenantRoleIfNotExists(unixUser UnixUser
RETURNS NULL ON NULL INPUT RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$ LANGUAGE plpgsql AS $$
DECLARE DECLARE
unixUserTenantRoleName varchar; unixUserTenantRoleDesc RbacRoleDescriptor;
unixUserTenantRoleUuid uuid; unixUserTenantRoleUuid uuid;
BEGIN BEGIN
unixUserTenantRoleName = unixUserTenant(unixUser.name); unixUserTenantRoleDesc = unixUserTenant(unixUser);
unixUserTenantRoleUuid = findRoleId(unixUserTenantRoleName); unixUserTenantRoleUuid = findRoleId(unixUserTenantRoleDesc);
IF unixUserTenantRoleUuid IS NOT NULL THEN IF unixUserTenantRoleUuid IS NOT NULL THEN
RETURN unixUserTenantRoleUuid; RETURN unixUserTenantRoleUuid;
END IF; END IF;
RETURN createRole( RETURN createRole(
unixUserTenantRoleName, unixUserTenantRoleDesc,
grantingPermissions(forObjectUuid => unixUser.uuid, permitOps => ARRAY['edit', 'add-domain']), grantingPermissions(forObjectUuid => unixUser.uuid, permitOps => ARRAY['view']),
beneathRole(unixUserAdmin(unixUser.name)) beneathRole(unixUserAdmin(unixUser))
); );
END; $$; END; $$;
@ -76,17 +79,17 @@ BEGIN
-- an owner role is created and assigned to the package's admin group -- an owner role is created and assigned to the package's admin group
unixuserOwnerRoleId = createRole( unixuserOwnerRoleId = createRole(
unixUserOwner(NEW.name), unixUserOwner(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']),
beneathRole(packageAdmin(parentPackage.name)) beneathRole(packageAdmin(parentPackage))
); );
-- and a unixuser admin role is created and assigned to the unixuser owner as well -- and a unixuser admin role is created and assigned to the unixuser owner as well
unixuserAdminRoleId = createRole( unixuserAdminRoleId = createRole(
unixUserAdmin(NEW.name), unixUserAdmin(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit', 'add-domain']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit']),
beneathRole(unixuserOwnerRoleId), beneathRole(unixuserOwnerRoleId),
beingItselfA(packageTenant(parentPackage.name)) beingItselfA(packageTenant(parentPackage))
); );
-- a tenent role is only created on demand -- a tenent role is only created on demand

View File

@ -16,25 +16,28 @@ CREATE TRIGGER createRbacObjectForDomain_Trigger
BEFORE INSERT ON Domain BEFORE INSERT ON Domain
FOR EACH ROW EXECUTE PROCEDURE createRbacObject(); FOR EACH ROW EXECUTE PROCEDURE createRbacObject();
CREATE OR REPLACE FUNCTION domainOwner(unixUserName varchar, domainName varchar) CREATE OR REPLACE FUNCTION domainOwner(dom Domain)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
begin begin
return roleName('domain', unixUserName || '/' || domainName, 'owner'); return roleDescriptor('domain', dom.uuid, 'owner');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION domainAdmin(unixUserName varchar, domainName varchar) CREATE OR REPLACE FUNCTION domainAdmin(dom Domain)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
begin begin
return roleName('domain', unixUserName || '/' || domainName, 'admin'); return roleDescriptor('domain', dom.uuid, 'admin');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION domainTenant(unixUserName varchar, domainName varchar) CREATE OR REPLACE FUNCTION domainTenant(dom Domain)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
begin begin
return roleName('domain', unixUserName || '/' || domainName, 'tenant'); return roleDescriptor('domain', dom.uuid, 'tenant');
end; $$; end; $$;
@ -42,7 +45,8 @@ CREATE OR REPLACE FUNCTION createRbacRulesForDomain()
RETURNS trigger RETURNS trigger
LANGUAGE plpgsql STRICT AS $$ LANGUAGE plpgsql STRICT AS $$
DECLARE DECLARE
parentUser unixuser; parentUser UnixUser;
parentPackage package;
domainOwnerRoleUuid uuid; domainOwnerRoleUuid uuid;
domainAdminRoleUuid uuid; domainAdminRoleUuid uuid;
BEGIN BEGIN
@ -50,25 +54,26 @@ BEGIN
RAISE EXCEPTION 'invalid usage of TRIGGER AFTER INSERT'; RAISE EXCEPTION 'invalid usage of TRIGGER AFTER INSERT';
END IF; END IF;
SELECT * FROM unixuser WHERE uuid=NEW.unixUserUuid into parentUser; 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 -- a domain owner role is created and assigned to the unixuser's admin role
domainOwnerRoleUuid = createRole( domainOwnerRoleUuid = createRole(
domainOwner(parentUser.name, NEW.name), domainOwner(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']),
beneathRole(unixUserAdmin(parentUser.name)) beneathRole(packageAdmin(parentPackage))
); );
-- a domain admin role is created and assigned to the domain's owner role -- a domain admin role is created and assigned to the domain's owner role
domainAdminRoleUuid = createRole( domainAdminRoleUuid = createRole(
domainAdmin(parentUser.name, NEW.name), domainAdmin(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit', 'add-emailaddress']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit', 'add-emailaddress']),
beneathRole(domainOwnerRoleUuid) beneathRole(domainOwnerRoleUuid)
); );
-- and a domain tenant role is created and assigned to the domain's admiin role -- and a domain tenant role is created and assigned to the domain's admiin role
perform createRole( perform createRole(
domainTenant(parentUser.name, NEW.name), domainTenant(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']),
beneathRole(domainAdminRoleUuid), beneathRole(domainAdminRoleUuid),
beingItselfA(createUnixUserTenantRoleIfNotExists(parentUser)) beingItselfA(createUnixUserTenantRoleIfNotExists(parentUser))

View File

@ -16,50 +16,51 @@ CREATE TRIGGER createRbacObjectForEMailAddress_Trigger
BEFORE INSERT ON EMailAddress BEFORE INSERT ON EMailAddress
FOR EACH ROW EXECUTE PROCEDURE createRbacObject(); FOR EACH ROW EXECUTE PROCEDURE createRbacObject();
CREATE OR REPLACE FUNCTION emailAddressOwner(emailAddress varchar) CREATE OR REPLACE FUNCTION emailAddressOwner(emAddr EMailAddress)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
begin begin
return roleName('emailaddress', emailAddress, 'owner'); return roleDescriptor('emailaddress', emAddr.uuid, 'owner');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION emailAddressAdmin(emailAddress varchar) CREATE OR REPLACE FUNCTION emailAddressAdmin(emAddr EMailAddress)
RETURNS varchar RETURNS RbacRoleDescriptor
LANGUAGE plpgsql STRICT AS $$ RETURNS NULL ON NULL INPUT
LANGUAGE plpgsql AS $$
begin begin
return roleName('emailaddress', emailAddress, 'admin'); return roleDescriptor('emailaddress', emAddr.uuid, 'admin');
end; $$; end; $$;
CREATE OR REPLACE FUNCTION createRbacRulesForEMailAddress() CREATE OR REPLACE FUNCTION createRbacRulesForEMailAddress()
RETURNS trigger RETURNS trigger
LANGUAGE plpgsql STRICT AS $$ LANGUAGE plpgsql STRICT AS $$
DECLARE DECLARE
parentDomain record; parentDomain Domain;
eMailAddress varchar;
eMailAddressOwnerRoleUuid uuid; eMailAddressOwnerRoleUuid uuid;
BEGIN BEGIN
IF TG_OP <> 'INSERT' THEN IF TG_OP <> 'INSERT' THEN
RAISE EXCEPTION 'invalid usage of TRIGGER AFTER INSERT'; RAISE EXCEPTION 'invalid usage of TRIGGER AFTER INSERT';
END IF; END IF;
SELECT d.name as name, u.name as unixUserName FROM domain d SELECT d.*
LEFT JOIN unixuser u ON u.uuid = d.unixuseruuid FROM domain d
WHERE d.uuid=NEW.domainUuid into parentDomain; LEFT JOIN unixuser u ON u.uuid = d.unixuseruuid
eMailAddress = NEW.localPart || '@' || parentDomain.name; WHERE d.uuid=NEW.domainUuid INTO parentDomain;
-- an owner role is created and assigned to the domains's admin group -- an owner role is created and assigned to the domains's admin group
eMailAddressOwnerRoleUuid = createRole( eMailAddressOwnerRoleUuid = createRole(
emailAddressOwner(eMailAddress), emailAddressOwner(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['*']),
beneathRole(domainAdmin( parentDomain.unixUserName, parentDomain.name)) beneathRole(domainAdmin( parentDomain))
); );
-- and an admin role is created and assigned to the unixuser owner as well -- and an admin role is created and assigned to the unixuser owner as well
perform createRole( perform createRole(
emailAddressAdmin(eMailAddress), emailAddressAdmin(NEW),
grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit']), grantingPermissions(forObjectUuid => NEW.uuid, permitOps => ARRAY['edit']),
beneathRole(eMailAddressOwnerRoleUuid), beneathRole(eMailAddressOwnerRoleUuid),
beingItselfA(domainTenant(parentDomain.unixUserName, parentDomain.name)) beingItselfA(domainTenant(parentDomain))
); );
RETURN NEW; RETURN NEW;