cc3d65a013
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. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
303 lines
13 KiB
SQL
303 lines
13 KiB
SQL
-- =============================================================================
|
|
-- Cross-Organization Rules Tests
|
|
-- =============================================================================
|
|
-- Tests inter-organizational access via the cross_org_rules table.
|
|
--
|
|
-- Scenarios:
|
|
-- 1. No cross-org rule: users in org A cannot access org B resources
|
|
-- 2. Cross-org permission: specific role in source org can access target org
|
|
-- 3. User must hold role in source org (not just any role)
|
|
-- 4. Cross-org prohibition: blocks access even if there's a regular permission
|
|
-- 5. Cross-org rules combine with activity hierarchy
|
|
-- 6. Cross-org rules combine with view hierarchy
|
|
-- 7. Temporal cross-org rules (valid_from / valid_until)
|
|
--
|
|
-- Company context:
|
|
-- Judy (sales_rep at Sales) is allowed to read GlobalTech reports via cross-org rule
|
|
-- Nina (eng_auditor at Engineering) has a temporal cross-org rule to read GlobalTech audit_logs
|
|
-- Diana (sales_manager at Sales) has a cross-org prohibition on writing Engineering docs
|
|
--
|
|
-- Prerequisites: 00_setup.sql -> 05_temporal.sql
|
|
-- =============================================================================
|
|
|
|
\echo ''
|
|
\echo '================================================================'
|
|
\echo '06 - CROSS-ORGANIZATIONAL RULES'
|
|
\echo '================================================================'
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Setup: additional roles and users for cross-org scenarios
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
-- Nina: eng_auditor role in Engineering Dept
|
|
INSERT INTO morbac.roles (id, org_id, name, description) VALUES
|
|
('20000000-0002-0000-0000-000000000003', '10000000-0000-0000-0000-000000000002', 'eng_auditor', 'Engineering Auditor');
|
|
|
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
|
('30000000-0000-0000-0000-000000000014', '20000000-0002-0000-0000-000000000003', '10000000-0000-0000-0000-000000000002');
|
|
|
|
-- Rules for eng_auditor in Engineering
|
|
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality)
|
|
SELECT o.id, r.id, 'read', 'audit_logs', c.id, 'permission'
|
|
FROM morbac.orgs o
|
|
JOIN morbac.roles r ON r.org_id = o.id AND r.name = 'eng_auditor'
|
|
JOIN morbac.contexts c ON c.name = 'always'
|
|
WHERE o.name = 'Engineering Dept';
|
|
|
|
-- Verify nina's role was set up correctly
|
|
SELECT morbac.t('Nina has eng_auditor role at Engineering',
|
|
morbac.user_has_role(
|
|
'30000000-0000-0000-0000-000000000014'::uuid,
|
|
'10000000-0000-0000-0000-000000000002'::uuid,
|
|
'eng_auditor'
|
|
), TRUE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 1: No cross-org rule - access between orgs is denied by default
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 1. No cross-org rule: access denied by default ---'
|
|
|
|
-- Dave (employee at GlobalTech) has no role in Engineering and no cross-org rule
|
|
SELECT morbac.t('Dave (GlobalTech employee) reads Engineering documents [no cross-org rule]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000004'::uuid,
|
|
'10000000-0000-0000-0000-000000000002'::uuid,
|
|
'read', 'documents'
|
|
), FALSE);
|
|
|
|
-- Judy (Engineering engineer) accesses GlobalTech financial_data (no cross-org rule yet)
|
|
SELECT morbac.t('Judy (Engineering engineer) reads GlobalTech financial_data [no cross-org rule]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000010'::uuid,
|
|
'10000000-0000-0000-0000-000000000001'::uuid,
|
|
'read', 'financial_data'
|
|
), FALSE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 2: Cross-org permission - Sales sales_rep reads GlobalTech reports
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 2. Cross-org permission ---'
|
|
|
|
-- Judy is sales_rep at Sales. Add a cross-org rule:
|
|
-- Sales sales_rep can read GlobalTech reports
|
|
INSERT INTO morbac.cross_org_rules
|
|
(source_org_id, target_org_id, role_id, activity, view, context_id, modality)
|
|
VALUES (
|
|
'10000000-0000-0000-0000-000000000003', -- source: Sales Dept
|
|
'10000000-0000-0000-0000-000000000001', -- target: GlobalTech HQ
|
|
'20000000-0003-0000-0000-000000000002', -- sales_rep role
|
|
'read', 'reports',
|
|
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
|
'permission'
|
|
);
|
|
|
|
-- Cross-org rule grants access
|
|
SELECT morbac.t('Judy (Sales sales_rep) reads GlobalTech reports via cross-org rule',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000010'::uuid,
|
|
'10000000-0000-0000-0000-000000000001'::uuid,
|
|
'read', 'reports'
|
|
), TRUE);
|
|
|
|
-- Judy at Sales can still read Sales docs normally (local rule, no cross-org needed)
|
|
SELECT morbac.t('Judy (Sales sales_rep) reads Sales documents [local rule]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000010'::uuid,
|
|
'10000000-0000-0000-0000-000000000003'::uuid,
|
|
'read', 'documents'
|
|
), TRUE);
|
|
|
|
-- Judy does NOT have permission to read GlobalTech documents (cross-org rule only covers reports)
|
|
SELECT morbac.t('Judy (Sales sales_rep) reads GlobalTech documents [no rule for documents]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000010'::uuid,
|
|
'10000000-0000-0000-0000-000000000001'::uuid,
|
|
'read', 'documents'
|
|
), FALSE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 3: User must hold role in source org
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 3. Role must be held in source org ---'
|
|
|
|
-- Karl has no role anywhere - cannot use the Sales->GlobalTech cross-org rule
|
|
SELECT morbac.t('Karl (no role) reads GlobalTech reports via cross-org rule [no role in source]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000011'::uuid,
|
|
'10000000-0000-0000-0000-000000000001'::uuid,
|
|
'read', 'reports'
|
|
), FALSE);
|
|
|
|
-- Karl also has no access to GlobalTech documents
|
|
SELECT morbac.t('Karl (no role) reads GlobalTech documents [no access anywhere]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000011'::uuid,
|
|
'10000000-0000-0000-0000-000000000001'::uuid,
|
|
'read', 'documents'
|
|
), FALSE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 4: Cross-org prohibition - blocks access even with regular permission
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 4. Cross-org prohibition ---'
|
|
|
|
-- Engineering engineer can normally read Engineering documents (local perm)
|
|
SELECT morbac.t('Judy (Engineering engineer) reads Engineering documents [local perm]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000010'::uuid,
|
|
'10000000-0000-0000-0000-000000000002'::uuid,
|
|
'read', 'documents'
|
|
), TRUE);
|
|
|
|
-- Add Diana (sales_manager at Sales) for cross-org prohibition test
|
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
|
('30000000-0000-0000-0000-000000000015', '20000000-0003-0000-0000-000000000001', '10000000-0000-0000-0000-000000000003');
|
|
|
|
-- Add cross-org prohibition: Sales sales_manager prohibited from writing Engineering documents
|
|
INSERT INTO morbac.cross_org_rules
|
|
(source_org_id, target_org_id, role_id, activity, view, context_id, modality)
|
|
VALUES (
|
|
'10000000-0000-0000-0000-000000000003', -- source: Sales Dept
|
|
'10000000-0000-0000-0000-000000000002', -- target: Engineering Dept
|
|
'20000000-0003-0000-0000-000000000001', -- sales_manager role
|
|
'write', 'documents',
|
|
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
|
'prohibition'
|
|
);
|
|
|
|
-- Also give a cross-org permission that would otherwise allow the write
|
|
INSERT INTO morbac.cross_org_rules
|
|
(source_org_id, target_org_id, role_id, activity, view, context_id, modality)
|
|
VALUES (
|
|
'10000000-0000-0000-0000-000000000003', -- source: Sales Dept
|
|
'10000000-0000-0000-0000-000000000002', -- target: Engineering Dept
|
|
'20000000-0003-0000-0000-000000000001', -- sales_manager role
|
|
'write', 'documents',
|
|
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
|
'permission'
|
|
);
|
|
|
|
-- Cross-org prohibition takes precedence over permission
|
|
SELECT morbac.t('Diana (Sales sales_manager) writes Engineering documents [cross-org prohibition wins]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000015'::uuid,
|
|
'10000000-0000-0000-0000-000000000002'::uuid,
|
|
'write', 'documents'
|
|
), FALSE);
|
|
|
|
-- Diana can read Engineering documents (no prohibition for read)
|
|
INSERT INTO morbac.cross_org_rules
|
|
(source_org_id, target_org_id, role_id, activity, view, context_id, modality)
|
|
VALUES (
|
|
'10000000-0000-0000-0000-000000000003', -- source: Sales Dept
|
|
'10000000-0000-0000-0000-000000000002', -- target: Engineering Dept
|
|
'20000000-0003-0000-0000-000000000001', -- sales_manager role
|
|
'read', 'documents',
|
|
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
|
'permission'
|
|
);
|
|
|
|
-- No prohibition for read, permission applies
|
|
SELECT morbac.t('Diana (Sales sales_manager) reads Engineering documents [cross-org permission]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000015'::uuid,
|
|
'10000000-0000-0000-0000-000000000002'::uuid,
|
|
'read', 'documents'
|
|
), TRUE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 5: Cross-org + activity hierarchy
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 5. Cross-org with activity hierarchy ---'
|
|
|
|
-- The sales_rep cross-org rule grants 'read reports' in GlobalTech.
|
|
-- With activity hierarchy (write->read), requesting 'write reports' also matches
|
|
-- the 'read' rule (get_effective_activities('write') = {write, read}).
|
|
SELECT morbac.t('Judy (Sales sales_rep) writes GlobalTech reports via cross-org + activity hierarchy',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000010'::uuid,
|
|
'10000000-0000-0000-0000-000000000001'::uuid,
|
|
'write', 'reports'
|
|
), TRUE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 6: Cross-org + view hierarchy
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 6. Cross-org with view hierarchy ---'
|
|
|
|
-- Add a cross-org rule for 'read documents' (not financial_data directly)
|
|
-- With view hierarchy (financial_data->documents), 'read documents' cross-org rule
|
|
-- also covers 'read financial_data' requests
|
|
INSERT INTO morbac.cross_org_rules
|
|
(source_org_id, target_org_id, role_id, activity, view, context_id, modality)
|
|
VALUES (
|
|
'10000000-0000-0000-0000-000000000003', -- source: Sales Dept
|
|
'10000000-0000-0000-0000-000000000001', -- target: GlobalTech HQ
|
|
'20000000-0003-0000-0000-000000000002', -- sales_rep role
|
|
'read', 'documents',
|
|
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
|
'permission'
|
|
);
|
|
|
|
-- get_effective_views('financial_data') = {financial_data, documents}
|
|
-- so 'read documents' cross-org rule covers 'read financial_data' request
|
|
SELECT morbac.t('Judy (Sales sales_rep) reads GlobalTech financial_data via cross-org + view hierarchy',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000010'::uuid,
|
|
'10000000-0000-0000-0000-000000000001'::uuid,
|
|
'read', 'financial_data'
|
|
), TRUE);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Section 7: Temporal cross-org rule
|
|
-- ---------------------------------------------------------------------------
|
|
\echo ''
|
|
\echo '--- 7. Temporal cross-org rule ---'
|
|
|
|
-- Add a time-limited cross-org rule for eng_auditor to read GlobalTech audit_logs
|
|
INSERT INTO morbac.cross_org_rules
|
|
(source_org_id, target_org_id, role_id, activity, view, context_id, modality, valid_from, valid_until)
|
|
VALUES (
|
|
'10000000-0000-0000-0000-000000000002', -- source: Engineering
|
|
'10000000-0000-0000-0000-000000000001', -- target: GlobalTech HQ
|
|
'20000000-0002-0000-0000-000000000003', -- eng_auditor role
|
|
'read', 'audit_logs',
|
|
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
|
'permission',
|
|
now() - interval '1 hour',
|
|
now() + interval '1 day'
|
|
);
|
|
|
|
-- Nina (Engineering eng_auditor) reads GlobalTech audit_logs within window
|
|
SELECT morbac.t('Nina (Engineering eng_auditor) reads GlobalTech audit_logs [temporal cross-org, active]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000014'::uuid,
|
|
'10000000-0000-0000-0000-000000000001'::uuid,
|
|
'read', 'audit_logs'
|
|
), TRUE);
|
|
|
|
-- Expire the cross-org rule
|
|
UPDATE morbac.cross_org_rules
|
|
SET valid_until = now() - interval '1 second'
|
|
WHERE source_org_id = '10000000-0000-0000-0000-000000000002'
|
|
AND target_org_id = '10000000-0000-0000-0000-000000000001'
|
|
AND role_id = '20000000-0002-0000-0000-000000000003'
|
|
AND activity = 'read' AND view = 'audit_logs';
|
|
|
|
-- Temporal rule expired
|
|
SELECT morbac.t('Nina (Engineering eng_auditor) reads GlobalTech audit_logs [temporal cross-org, expired]',
|
|
morbac.is_allowed_nocache(
|
|
'30000000-0000-0000-0000-000000000014'::uuid,
|
|
'10000000-0000-0000-0000-000000000001'::uuid,
|
|
'read', 'audit_logs'
|
|
), FALSE);
|
|
|
|
\echo ''
|
|
\echo '=== Cross-Organization Rules Tests Completed ==='
|