3a24e1c726
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/216 Reviewed-by: Marc Sandlus <hsh-marcsandlus@noreply.dev.hostsharing.net>
236 lines
8.7 KiB
PL/PgSQL
236 lines
8.7 KiB
PL/PgSQL
--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; $$;
|
|
--//
|