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>
pgmorbac
Multi-Organization Based Access Control for PostgreSQL
A PostgreSQL extension implementing the Multi-OrBAC access control model - enabling organization-centric, context-aware, and hierarchical access control with advanced features like delegation, separation of duty, and cross-organizational policies.
Features
- Multi-organization with organizational hierarchy
- Unattributed (no-org) objects as a first-class rule target
- Role-based access with full hierarchy support
- Activity and view hierarchies with transitive permission inheritance
- Prohibition precedence over permissions
- Temporal delegation with time bounds
- Temporal constraints on rules with validity periods
- Separation of duty constraints
- Derived roles computed from application logic
- Cross-organizational policies
- Context-aware rules
- Audit logging for security-critical operations
- Row-level security integration
Installation
Prerequisites
- PostgreSQL 13 or higher
- Development tools:
make,bash
Quick Install
# Clone repository
git clone https://git.villains.fr/crudy/pgmorbac.git
cd pgmorbac
# Build and install extension
make build # Build versioned file from src/
sudo make install # Install to PostgreSQL
# Enable in your database
psql -d mydb -c "CREATE EXTENSION pgmorbac;"
Alternative: Using install script
# Build first
make build
# Run installer
sudo ./tools/install.sh
Manual Install
# Build versioned file from source
make build # Concatenates src/ files into pgmorbac--0.1.0.sql
# Copy files to PostgreSQL extension directory
sudo cp pgmorbac.control $(pg_config --sharedir)/extension/
sudo cp pgmorbac--0.1.0.sql $(pg_config --sharedir)/extension/
# Enable in PostgreSQL
psql -d mydb -c "CREATE EXTENSION pgmorbac;"
Quick Start
-- 1. Create organization
INSERT INTO morbac.orgs (name) VALUES ('Acme Corp');
-- 2. Create role
INSERT INTO morbac.roles (org_id, name)
SELECT id, 'employee' FROM morbac.orgs WHERE name = 'Acme Corp';
-- 3. Assign user to role
INSERT INTO morbac.user_roles (user_id, role_id, org_id)
SELECT
'00000000-0000-0000-0000-000000000001'::uuid,
r.id,
o.id
FROM morbac.orgs o
JOIN morbac.roles r ON r.org_id = o.id
WHERE o.name = 'Acme Corp' AND r.name = 'employee';
-- 4. Define policy
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality)
VALUES ('Acme Corp', 'employee', 'read', 'documents', 'permission');
-- 5. Compile policy
SELECT * FROM morbac.compile_policy();
-- 6. Check authorization
SELECT morbac.is_allowed(
'00000000-0000-0000-0000-000000000001'::uuid,
(SELECT id FROM morbac.orgs WHERE name = 'Acme Corp'),
'read',
'documents'
); -- Returns: true
-- 7. Initialize performance cache (recommended for production)
SELECT morbac.refresh_hierarchy_cache();
Usage
Authorization Check
-- Production (cached by default)
SELECT morbac.is_allowed(user_id, org_id, activity, view);
-- Debugging (bypasses cache)
SELECT morbac.is_allowed_nocache(user_id, org_id, activity, view);
-- Unattributed object (no org): pass NULL as the org
SELECT morbac.is_allowed(user_id, NULL, activity, view);
-- Capability probe for UI gating (any org, unattributed, or global)
SELECT morbac.has_permission(user_id, activity, view);
See PERFORMANCE.md for optimization details.
Row-Level Security (RLS)
-- Enable RLS on your table
ALTER TABLE app.documents ENABLE ROW LEVEL SECURITY;
-- Table without an org column
CREATE POLICY doc_access ON app.documents
FOR SELECT
USING (morbac.rls_check('read', 'documents'));
-- Table with an org column: pass it. A NULL org_id means the record
-- is unattributed (awaiting attribution).
CREATE POLICY doc_access ON app.documents
FOR SELECT
USING (morbac.rls_check('read', 'documents', org_id));
Unattributed (no-org) Records
A record whose org_id is NULL is unattributed. Give a role access to that pool
without granting anything org-wide:
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, scope)
SELECT o.id, r.id, 'read', 'documents', c.id, 'permission', 'unattributed'
FROM morbac.orgs o
JOIN morbac.roles r ON r.org_id = o.id AND r.name = 'triage'
JOIN morbac.contexts c ON c.name = 'always'
WHERE o.name = 'Acme Corp';
Choose which records a query returns:
SET morbac.org_ids = '[null]'; -- unattributed only (attribution queue)
SET morbac.org_ids = '["<org-uuid>", null]';-- that org plus unattributed
SET morbac.org_id = '<org-uuid>'; -- that org only
-- nothing set -- everything authorized, unattributed included
See DOCUMENTATION.md for the full org target vocabulary
(a specific organization, unattributed, or all).
Advanced Features
-- Temporal rules: Rule active only during business hours or specific period
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from, valid_until)
VALUES (org_id, role_id, 'write', 'sensitive_data', ctx_id, 'permission',
'2024-01-01 00:00:00', '2024-12-31 23:59:59');
-- Delegation: Alice delegates role to Bob for 1 week
INSERT INTO morbac.delegations (delegator_id, delegatee_id, role_id, org_id, valid_until)
VALUES (alice_id, bob_id, role_id, org_id, NOW() + INTERVAL '7 days');
-- Separation of Duty: Invoice creator and approver roles are mutually exclusive
INSERT INTO morbac.sod_conflicts (role_a_id, role_b_id, org_id)
VALUES (creator_role_id, approver_role_id, org_id);
-- Cross-Org: Global auditor can access subsidiary
INSERT INTO morbac.cross_org_rules (source_org_id, target_org_id, role_id, activity, view, context_id, modality)
VALUES (global_org_id, subsidiary_org_id, auditor_role_id, 'read', 'reports',
(SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission');
-- Audit logging: Track changes to user roles
SELECT morbac.enable_audit('user_roles');
-- Query audit log
SELECT * FROM morbac.audit_log
WHERE table_name = 'user_roles'
ORDER BY timestamp DESC
LIMIT 10;
Development & Testing
# Development workflow
# 1. Edit source files in src/ directory
# 2. Build and test
make build # Build versioned pgmorbac--X.Y.Z.sql from src/
make test # Build and run test suite
# Manual testing
make build
createdb morbac_test
psql -d morbac_test -f pgmorbac.sql
# ... test manually ...
dropdb morbac_test
See DEVELOPMENT.md for source code organization.
Documentation
- DOCUMENTATION.md: Architecture, API reference, integration guides
- PERFORMANCE.md: Performance optimization and caching guide
- ADMIN_GUIDE.md: Organization administrator setup and delegation
- DEVELOPMENT.md: Development workflow and version management
- SECURITY.md: Security policy
- CONTRIBUTING.md: Contribution guidelines
- CHANGELOG.md: Version history
Architecture
Authorization decision flow:
- Collect all roles for user (direct, delegated, derived, hierarchy)
- Filter out negative role assignments
- Find the highest-priority applicable prohibition and highest-priority applicable permission
- If a permission with strictly higher priority than the prohibition exists, allow
- If a prohibition exists (and no higher-priority permission), deny
- If no prohibition, allow if any permission was found; otherwise deny
Prohibitions win over permissions at equal or unset priority.
Research
Based on the CNRS research paper: "Extending the OrBAC model to handle multi-organization environments"
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
See CONTRIBUTING.md for guidelines.
git clone https://git.villains.fr/crudy/pgmorbac.git
cd pgmorbac
# Edit source files in src/
vim src/authorization.sql
# Build and test your changes
make check # Run test suite
# When ready to release
# 1. Update version in pgmorbac.control
# 2. Build versioned file
make build # Creates pgmorbac.sql from src/
# 3. Tag in git
git tag v1.0.1
git push --tags
Support
- Read the documentation
- Report bugs via Gitea Issues
- Security issues: see SECURITY.md
Comparison with Traditional RBAC
| Feature | Traditional RBAC | Traditional OrBAC | pgmorbac (Multi-OrBAC) |
|---|---|---|---|
| Multi-tenancy | No | Single organization | Multi-organization |
| Prohibitions | No | Yes | Yes |
| Context-aware | No | Yes | Yes |
| Hierarchies | Basic roles only | Roles, activities, views | Organizations, roles, activities, views |
| Delegation | No | Yes | Yes (temporal) |
| Separation of Duty | No | Possible | Database-enforced |
| Cross-organization | No | No | Yes |