From b68bded4bbe6314c0233fd871777f161baba6b3b Mon Sep 17 00:00:00 2001 From: Marc Villain Date: Fri, 20 Feb 2026 11:01:32 +0100 Subject: [PATCH] docs: cleanup documentation files --- README.md | 46 +++--- docs/ADMIN_GUIDE.md | 31 ++-- docs/DEVELOPMENT.md | 14 +- docs/DOCUMENTATION.md | 364 +++++++++++++++--------------------------- docs/PERFORMANCE.md | 16 +- 5 files changed, 183 insertions(+), 288 deletions(-) diff --git a/README.md b/README.md index 6165268..ccd4f87 100644 --- a/README.md +++ b/README.md @@ -146,9 +146,9 @@ VALUES (org_id, role_id, 'write', 'sensitive_data', ctx_id, 'permission', INSERT INTO morbac.delegations (delegator_user_id, delegate_user_id, role_id, org_id, valid_until) VALUES (alice_id, bob_id, role_id, org_id, NOW() + INTERVAL '7 days'); --- Separation of Duty: Preparer and approver are mutually exclusive +-- Separation of Duty: Invoice creator and approver roles are mutually exclusive INSERT INTO morbac.sod_conflicts (role1_id, role2_id, org_id) -VALUES (preparer_role_id, approver_role_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, modality) @@ -185,15 +185,13 @@ See [src/README.md](src/README.md) for source code organization. ## Documentation -- [DOCUMENTATION.md](docs/DOCUMENTATION.md) - Architecture, API reference, integration guides -- [PERFORMANCE.md](docs/PERFORMANCE.md) - Performance optimization and caching guide -- [ADMIN_GUIDE.md](docs/ADMIN_GUIDE.md) - Organization administrator setup and delegation -- [DEVELOPMENT.md](docs/DEVELOPMENT.md) - Development workflow and version management -- [SECURITY.md](docs/SECURITY.md) - Security policy -- [CONTRIBUTING.md](docs/CONTRIBUTING.md) - Contribution guidelines -- [src/README.md](src/README.md) - Source code organization -- [tests/README.md](tests/README.md) - Test structure and setup -- [CHANGELOG.md](CHANGELOG.md) - Version history +- [DOCUMENTATION.md](docs/DOCUMENTATION.md): Architecture, API reference, integration guides +- [PERFORMANCE.md](docs/PERFORMANCE.md): Performance optimization and caching guide +- [ADMIN_GUIDE.md](docs/ADMIN_GUIDE.md): Organization administrator setup and delegation +- [DEVELOPMENT.md](docs/DEVELOPMENT.md): Development workflow and version management +- [SECURITY.md](SECURITY.md): Security policy +- [CONTRIBUTING.md](CONTRIBUTING.md): Contribution guidelines +- [CHANGELOG.md](CHANGELOG.md): Version history ## Architecture @@ -201,8 +199,8 @@ Authorization decision flow: 1. Collect all roles for user (direct, delegated, derived, hierarchy) 2. Filter out negative role assignments -3. Check for prohibitions - if found, deny access -4. Check for permissions - if found, allow access +3. Check for prohibitions: if found, deny access +4. Check for permissions: if found, allow access 5. Default deny Prohibitions always override permissions. @@ -216,7 +214,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file ## Contributing -See [CONTRIBUTING.md](docs/CONTRIBUTING.md) for guidelines. +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. ```bash git clone https://github.com/yourusername/pg_morbac.git @@ -242,16 +240,16 @@ git push --tags - Read the [documentation](docs/DOCUMENTATION.md) - Report bugs via [GitHub Issues](https://github.com/yourusername/pg_morbac/issues) - Ask questions in [Discussions](https://github.com/yourusername/pg_morbac/discussions) -- Security issues: see [SECURITY.md](docs/SECURITY.md) +- Security issues: see [SECURITY.md](SECURITY.md) ## Comparison with Traditional RBAC -| Feature | Traditional RBAC | pg_morbac | -|---------|-----------------|-----------| -| Multi-tenancy | No | Yes | -| Prohibitions | No | Yes | -| Context-aware | No | Yes | -| Hierarchies | Basic roles only | Organizations, roles, activities, views | -| Delegation | No | Yes | -| Separation of Duty | No | Yes | -| Cross-organization | No | Yes | +| Feature | Traditional RBAC | Traditional OrBAC | pg_morbac (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 | diff --git a/docs/ADMIN_GUIDE.md b/docs/ADMIN_GUIDE.md index f45bcba..05aeba9 100644 --- a/docs/ADMIN_GUIDE.md +++ b/docs/ADMIN_GUIDE.md @@ -13,19 +13,14 @@ Multi-OrBAC allows you to delegate administrative capabilities to specific roles ## Administration Rules -The `morbac.admin_rules` table defines what administrative actions specific roles can perform: +The `morbac.admin_rules` table defines administrative capabilities for specific roles: -```sql -CREATE TABLE morbac.admin_rules ( - id UUID PRIMARY KEY, - org_id UUID REFERENCES morbac.orgs(id), - role_id UUID REFERENCES morbac.roles(id), - admin_activity TEXT, -- 'manage', 'assign_role', etc. - admin_target TEXT, -- 'policies', 'roles', role name, etc. - modality morbac.modality, -- 'permission' or 'prohibition' - context_id UUID REFERENCES morbac.contexts(id) -); -``` +**Key columns:** +- `org_id`, `role_id`: Role receiving capabilities +- `admin_activity`: Action type (e.g., 'manage', 'assign_role') +- `admin_target`: Target type (e.g., 'policies', 'roles', specific role name, '*' for wildcard) +- `modality`: Permission or prohibition +- `context_id`: Optional conditional evaluation ## Common Admin Patterns @@ -375,9 +370,9 @@ SELECT morbac.admin_assign_role( Multi-OrBAC provides complete delegation of administrative capabilities: -- **No superadmins needed** for day-to-day operations -- **Organization-scoped** - admins only affect their own org -- **Fine-grained** - control exactly what each admin role can do -- **Safe** - automatic constraint validation (SoD, cardinality) -- **Auditable** - all actions go through tracked functions -- **Context-aware** - limit when admin actions can occur +- **No superadmins needed**: for day-to-day operations +- **Organization-scoped**: admins only affect their own org +- **Fine-grained**: control exactly what each admin role can do +- **Safe**: automatic constraint validation (SoD, cardinality) +- **Auditable**: all actions go through tracked functions +- **Context-aware**: limit when admin actions can occur diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 17a881e..31fb429 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -25,7 +25,7 @@ When ready to release a new version: 2. **Create release**: Run `make release` - Reads version from `.control` file - - Copies `pg_morbac.sql` → `pg_morbac--X.Y.Z.sql` + - Copies `pg_morbac.sql` to `pg_morbac--X.Y.Z.sql` 3. **Test**: Run `make test` with the versioned file @@ -49,12 +49,12 @@ These contain only the ALTER/ADD statements needed for the upgrade. ## Make Targets -- `make test` - Test with development file (temporary database) -- `make test` - Full test with persistent database -- `make release` - Generate versioned file from development file -- `make install` - Create release and install in PostgreSQL -- `make cleanup` - Drop test database -- `make help` - Show all targets +- `make test`: Test with development file (temporary database) +- `make test`: Full test with persistent database +- `make release`: Generate versioned file from development file +- `make install`: Create release and install in PostgreSQL +- `make cleanup`: Drop test database +- `make help`: Show all targets ## Benefits diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md index 57e8e88..3eed67f 100644 --- a/docs/DOCUMENTATION.md +++ b/docs/DOCUMENTATION.md @@ -19,12 +19,12 @@ Complete technical documentation for the Multi-OrBAC PostgreSQL extension. pg_morbac is built on these principles: -1. **Pure PostgreSQL** - No external dependencies (except pgcrypto) -2. **Schema Isolation** - All objects in `morbac` schema -3. **Default Deny** - No permission = access denied -4. **Prohibition Precedence** - Prohibitions checked first, always override permissions -5. **Organization-Centric** - All policies scoped to organizations -6. **Multi-Tenant Native** - Users and resources can span organizations +1. **Pure PostgreSQL**: No external dependencies (except pgcrypto) +2. **Schema Isolation**: All objects in `morbac` schema +3. **Default Deny**: No permission = access denied +4. **Prohibition Precedence**: Prohibitions checked first, always override permissions +5. **Organization-Centric**: All policies scoped to organizations +6. **Multi-Tenant Native**: Users and resources can span organizations ### Authorization Flow @@ -92,160 +92,108 @@ erDiagram ### Core Tables -**morbac.orgs** - Organizations with hierarchy support (`parent_id` self-reference). Unique name required. Supports metadata as JSONB. +**morbac.orgs**: Organizations with hierarchy support (`parent_id` self-reference). Unique name required. Supports metadata as JSONB. -**morbac.roles** - Roles scoped to organizations. Unique `(org_id, name)` constraint ensures role names are unique within each org. +**morbac.roles**: Roles scoped to organizations. Unique `(org_id, name)` constraint ensures role names are unique within each org. -**morbac.role_hierarchy** - Role inheritance via `(senior_role_id, junior_role_id)`. Senior roles inherit all junior permissions. Supports transitive closure. +**morbac.role_hierarchy**: Role inheritance via `(senior_role_id, junior_role_id)`. Senior roles inherit all junior permissions. Supports transitive closure. -**morbac.user_roles** - Direct user-to-role assignments. Primary key on `(user_id, role_id, org_id)`. Note: `user_id` is external (application manages users). +**morbac.user_roles**: Direct user-to-role assignments. Primary key on `(user_id, role_id, org_id)`. Note: `user_id` is external (application manages users). -**morbac.activities** - Abstract actions (global scope). Text primary key. Examples: `read`, `write`, `delete`, `approve`. +**morbac.activities**: Abstract actions (global scope). Text primary key. Examples: `read`, `write`, `delete`, `approve`. -**morbac.activity_hierarchy** - Activity inheritance via `(parent_activity, child_activity)`. Permission to parent grants child activities. +**morbac.activity_hierarchy**: Activity inheritance via `(parent_activity, child_activity)`. Permission to parent grants child activities. -**morbac.views** - Abstract object categories (global scope). Text primary key. Examples: `documents`, `reports`, `financial_data`. +**morbac.views**: Abstract object categories (global scope). Text primary key. Examples: `documents`, `reports`, `financial_data`. -**morbac.view_hierarchy** - View inheritance via `(parent_view, child_view)`. Access to parent grants child views. +**morbac.view_hierarchy**: View inheritance via `(parent_view, child_view)`. Access to parent grants child views. -**morbac.contexts** - Contextual conditions as callable predicates. Column `evaluator` (REGPROC) references a function returning BOOLEAN (preferably STABLE). Built-in context `always` returns true. +**morbac.contexts**: Contextual conditions as callable predicates. Column `evaluator` (REGPROC) references a function returning BOOLEAN (preferably STABLE). Built-in context `always` returns true. -**morbac.rules** - Core policy rules linking org, role, activity, view, context, and modality. Indexed on `(org_id, role_id, activity, view, modality)` for fast lookups. +**morbac.rules**: Core policy rules linking org, role, activity, view, context, and modality. Indexed on `(org_id, role_id, activity, view, modality)` for fast lookups. -**morbac.policy** - Policy DSL using names instead of UUIDs. Insert here, then call `compile_policy()` to generate rules. +**morbac.policy**: Policy DSL using names instead of UUIDs. Insert here, then call `compile_policy()` to generate rules. ### Advanced Feature Tables -#### morbac.delegations +**morbac.delegations** -Temporal role delegation. +Temporal role delegation with time bounds. -```sql -CREATE TABLE morbac.delegations ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - delegator_user_id UUID NOT NULL, - delegate_user_id UUID NOT NULL, - role_id UUID NOT NULL REFERENCES morbac.roles(id) ON DELETE CASCADE, - org_id UUID NOT NULL REFERENCES morbac.orgs(id) ON DELETE CASCADE, - valid_from TIMESTAMPTZ NOT NULL DEFAULT now(), - valid_until TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - CHECK (valid_until IS NULL OR valid_until > valid_from) -); -``` +**Key columns:** +- `delegator_user_id`: User granting the role +- `delegate_user_id`: User receiving the role +- `role_id`, `org_id`: Role being delegated +- `valid_from`, `valid_until`: Time window (NULL = indefinite) -**Columns:** -- `valid_from` - Delegation start time -- `valid_until` - Optional end time (NULL = indefinite) +**Behavior:** Automatically included in `get_comprehensive_roles()` when active. -**Automatic handling:** Included in `get_comprehensive_roles()` when active. +**morbac.negative_role_assignments** -#### morbac.negative_role_assignments +Explicit role prohibitions with highest precedence. -Explicit role prohibitions. +**Key columns:** +- `user_id`, `role_id`, `org_id`: Assignment to prohibit +- `reason`: Explanation for prohibition -```sql -CREATE TABLE morbac.negative_role_assignments ( - user_id UUID NOT NULL, - role_id UUID NOT NULL REFERENCES morbac.roles(id) ON DELETE CASCADE, - org_id UUID NOT NULL REFERENCES morbac.orgs(id) ON DELETE CASCADE, - reason TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - PRIMARY KEY (user_id, role_id, org_id) -); -``` +**Behavior:** Overrides direct assignments, delegations, and derived roles. -**Precedence:** Overrides direct assignments, delegations, and derived roles. +**morbac.sod_conflicts** -#### morbac.sod_conflicts +Separation of Duty constraints enforce mutually exclusive roles. -Separation of Duty constraints. +**Key columns:** +- `role1_id`, `role2_id`: Conflicting role pair +- `org_id`: Organization scope +- `description`: Explanation of conflict -```sql -CREATE TABLE morbac.sod_conflicts ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - role1_id UUID NOT NULL REFERENCES morbac.roles(id) ON DELETE CASCADE, - role2_id UUID NOT NULL REFERENCES morbac.roles(id) ON DELETE CASCADE, - org_id UUID NOT NULL REFERENCES morbac.orgs(id) ON DELETE CASCADE, - description TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - UNIQUE(role1_id, role2_id, org_id), - CHECK (role1_id < role2_id) -); -``` +**Behavior:** Prevents users from holding both roles simultaneously. Validated via `check_sod_violation()` before role assignment. -**Usage:** Validated via `check_sod_violation()` before role assignment. +**morbac.role_cardinality** -#### morbac.role_cardinality +Constrains the number of users per role. -Min/max users per role. +**Key columns:** +- `role_id`, `org_id`: Role to constrain +- `min_users`: Minimum required users (default 0) +- `max_users`: Maximum allowed users (NULL = unlimited) -```sql -CREATE TABLE morbac.role_cardinality ( - role_id UUID PRIMARY KEY REFERENCES morbac.roles(id) ON DELETE CASCADE, - org_id UUID NOT NULL REFERENCES morbac.orgs(id) ON DELETE CASCADE, - min_users INTEGER NOT NULL DEFAULT 0, - max_users INTEGER, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - CHECK (min_users >= 0), - CHECK (max_users IS NULL OR max_users >= min_users) -); -``` +**Behavior:** Validated via `check_cardinality_violation()` before role assignment. -**Usage:** Validated via `check_cardinality_violation()`. +**morbac.derived_roles** -#### morbac.derived_roles +Dynamically computed roles via custom functions. -Dynamically computed roles. +**Key columns:** +- `role_id`, `org_id`: Role to compute +- `evaluator`: Function returning `TABLE(user_id UUID, org_id UUID)` +- `description`: Explanation of computation logic -```sql -CREATE TABLE morbac.derived_roles ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - role_id UUID NOT NULL REFERENCES morbac.roles(id) ON DELETE CASCADE, - org_id UUID NOT NULL REFERENCES morbac.orgs(id) ON DELETE CASCADE, - evaluator REGPROC NOT NULL, - description TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); -``` +**Behavior:** Function is called at runtime to determine role membership. Included in `get_comprehensive_roles()`. -**Columns:** -- `evaluator` - Function returning `TABLE(user_id UUID, org_id UUID)` +**morbac.cross_org_rules** -#### morbac.cross_org_rules +Inter-organizational access policies. -Inter-organizational rules. +**Key columns:** +- `source_org_id`: Organization where role is held +- `target_org_id`: Organization where access is granted +- `role_id`, `activity`, `view`: Policy specification +- `modality`: Permission or prohibition -```sql -CREATE TABLE morbac.cross_org_rules ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - source_org_id UUID NOT NULL REFERENCES morbac.orgs(id) ON DELETE CASCADE, - target_org_id UUID NOT NULL REFERENCES morbac.orgs(id) ON DELETE CASCADE, - role_id UUID NOT NULL REFERENCES morbac.roles(id) ON DELETE CASCADE, - activity TEXT NOT NULL REFERENCES morbac.activities(name) ON DELETE CASCADE, - view TEXT NOT NULL REFERENCES morbac.views(name) ON DELETE CASCADE, - modality morbac.modality NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT now() -); -``` +**Behavior:** Allows roles in source organization to access resources in target organization. -**Usage:** Allows roles in `source_org` to access resources in `target_org`. +**morbac.admin_rules** -#### morbac.admin_rules +Administration meta-policies for delegated management. -Administration meta-policies. +**Key columns:** +- `org_id`, `role_id`: Role receiving admin capabilities +- `can_manage_policies`: Can create/modify policies +- `can_manage_roles`: Can create/modify roles +- `can_manage_users`: Can assign/revoke user roles -```sql -CREATE TABLE morbac.admin_rules ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - org_id UUID NOT NULL REFERENCES morbac.orgs(id) ON DELETE CASCADE, - role_id UUID NOT NULL REFERENCES morbac.roles(id) ON DELETE CASCADE, - can_manage_policies BOOLEAN NOT NULL DEFAULT false, - can_manage_roles BOOLEAN NOT NULL DEFAULT false, - can_manage_users BOOLEAN NOT NULL DEFAULT false, - created_at TIMESTAMPTZ NOT NULL DEFAULT now(), - UNIQUE(org_id, role_id) -); +**Behavior:** Enables organization-scoped administrators without database superuser privileges. ``` ## Core Concepts @@ -254,10 +202,10 @@ CREATE TABLE morbac.admin_rules ( Multi-OrBAC implements four modalities: -1. **Permission** - Allows action (if no prohibition) -2. **Prohibition** - Denies action (always wins) -3. **Obligation** - Must be done (informational only) -4. **Recommendation** - Should be done (informational only) +1. **Permission**: Allows action (if no prohibition) +2. **Prohibition**: Denies action (always wins) +3. **Obligation**: Must be done (informational only) +4. **Recommendation**: Should be done (informational only) Only permissions and prohibitions affect `is_allowed()` decisions. @@ -373,41 +321,42 @@ VALUES (user_uuid, admin_role_id, org_id, 'Security audit requirement'); ### Separation of Duty -Mutually exclusive role constraints: +Prevents users from holding conflicting roles that could enable fraud or abuse. + +**Example: Financial Controls** + +In an accounting system, the same person should not both create and approve invoices: ```sql -INSERT INTO morbac.sod_conflicts (role1_id, role2_id, org_id, description) -VALUES (preparer_role_id, approver_role_id, org_id, 'Cannot prepare and approve same transaction'); +-- Create roles +INSERT INTO morbac.roles (org_id, name) VALUES + (org_id, 'invoice_creator'), + (org_id, 'invoice_approver'); -SELECT morbac.check_sod_violation(user_uuid, org_id); +-- Define conflict +INSERT INTO morbac.sod_conflicts (role1_id, role2_id, org_id, description) +VALUES (creator_role_id, approver_role_id, org_id, 'Financial controls: prevent self-approval'); + +-- Check before assignment +SELECT morbac.check_sod_violation(alice_uuid, org_id); +-- Returns: empty array if valid, or ['invoice_creator', 'invoice_approver'] if conflict exists ``` +If Alice already has the `invoice_creator` role, attempting to assign `invoice_approver` will violate the constraint. + ### Cardinality Constraints -Min/max users per role: +Enforce minimum and maximum users per role: ```sql +-- Require 1-3 administrators INSERT INTO morbac.role_cardinality (role_id, org_id, min_users, max_users) VALUES (admin_role_id, org_id, 1, 3); +-- Validate before assignment SELECT morbac.check_cardinality_violation(admin_role_id, org_id); +-- Returns: error message if constraint violated, NULL if valid ``` -Check before INSERT INTO user_roles -```sql --- Require 1-3 admins -INSERT INTO morbac.role_cardinality (role_id, org_id, min_users, max_users) -VALUES (admin_role_id, org_id, 1, 3); - --- Validate -SELECT morbac.check_cardinality_violation(admin_role_id, org_id); --- Returns: TEXT (error message) or NULL (valid) -``` - -### Derived Roles - -```sql --- Define evaluator -### Derived Roles Dynamically computed roles via custom functions: @@ -591,55 +540,55 @@ WHERE table_name = 'rules' ### Authorization Functions -**`is_allowed(user_id, org_id, activity, view)`** - Main authorization decision. Returns BOOLEAN. Checks prohibitions first, then permissions, defaults to deny. +**`is_allowed(user_id, org_id, activity, view)`**: Main authorization decision. Returns BOOLEAN. Checks prohibitions first, then permissions, defaults to deny. ```sql SELECT morbac.is_allowed(user_uuid, org_uuid, 'read', 'documents'); ``` -**`get_comprehensive_roles(user_id, org_id)`** - Returns all roles for user (direct, delegated, derived, hierarchy, minus negative assignments). +**`get_comprehensive_roles(user_id, org_id)`**: Returns all roles for user (direct, delegated, derived, hierarchy, minus negative assignments). -**`get_effective_roles(user_id, org_id)`** - Alias for `get_comprehensive_roles()`. +**`get_effective_roles(user_id, org_id)`**: Alias for `get_comprehensive_roles()`. ### Hierarchy Functions -**`get_org_ancestors(org_id)`** - Returns all parent organizations with depth. +**`get_org_ancestors(org_id)`**: Returns all parent organizations with depth. -**`get_org_descendants(org_id)`** - Returns all child organizations with depth. +**`get_org_descendants(org_id)`**: Returns all child organizations with depth. -**`get_inherited_roles(role_id)`** - Returns all junior roles (transitive). +**`get_inherited_roles(role_id)`**: Returns all junior roles (transitive). -**`get_effective_activities(activity)`** - Returns activity plus all child activities. +**`get_effective_activities(activity)`**: Returns activity plus all child activities. -**`get_effective_views(view)`** - Returns view plus all child views. +**`get_effective_views(view)`**: Returns view plus all child views. ### Validation Functions -**`check_sod_violation(user_id, org_id)`** - Returns TEXT[] of conflicting role pairs or empty array. +**`check_sod_violation(user_id, org_id)`**: Returns empty array if valid, or array of conflicting role name pairs if violations exist. -**`check_cardinality_violation(role_id, org_id)`** - Returns error message or NULL if valid. +**`check_cardinality_violation(role_id, org_id)`**: Returns error message if constraint violated, NULL if valid. ### Administration Functions -**`is_admin_allowed(user_id, org_id, capability)`** - Check administrative permissions. Capabilities: `manage_policies`, `manage_roles`, `manage_users`. +**`is_admin_allowed(user_id, org_id, capability)`**: Check administrative permissions. Capabilities: `manage_policies`, `manage_roles`, `manage_users`. -**`eval_derived_role(user_id, org_id, derived_role_id)`** - Evaluate if user has derived role. +**`eval_derived_role(user_id, org_id, derived_role_id)`**: Evaluate if user has derived role. ### Context Functions -**`eval_context(context_id)`** - Evaluate a context predicate. +**`eval_context(context_id)`**: Evaluate a context predicate. ### Policy DSL Functions -**`compile_policy()`** - Compile policy DSL into rules. Returns TABLE with success status and messages. Idempotent. +**`compile_policy()`**: Compile policy DSL into rules. Returns TABLE with success status and messages. Idempotent. ### RLS Helper Functions -**`current_user_id()`** - Get user ID from `request.header.x-user-id` (PostgREST) or `current_setting('morbac.user_id')`. +**`current_user_id()`**: Get user ID from `request.header.x-user-id` (PostgREST) or `current_setting('morbac.user_id')`. -**`current_org_id()`** - Get org ID from `request.header.x-org-id` (PostgREST) or `current_setting('morbac.org_id')`. +**`current_org_id()`**: Get org ID from `request.header.x-org-id` (PostgREST) or `current_setting('morbac.org_id')`. -**`rls_check(activity, view)`** - Authorization check for RLS policies using current user/org context. +**`rls_check(activity, view)`**: Authorization check for RLS policies using current user/org context. ```sql CREATE POLICY my_policy ON app.table @@ -648,11 +597,11 @@ FOR SELECT USING (morbac.rls_check('read', 'documents')); ### Informational Functions -**`pending_obligations(user_id, org_id)`** - Returns obligations for user (informational only). +**`pending_obligations(user_id, org_id)`**: Returns obligations for user (informational only). -**`pending_recommendations(user_id, org_id)`** - Returns recommendations for user (informational only). +**`pending_recommendations(user_id, org_id)`**: Returns recommendations for user (informational only). -**`user_has_role(user_id, org_id, role_name)`** - Check if user has specific role by name. +**`user_has_role(user_id, org_id, role_name)`**: Check if user has specific role by name. ```sql morbac.user_has_role( @@ -666,9 +615,7 @@ morbac.user_has_role( ### PostgREST Integration -#### 1. Setup Headers - -Configure PostgREST to pass user/org context: +Configure PostgREST to pass user/org context via headers: ```nginx # Nginx config @@ -676,18 +623,17 @@ proxy_set_header X-User-Id $user_id; proxy_set_header X-Org-Id $org_id; ``` -#### 2. Enable RLS +Enable RLS and grant permissions: ```sql ALTER TABLE app.documents ENABLE ROW LEVEL SECURITY; --- Grant usage to PostgREST role GRANT USAGE ON SCHEMA morbac TO postgrest_role; GRANT SELECT ON ALL TABLES IN SCHEMA morbac TO postgrest_role; GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA morbac TO postgrest_role; ``` -#### 3. Create RLS Policies +Create RLS policies: ```sql CREATE POLICY document_read ON app.documents @@ -699,37 +645,13 @@ FOR INSERT WITH CHECK (morbac.rls_check('write', 'documents')); ``` -#### 4. Set Context (alternative to headers) +Alternative method using session variables: ```sql --- In application connection SET morbac.user_id = '123e4567-e89b-12d3-a456-426614174000'; SET morbac.org_id = '987fcdeb-51a2-43d7-9c6e-5a8b7c9d0e1f'; ``` -## Integration Guides - -### PostgREST Integration - -Configure PostgREST to pass headers, enable RLS, and create policies: - -```sql --- Enable RLS -ALTER TABLE app.documents ENABLE ROW LEVEL SECURITY; - -GRANT USAGE ON SCHEMA morbac TO postgrest_role; -GRANT SELECT ON ALL TABLES IN SCHEMA morbac TO postgrest_role; -GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA morbac TO postgrest_role; - --- Create RLS policies -CREATE POLICY document_read ON app.documents -FOR SELECT USING (morbac.rls_check('read', 'documents')); - --- Alternative: Set context directly -SET morbac.user_id = '123e4567...'; -SET morbac.org_id = '987fcdeb...'; -``` - ### Application Integration Python example: @@ -754,19 +676,17 @@ const res = await client.query('SELECT * FROM app.documents'); ### Multi-Organization Resources -Resources can belong to multiple organizations: +Resources can belong to multiple organizations using a junction table: ```sql -CREATE TABLE app.document_orgs ( - document_id UUID, - org_id UUID, - PRIMARY KEY (document_id, org_id) -); - +-- Application defines document-org relationships +-- morbac.rls_check() enforces access rules CREATE POLICY doc_access ON app.documents FOR SELECT USING ( - EXISTS (SELECT 1 FROM app.document_orgs - WHERE document_id = app.documents.id - AND org_id = morbac.current_org_id()) + EXISTS ( + SELECT 1 FROM app.document_orgs + WHERE document_id = app.documents.id + AND org_id = morbac.current_org_id() + ) AND morbac.rls_check('read', 'documents') ); ``` @@ -792,12 +712,6 @@ Context optimization: - Keep context logic simple All foreign keys are indexed. Critical composite indexes exist on `(user_id, org_id)` and `(org_id, role_id, activity, view, modality)`. -CREATE INDEX idx_rules_lookup ON morbac.rules(org_id, role_id, activity, view, modality); - --- Hierarchy traversal -CREATE INDEX idx_role_hierarchy_senior ON morbac.role_hierarchy(senior_role_id); -CREATE INDEX idx_role_hierarchy_junior ON morbac.role_hierarchy(junior_role_id); -``` ### Benchmarking @@ -837,7 +751,7 @@ Not protected against: ### Security Best Practices -#### 1. Context Function Security +**Context Function Security** ```sql -- BAD: Leaks information @@ -853,7 +767,7 @@ RETURNS BOOLEAN STABLE AS $$ $$; ``` -#### 2. User ID Validation +**User ID Validation** ```sql -- Validate user exists before authorization @@ -865,7 +779,7 @@ SELECT morbac.is_allowed( ); ``` -#### 3. Org Context Validation +**Org Context Validation** ```sql -- Verify user belongs to org @@ -875,18 +789,6 @@ SELECT EXISTS( ); ``` -#### 4. Delegation Auditing - -```sql --- Log delegations -CREATE TABLE app.delegation_audit ( - delegation_id UUID REFERENCES morbac.delegations(id), - action TEXT, - by_user UUID, - at_time TIMESTAMPTZ DEFAULT now() -); -``` - ### Comparison: Multi-OrBAC vs Traditional RBAC | Aspect | Traditional RBAC | Multi-OrBAC (pg_morbac) | @@ -905,7 +807,7 @@ CREATE TABLE app.delegation_audit ( ## Additional Resources - [Multi-OrBAC Research Paper](https://webhost.laas.fr/TSF/deswarte/Publications/06427.pdf) -- [test_morbac.sql](test_morbac.sql) - Comprehensive examples -- [CHANGELOG.md](CHANGELOG.md) - Version history -- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution guidelines -- [SECURITY.md](SECURITY.md) - Security policy +- [test_morbac.sql](test_morbac.sql): Comprehensive examples +- [CHANGELOG.md](CHANGELOG.md): Version history +- [CONTRIBUTING.md](CONTRIBUTING.md): Contribution guidelines +- [SECURITY.md](SECURITY.md): Security policy diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md index 5b32ec9..52e3e0a 100644 --- a/docs/PERFORMANCE.md +++ b/docs/PERFORMANCE.md @@ -6,11 +6,11 @@ This guide explains the performance optimizations built into pg_morbac and how t pg_morbac includes several layers of optimization: -1. **Materialized Views** - Precomputed hierarchy transitive closures -2. **Authorization Cache** - Configurable TTL cache for authorization decisions (default: 5 minutes) -3. **Composite Indexes** - Optimized indexes for common query patterns -4. **Partial Indexes** - Indexes only on active (temporally valid) rules -5. **Configurable Settings** - All magic numbers centralized in morbac.config table +1. **Materialized Views**: Precomputed hierarchy transitive closures +2. **Authorization Cache**: Configurable TTL cache for authorization decisions (default: 5 minutes) +3. **Composite Indexes**: Optimized indexes for common query patterns +4. **Partial Indexes**: Indexes only on active (temporally valid) rules +5. **Configurable Settings**: All magic numbers centralized in morbac.config table ## Initial Setup @@ -42,9 +42,9 @@ SELECT morbac.refresh_hierarchy_cache(); ``` **Available configuration keys:** -- `cache_ttl_seconds` - Authorization cache TTL in seconds (default: 300) -- `hierarchy_max_depth` - Maximum hierarchy depth to prevent infinite loops (default: 10) -- `enable_audit_by_default` - Whether to enable audit logging on installation (default: false) +- `cache_ttl_seconds`: Authorization cache TTL in seconds (default: 300) +- `hierarchy_max_depth`: Maximum hierarchy depth to prevent infinite loops (default: 10) +- `enable_audit_by_default`: Whether to enable audit logging on installation (default: false) ## Using Cached Authorization