tests: cleanup structure
This commit is contained in:
@@ -42,8 +42,7 @@ test:
|
|||||||
@echo "Testing with development file..."
|
@echo "Testing with development file..."
|
||||||
-dropdb morbac_test 2>/dev/null || true
|
-dropdb morbac_test 2>/dev/null || true
|
||||||
createdb morbac_test
|
createdb morbac_test
|
||||||
psql -d morbac_test -f $(DEV_SQL)
|
@./tests/run_tests.sh morbac_test
|
||||||
psql -d morbac_test -f test_morbac.sql
|
|
||||||
dropdb morbac_test
|
dropdb morbac_test
|
||||||
@echo "Test completed."
|
@echo "Test completed."
|
||||||
|
|
||||||
|
|||||||
-744
@@ -1,744 +0,0 @@
|
|||||||
-- =============================================================================
|
|
||||||
-- Test Script for morbac_pg Extension
|
|
||||||
-- =============================================================================
|
|
||||||
-- This script demonstrates and tests the Multi-OrBAC implementation
|
|
||||||
--
|
|
||||||
-- Test Scenario:
|
|
||||||
-- - Two organizations: "Acme Corp" and "Beta Inc"
|
|
||||||
-- - Multiple users with different roles
|
|
||||||
-- - Permission and prohibition rules
|
|
||||||
-- - Context evaluation
|
|
||||||
-- - Multi-organization resource access
|
|
||||||
-- - Obligations and recommendations
|
|
||||||
-- =============================================================================
|
|
||||||
|
|
||||||
-- Clean up any previous test
|
|
||||||
DROP EXTENSION IF EXISTS morbac_pg CASCADE;
|
|
||||||
DROP SCHEMA IF EXISTS morbac CASCADE;
|
|
||||||
|
|
||||||
-- Install the extension
|
|
||||||
CREATE EXTENSION morbac_pg;
|
|
||||||
|
|
||||||
-- Verify schema and tables exist
|
|
||||||
\echo '=== Schema and Tables Created ==='
|
|
||||||
SELECT schemaname, tablename
|
|
||||||
FROM pg_tables
|
|
||||||
WHERE schemaname = 'morbac'
|
|
||||||
ORDER BY tablename;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 1: Create Organizations ==='
|
|
||||||
|
|
||||||
-- Create two organizations
|
|
||||||
INSERT INTO morbac.orgs (id, name) VALUES
|
|
||||||
('11111111-1111-1111-1111-111111111111', 'Acme Corp'),
|
|
||||||
('22222222-2222-2222-2222-222222222222', 'Beta Inc');
|
|
||||||
|
|
||||||
-- Create hierarchical organization (Acme Subsidiary under Acme Corp)
|
|
||||||
INSERT INTO morbac.orgs (id, name, parent_id) VALUES
|
|
||||||
('33333333-3333-3333-3333-333333333333', 'Acme Subsidiary', '11111111-1111-1111-1111-111111111111');
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
o.name,
|
|
||||||
COALESCE(p.name, 'None') as parent_organization
|
|
||||||
FROM morbac.orgs o
|
|
||||||
LEFT JOIN morbac.orgs p ON o.parent_id = p.id
|
|
||||||
ORDER BY o.parent_id NULLS FIRST, o.name;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 2: Create Roles ==='
|
|
||||||
|
|
||||||
-- Create roles in Acme Corp
|
|
||||||
INSERT INTO morbac.roles (id, org_id, name, description) VALUES
|
|
||||||
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', 'admin', 'Administrator role'),
|
|
||||||
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '11111111-1111-1111-1111-111111111111', 'employee', 'Regular employee'),
|
|
||||||
('cccccccc-cccc-cccc-cccc-cccccccccccc', '11111111-1111-1111-1111-111111111111', 'contractor', 'External contractor'),
|
|
||||||
('ffffffff-ffff-ffff-ffff-ffffffffffff', '11111111-1111-1111-1111-111111111111', 'viewer', 'Read-only viewer');
|
|
||||||
|
|
||||||
-- Create roles in Beta Inc
|
|
||||||
INSERT INTO morbac.roles (id, org_id, name, description) VALUES
|
|
||||||
('dddddddd-dddd-dddd-dddd-dddddddddddd', '22222222-2222-2222-2222-222222222222', 'manager', 'Manager role'),
|
|
||||||
('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', '22222222-2222-2222-2222-222222222222', 'staff', 'Staff member');
|
|
||||||
|
|
||||||
SELECT r.name as role, o.name as organization
|
|
||||||
FROM morbac.roles r
|
|
||||||
JOIN morbac.orgs o ON r.org_id = o.id
|
|
||||||
ORDER BY o.name, r.name;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 2b: Create Role Hierarchy ==='
|
|
||||||
|
|
||||||
-- Role hierarchy in Acme Corp:
|
|
||||||
-- admin > employee > viewer
|
|
||||||
-- (admin inherits from employee, employee inherits from viewer)
|
|
||||||
INSERT INTO morbac.role_hierarchy (senior_role_id, junior_role_id) VALUES
|
|
||||||
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'), -- admin inherits from employee
|
|
||||||
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'ffffffff-ffff-ffff-ffff-ffffffffffff'); -- employee inherits from viewer
|
|
||||||
|
|
||||||
\echo 'Role hierarchy created:'
|
|
||||||
SELECT
|
|
||||||
sr.name as senior_role,
|
|
||||||
jr.name as junior_role,
|
|
||||||
o.name as organization
|
|
||||||
FROM morbac.role_hierarchy rh
|
|
||||||
JOIN morbac.roles sr ON rh.senior_role_id = sr.id
|
|
||||||
JOIN morbac.roles jr ON rh.junior_role_id = jr.id
|
|
||||||
JOIN morbac.orgs o ON sr.org_id = o.id;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 3: Assign Users to Roles ==='
|
|
||||||
|
|
||||||
-- Define test users (in real scenario, these would come from auth system)
|
|
||||||
-- User Alice (admin at Acme)
|
|
||||||
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
|
||||||
('aaaaaaaa-0000-0000-0000-000000000001', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111');
|
|
||||||
|
|
||||||
-- User Bob (employee at Acme)
|
|
||||||
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
|
||||||
('bbbbbbbb-0000-0000-0000-000000000002', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '11111111-1111-1111-1111-111111111111');
|
|
||||||
|
|
||||||
-- User Charlie (contractor at Acme AND staff at Beta - multi-org user)
|
|
||||||
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
|
||||||
('cccccccc-0000-0000-0000-000000000003', 'cccccccc-cccc-cccc-cccc-cccccccccccc', '11111111-1111-1111-1111-111111111111'),
|
|
||||||
('cccccccc-0000-0000-0000-000000000003', 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', '22222222-2222-2222-2222-222222222222');
|
|
||||||
|
|
||||||
-- User Diana (manager at Beta)
|
|
||||||
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
|
||||||
('dddddddd-0000-0000-0000-000000000004', 'dddddddd-dddd-dddd-dddd-dddddddddddd', '22222222-2222-2222-2222-222222222222');
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
ur.user_id,
|
|
||||||
r.name as role,
|
|
||||||
o.name as organization
|
|
||||||
FROM morbac.user_roles ur
|
|
||||||
JOIN morbac.roles r ON ur.role_id = r.id
|
|
||||||
JOIN morbac.orgs o ON ur.org_id = o.id
|
|
||||||
ORDER BY ur.user_id;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 4: Define Activities and Views ==='
|
|
||||||
|
|
||||||
INSERT INTO morbac.activities (name, description) VALUES
|
|
||||||
('read', 'Read/view data'),
|
|
||||||
('write', 'Create/modify data'),
|
|
||||||
('delete', 'Delete data'),
|
|
||||||
('approve', 'Approve actions');
|
|
||||||
|
|
||||||
INSERT INTO morbac.views (name, description) VALUES
|
|
||||||
('documents', 'Document resources'),
|
|
||||||
('reports', 'Report resources'),
|
|
||||||
('sensitive_data', 'Sensitive/confidential data'),
|
|
||||||
('temp_documents', 'Temporary documents'),
|
|
||||||
('future_documents', 'Future documents'),
|
|
||||||
('active_documents', 'Active documents');
|
|
||||||
|
|
||||||
SELECT * FROM morbac.activities ORDER BY name;
|
|
||||||
SELECT * FROM morbac.views ORDER BY name;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 5: Create Additional Contexts ==='
|
|
||||||
|
|
||||||
-- Business hours context (simplified - always true for this test)
|
|
||||||
CREATE OR REPLACE FUNCTION morbac.context_business_hours()
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
LANGUAGE plpgsql
|
|
||||||
STABLE
|
|
||||||
AS $$
|
|
||||||
BEGIN
|
|
||||||
-- In production, check current time against business hours
|
|
||||||
-- For test, return TRUE
|
|
||||||
RETURN TRUE;
|
|
||||||
END;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
INSERT INTO morbac.contexts (name, description, evaluator) VALUES
|
|
||||||
('business_hours', 'During business hours', 'morbac.context_business_hours'::regproc);
|
|
||||||
|
|
||||||
-- Weekend context (always false for test)
|
|
||||||
CREATE OR REPLACE FUNCTION morbac.context_weekend()
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
LANGUAGE plpgsql
|
|
||||||
STABLE
|
|
||||||
AS $$
|
|
||||||
BEGIN
|
|
||||||
-- In production, check if today is weekend
|
|
||||||
-- For test, return FALSE
|
|
||||||
RETURN FALSE;
|
|
||||||
END;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
INSERT INTO morbac.contexts (name, description, evaluator) VALUES
|
|
||||||
('weekend', 'During weekend', 'morbac.context_weekend'::regproc);
|
|
||||||
|
|
||||||
SELECT name, description FROM morbac.contexts ORDER BY name;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 6: Define Policy Using DSL ==='
|
|
||||||
|
|
||||||
-- Acme Corp policies
|
|
||||||
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality, context_name) VALUES
|
|
||||||
-- Admins can do everything on documents
|
|
||||||
('Acme Corp', 'admin', 'read', 'documents', 'permission', 'always'),
|
|
||||||
('Acme Corp', 'admin', 'write', 'documents', 'permission', 'always'),
|
|
||||||
('Acme Corp', 'admin', 'delete', 'documents', 'permission', 'always'),
|
|
||||||
|
|
||||||
-- Employees can read and write documents
|
|
||||||
('Acme Corp', 'employee', 'read', 'documents', 'permission', 'always'),
|
|
||||||
('Acme Corp', 'employee', 'write', 'documents', 'permission', 'business_hours'),
|
|
||||||
|
|
||||||
-- Contractors can only read documents
|
|
||||||
('Acme Corp', 'contractor', 'read', 'documents', 'permission', 'always'),
|
|
||||||
|
|
||||||
-- PROHIBITION: Contractors cannot access sensitive data
|
|
||||||
('Acme Corp', 'contractor', 'read', 'sensitive_data', 'prohibition', 'always'),
|
|
||||||
('Acme Corp', 'contractor', 'write', 'sensitive_data', 'prohibition', 'always'),
|
|
||||||
|
|
||||||
-- OBLIGATION: Employees must review reports weekly
|
|
||||||
('Acme Corp', 'employee', 'read', 'reports', 'obligation', 'always'),
|
|
||||||
|
|
||||||
-- Admins can access reports
|
|
||||||
('Acme Corp', 'admin', 'read', 'reports', 'permission', 'always'),
|
|
||||||
('Acme Corp', 'admin', 'approve', 'reports', 'permission', 'always');
|
|
||||||
|
|
||||||
-- Beta Inc policies
|
|
||||||
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality, context_name) VALUES
|
|
||||||
-- Managers have full access
|
|
||||||
('Beta Inc', 'manager', 'read', 'documents', 'permission', 'always'),
|
|
||||||
('Beta Inc', 'manager', 'write', 'documents', 'permission', 'always'),
|
|
||||||
('Beta Inc', 'manager', 'read', 'reports', 'permission', 'always'),
|
|
||||||
|
|
||||||
-- Staff can read documents
|
|
||||||
('Beta Inc', 'staff', 'read', 'documents', 'permission', 'always'),
|
|
||||||
|
|
||||||
-- PROHIBITION: No document modifications on weekends (example)
|
|
||||||
('Beta Inc', 'staff', 'write', 'documents', 'prohibition', 'weekend'),
|
|
||||||
|
|
||||||
-- RECOMMENDATION: Staff should review reports
|
|
||||||
('Beta Inc', 'staff', 'read', 'reports', 'recommendation', 'always');
|
|
||||||
|
|
||||||
SELECT org_name, role_name, activity, view, modality, context_name
|
|
||||||
FROM morbac.policy
|
|
||||||
ORDER BY org_name, role_name, modality;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 7: Compile Policy ==='
|
|
||||||
|
|
||||||
SELECT * FROM morbac.compile_policy();
|
|
||||||
|
|
||||||
-- Verify rules were created
|
|
||||||
\echo ''
|
|
||||||
\echo 'Rules created by policy compiler:'
|
|
||||||
SELECT
|
|
||||||
o.name as org,
|
|
||||||
r.name as role,
|
|
||||||
ru.activity,
|
|
||||||
ru.view,
|
|
||||||
ru.modality,
|
|
||||||
c.name as context
|
|
||||||
FROM morbac.rules ru
|
|
||||||
JOIN morbac.orgs o ON ru.org_id = o.id
|
|
||||||
JOIN morbac.roles r ON ru.role_id = r.id
|
|
||||||
JOIN morbac.contexts c ON ru.context_id = c.id
|
|
||||||
ORDER BY o.name, r.name, ru.modality, ru.activity;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 8: Authorization Decisions ==='
|
|
||||||
|
|
||||||
-- Test Alice (admin at Acme)
|
|
||||||
\echo 'Alice (admin at Acme Corp):'
|
|
||||||
SELECT
|
|
||||||
'read documents' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'read',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'delete documents' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'delete',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
-- Test Bob (employee at Acme)
|
|
||||||
\echo ''
|
|
||||||
\echo 'Bob (employee at Acme Corp):'
|
|
||||||
SELECT
|
|
||||||
'read documents' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'read',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'delete documents' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'delete',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
-- Test Charlie (contractor at Acme)
|
|
||||||
\echo ''
|
|
||||||
\echo 'Charlie (contractor at Acme Corp):'
|
|
||||||
SELECT
|
|
||||||
'read documents' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'read',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
SELECT
|
|
||||||
'write documents' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'write',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
-- Test PROHIBITION precedence
|
|
||||||
\echo ''
|
|
||||||
\echo 'Testing PROHIBITION PRECEDENCE:'
|
|
||||||
\echo 'Charlie (contractor) trying to read sensitive_data (should be DENIED by prohibition):'
|
|
||||||
SELECT
|
|
||||||
'read sensitive_data' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'read',
|
|
||||||
'sensitive_data'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
-- Test Charlie at Beta Inc (multi-org user)
|
|
||||||
\echo ''
|
|
||||||
\echo 'Charlie as staff at Beta Inc:'
|
|
||||||
SELECT
|
|
||||||
'read documents' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
|
||||||
'22222222-2222-2222-2222-222222222222'::uuid,
|
|
||||||
'read',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
-- Test Diana (manager at Beta)
|
|
||||||
\echo ''
|
|
||||||
\echo 'Diana (manager at Beta Inc):'
|
|
||||||
SELECT
|
|
||||||
'write documents' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'dddddddd-0000-0000-0000-000000000004'::uuid,
|
|
||||||
'22222222-2222-2222-2222-222222222222'::uuid,
|
|
||||||
'write',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 8b: Role Hierarchy Inheritance ==='
|
|
||||||
|
|
||||||
-- Assign user Eve only viewer role
|
|
||||||
\echo 'Creating user Eve with only viewer role at Acme:'
|
|
||||||
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
|
||||||
('eeeeeeee-0000-0000-0000-000000000005', 'ffffffff-ffff-ffff-ffff-ffffffffffff', '11111111-1111-1111-1111-111111111111');
|
|
||||||
|
|
||||||
-- Add permission for viewer role
|
|
||||||
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality) VALUES
|
|
||||||
('Acme Corp', 'viewer', 'read', 'documents', 'permission');
|
|
||||||
|
|
||||||
SELECT * FROM morbac.compile_policy();
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo 'Eve (has viewer role, which employee inherits from):'
|
|
||||||
SELECT
|
|
||||||
'read documents' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'eeeeeeee-0000-0000-0000-000000000005'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'read',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo 'Bob (employee) should inherit permissions from viewer role:'
|
|
||||||
\echo 'Effective roles for Bob (should include employee and viewer via hierarchy):'
|
|
||||||
SELECT r.name, er.depth
|
|
||||||
FROM morbac.get_effective_roles(
|
|
||||||
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid
|
|
||||||
) er
|
|
||||||
JOIN morbac.roles r ON er.role_id = r.id
|
|
||||||
ORDER BY er.depth;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo 'Alice (admin) should inherit from both employee and viewer:'
|
|
||||||
SELECT r.name, er.depth
|
|
||||||
FROM morbac.get_effective_roles(
|
|
||||||
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid
|
|
||||||
) er
|
|
||||||
JOIN morbac.roles r ON er.role_id = r.id
|
|
||||||
ORDER BY er.depth;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 8c: Organization Hierarchy ==='
|
|
||||||
|
|
||||||
\echo 'Ancestors of Acme Subsidiary:'
|
|
||||||
SELECT o.name, oa.depth
|
|
||||||
FROM morbac.get_org_ancestors('33333333-3333-3333-3333-333333333333'::uuid) oa
|
|
||||||
JOIN morbac.orgs o ON oa.org_id = o.id
|
|
||||||
ORDER BY oa.depth;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo 'Descendants of Acme Corp (should include subsidiary):'
|
|
||||||
SELECT o.name, od.depth
|
|
||||||
FROM morbac.get_org_descendants('11111111-1111-1111-1111-111111111111'::uuid) od
|
|
||||||
JOIN morbac.orgs o ON od.org_id = o.id
|
|
||||||
ORDER BY od.depth;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 9: Obligations and Recommendations ==='
|
|
||||||
|
|
||||||
\echo 'Obligations for Bob (employee at Acme):'
|
|
||||||
SELECT * FROM morbac.pending_obligations(
|
|
||||||
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid
|
|
||||||
);
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo 'Recommendations for Charlie (staff at Beta):'
|
|
||||||
SELECT * FROM morbac.pending_recommendations(
|
|
||||||
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
|
||||||
'22222222-2222-2222-2222-222222222222'::uuid
|
|
||||||
);
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 10: Utility Functions ==='
|
|
||||||
|
|
||||||
\echo 'Roles for Charlie at Acme Corp:'
|
|
||||||
SELECT * FROM morbac.user_roles_in_org(
|
|
||||||
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid
|
|
||||||
);
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo 'Roles for Charlie at Beta Inc:'
|
|
||||||
SELECT * FROM morbac.user_roles_in_org(
|
|
||||||
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
|
||||||
'22222222-2222-2222-2222-222222222222'::uuid
|
|
||||||
);
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo 'Does Alice have admin role at Acme Corp?'
|
|
||||||
SELECT morbac.user_has_role(
|
|
||||||
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'admin'
|
|
||||||
) as has_admin_role;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 11: Context Evaluation ==='
|
|
||||||
|
|
||||||
\echo 'Evaluating always context:'
|
|
||||||
SELECT morbac.eval_context(
|
|
||||||
(SELECT id FROM morbac.contexts WHERE name = 'always')
|
|
||||||
) as result;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo 'Evaluating business_hours context:'
|
|
||||||
SELECT morbac.eval_context(
|
|
||||||
(SELECT id FROM morbac.contexts WHERE name = 'business_hours')
|
|
||||||
) as result;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo 'Evaluating weekend context:'
|
|
||||||
SELECT morbac.eval_context(
|
|
||||||
(SELECT id FROM morbac.contexts WHERE name = 'weekend')
|
|
||||||
) as result;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 12: Policy Re-compilation (Idempotency Test) ==='
|
|
||||||
|
|
||||||
\echo 'Compiling policy again (should show 0 new rules, idempotent):'
|
|
||||||
SELECT * FROM morbac.compile_policy();
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 13: Adding New Policy Entry ==='
|
|
||||||
|
|
||||||
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality, context_name) VALUES
|
|
||||||
('Acme Corp', 'admin', 'approve', 'documents', 'permission', 'always');
|
|
||||||
|
|
||||||
\echo 'Compiling new policy entry:'
|
|
||||||
SELECT * FROM morbac.compile_policy();
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 14: Delegation ==='
|
|
||||||
|
|
||||||
-- Alice delegates admin role to user Frank temporarily
|
|
||||||
INSERT INTO morbac.delegations (delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until) VALUES
|
|
||||||
('aaaaaaaa-0000-0000-0000-000000000001', 'ffffffff-0000-0000-0000-000000000006', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', now(), now() + interval '1 day');
|
|
||||||
|
|
||||||
\echo 'Frank (via delegation from Alice) should have admin permissions:'
|
|
||||||
SELECT
|
|
||||||
'delete documents (delegated)' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'ffffffff-0000-0000-0000-000000000006'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'delete',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
\echo 'Frank effective roles (should include delegated admin):'
|
|
||||||
SELECT r.name, cr.source
|
|
||||||
FROM morbac.get_comprehensive_roles('ffffffff-0000-0000-0000-000000000006'::uuid, '11111111-1111-1111-1111-111111111111'::uuid) cr
|
|
||||||
JOIN morbac.roles r ON cr.role_id = r.id;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 15: Separation of Duty ==='
|
|
||||||
|
|
||||||
-- Define SoD conflict: auditor and accountant roles are mutually exclusive
|
|
||||||
INSERT INTO morbac.roles (id, org_id, name, description) VALUES
|
|
||||||
('11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111', 'auditor', 'Auditor role'),
|
|
||||||
('22222222-2222-2222-2222-222222222222', '11111111-1111-1111-1111-111111111111', 'accountant', 'Accountant role');
|
|
||||||
|
|
||||||
INSERT INTO morbac.sod_conflicts (role_a_id, role_b_id, org_id, description) VALUES
|
|
||||||
('11111111-1111-1111-1111-111111111111', '22222222-2222-2222-2222-222222222222', '11111111-1111-1111-1111-111111111111', 'Auditors and accountants are mutually exclusive');
|
|
||||||
|
|
||||||
-- Assign user to auditor role
|
|
||||||
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
|
||||||
('99999999-0000-0000-0000-000000000009', '11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111');
|
|
||||||
|
|
||||||
\echo 'Check SoD violation if we try to assign accountant role to auditor:'
|
|
||||||
SELECT morbac.check_sod_violation(
|
|
||||||
'99999999-0000-0000-0000-000000000009'::uuid,
|
|
||||||
'22222222-2222-2222-2222-222222222222'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid
|
|
||||||
) as would_violate_sod;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 16: Activity and View Hierarchies ==='
|
|
||||||
|
|
||||||
-- Define activity hierarchy: write implies read
|
|
||||||
INSERT INTO morbac.activity_hierarchy (senior_activity, junior_activity) VALUES
|
|
||||||
('write', 'read');
|
|
||||||
|
|
||||||
-- Define view hierarchy: sensitive_data is a type of documents
|
|
||||||
INSERT INTO morbac.view_hierarchy (senior_view, junior_view) VALUES
|
|
||||||
('sensitive_data', 'documents');
|
|
||||||
|
|
||||||
\echo 'Activities implied by write:'
|
|
||||||
SELECT * FROM morbac.get_effective_activities('write');
|
|
||||||
|
|
||||||
\echo 'Views for sensitive_data (should include documents):'
|
|
||||||
SELECT * FROM morbac.get_effective_views('sensitive_data');
|
|
||||||
|
|
||||||
\echo 'Bob has permission for read+documents, so write+documents should work (activity hierarchy):'
|
|
||||||
SELECT
|
|
||||||
'write documents (via activity hierarchy)' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'write',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 17: Negative Role Assignment ==='
|
|
||||||
|
|
||||||
-- Explicitly prohibit contractor from ever being admin
|
|
||||||
INSERT INTO morbac.negative_role_assignments (user_id, role_id, org_id, reason) VALUES
|
|
||||||
('cccccccc-0000-0000-0000-000000000003', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', 'Contractors cannot be administrators');
|
|
||||||
|
|
||||||
\echo 'Charlie cannot have admin role even if explicitly assigned (negative assignment takes precedence).'
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 18: Cardinality Constraints ==='
|
|
||||||
|
|
||||||
-- Set max 2 users for admin role
|
|
||||||
INSERT INTO morbac.role_cardinality (role_id, min_users, max_users, description) VALUES
|
|
||||||
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 1, 2, 'Admin role limited to 2 users minimum 1');
|
|
||||||
|
|
||||||
\echo 'Check cardinality when adding user (1 admin exists, max is 2, should be OK):'
|
|
||||||
SELECT morbac.check_cardinality_violation('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid, TRUE) as violation;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 19: Administration Rules ==='
|
|
||||||
|
|
||||||
-- Only admin role can create rules
|
|
||||||
INSERT INTO morbac.admin_rules (org_id, role_id, admin_activity, admin_target, context_id, modality) VALUES
|
|
||||||
('11111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'create_rule', 'rules', (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission');
|
|
||||||
|
|
||||||
\echo 'Alice (admin) can create rules:'
|
|
||||||
SELECT morbac.is_admin_allowed(
|
|
||||||
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'create_rule',
|
|
||||||
'rules'
|
|
||||||
) as can_create_rules;
|
|
||||||
|
|
||||||
\echo 'Bob (employee) cannot create rules:'
|
|
||||||
SELECT morbac.is_admin_allowed(
|
|
||||||
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'create_rule',
|
|
||||||
'rules'
|
|
||||||
) as can_create_rules;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 20: Cross-Organization Rules ==='
|
|
||||||
|
|
||||||
-- Allow Beta managers to read Acme documents (inter-org collaboration)
|
|
||||||
INSERT INTO morbac.cross_org_rules (source_org_id, target_org_id, role_id, activity, view, context_id, modality) VALUES
|
|
||||||
('22222222-2222-2222-2222-222222222222', '11111111-1111-1111-1111-111111111111', 'dddddddd-dddd-dddd-dddd-dddddddddddd', 'read', 'documents', (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission');
|
|
||||||
|
|
||||||
\echo 'Diana (manager at Beta) can read Acme documents via cross-org rule:'
|
|
||||||
SELECT
|
|
||||||
'read Acme documents from Beta' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'dddddddd-0000-0000-0000-000000000004'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'read',
|
|
||||||
'documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 21: Temporal Constraints on Rules ==='
|
|
||||||
|
|
||||||
-- Create a time-limited rule (expires in the past)
|
|
||||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_until)
|
|
||||||
VALUES (
|
|
||||||
'11111111-1111-1111-1111-111111111111',
|
|
||||||
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', -- employee role
|
|
||||||
'write',
|
|
||||||
'temp_documents',
|
|
||||||
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
|
||||||
'permission',
|
|
||||||
'2020-01-01 00:00:00+00' -- Expired rule
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create a future rule (not yet active)
|
|
||||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from)
|
|
||||||
VALUES (
|
|
||||||
'11111111-1111-1111-1111-111111111111',
|
|
||||||
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
||||||
'write',
|
|
||||||
'future_documents',
|
|
||||||
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
|
||||||
'permission',
|
|
||||||
'2030-01-01 00:00:00+00' -- Future rule
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create an active time-bounded rule
|
|
||||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from, valid_until)
|
|
||||||
VALUES (
|
|
||||||
'11111111-1111-1111-1111-111111111111',
|
|
||||||
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
|
||||||
'write',
|
|
||||||
'active_documents',
|
|
||||||
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
|
||||||
'permission',
|
|
||||||
'2020-01-01 00:00:00+00', -- Started in the past
|
|
||||||
'2030-12-31 23:59:59+00' -- Ends in the future
|
|
||||||
);
|
|
||||||
|
|
||||||
\echo 'Alice (employee) access to temporal-constrained documents:'
|
|
||||||
SELECT
|
|
||||||
'write to temp_documents (expired rule)' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'write',
|
|
||||||
'temp_documents'
|
|
||||||
) as allowed
|
|
||||||
UNION ALL
|
|
||||||
SELECT
|
|
||||||
'write to future_documents (not yet active)' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'write',
|
|
||||||
'future_documents'
|
|
||||||
) as allowed
|
|
||||||
UNION ALL
|
|
||||||
SELECT
|
|
||||||
'write to active_documents (currently valid)' as action,
|
|
||||||
morbac.is_allowed(
|
|
||||||
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
|
||||||
'11111111-1111-1111-1111-111111111111'::uuid,
|
|
||||||
'write',
|
|
||||||
'active_documents'
|
|
||||||
) as allowed;
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== Test 22: Audit Logging ==='
|
|
||||||
|
|
||||||
-- Enable audit on user_roles table
|
|
||||||
SELECT morbac.enable_audit('user_roles');
|
|
||||||
|
|
||||||
\echo 'Audit trigger created for user_roles table'
|
|
||||||
|
|
||||||
-- Perform some auditable operations
|
|
||||||
INSERT INTO morbac.user_roles (user_id, role_id, org_id)
|
|
||||||
VALUES (
|
|
||||||
'ffffffff-0000-0000-0000-000000000001'::uuid,
|
|
||||||
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', -- employee role
|
|
||||||
'11111111-1111-1111-1111-111111111111'
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Update an existing user_role
|
|
||||||
UPDATE morbac.user_roles
|
|
||||||
SET active = false
|
|
||||||
WHERE user_id = 'aaaaaaaa-0000-0000-0000-000000000001'::uuid
|
|
||||||
AND role_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
||||||
|
|
||||||
-- Delete a user role
|
|
||||||
DELETE FROM morbac.user_roles
|
|
||||||
WHERE user_id = 'ffffffff-0000-0000-0000-000000000001'::uuid
|
|
||||||
AND role_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
|
||||||
|
|
||||||
\echo 'Audit log entries for user_roles operations:'
|
|
||||||
SELECT
|
|
||||||
operation,
|
|
||||||
record_id,
|
|
||||||
CASE
|
|
||||||
WHEN old_data IS NOT NULL THEN old_data->>'active'
|
|
||||||
ELSE 'N/A'
|
|
||||||
END as old_active,
|
|
||||||
CASE
|
|
||||||
WHEN new_data IS NOT NULL THEN new_data->>'active'
|
|
||||||
ELSE 'N/A'
|
|
||||||
END as new_active,
|
|
||||||
session_user,
|
|
||||||
timestamp
|
|
||||||
FROM morbac.audit_log
|
|
||||||
WHERE table_name = 'user_roles'
|
|
||||||
ORDER BY timestamp;
|
|
||||||
|
|
||||||
\echo 'Disable audit on user_roles:'
|
|
||||||
SELECT morbac.disable_audit('user_roles');
|
|
||||||
|
|
||||||
\echo ''
|
|
||||||
\echo '=== ALL TESTS COMPLETED SUCCESSFULLY ==='
|
|
||||||
\echo ''
|
|
||||||
\echo 'Summary:'
|
|
||||||
\echo '- Extension installed and schema created'
|
|
||||||
\echo '- Organizations with hierarchy'
|
|
||||||
\echo '- Roles with hierarchy'
|
|
||||||
\echo '- Delegation support'
|
|
||||||
\echo '- Separation of Duty constraints'
|
|
||||||
\echo '- Activity and View hierarchies'
|
|
||||||
\echo '- Negative role assignments'
|
|
||||||
\echo '- Cardinality constraints'
|
|
||||||
\echo '- Administration rules'
|
|
||||||
\echo '- Cross-organizational rules'
|
|
||||||
\echo '- Temporal constraints on rules (valid_from, valid_until)'
|
|
||||||
\echo '- Audit logging for security-critical operations'
|
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
-- =============================================================================
|
||||||
|
-- Test Setup - Creates test data for all tests
|
||||||
|
-- =============================================================================
|
||||||
|
-- This file creates organizations, roles, users, activities, views, and contexts
|
||||||
|
-- that are used across multiple test files.
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
-- Clean up any previous test
|
||||||
|
DROP EXTENSION IF EXISTS morbac_pg CASCADE;
|
||||||
|
DROP SCHEMA IF EXISTS morbac CASCADE;
|
||||||
|
|
||||||
|
-- Install the extension
|
||||||
|
CREATE EXTENSION morbac_pg;
|
||||||
|
|
||||||
|
-- Verify schema and tables exist
|
||||||
|
\echo '=== Schema and Tables Created ==='
|
||||||
|
SELECT schemaname, tablename
|
||||||
|
FROM pg_tables
|
||||||
|
WHERE schemaname = 'morbac'
|
||||||
|
ORDER BY tablename;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Setup: Organizations ==='
|
||||||
|
|
||||||
|
-- Create two organizations
|
||||||
|
INSERT INTO morbac.orgs (id, name) VALUES
|
||||||
|
('11111111-1111-1111-1111-111111111111', 'Acme Corp'),
|
||||||
|
('22222222-2222-2222-2222-222222222222', 'Beta Inc');
|
||||||
|
|
||||||
|
-- Create hierarchical organization (Acme Subsidiary under Acme Corp)
|
||||||
|
INSERT INTO morbac.orgs (id, name, parent_id) VALUES
|
||||||
|
('33333333-3333-3333-3333-333333333333', 'Acme Subsidiary', '11111111-1111-1111-1111-111111111111');
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
o.name,
|
||||||
|
COALESCE(p.name, 'None') as parent_organization
|
||||||
|
FROM morbac.orgs o
|
||||||
|
LEFT JOIN morbac.orgs p ON o.parent_id = p.id
|
||||||
|
ORDER BY o.parent_id NULLS FIRST, o.name;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Setup: Roles ==='
|
||||||
|
|
||||||
|
-- Create roles in Acme Corp
|
||||||
|
INSERT INTO morbac.roles (id, org_id, name, description) VALUES
|
||||||
|
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', 'admin', 'Administrator role'),
|
||||||
|
('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '11111111-1111-1111-1111-111111111111', 'employee', 'Regular employee'),
|
||||||
|
('cccccccc-cccc-cccc-cccc-cccccccccccc', '11111111-1111-1111-1111-111111111111', 'contractor', 'External contractor'),
|
||||||
|
('ffffffff-ffff-ffff-ffff-ffffffffffff', '11111111-1111-1111-1111-111111111111', 'viewer', 'Read-only viewer');
|
||||||
|
|
||||||
|
-- Create roles in Beta Inc
|
||||||
|
INSERT INTO morbac.roles (id, org_id, name, description) VALUES
|
||||||
|
('dddddddd-dddd-dddd-dddd-dddddddddddd', '22222222-2222-2222-2222-222222222222', 'manager', 'Manager role'),
|
||||||
|
('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', '22222222-2222-2222-2222-222222222222', 'staff', 'Staff member');
|
||||||
|
|
||||||
|
SELECT r.name as role, o.name as organization
|
||||||
|
FROM morbac.roles r
|
||||||
|
JOIN morbac.orgs o ON r.org_id = o.id
|
||||||
|
ORDER BY o.name, r.name;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Setup: User Role Assignments ==='
|
||||||
|
|
||||||
|
-- User Alice (admin at Acme)
|
||||||
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
||||||
|
('aaaaaaaa-0000-0000-0000-000000000001', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111');
|
||||||
|
|
||||||
|
-- User Bob (employee at Acme)
|
||||||
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
||||||
|
('bbbbbbbb-0000-0000-0000-000000000002', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '11111111-1111-1111-1111-111111111111');
|
||||||
|
|
||||||
|
-- User Charlie (contractor at Acme AND staff at Beta - multi-org user)
|
||||||
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
||||||
|
('cccccccc-0000-0000-0000-000000000003', 'cccccccc-cccc-cccc-cccc-cccccccccccc', '11111111-1111-1111-1111-111111111111'),
|
||||||
|
('cccccccc-0000-0000-0000-000000000003', 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', '22222222-2222-2222-2222-222222222222');
|
||||||
|
|
||||||
|
-- User Diana (manager at Beta)
|
||||||
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
||||||
|
('dddddddd-0000-0000-0000-000000000004', 'dddddddd-dddd-dddd-dddd-dddddddddddd', '22222222-2222-2222-2222-222222222222');
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
ur.user_id,
|
||||||
|
r.name as role,
|
||||||
|
o.name as organization
|
||||||
|
FROM morbac.user_roles ur
|
||||||
|
JOIN morbac.roles r ON ur.role_id = r.id
|
||||||
|
JOIN morbac.orgs o ON ur.org_id = o.id
|
||||||
|
ORDER BY ur.user_id;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Setup: Activities and Views ==='
|
||||||
|
|
||||||
|
INSERT INTO morbac.activities (name, description) VALUES
|
||||||
|
('read', 'Read/view data'),
|
||||||
|
('write', 'Create/modify data'),
|
||||||
|
('delete', 'Delete data'),
|
||||||
|
('approve', 'Approve actions');
|
||||||
|
|
||||||
|
INSERT INTO morbac.views (name, description) VALUES
|
||||||
|
('documents', 'Document resources'),
|
||||||
|
('reports', 'Report resources'),
|
||||||
|
('sensitive_data', 'Sensitive/confidential data'),
|
||||||
|
('temp_documents', 'Temporary documents'),
|
||||||
|
('future_documents', 'Future documents'),
|
||||||
|
('active_documents', 'Active documents');
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Setup: Additional Contexts ==='
|
||||||
|
|
||||||
|
-- Business hours context (simplified - always true for this test)
|
||||||
|
CREATE OR REPLACE FUNCTION morbac.context_business_hours()
|
||||||
|
RETURNS BOOLEAN
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
STABLE
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN TRUE;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
INSERT INTO morbac.contexts (name, description, evaluator) VALUES
|
||||||
|
('business_hours', 'During business hours', 'morbac.context_business_hours'::regproc);
|
||||||
|
|
||||||
|
-- Weekend context (always false for test)
|
||||||
|
CREATE OR REPLACE FUNCTION morbac.context_weekend()
|
||||||
|
RETURNS BOOLEAN
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
STABLE
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN FALSE;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
INSERT INTO morbac.contexts (name, description, evaluator) VALUES
|
||||||
|
('weekend', 'During weekend', 'morbac.context_weekend'::regproc);
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Setup: Policy DSL ==='
|
||||||
|
|
||||||
|
-- Acme Corp policies
|
||||||
|
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality, context_name) VALUES
|
||||||
|
-- Admins can do everything on documents
|
||||||
|
('Acme Corp', 'admin', 'read', 'documents', 'permission', 'always'),
|
||||||
|
('Acme Corp', 'admin', 'write', 'documents', 'permission', 'always'),
|
||||||
|
('Acme Corp', 'admin', 'delete', 'documents', 'permission', 'always'),
|
||||||
|
|
||||||
|
-- Employees can read and write documents
|
||||||
|
('Acme Corp', 'employee', 'read', 'documents', 'permission', 'always'),
|
||||||
|
('Acme Corp', 'employee', 'write', 'documents', 'permission', 'business_hours'),
|
||||||
|
|
||||||
|
-- Contractors can only read documents
|
||||||
|
('Acme Corp', 'contractor', 'read', 'documents', 'permission', 'always'),
|
||||||
|
|
||||||
|
-- PROHIBITION: Contractors cannot access sensitive data
|
||||||
|
('Acme Corp', 'contractor', 'read', 'sensitive_data', 'prohibition', 'always'),
|
||||||
|
('Acme Corp', 'contractor', 'write', 'sensitive_data', 'prohibition', 'always'),
|
||||||
|
|
||||||
|
-- OBLIGATION: Employees must review reports weekly
|
||||||
|
('Acme Corp', 'employee', 'read', 'reports', 'obligation', 'always'),
|
||||||
|
|
||||||
|
-- Admins can access reports
|
||||||
|
('Acme Corp', 'admin', 'read', 'reports', 'permission', 'always'),
|
||||||
|
('Acme Corp', 'admin', 'approve', 'reports', 'permission', 'always');
|
||||||
|
|
||||||
|
-- Beta Inc policies
|
||||||
|
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality, context_name) VALUES
|
||||||
|
-- Managers have full access
|
||||||
|
('Beta Inc', 'manager', 'read', 'documents', 'permission', 'always'),
|
||||||
|
('Beta Inc', 'manager', 'write', 'documents', 'permission', 'always'),
|
||||||
|
('Beta Inc', 'manager', 'read', 'reports', 'permission', 'always'),
|
||||||
|
|
||||||
|
-- Staff can read documents
|
||||||
|
('Beta Inc', 'staff', 'read', 'documents', 'permission', 'always'),
|
||||||
|
|
||||||
|
-- PROHIBITION: No document modifications on weekends (example)
|
||||||
|
('Beta Inc', 'staff', 'write', 'documents', 'prohibition', 'weekend'),
|
||||||
|
|
||||||
|
-- RECOMMENDATION: Staff should review reports
|
||||||
|
('Beta Inc', 'staff', 'read', 'reports', 'recommendation', 'always');
|
||||||
|
|
||||||
|
-- Compile policy
|
||||||
|
SELECT * FROM morbac.compile_policy();
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Setup Complete ==='
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- MORBAC PostgreSQL Extension - Authorization Decision Tests
|
||||||
|
-- ============================================================================
|
||||||
|
-- This test file covers authorization decision tests including:
|
||||||
|
-- - Basic authorization decisions (is_allowed function)
|
||||||
|
-- - Role hierarchy inheritance
|
||||||
|
-- - Organization hierarchy
|
||||||
|
-- - Prohibition precedence
|
||||||
|
-- - Multi-organization user access
|
||||||
|
--
|
||||||
|
-- Prerequisites: Assumes 00_setup.sql has been run to set up base data
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Authorization Decisions ==='
|
||||||
|
|
||||||
|
-- Test Alice (admin at Acme)
|
||||||
|
\echo 'Alice (admin at Acme Corp):'
|
||||||
|
SELECT
|
||||||
|
'read documents' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'read',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'delete documents' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'delete',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
-- Test Bob (employee at Acme)
|
||||||
|
\echo ''
|
||||||
|
\echo 'Bob (employee at Acme Corp):'
|
||||||
|
SELECT
|
||||||
|
'read documents' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'read',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'delete documents' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'delete',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
-- Test Charlie (contractor at Acme)
|
||||||
|
\echo ''
|
||||||
|
\echo 'Charlie (contractor at Acme Corp):'
|
||||||
|
SELECT
|
||||||
|
'read documents' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'read',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'write documents' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'write',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
-- Test PROHIBITION precedence
|
||||||
|
\echo ''
|
||||||
|
\echo 'Testing PROHIBITION PRECEDENCE:'
|
||||||
|
\echo 'Charlie (contractor) trying to read sensitive_data (should be DENIED by prohibition):'
|
||||||
|
SELECT
|
||||||
|
'read sensitive_data' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'read',
|
||||||
|
'sensitive_data'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
-- Test Charlie at Beta Inc (multi-org user)
|
||||||
|
\echo ''
|
||||||
|
\echo 'Charlie as staff at Beta Inc:'
|
||||||
|
SELECT
|
||||||
|
'read documents' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
||||||
|
'22222222-2222-2222-2222-222222222222'::uuid,
|
||||||
|
'read',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
-- Test Diana (manager at Beta)
|
||||||
|
\echo ''
|
||||||
|
\echo 'Diana (manager at Beta Inc):'
|
||||||
|
SELECT
|
||||||
|
'write documents' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'dddddddd-0000-0000-0000-000000000004'::uuid,
|
||||||
|
'22222222-2222-2222-2222-222222222222'::uuid,
|
||||||
|
'write',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Role Hierarchy Inheritance ==='
|
||||||
|
|
||||||
|
-- Assign user Eve only viewer role
|
||||||
|
\echo 'Creating user Eve with only viewer role at Acme:'
|
||||||
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
||||||
|
('eeeeeeee-0000-0000-0000-000000000005', 'ffffffff-ffff-ffff-ffff-ffffffffffff', '11111111-1111-1111-1111-111111111111');
|
||||||
|
|
||||||
|
-- Add permission for viewer role
|
||||||
|
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality) VALUES
|
||||||
|
('Acme Corp', 'viewer', 'read', 'documents', 'permission');
|
||||||
|
|
||||||
|
SELECT * FROM morbac.compile_policy();
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo 'Eve (has viewer role, which employee inherits from):'
|
||||||
|
SELECT
|
||||||
|
'read documents' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'eeeeeeee-0000-0000-0000-000000000005'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'read',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo 'Bob (employee) should inherit permissions from viewer role:'
|
||||||
|
\echo 'Effective roles for Bob (should include employee and viewer via hierarchy):'
|
||||||
|
SELECT r.name, er.depth
|
||||||
|
FROM morbac.get_effective_roles(
|
||||||
|
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid
|
||||||
|
) er
|
||||||
|
JOIN morbac.roles r ON er.role_id = r.id
|
||||||
|
ORDER BY er.depth;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo 'Alice (admin) should inherit from both employee and viewer:'
|
||||||
|
SELECT r.name, er.depth
|
||||||
|
FROM morbac.get_effective_roles(
|
||||||
|
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid
|
||||||
|
) er
|
||||||
|
JOIN morbac.roles r ON er.role_id = r.id
|
||||||
|
ORDER BY er.depth;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Organization Hierarchy ==='
|
||||||
|
|
||||||
|
\echo 'Ancestors of Acme Subsidiary:'
|
||||||
|
SELECT o.name, oa.depth
|
||||||
|
FROM morbac.get_org_ancestors('33333333-3333-3333-3333-333333333333'::uuid) oa
|
||||||
|
JOIN morbac.orgs o ON oa.org_id = o.id
|
||||||
|
ORDER BY oa.depth;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo 'Descendants of Acme Corp (should include subsidiary):'
|
||||||
|
SELECT o.name, od.depth
|
||||||
|
FROM morbac.get_org_descendants('11111111-1111-1111-1111-111111111111'::uuid) od
|
||||||
|
JOIN morbac.orgs o ON od.org_id = o.id
|
||||||
|
ORDER BY od.depth;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Authorization Decision Tests Completed ==='
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- MORBAC PostgreSQL Extension - Activity and View Hierarchies Tests
|
||||||
|
-- ============================================================================
|
||||||
|
-- This test file covers activity and view hierarchy tests including:
|
||||||
|
-- - Activity hierarchy (e.g., write implies read)
|
||||||
|
-- - View hierarchy (e.g., sensitive_data is a type of documents)
|
||||||
|
-- - Effective activities and views
|
||||||
|
-- - Permission inheritance through hierarchies
|
||||||
|
--
|
||||||
|
-- Prerequisites: Assumes 00_setup.sql has been run to set up base data
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Activity and View Hierarchies ==='
|
||||||
|
|
||||||
|
-- Define activity hierarchy: write implies read
|
||||||
|
INSERT INTO morbac.activity_hierarchy (senior_activity, junior_activity) VALUES
|
||||||
|
('write', 'read');
|
||||||
|
|
||||||
|
-- Define view hierarchy: sensitive_data is a type of documents
|
||||||
|
INSERT INTO morbac.view_hierarchy (senior_view, junior_view) VALUES
|
||||||
|
('sensitive_data', 'documents');
|
||||||
|
|
||||||
|
\echo 'Activities implied by write:'
|
||||||
|
SELECT * FROM morbac.get_effective_activities('write');
|
||||||
|
|
||||||
|
\echo 'Views for sensitive_data (should include documents):'
|
||||||
|
SELECT * FROM morbac.get_effective_views('sensitive_data');
|
||||||
|
|
||||||
|
\echo 'Bob has permission for read+documents, so write+documents should work (activity hierarchy):'
|
||||||
|
SELECT
|
||||||
|
'write documents (via activity hierarchy)' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'write',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Activity and View Hierarchies Tests Completed ==='
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- MORBAC PostgreSQL Extension - Delegation Tests
|
||||||
|
-- ============================================================================
|
||||||
|
-- This test file covers delegation functionality including:
|
||||||
|
-- - Temporary role delegation
|
||||||
|
-- - Delegated permission inheritance
|
||||||
|
-- - Comprehensive role reporting (direct + delegated)
|
||||||
|
-- - Time-limited delegation (valid_from/valid_until)
|
||||||
|
--
|
||||||
|
-- Prerequisites: Assumes 00_setup.sql has been run to set up base data
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Delegation ==='
|
||||||
|
|
||||||
|
-- Alice delegates admin role to user Frank temporarily
|
||||||
|
INSERT INTO morbac.delegations (delegator_id, delegatee_id, role_id, org_id, valid_from, valid_until) VALUES
|
||||||
|
('aaaaaaaa-0000-0000-0000-000000000001', 'ffffffff-0000-0000-0000-000000000006', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', now(), now() + interval '1 day');
|
||||||
|
|
||||||
|
\echo 'Frank (via delegation from Alice) should have admin permissions:'
|
||||||
|
SELECT
|
||||||
|
'delete documents (delegated)' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'ffffffff-0000-0000-0000-000000000006'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'delete',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
\echo 'Frank effective roles (should include delegated admin):'
|
||||||
|
SELECT r.name, cr.source
|
||||||
|
FROM morbac.get_comprehensive_roles('ffffffff-0000-0000-0000-000000000006'::uuid, '11111111-1111-1111-1111-111111111111'::uuid) cr
|
||||||
|
JOIN morbac.roles r ON cr.role_id = r.id;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Delegation Tests Completed ==='
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- MORBAC PostgreSQL Extension - Constraint Tests
|
||||||
|
-- ============================================================================
|
||||||
|
-- This test file covers various constraints including:
|
||||||
|
-- - Separation of Duty (SoD) conflicts
|
||||||
|
-- - Negative role assignments
|
||||||
|
-- - Role cardinality constraints (min/max users per role)
|
||||||
|
--
|
||||||
|
-- Prerequisites: Assumes 00_setup.sql has been run to set up base data
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Separation of Duty ==='
|
||||||
|
|
||||||
|
-- Define SoD conflict: auditor and accountant roles are mutually exclusive
|
||||||
|
INSERT INTO morbac.roles (id, org_id, name, description) VALUES
|
||||||
|
('11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111', 'auditor', 'Auditor role'),
|
||||||
|
('22222222-2222-2222-2222-222222222222', '11111111-1111-1111-1111-111111111111', 'accountant', 'Accountant role');
|
||||||
|
|
||||||
|
INSERT INTO morbac.sod_conflicts (role_a_id, role_b_id, org_id, description) VALUES
|
||||||
|
('11111111-1111-1111-1111-111111111111', '22222222-2222-2222-2222-222222222222', '11111111-1111-1111-1111-111111111111', 'Auditors and accountants are mutually exclusive');
|
||||||
|
|
||||||
|
-- Assign user to auditor role
|
||||||
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id) VALUES
|
||||||
|
('99999999-0000-0000-0000-000000000009', '11111111-1111-1111-1111-111111111111', '11111111-1111-1111-1111-111111111111');
|
||||||
|
|
||||||
|
\echo 'Check SoD violation if we try to assign accountant role to auditor:'
|
||||||
|
SELECT morbac.check_sod_violation(
|
||||||
|
'99999999-0000-0000-0000-000000000009'::uuid,
|
||||||
|
'22222222-2222-2222-2222-222222222222'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid
|
||||||
|
) as would_violate_sod;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Negative Role Assignment ==='
|
||||||
|
|
||||||
|
-- Explicitly prohibit contractor from ever being admin
|
||||||
|
INSERT INTO morbac.negative_role_assignments (user_id, role_id, org_id, reason) VALUES
|
||||||
|
('cccccccc-0000-0000-0000-000000000003', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '11111111-1111-1111-1111-111111111111', 'Contractors cannot be administrators');
|
||||||
|
|
||||||
|
\echo 'Charlie cannot have admin role even if explicitly assigned (negative assignment takes precedence).'
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Cardinality Constraints ==='
|
||||||
|
|
||||||
|
-- Set max 2 users for admin role
|
||||||
|
INSERT INTO morbac.role_cardinality (role_id, min_users, max_users, description) VALUES
|
||||||
|
('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 1, 2, 'Admin role limited to 2 users minimum 1');
|
||||||
|
|
||||||
|
\echo 'Check cardinality when adding user (1 admin exists, max is 2, should be OK):'
|
||||||
|
SELECT morbac.check_cardinality_violation('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid, TRUE) as violation;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Constraint Tests Completed ==='
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- MORBAC PostgreSQL Extension - Temporal Constraint Tests
|
||||||
|
-- ============================================================================
|
||||||
|
-- This test file covers temporal constraints including:
|
||||||
|
-- - Rules with valid_from timestamps (future activation)
|
||||||
|
-- - Rules with valid_until timestamps (expiration)
|
||||||
|
-- - Rules with both valid_from and valid_until (time windows)
|
||||||
|
-- - Authorization decisions respecting temporal bounds
|
||||||
|
--
|
||||||
|
-- Prerequisites: Assumes 00_setup.sql has been run to set up base data
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Temporal Constraints on Rules ==='
|
||||||
|
|
||||||
|
-- Create a time-limited rule (expires in the past)
|
||||||
|
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_until)
|
||||||
|
VALUES (
|
||||||
|
'11111111-1111-1111-1111-111111111111',
|
||||||
|
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', -- employee role
|
||||||
|
'write',
|
||||||
|
'temp_documents',
|
||||||
|
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
||||||
|
'permission',
|
||||||
|
'2020-01-01 00:00:00+00' -- Expired rule
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create a future rule (not yet active)
|
||||||
|
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from)
|
||||||
|
VALUES (
|
||||||
|
'11111111-1111-1111-1111-111111111111',
|
||||||
|
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||||
|
'write',
|
||||||
|
'future_documents',
|
||||||
|
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
||||||
|
'permission',
|
||||||
|
'2030-01-01 00:00:00+00' -- Future rule
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create an active time-bounded rule
|
||||||
|
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from, valid_until)
|
||||||
|
VALUES (
|
||||||
|
'11111111-1111-1111-1111-111111111111',
|
||||||
|
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||||
|
'write',
|
||||||
|
'active_documents',
|
||||||
|
(SELECT id FROM morbac.contexts WHERE name = 'always'),
|
||||||
|
'permission',
|
||||||
|
'2020-01-01 00:00:00+00', -- Started in the past
|
||||||
|
'2030-12-31 23:59:59+00' -- Ends in the future
|
||||||
|
);
|
||||||
|
|
||||||
|
\echo 'Alice (employee) access to temporal-constrained documents:'
|
||||||
|
SELECT
|
||||||
|
'write to temp_documents (expired rule)' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'write',
|
||||||
|
'temp_documents'
|
||||||
|
) as allowed
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
'write to future_documents (not yet active)' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'write',
|
||||||
|
'future_documents'
|
||||||
|
) as allowed
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
'write to active_documents (currently valid)' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'write',
|
||||||
|
'active_documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Temporal Constraint Tests Completed ==='
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- MORBAC PostgreSQL Extension - Cross-Organizational Rules Tests
|
||||||
|
-- ============================================================================
|
||||||
|
-- This test file covers cross-organizational authorization including:
|
||||||
|
-- - Cross-organization rules (access between different orgs)
|
||||||
|
-- - Inter-organizational collaboration scenarios
|
||||||
|
-- - Cross-org permission validation
|
||||||
|
--
|
||||||
|
-- Prerequisites: Assumes 00_setup.sql has been run to set up base data
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Cross-Organization Rules ==='
|
||||||
|
|
||||||
|
-- Allow Beta managers to read Acme documents (inter-org collaboration)
|
||||||
|
INSERT INTO morbac.cross_org_rules (source_org_id, target_org_id, role_id, activity, view, context_id, modality) VALUES
|
||||||
|
('22222222-2222-2222-2222-222222222222', '11111111-1111-1111-1111-111111111111', 'dddddddd-dddd-dddd-dddd-dddddddddddd', 'read', 'documents', (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission');
|
||||||
|
|
||||||
|
\echo 'Diana (manager at Beta) can read Acme documents via cross-org rule:'
|
||||||
|
SELECT
|
||||||
|
'read Acme documents from Beta' as action,
|
||||||
|
morbac.is_allowed(
|
||||||
|
'dddddddd-0000-0000-0000-000000000004'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'read',
|
||||||
|
'documents'
|
||||||
|
) as allowed;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Cross-Organization Rules Tests Completed ==='
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- MORBAC PostgreSQL Extension - Audit Logging Tests
|
||||||
|
-- ============================================================================
|
||||||
|
-- This test file covers audit logging functionality including:
|
||||||
|
-- - Enabling audit on tables
|
||||||
|
-- - Auditing INSERT operations
|
||||||
|
-- - Auditing UPDATE operations
|
||||||
|
-- - Auditing DELETE operations
|
||||||
|
-- - Audit log queries and reporting
|
||||||
|
-- - Disabling audit
|
||||||
|
--
|
||||||
|
-- Prerequisites: Assumes 00_setup.sql has been run to set up base data
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Audit Logging ==='
|
||||||
|
|
||||||
|
-- Enable audit on user_roles table
|
||||||
|
SELECT morbac.enable_audit('user_roles');
|
||||||
|
|
||||||
|
\echo 'Audit trigger created for user_roles table'
|
||||||
|
|
||||||
|
-- Perform some auditable operations
|
||||||
|
INSERT INTO morbac.user_roles (user_id, role_id, org_id)
|
||||||
|
VALUES (
|
||||||
|
'ffffffff-0000-0000-0000-000000000001'::uuid,
|
||||||
|
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', -- employee role
|
||||||
|
'11111111-1111-1111-1111-111111111111'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Update an existing user_role
|
||||||
|
UPDATE morbac.user_roles
|
||||||
|
SET active = false
|
||||||
|
WHERE user_id = 'aaaaaaaa-0000-0000-0000-000000000001'::uuid
|
||||||
|
AND role_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
||||||
|
|
||||||
|
-- Delete a user role
|
||||||
|
DELETE FROM morbac.user_roles
|
||||||
|
WHERE user_id = 'ffffffff-0000-0000-0000-000000000001'::uuid
|
||||||
|
AND role_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
|
||||||
|
|
||||||
|
\echo 'Audit log entries for user_roles operations:'
|
||||||
|
SELECT
|
||||||
|
operation,
|
||||||
|
record_id,
|
||||||
|
CASE
|
||||||
|
WHEN old_data IS NOT NULL THEN old_data->>'active'
|
||||||
|
ELSE 'N/A'
|
||||||
|
END as old_active,
|
||||||
|
CASE
|
||||||
|
WHEN new_data IS NOT NULL THEN new_data->>'active'
|
||||||
|
ELSE 'N/A'
|
||||||
|
END as new_active,
|
||||||
|
session_user,
|
||||||
|
timestamp
|
||||||
|
FROM morbac.audit_log
|
||||||
|
WHERE table_name = 'user_roles'
|
||||||
|
ORDER BY timestamp;
|
||||||
|
|
||||||
|
\echo 'Disable audit on user_roles:'
|
||||||
|
SELECT morbac.disable_audit('user_roles');
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Audit Logging Tests Completed ==='
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- MORBAC PostgreSQL Extension - Administration Rules Tests
|
||||||
|
-- ============================================================================
|
||||||
|
-- This test file covers administration rules including:
|
||||||
|
-- - Administrative activities (create_rule, etc.)
|
||||||
|
-- - Administrative permission checks (is_admin_allowed)
|
||||||
|
-- - Role-based administrative access control
|
||||||
|
-- - Administrative authorization decisions
|
||||||
|
--
|
||||||
|
-- Prerequisites: Assumes 00_setup.sql has been run to set up base data
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Administration Rules ==='
|
||||||
|
|
||||||
|
-- Only admin role can create rules
|
||||||
|
INSERT INTO morbac.admin_rules (org_id, role_id, admin_activity, admin_target, context_id, modality) VALUES
|
||||||
|
('11111111-1111-1111-1111-111111111111', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'create_rule', 'rules', (SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission');
|
||||||
|
|
||||||
|
\echo 'Alice (admin) can create rules:'
|
||||||
|
SELECT morbac.is_admin_allowed(
|
||||||
|
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'create_rule',
|
||||||
|
'rules'
|
||||||
|
) as can_create_rules;
|
||||||
|
|
||||||
|
\echo 'Bob (employee) cannot create rules:'
|
||||||
|
SELECT morbac.is_admin_allowed(
|
||||||
|
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'create_rule',
|
||||||
|
'rules'
|
||||||
|
) as can_create_rules;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Administration Rules Tests Completed ==='
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- MORBAC PostgreSQL Extension - Utility Functions and Context Tests
|
||||||
|
-- ============================================================================
|
||||||
|
-- This test file covers utility functions and context evaluation including:
|
||||||
|
-- - Obligations (pending_obligations)
|
||||||
|
-- - Recommendations (pending_recommendations)
|
||||||
|
-- - Context evaluation (eval_context)
|
||||||
|
-- - User role queries (user_roles_in_org, user_has_role)
|
||||||
|
-- - Policy compilation and idempotency
|
||||||
|
-- - Adding new policy entries
|
||||||
|
--
|
||||||
|
-- Prerequisites: Assumes 00_setup.sql has been run to set up base data
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Obligations and Recommendations ==='
|
||||||
|
|
||||||
|
\echo 'Obligations for Bob (employee at Acme):'
|
||||||
|
SELECT * FROM morbac.pending_obligations(
|
||||||
|
'bbbbbbbb-0000-0000-0000-000000000002'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo 'Recommendations for Charlie (staff at Beta):'
|
||||||
|
SELECT * FROM morbac.pending_recommendations(
|
||||||
|
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
||||||
|
'22222222-2222-2222-2222-222222222222'::uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Utility Functions ==='
|
||||||
|
|
||||||
|
\echo 'Roles for Charlie at Acme Corp:'
|
||||||
|
SELECT * FROM morbac.user_roles_in_org(
|
||||||
|
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo 'Roles for Charlie at Beta Inc:'
|
||||||
|
SELECT * FROM morbac.user_roles_in_org(
|
||||||
|
'cccccccc-0000-0000-0000-000000000003'::uuid,
|
||||||
|
'22222222-2222-2222-2222-222222222222'::uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo 'Does Alice have admin role at Acme Corp?'
|
||||||
|
SELECT morbac.user_has_role(
|
||||||
|
'aaaaaaaa-0000-0000-0000-000000000001'::uuid,
|
||||||
|
'11111111-1111-1111-1111-111111111111'::uuid,
|
||||||
|
'admin'
|
||||||
|
) as has_admin_role;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Context Evaluation ==='
|
||||||
|
|
||||||
|
\echo 'Evaluating always context:'
|
||||||
|
SELECT morbac.eval_context(
|
||||||
|
(SELECT id FROM morbac.contexts WHERE name = 'always')
|
||||||
|
) as result;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo 'Evaluating business_hours context:'
|
||||||
|
SELECT morbac.eval_context(
|
||||||
|
(SELECT id FROM morbac.contexts WHERE name = 'business_hours')
|
||||||
|
) as result;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo 'Evaluating weekend context:'
|
||||||
|
SELECT morbac.eval_context(
|
||||||
|
(SELECT id FROM morbac.contexts WHERE name = 'weekend')
|
||||||
|
) as result;
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Policy Re-compilation (Idempotency Test) ==='
|
||||||
|
|
||||||
|
\echo 'Compiling policy again (should show 0 new rules, idempotent):'
|
||||||
|
SELECT * FROM morbac.compile_policy();
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Adding New Policy Entry ==='
|
||||||
|
|
||||||
|
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality, context_name) VALUES
|
||||||
|
('Acme Corp', 'admin', 'approve', 'documents', 'permission', 'always');
|
||||||
|
|
||||||
|
\echo 'Compiling new policy entry:'
|
||||||
|
SELECT * FROM morbac.compile_policy();
|
||||||
|
|
||||||
|
\echo ''
|
||||||
|
\echo '=== Utility Functions and Context Tests Completed ==='
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Test Organization
|
||||||
|
|
||||||
|
Tests are organized in the `tests/` directory:
|
||||||
|
|
||||||
|
- **00_setup.sql** - Common setup (organizations, roles, users, activities, views)
|
||||||
|
- **01_authorization.sql** - Basic authorization tests
|
||||||
|
- **02_hierarchies.sql** - Role, org, activity, view hierarchy tests
|
||||||
|
- **03_delegation.sql** - Role delegation tests
|
||||||
|
- **04_constraints.sql** - SoD and cardinality constraint tests
|
||||||
|
- **05_temporal.sql** - Temporal validity tests
|
||||||
|
- **06_cross_org.sql** - Cross-organization access tests
|
||||||
|
- **07_audit.sql** - Audit logging tests
|
||||||
|
- **08_admin.sql** - Administration permission tests
|
||||||
|
- **09_utilities.sql** - Utility function tests
|
||||||
|
|
||||||
|
## Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests (creates temp database, runs tests, cleans up)
|
||||||
|
make test
|
||||||
|
|
||||||
|
# Run tests manually with persistent database
|
||||||
|
createdb morbac_test
|
||||||
|
./tests/run_tests.sh morbac_test
|
||||||
|
# Database remains for inspection
|
||||||
|
dropdb morbac_test # when done
|
||||||
|
|
||||||
|
# Run individual test file
|
||||||
|
psql -d morbac_test -f tests/01_authorization.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Data
|
||||||
|
|
||||||
|
All tests use common setup from `00_setup.sql`:
|
||||||
|
|
||||||
|
**Organizations:**
|
||||||
|
- Acme Corp (11111111-1111-1111-1111-111111111111)
|
||||||
|
- Beta Inc (22222222-2222-2222-2222-222222222222)
|
||||||
|
- Acme Subsidiary (child of Acme Corp)
|
||||||
|
|
||||||
|
**Test Users:**
|
||||||
|
- Alice: aaaaaaaa-0000-0000-0000-000000000001 (admin at Acme)
|
||||||
|
- Bob: bbbbbbbb-0000-0000-0000-000000000002 (employee at Acme)
|
||||||
|
- Charlie: cccccccc-0000-0000-0000-000000000003 (contractor at Acme, staff at Beta)
|
||||||
|
- Dave: dddddddd-0000-0000-0000-000000000004 (manager at Beta)
|
||||||
|
- Eve: eeeeeeee-0000-0000-0000-000000000005 (viewer at Acme)
|
||||||
|
|
||||||
|
**Activities:** read, write, delete, manage, export, approve
|
||||||
|
|
||||||
|
**Views:** documents, reports, confidential_data, public_data, financial_records, projects
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# Test Runner for morbac_pg Extension
|
||||||
|
# =============================================================================
|
||||||
|
# Runs all tests in order, loading setup data and executing each test file.
|
||||||
|
# Usage: ./run_tests.sh [database_name]
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Database name (default: morbac_test)
|
||||||
|
DB="${1:-morbac_test}"
|
||||||
|
TEST_DIR="$(dirname "$0")"
|
||||||
|
|
||||||
|
echo "======================================"
|
||||||
|
echo "morbac_pg Extension Test Suite"
|
||||||
|
echo "======================================"
|
||||||
|
echo "Database: $DB"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Install extension
|
||||||
|
echo "=== Installing Extension ==="
|
||||||
|
psql -d "$DB" -f "$TEST_DIR/../morbac_pg.sql"
|
||||||
|
|
||||||
|
# Load common setup
|
||||||
|
echo ""
|
||||||
|
echo "=== Loading Test Setup ==="
|
||||||
|
psql -d "$DB" -f "$TEST_DIR/00_setup.sql"
|
||||||
|
|
||||||
|
# Run all test files in order
|
||||||
|
echo ""
|
||||||
|
echo "=== Running Tests ==="
|
||||||
|
|
||||||
|
for test_file in "$TEST_DIR"/[0-9][0-9]_*.sql; do
|
||||||
|
if [ -f "$test_file" ]; then
|
||||||
|
test_name=$(basename "$test_file" .sql)
|
||||||
|
echo ""
|
||||||
|
echo "--- Running: $test_name ---"
|
||||||
|
psql -d "$DB" -f "$test_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "======================================"
|
||||||
|
echo "All Tests Completed"
|
||||||
|
echo "======================================"
|
||||||
Reference in New Issue
Block a user