--
-- acs-kernel/sql/acs-permissions-create.sql
--
-- The ACS core permission system. The knowledge level of system
-- allows you to define a hierarchical system of privileges. The
-- operational level allows you to grant to any party a privilege on
-- any object.
--
-- @author Rafael Schloming (rhs@mit.edu)
--
-- @creation-date 2000-08-13
--
-- @cvs-id $Id: acs-permissions-create.sql,v 1.44 2024/09/11 06:15:47 gustafn Exp $
--
---------------------------------------------
-- KNOWLEDGE LEVEL: PRIVILEGES AND ACTIONS --
---------------------------------------------
CREATE TABLE acs_privileges (
privilege varchar(100) not null constraint acs_privileges_privilege_pk
primary key,
pretty_name varchar(100),
pretty_plural varchar(100)
);
COMMENT ON TABLE acs_privileges is '
Privileges share a global namespace. This is to avoid a
situation where granting the foo privilege on one type of object can
have an entirely different meaning than granting the foo privilege on
another type of object.
';
CREATE TABLE acs_privilege_hierarchy (
privilege varchar(100) not null
constraint acs_priv_hier_priv_fk
references acs_privileges (privilege),
child_privilege varchar(100) not null
constraint acs_priv_hier_child_priv_fk
references acs_privileges (privilege),
constraint acs_privilege_hierarchy_pk
primary key (privilege, child_privilege)
);
comment on table acs_privilege_hierarchy is '
The acs_privilege_hierarchy gives us an easy way to say: The foo
privilege is a superset of the bar privilege.
';
CREATE INDEX acs_priv_hier_child_priv_idx ON acs_privilege_hierarchy (child_privilege);
CREATE OR REPLACE VIEW acs_privilege_descendant_map AS
WITH RECURSIVE privilege_desc(parent, child) AS (
SELECT child_privilege as parent, child_privilege as child FROM acs_privilege_hierarchy
UNION ALL
SELECT privilege as parent, privilege as child FROM
(SELECT privilege FROM acs_privilege_hierarchy
EXCEPT
SELECT child_privilege FROM acs_privilege_hierarchy) identity
UNION ALL
SELECT h.privilege as parent, pd.child
FROM acs_privilege_hierarchy h, privilege_desc pd
WHERE pd.parent = h.child_privilege
) SELECT privilege_desc.parent, privilege_desc.child FROM privilege_desc;
-- This trigger is used to create a pseudo-tree hierarchy that
-- can be used to emulate tree queries on the acs_privilege_hierarchy table.
-- The acs_privilege_hierarchy table maintains the permissions structure, but
-- it has a complication in that the same privileges can exist in more than one
-- path in the tree. As such, tree queries cannot be represented by the
-- usual tree query methods used for openacs.
-- DCW, 2001-03-15.
-- usage: queries directly on acs_privilege_hierarchy don't seem to occur
-- in many places. Rather it seems that acs_privilege_hierarchy is
-- used to build the view: acs_privilege_descendant_map. I did however
-- find one tree query in content-perms.sql that looks like the
-- following:
-- select privilege, child_privilege from acs_privilege_hierarchy
-- connect by prior privilege = child_privilege
-- start with child_privilege = 'cm_perm'
-- This query is used to find all of the ancestor privileges of 'cm_perm'.
-- The equivalent recursive query for PostgreSQL would be:
-- with recursive privilege_path AS (
-- select privilege, child_privilege from acs_privilege_hierarchy
-- where child_privilege = 'cm_perm'
-- UNION
-- select ph.privilege, ph.child_privilege from acs_privilege_hierarchy ph, privilege_path pp
-- where ph.child_privilege = pp.privilege
-- ) select * from privilege_path;
--
-- Also since acs_privilege_descendant_map is simply a path enumeration of
-- acs_privilege_hierarchy, we should be able to replace the above connect-by
-- with:
-- select privilege
-- from acs_privilege_descendant_map
-- where descendant = 'cm_perm'
-- This would be better, since the same query could be used for both oracle
-- and PostgreSQL.
select define_function_args('acs_privilege__create_privilege','privilege,pretty_name;null,pretty_plural;null');
--
-- procedure acs_privilege__create_privilege/3
--
CREATE OR REPLACE FUNCTION acs_privilege__create_privilege(
create_privilege__privilege varchar,
create_privilege__pretty_name varchar, -- default null
create_privilege__pretty_plural varchar -- default null
) RETURNS integer AS $$
DECLARE
BEGIN
insert into acs_privileges
(privilege, pretty_name, pretty_plural)
values
(create_privilege__privilege,
create_privilege__pretty_name,
create_privilege__pretty_plural);
return 0;
END;
$$ LANGUAGE plpgsql;
--
-- procedure acs_privilege__create_privilege/1
--
CREATE OR REPLACE FUNCTION acs_privilege__create_privilege(
create_privilege__privilege varchar
) RETURNS integer AS $$
DECLARE
BEGIN
return acs_privilege__create_privilege(create_privilege__privilege, null, null);
END;
$$ LANGUAGE plpgsql;
select define_function_args('acs_privilege__drop_privilege','privilege');
--
-- procedure acs_privilege__drop_privilege/1
--
CREATE OR REPLACE FUNCTION acs_privilege__drop_privilege(
drop_privilege__privilege varchar
) RETURNS integer AS $$
DECLARE
BEGIN
delete from acs_privileges
where privilege = drop_privilege__privilege;
return 0;
END;
$$ LANGUAGE plpgsql;
select define_function_args('acs_privilege__add_child','privilege,child_privilege');
--
-- procedure acs_privilege__add_child/2
--
CREATE OR REPLACE FUNCTION acs_privilege__add_child(
add_child__privilege varchar,
add_child__child_privilege varchar
) RETURNS integer AS $$
DECLARE
BEGIN
insert into acs_privilege_hierarchy
(privilege, child_privilege)
values
(add_child__privilege, add_child__child_privilege);
return 0;
END;
$$ LANGUAGE plpgsql;
select define_function_args('acs_privilege__remove_child','privilege,child_privilege');
--
-- procedure acs_privilege__remove_child/2
--
CREATE OR REPLACE FUNCTION acs_privilege__remove_child(
remove_child__privilege varchar,
remove_child__child_privilege varchar
) RETURNS integer AS $$
DECLARE
BEGIN
delete from acs_privilege_hierarchy
where privilege = remove_child__privilege
and child_privilege = remove_child__child_privilege;
return 0;
END;
$$ LANGUAGE plpgsql;
------------------------------------
-- OPERATIONAL LEVEL: PERMISSIONS --
------------------------------------
create table acs_permissions (
object_id integer not null
constraint acs_permissions_object_id_fk
references acs_objects (object_id)
on delete cascade,
grantee_id integer not null
constraint acs_permissions_grantee_id_fk
references parties (party_id)
on delete cascade,
privilege varchar(100) not null
constraint acs_permissions_privilege_fk
references acs_privileges (privilege)
on delete cascade,
constraint acs_permissions_pk
primary key (object_id, grantee_id, privilege)
);
create index acs_permissions_grantee_idx on acs_permissions (grantee_id);
create index acs_permissions_privilege_idx on acs_permissions (privilege);
create index acs_permissions_object_id_idx on acs_permissions(object_id);
--
-- The following view was replaced by acs_permission.permissions_all()
-- in Jul 6, 2016
--
-- create view acs_permissions_all
-- as select op.object_id, p.grantee_id, p.privilege
-- from acs_object_paths op, acs_permissions p
-- where op.ancestor_id = p.object_id;
-- create view acs_object_grantee_priv_map
-- as select a.object_id, a.grantee_id, m.descendant as privilege
-- from acs_permissions_all a, acs_privilege_descendant_map m
-- where a.privilege = m.privilege;
--
-- Obsolete and deprecated view.
--
-- create view all_object_party_privilege_map as
-- select * from acs_object_party_privilege_map;
-- This table acts as a mutex for inserts/deletes from acs_permissions.
-- This is used since postgresql's exception handing mechanism is non-
-- existent. A dup insert on acs_permissions will roll-back the
-- transaction and give an error, which is not what we want. Using a
-- separate table for locking allows us exclusive access for
-- inserts/deletes, but does not block readers. That way we don't
-- slow down permissions-checking which is known to have performance
-- problems already.
-- (OpenACS - DanW)
create table acs_permissions_lock (
lck integer
);
CREATE OR REPLACE FUNCTION acs_permissions_lock_tr () RETURNS trigger AS $$
BEGIN
raise EXCEPTION 'FOR LOCKING ONLY, NO DML STATEMENTS ALLOWED';
return null;
END;
$$ LANGUAGE plpgsql;
create trigger acs_permissions_lock_tr
before insert or update or delete on acs_permissions_lock
for each row execute procedure acs_permissions_lock_tr();
--
-- Create an SQL schema to allow the same dot notation as in
-- Oracle. The advantage of this notation is that the function can be
-- called identically for PostgreSQL and Oracle, so much duplicated
-- code can be removed.
--
-- Actually, at least all permission functions should be defined this
-- way, keeping the old "__" notation around for backwards
-- compatibility for custom packages.
--
-- TODO: handling of schema names in define_function_args
--
CREATE SCHEMA acs_permission;
--
-- procedure acs_permission.permission_p/3
--
CREATE OR REPLACE FUNCTION acs_permission.permission_p(
p_object_id integer,
p_party_id integer,
p_privilege varchar
) RETURNS boolean AS $$
DECLARE
v_security_context_root integer;
BEGIN
v_security_context_root := acs__magic_object_id('security_context_root');
RETURN EXISTS (WITH RECURSIVE
object_context(object_id, context_id) AS (
SELECT p_object_id, p_object_id
FROM acs_objects
WHERE object_id = p_object_id
UNION ALL
SELECT ao.object_id,
CASE WHEN (ao.security_inherit_p = 'f' OR ao.context_id IS NULL)
THEN v_security_context_root ELSE ao.context_id END
FROM object_context oc, acs_objects ao
WHERE ao.object_id = oc.context_id
AND ao.object_id != v_security_context_root
), privilege_ancestors(privilege, child_privilege) AS (
SELECT p_privilege, p_privilege
UNION ALL
SELECT aph.privilege, aph.child_privilege
FROM privilege_ancestors pa
JOIN acs_privilege_hierarchy aph ON aph.child_privilege = pa.privilege
)
SELECT 1 FROM acs_permissions p
JOIN party_approved_member_map pap ON pap.party_id = p.grantee_id
JOIN privilege_ancestors pa ON pa.privilege = p.privilege
JOIN object_context oc ON p.object_id = oc.context_id
WHERE pap.member_id = p_party_id
);
END;
$$ LANGUAGE plpgsql stable;
--
-- procedure acs_permission.permission_p_recursive_array/3
--
-- Return for a an array of objects a set of objects where the
-- specified user has the specified rights.
CREATE OR REPLACE FUNCTION acs_permission.permission_p_recursive_array(
p_objects integer[],
p_party_id integer,
p_privilege varchar
) RETURNS table (object_id integer, orig_object_id integer) AS $$
DECLARE
v_security_context_root integer;
BEGIN
v_security_context_root := acs__magic_object_id('security_context_root');
RETURN QUERY WITH RECURSIVE
object_context(obj_id, context_id, orig_obj_id) AS (
SELECT unnest(p_objects), unnest(p_objects), unnest(p_objects)
UNION ALL
SELECT
ao.object_id,
CASE WHEN (ao.security_inherit_p = 'f' OR ao.context_id IS NULL)
THEN v_security_context_root ELSE ao.context_id END,
oc.orig_obj_id
FROM object_context oc, acs_objects ao
WHERE ao.object_id = oc.context_id
AND ao.object_id != v_security_context_root
), privilege_ancestors(privilege, child_privilege) AS (
SELECT p_privilege, p_privilege
UNION ALL
SELECT aph.privilege, aph.child_privilege
FROM privilege_ancestors pa
JOIN acs_privilege_hierarchy aph ON aph.child_privilege = pa.privilege
)
SELECT p.object_id, oc.orig_obj_id
FROM acs_permissions p
JOIN party_approved_member_map pap ON pap.party_id = p.grantee_id
JOIN privilege_ancestors pa ON pa.privilege = p.privilege
JOIN object_context oc ON p.object_id = oc.context_id
WHERE pap.member_id = p_party_id;
END;
$$ LANGUAGE plpgsql stable;
--
-- procedure acs_permission.parties_with_object_privilege/2
--
-- Find all party_ids which have a given privilege on a given
-- object. The function is equivalent to an SQL query on the
-- deprecated acs_object_party_privilege_map such as e.g.:
--
-- select p.party_id
-- from acs_object_party_privilege_map p
-- where p.object_id = :object_id
-- and p.privilege = 'admin';
--
CREATE OR REPLACE FUNCTION acs_permission.parties_with_object_privilege(
p_object_id integer,
p_privilege varchar
) RETURNS table (party_id integer) AS $$
DECLARE
v_security_context_root integer;
BEGIN
v_security_context_root := acs__magic_object_id('security_context_root');
RETURN QUERY
WITH RECURSIVE
object_context(obj_id, context_id, orig_obj_id) AS (
SELECT p_object_id, p_object_id, p_object_id
UNION ALL
SELECT
ao.object_id,
CASE WHEN (ao.security_inherit_p = 'f' OR ao.context_id IS NULL)
THEN v_security_context_root ELSE ao.context_id END,
oc.orig_obj_id
FROM object_context oc, acs_objects ao
WHERE ao.object_id = oc.context_id
AND ao.object_id != v_security_context_root
), privilege_ancestors(privilege, child_privilege) AS (
SELECT p_privilege, p_privilege
UNION ALL
SELECT aph.privilege, aph.child_privilege
FROM privilege_ancestors pa
JOIN acs_privilege_hierarchy aph ON aph.child_privilege = pa.privilege
)
SELECT pap.member_id
FROM acs_permissions p
JOIN party_approved_member_map pap ON pap.party_id = p.grantee_id
JOIN privilege_ancestors pa ON pa.privilege = p.privilege
JOIN object_context oc ON p.object_id = oc.context_id;
END;
$$ LANGUAGE plpgsql stable;
--
-- procedure acs_permission.permissions_all/1
--
-- Return the permissions for an object from the object context
-- hierarchy. The call
--
-- select * from acs_permission.permissions_all(:object_id)
--
-- is compatible with the old/Oracle call
--
-- select * from acs_permission_all where where object_id = :object_id
--
--
CREATE OR REPLACE FUNCTION acs_permission.permissions_all(
p_object_id integer
) RETURNS table (object_id integer, grantee_id integer, privilege varchar) AS $$
DECLARE
v_security_context_root integer;
BEGIN
v_security_context_root := acs__magic_object_id('security_context_root');
RETURN QUERY
WITH RECURSIVE object_context(obj_id, context_id, orig_obj_id) AS (
SELECT p_object_id, p_object_id, p_object_id
UNION ALL
SELECT
ao.object_id,
CASE WHEN (ao.security_inherit_p = 'f' OR ao.context_id IS NULL)
THEN v_security_context_root ELSE ao.context_id END,
oc.orig_obj_id
FROM object_context oc, acs_objects ao
WHERE ao.object_id = oc.context_id
AND ao.object_id != v_security_context_root
)
select p_object_id, p.grantee_id, p.privilege
from object_context oc, acs_permissions p where p.object_id = oc.context_id;
END;
$$ LANGUAGE plpgsql stable;
--
-- procedure acs_permission.grant_permission/3
--
CREATE OR REPLACE FUNCTION acs_permission.grant_permission(
p_object_id integer,
p_grantee_id integer,
p_privilege varchar
) RETURNS integer AS $$
DECLARE
BEGIN
insert into acs_permissions
(object_id, grantee_id, privilege)
values
(p_object_id, p_grantee_id, p_privilege);
return 0;
EXCEPTION
when unique_violation then
return 0;
END;
$$ LANGUAGE plpgsql;
--
-- procedure acs_permission.revoke_permission/3
--
CREATE OR REPLACE FUNCTION acs_permission.revoke_permission(
p_object_id integer,
p_grantee_id integer,
p_privilege varchar
) RETURNS integer AS $$
DECLARE
BEGIN
delete from acs_permissions
where object_id = p_object_id
and grantee_id = p_grantee_id
and privilege = p_privilege;
return 0;
END;
$$ LANGUAGE plpgsql;
---
--- Functions for backwards compatibility
---
select define_function_args('acs_permission__permission_p','object_id,party_id,privilege');
--DROP FUNCTION IF EXISTS acs_permission__permission_p(integer, integer, varchar);
CREATE OR REPLACE FUNCTION acs_permission__permission_p(
p_object_id integer,
p_party_id integer,
p_privilege varchar
) RETURNS boolean AS $$
BEGIN
RETURN acs_permission.permission_p(p_object_id, p_party_id, p_privilege);
END;
$$ LANGUAGE plpgsql stable;
select define_function_args('acs_permission__permission_p_recursive_array','objects,party_id,privilege');
--DROP FUNCTION IF EXISTS acs_permission__permission_p_recursive_array(integer[], integer, varchar);
CREATE OR REPLACE FUNCTION acs_permission__permission_p_recursive_array(
p_objects integer[],
p_party_id integer,
p_privilege varchar
) RETURNS table (object_id integer, orig_object_id integer) AS $$
SELECT acs_permission.permission_p_recursive_array($1, $2, $3);
$$ LANGUAGE sql stable;
select define_function_args('acs_permission__grant_permission','object_id,grantee_id,privilege');
--DROP FUNCTION IF EXISTS acs_permission__grant_permission(integer, integer, varchar);
CREATE OR REPLACE FUNCTION acs_permission__grant_permission(
p_object_id integer,
p_grantee_id integer,
p_privilege varchar
) RETURNS integer AS $$
DECLARE
BEGIN
RETURN acs_permission.grant_permission(p_object_id, p_grantee_id, p_privilege);
END;
$$ LANGUAGE plpgsql;
select define_function_args('acs_permission__revoke_permission','object_id,grantee_id,privilege');
-- DROP FUNCTION IF EXISTS acs_permission__revoke_permission(integer, integer, varchar);
CREATE OR REPLACE FUNCTION acs_permission__revoke_permission(
p_object_id integer,
p_grantee_id integer,
p_privilege varchar
) RETURNS integer AS $$
DECLARE
BEGIN
RETURN acs_permission.revoke_permission(p_object_id, p_grantee_id, p_privilege);
END;
$$ LANGUAGE plpgsql;
--
-- Local variables:
-- mode: sql
-- indent-tabs-mode: nil
-- End: