--liquibase formatted sql -- ============================================================================ --changeset michael.hoennig:rbac-global-OBJECT runOnChange:true validCheckSum:ANY endDelimiter:--// -- ---------------------------------------------------------------------------- /* The purpose of this table is provide root business objects which can be referenced from global roles. Without this table, these columns needed to be nullable and many queries would be more complicated. In production databases, there is only a single row in this table, in test stages, there can be one row for each test data realm. */ create table if not exists rbac.global ( uuid uuid primary key references rbac.object (uuid) on delete cascade, name varchar(63) unique ); create unique index if not exists Global_Singleton on rbac.global ((0)); grant select on rbac.global to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; --// -- ============================================================================ --changeset michael.hoennig:rbac-global-IS-GLOBAL-ADMIN runOnChange:true validCheckSum:ANY endDelimiter:--// -- ------------------------------------------------------------------ create or replace function rbac.isGlobalAdmin() returns boolean language plpgsql as $$ declare isGlobalAdmin text; begin isGlobalAdmin := current_setting('hsadminng.isGlobalAdmin', true); if isGlobalAdmin is not null then return isGlobalAdmin::boolean; end if; raise exception '`hsadminng.isGlobalAdmin` should have been set by `rbac.defineContext()`'; end; $$; --// -- ============================================================================ --changeset michael.hoennig:rbac-global-HAS-GLOBAL-ADMIN-ROLE runOnChange:true validCheckSum:ANY endDelimiter:--// -- ---------------------------------------------------------------------------- /* Returns true if the current user is a global admin and has no assumed role. ATTENTION: It's false if the global-admin role is assumed, because the global admin role does not have the global admin role, but it is the global admin role. The differentiation is important for the cases where this function is used. */ create or replace function rbac.hasGlobalAdminRole() returns boolean stable -- leakproof language plpgsql as $$ declare assumedRoles text; begin begin assumedRoles := current_setting('hsadminng.assumedRoles'); exception when others then assumedRoles := null; end; return TRIM(COALESCE(assumedRoles, '')) = '' and rbac.isGlobalAdmin(); end; $$; --// -- ============================================================================ --changeset michael.hoennig:rbac-global-HAS-GLOBAL-PERMISSION endDelimiter:--// -- ------------------------------------------------------------------ create or replace function rbac.hasGlobalPermission(op rbac.RbacOp) returns boolean language sql as $$ -- TODO.perf: this could to be optimized select (select uuid from rbac.global) in (select rbac.queryAccessibleObjectUuidsOfSubjectIds(op, 'rbac.global', rbac.currentSubjectOrAssumedRolesUuids())); $$; --// -- ============================================================================ --changeset michael.hoennig:rbac-global-IDENTITY-VIEW runOnChange:true validCheckSum:ANY endDelimiter:--// -- ---------------------------------------------------------------------------- /* Creates a view to the rbac.global object table which maps the identifying name to the objectUuid. */ create or replace view rbac.global_iv as select target.uuid, target.name as idName from rbac.global as target; grant all privileges on rbac.global_iv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; /* Returns the objectUuid for a given identifying name (in this case the idName). */ create or replace function rbac.global_uuid_by_id_name(idName varchar) returns uuid language sql strict as $$ select uuid from rbac.global_iv iv where iv.idName = global_uuid_by_id_name.idName; $$; /* Returns the identifying name for a given objectUuid (in this case the idName). */ create or replace function rbac.global_id_name_by_uuid(uuid uuid) returns varchar language sql strict as $$ select idName from rbac.global_iv iv where iv.uuid = global_id_name_by_uuid.uuid; $$; --// --liquibase formatted sql -- ============================================================================ --changeset michael.hoennig:rbac-global-PSEUDO-OBJECT endDelimiter:--// -- ---------------------------------------------------------------------------- /** A single row to be referenced as a rbac.Global object. */ begin transaction; call base.defineContext('initializing table "rbac.global"', null, null, null); insert into rbac.object (objecttable) values ('rbac.global'); insert into rbac.global (uuid, name) values ((select uuid from rbac.object where objectTable = 'rbac.global'), 'global'); commit; --// -- ============================================================================ --changeset michael.hoennig:rbac-global-ADMIN-ROLE endDelimiter:--// -- ---------------------------------------------------------------------------- /* A rbac.Global administrator role. */ create or replace function rbac.global_ADMIN(assumed boolean = true) returns rbac.RoleDescriptor returns null on null input stable -- leakproof language sql as $$ select 'rbac.global', (select uuid from rbac.object where objectTable = 'rbac.global'), 'ADMIN'::rbac.RoleType, assumed; $$; begin transaction; call base.defineContext('creating role:rbac.global#global:ADMIN', null, null, null); select rbac.createRole(rbac.global_ADMIN()); commit; --// -- ============================================================================ --changeset michael.hoennig:rbac-global-GUEST-ROLE runOnChange:true validCheckSum:ANY endDelimiter:--// -- ---------------------------------------------------------------------------- /* A rbac.Global guest role. */ create or replace function rbac.global_GUEST(assumed boolean = true) returns rbac.RoleDescriptor returns null on null input stable -- leakproof language sql as $$ select 'rbac.global', (select uuid from rbac.object where objectTable = 'rbac.global'), 'GUEST'::rbac.RoleType, assumed; $$; do language plpgsql $$ begin call base.defineContext('creating role:rbac.global#global:guest', null, null, null); begin perform rbac.createRole(rbac.global_GUEST()); exception when unique_violation then null; -- ignore if it already exists from prev execution of this changeset end; end; $$; --// -- ============================================================================ --changeset michael.hoennig:rbac-global-ADMIN-USERS context:!without-test-data endDelimiter:--// -- ---------------------------------------------------------------------------- /* Create two users and assign both to the administrators' role. */ do language plpgsql $$ declare admins uuid ; begin call base.defineContext('creating fake test-realm admin users', null, null, null); admins = rbac.findRoleId(rbac.global_ADMIN()); call rbac.grantRoleToSubjectUnchecked(admins, admins, rbac.create_subject('superuser-alex@hostsharing.net')); call rbac.grantRoleToSubjectUnchecked(admins, admins, rbac.create_subject('superuser-fran@hostsharing.net')); perform rbac.create_subject('selfregistered-user-drew@hostsharing.org'); perform rbac.create_subject('selfregistered-test-user@hostsharing.org'); end; $$; --// -- ============================================================================ --changeset michael.hoennig:rbac-global-TEST context:!without-test-data runAlways:true endDelimiter:--// -- ---------------------------------------------------------------------------- /* Tests if rbac.currentSubjectUuid() can fetch the user from the session variable. */ do language plpgsql $$ declare userName varchar; begin call base.defineContext('testing currentSubjectUuid', null, 'superuser-fran@hostsharing.net', null); select userName from rbac.subject where uuid = rbac.currentSubjectUuid() into userName; if userName <> 'superuser-fran@hostsharing.net' then raise exception 'setting or fetching initial currentSubject failed, got: %', userName; end if; call base.defineContext('testing currentSubjectUuid', null, 'superuser-alex@hostsharing.net', null); select userName from rbac.subject where uuid = rbac.currentSubjectUuid() into userName; if userName = 'superuser-alex@hostsharing.net' then raise exception 'currentSubject should not change in one transaction, but did change, got: %', userName; end if; end; $$; --//