docs: complete function reference, drop unicode punctuation
Documents refresh_hierarchy_cache, is_rule_valid and org_in_scope, and corrects the get_org_scope scope list which still omitted unattributed and all. Replaces em dashes and other typographic unicode with ASCII throughout the schema comments, the test suite and the documentation. Comments and prose are ASCII only.
This commit is contained in:
@@ -19,7 +19,7 @@ BEGIN
|
||||
IF EXISTS (SELECT 1 FROM morbac.activity_view_bindings WHERE activity = NEW.activity)
|
||||
AND NOT EXISTS (SELECT 1 FROM morbac.activity_view_bindings WHERE activity = NEW.activity AND view = NEW.view)
|
||||
THEN
|
||||
RAISE EXCEPTION 'Activity "%" is not allowed on view "%" — add a binding to morbac.activity_view_bindings to permit it',
|
||||
RAISE EXCEPTION 'Activity "%" is not allowed on view "%" - add a binding to morbac.activity_view_bindings to permit it',
|
||||
NEW.activity, NEW.view;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
|
||||
+2
-2
@@ -125,7 +125,7 @@ CREATE TRIGGER trg_invalidate_cache_user_rules
|
||||
AFTER INSERT OR UPDATE OR DELETE ON morbac.user_rules
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.invalidate_cache_on_user_rule_change();
|
||||
|
||||
-- Global rules have no org scope — any change invalidates the entire cache
|
||||
-- Global rules have no org scope - any change invalidates the entire cache
|
||||
CREATE OR REPLACE FUNCTION morbac.invalidate_cache_on_global_rule_change()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
@@ -141,7 +141,7 @@ CREATE TRIGGER trg_invalidate_cache_global_rules
|
||||
AFTER INSERT OR UPDATE OR DELETE ON morbac.global_rules
|
||||
FOR EACH ROW EXECUTE FUNCTION morbac.invalidate_cache_on_global_rule_change();
|
||||
|
||||
-- system_principals changes affect prohibition bypass — invalidate per user
|
||||
-- system_principals changes affect prohibition bypass - invalidate per user
|
||||
CREATE OR REPLACE FUNCTION morbac.invalidate_cache_on_system_principal_change()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
-- Scope:
|
||||
-- - rules.scope selects which objects a rule covers: a specific org
|
||||
-- (self/subtree/descendants/...), 'unattributed', or 'all'.
|
||||
-- Evaluated at query time via org_in_scope() — new orgs are covered automatically.
|
||||
-- Evaluated at query time via org_in_scope() - new orgs are covered automatically.
|
||||
-- - cross_org_rules.source_org_id is always required: user must hold the role there.
|
||||
-- - user_rules target a specific user directly (no role required); their org_id
|
||||
-- is a specific org, or NULL for unattributed objects.
|
||||
@@ -44,10 +44,10 @@ BEGIN
|
||||
SELECT 1 FROM morbac.system_principals WHERE user_id = p_user_id
|
||||
);
|
||||
|
||||
-- STEPS 1-3.5: Prohibitions — skipped entirely for system principals
|
||||
-- STEPS 1-3.5: Prohibitions - skipped entirely for system principals
|
||||
IF NOT v_is_system_principal THEN
|
||||
|
||||
-- STEP 1: Local prohibitions — find the highest-priority applicable one
|
||||
-- STEP 1: Local prohibitions - find the highest-priority applicable one
|
||||
FOR v_rule IN
|
||||
SELECT r.context_id, COALESCE(r.priority, 0) AS prio
|
||||
FROM morbac.rules r
|
||||
@@ -65,7 +65,7 @@ BEGIN
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 2: Cross-org prohibitions — update max if a higher priority is found
|
||||
-- STEP 2: Cross-org prohibitions - update max if a higher priority is found
|
||||
FOR v_rule IN
|
||||
SELECT cr.context_id, COALESCE(cr.priority, 0) AS prio
|
||||
FROM morbac.cross_org_rules cr
|
||||
@@ -85,7 +85,7 @@ BEGIN
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 3: User-level prohibitions — direct user rules, update max if higher
|
||||
-- STEP 3: User-level prohibitions - direct user rules, update max if higher
|
||||
FOR v_rule IN
|
||||
SELECT ur.context_id, COALESCE(ur.priority, 0) AS prio
|
||||
FROM morbac.user_rules ur
|
||||
@@ -126,7 +126,7 @@ BEGIN
|
||||
|
||||
END IF; -- v_is_system_principal
|
||||
|
||||
-- STEP 4: Local permissions — find the highest-priority applicable one
|
||||
-- STEP 4: Local permissions - find the highest-priority applicable one
|
||||
FOR v_rule IN
|
||||
SELECT r.context_id, COALESCE(r.priority, 0) AS prio
|
||||
FROM morbac.rules r
|
||||
@@ -144,7 +144,7 @@ BEGIN
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 5: Cross-org permissions — update max if higher found
|
||||
-- STEP 5: Cross-org permissions - update max if higher found
|
||||
FOR v_rule IN
|
||||
SELECT cr.context_id, COALESCE(cr.priority, 0) AS prio
|
||||
FROM morbac.cross_org_rules cr
|
||||
@@ -164,7 +164,7 @@ BEGIN
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
-- STEP 6: User-level permissions — direct user rules, update max if higher
|
||||
-- STEP 6: User-level permissions - direct user rules, update max if higher
|
||||
FOR v_rule IN
|
||||
SELECT ur.context_id, COALESCE(ur.priority, 0) AS prio
|
||||
FROM morbac.user_rules ur
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- Global rules: Rule(user_id, activity, view, context, modality)
|
||||
--
|
||||
-- No org_id or role_id — applies system-wide regardless of org membership or roles.
|
||||
-- No org_id or role_id - applies system-wide regardless of org membership or roles.
|
||||
-- user_id NULL = every user; non-NULL = specific user only.
|
||||
-- activity NULL = any activity; view NULL = any view.
|
||||
--
|
||||
|
||||
+10
-10
@@ -53,16 +53,16 @@ COMMENT ON FUNCTION morbac.get_org_descendants(UUID) IS
|
||||
-- Get a named scope of organizations relative to a given org.
|
||||
--
|
||||
-- Supported scopes:
|
||||
-- 'self' — the org itself only (depth = 0)
|
||||
-- 'children' — direct children only (descendants at depth = 1)
|
||||
-- 'descendants' — all descendants, excluding self (depth > 0)
|
||||
-- 'subtree' — self + all descendants (equivalent to get_org_descendants)
|
||||
-- 'parent' — direct parent only (ancestor at depth = 1)
|
||||
-- 'ancestors' — all ancestors, excluding self (depth > 0)
|
||||
-- 'lineage' — self + all ancestors (equivalent to get_org_ancestors)
|
||||
-- 'root' — topmost ancestor only (max depth ancestor)
|
||||
-- 'unattributed' — the no-org bucket; resolves to no real orgs (empty set)
|
||||
-- 'all' — every organization (unattributed is not an org, so it is
|
||||
-- 'self' - the org itself only (depth = 0)
|
||||
-- 'children' - direct children only (descendants at depth = 1)
|
||||
-- 'descendants' - all descendants, excluding self (depth > 0)
|
||||
-- 'subtree' - self + all descendants (equivalent to get_org_descendants)
|
||||
-- 'parent' - direct parent only (ancestor at depth = 1)
|
||||
-- 'ancestors' - all ancestors, excluding self (depth > 0)
|
||||
-- 'lineage' - self + all ancestors (equivalent to get_org_ancestors)
|
||||
-- 'root' - topmost ancestor only (max depth ancestor)
|
||||
-- 'unattributed' - the no-org bucket; resolves to no real orgs (empty set)
|
||||
-- 'all' - every organization (unattributed is not an org, so it is
|
||||
-- not listed here; org_in_scope('all') does cover it)
|
||||
--
|
||||
-- Optional p_max_depth limits how many levels are traversed (NULL = unlimited).
|
||||
|
||||
+12
-12
@@ -1,17 +1,17 @@
|
||||
-- Core OrBAC rule relation: Rule(org, role, activity, view, context, modality)
|
||||
--
|
||||
-- scope controls which orgs this rule covers relative to org_id:
|
||||
-- 'self' — exact org only (default, current behavior)
|
||||
-- 'subtree' — org + all descendants
|
||||
-- 'descendants' — all descendants, excluding self
|
||||
-- 'children' — direct children only
|
||||
-- 'parent' — direct parent only
|
||||
-- 'ancestors' — all ancestors, excluding self
|
||||
-- 'lineage' — self + all ancestors
|
||||
-- 'root' — topmost ancestor only
|
||||
-- 'unattributed' — no-org objects only (org_id column above is the declaring authority)
|
||||
-- 'all' — every org, unattributed included
|
||||
-- Evaluated at query time via org_in_scope() — new orgs are picked up automatically.
|
||||
-- 'self' - exact org only (default, current behavior)
|
||||
-- 'subtree' - org + all descendants
|
||||
-- 'descendants' - all descendants, excluding self
|
||||
-- 'children' - direct children only
|
||||
-- 'parent' - direct parent only
|
||||
-- 'ancestors' - all ancestors, excluding self
|
||||
-- 'lineage' - self + all ancestors
|
||||
-- 'root' - topmost ancestor only
|
||||
-- 'unattributed' - no-org objects only (org_id column above is the declaring authority)
|
||||
-- 'all' - every org, unattributed included
|
||||
-- Evaluated at query time via org_in_scope() - new orgs are picked up automatically.
|
||||
--
|
||||
-- Org target vocabulary: a specific org (the tree scopes), 'unattributed', or 'all'.
|
||||
|
||||
@@ -44,7 +44,7 @@ INCLUDE (role_id, context_id)
|
||||
WHERE is_active = true;
|
||||
|
||||
COMMENT ON TABLE morbac.rules IS 'Core OrBAC rules - Permission, Prohibition, Obligation, Recommendation';
|
||||
COMMENT ON COLUMN morbac.rules.scope IS 'Object scope: self (default), subtree, descendants, children, parent, ancestors, lineage, root, unattributed, all. Selects which objects (by org) the rule reaches: a specific org via the tree scopes, unattributed for no-org objects only, or all for every org including unattributed. Evaluated at query time — new orgs are covered automatically.';
|
||||
COMMENT ON COLUMN morbac.rules.scope IS 'Object scope: self (default), subtree, descendants, children, parent, ancestors, lineage, root, unattributed, all. Selects which objects (by org) the rule reaches: a specific org via the tree scopes, unattributed for no-org objects only, or all for every org including unattributed. Evaluated at query time - new orgs are covered automatically.';
|
||||
COMMENT ON COLUMN morbac.rules.modality IS 'Deontic modality: permission, prohibition, obligation, recommendation';
|
||||
COMMENT ON COLUMN morbac.rules.priority IS 'Optional rule priority (higher wins). NULL = 0. A permission with higher priority than a prohibition overrides it.';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-- - No targeted global_rules prohibitions
|
||||
-- - All prohibitions are skipped in is_allowed_nocache() (see authorization.sql)
|
||||
--
|
||||
-- This table has no INSERT/UPDATE/DELETE RLS policies — only the database owner
|
||||
-- This table has no INSERT/UPDATE/DELETE RLS policies - only the database owner
|
||||
-- can register or remove system principals (done in SQL at deploy time).
|
||||
-- SELECT is gated by is_allowed() like all other system tables.
|
||||
|
||||
@@ -17,7 +17,7 @@ CREATE TABLE morbac.system_principals (
|
||||
);
|
||||
|
||||
COMMENT ON TABLE morbac.system_principals IS
|
||||
'Registry of backend service accounts. Immutable at the trigger level — no policy can touch them.';
|
||||
'Registry of backend service accounts. Immutable at the trigger level - no policy can touch them.';
|
||||
COMMENT ON COLUMN morbac.system_principals.user_id IS
|
||||
'External user UUID of the service account';
|
||||
|
||||
|
||||
+5
-5
@@ -85,7 +85,7 @@ CREATE POLICY user_roles_delete ON morbac.user_roles FOR DELETE
|
||||
USING (morbac.is_allowed(morbac.current_user_id(), org_id, 'delete',
|
||||
morbac.get_config('system_view.user_roles')));
|
||||
|
||||
-- morbac.contexts (global — use current session org for writes)
|
||||
-- morbac.contexts (global - use current session org for writes)
|
||||
ALTER TABLE morbac.contexts ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY contexts_select ON morbac.contexts FOR SELECT
|
||||
@@ -104,7 +104,7 @@ CREATE POLICY contexts_delete ON morbac.contexts FOR DELETE
|
||||
USING (morbac.is_allowed(morbac.current_user_id(), morbac.current_org_id(), 'delete',
|
||||
morbac.get_config('system_view.contexts')));
|
||||
|
||||
-- morbac.activities (global — use current session org for writes)
|
||||
-- morbac.activities (global - use current session org for writes)
|
||||
ALTER TABLE morbac.activities ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY activities_select ON morbac.activities FOR SELECT
|
||||
@@ -123,7 +123,7 @@ CREATE POLICY activities_delete ON morbac.activities FOR DELETE
|
||||
USING (morbac.is_allowed(morbac.current_user_id(), morbac.current_org_id(), 'delete',
|
||||
morbac.get_config('system_view.activities')));
|
||||
|
||||
-- morbac.views (global — use current session org for writes)
|
||||
-- morbac.views (global - use current session org for writes)
|
||||
ALTER TABLE morbac.views ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY views_select ON morbac.views FOR SELECT
|
||||
@@ -180,14 +180,14 @@ CREATE POLICY user_rules_delete ON morbac.user_rules FOR DELETE
|
||||
USING (morbac.is_allowed(morbac.current_user_id(), org_id, 'delete',
|
||||
morbac.get_config('system_view.user_rules')));
|
||||
|
||||
-- morbac.system_principals (no org_id — SELECT only; INSERT/UPDATE/DELETE reserved for DB owner)
|
||||
-- morbac.system_principals (no org_id - SELECT only; INSERT/UPDATE/DELETE reserved for DB owner)
|
||||
ALTER TABLE morbac.system_principals ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY system_principals_select ON morbac.system_principals FOR SELECT
|
||||
USING (morbac.is_allowed(morbac.current_user_id(), morbac.current_org_id(), 'read',
|
||||
morbac.get_config('system_view.system_principals')));
|
||||
|
||||
-- morbac.global_rules (no org_id — use current session org for write checks)
|
||||
-- morbac.global_rules (no org_id - use current session org for write checks)
|
||||
ALTER TABLE morbac.global_rules ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY global_rules_select ON morbac.global_rules FOR SELECT
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ CREATE INDEX idx_user_rules_activity_view ON morbac.user_rules(activity, view);
|
||||
CREATE INDEX idx_user_rules_modality ON morbac.user_rules(modality);
|
||||
CREATE INDEX idx_user_rules_lookup ON morbac.user_rules(user_id, org_id, activity, modality, view);
|
||||
|
||||
COMMENT ON TABLE morbac.user_rules IS 'Direct user-level rules — grant or prohibit access for a specific user, bypassing the role system';
|
||||
COMMENT ON TABLE morbac.user_rules IS 'Direct user-level rules - grant or prohibit access for a specific user, bypassing the role system';
|
||||
COMMENT ON COLUMN morbac.user_rules.user_id IS 'User this rule applies to directly';
|
||||
COMMENT ON COLUMN morbac.user_rules.org_id IS 'Org where the resource resides; NULL targets unattributed (no-org) objects';
|
||||
COMMENT ON COLUMN morbac.user_rules.modality IS 'Deontic modality: permission, prohibition, obligation, recommendation';
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ COMMENT ON TABLE morbac.view_hierarchy IS 'View hierarchy - senior views inherit
|
||||
COMMENT ON COLUMN morbac.view_hierarchy.senior_view IS 'Senior view (more specific)';
|
||||
COMMENT ON COLUMN morbac.view_hierarchy.junior_view IS 'Junior view (more general)';
|
||||
|
||||
-- Default system view names — match system_view.* config keys.
|
||||
-- Default system view names - match system_view.* config keys.
|
||||
-- Override config values to rename; the new name must be seeded here too.
|
||||
INSERT INTO morbac.views (name, description) VALUES
|
||||
('orgs', 'Organizations table'),
|
||||
|
||||
Reference in New Issue
Block a user