marc cc3d65a013 docs: complete function reference, drop unicode punctuation
Documents refresh_hierarchy_cache, is_rule_valid and org_in_scope, and
corrects the get_org_scope scope list which still omitted unattributed
and all.

Replaces em dashes and other typographic unicode with ASCII throughout
the schema comments, the test suite and the documentation. Comments and
prose are ASCII only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 07:26:54 +02:00
2026-02-22 22:29:52 +01:00

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.

License: MIT PostgreSQL 13+

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

Architecture

Authorization decision flow:

  1. Collect all roles for user (direct, delegated, derived, hierarchy)
  2. Filter out negative role assignments
  3. Find the highest-priority applicable prohibition and highest-priority applicable permission
  4. If a permission with strictly higher priority than the prohibition exists, allow
  5. If a prohibition exists (and no higher-priority permission), deny
  6. 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

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
S
Description
Multi-OrBAC access control extension for PostgreSQL
Readme MIT 367 KiB
Languages
PLpgSQL 82.5%
Shell 9.7%
JavaScript 5.2%
Makefile 2.6%