fix(docs): update to latest code and functionnalities

This commit is contained in:
2026-03-27 22:06:50 +01:00
parent 145f50749b
commit b3d9c8dbce
+69 -56
View File
@@ -22,7 +22,7 @@ pgmorbac is built on these principles:
1. **Pure PostgreSQL**: No external dependencies 1. **Pure PostgreSQL**: No external dependencies
2. **Schema Isolation**: All objects in `morbac` schema 2. **Schema Isolation**: All objects in `morbac` schema
3. **Default Deny**: No permission = access denied 3. **Default Deny**: No permission = access denied
4. **Prohibition Precedence**: Prohibitions checked first, always override permissions 4. **Prohibition Precedence**: Prohibitions override permissions at equal or unset priority; a permission with strictly higher priority wins
5. **Organization-Centric**: All policies scoped to organizations 5. **Organization-Centric**: All policies scoped to organizations
6. **Multi-Tenant Native**: Users and resources can span organizations 6. **Multi-Tenant Native**: Users and resources can span organizations
@@ -39,16 +39,19 @@ flowchart TD
D --> G D --> G
E --> G E --> G
F --> G F --> G
G --> H{Check Prohibitions} G --> H[Find Highest-Priority Prohibition]
H -->|Found| I[DENY] G --> I[Find Highest-Priority Permission]
H -->|Not Found| J{Check Permissions} H --> J{Compare Priorities}
J -->|Found| K[ALLOW] I --> J
J -->|Not Found| L[DENY - Default] J -->|Permission priority > Prohibition priority| K[ALLOW]
J -->|Prohibition exists, no higher-priority permission| L[DENY]
J -->|No prohibition, permission found| K
J -->|Neither found| M[DENY - Default]
``` ```
### Performance Optimizations ### Performance Optimizations
**Prohibition-first checking:** Prohibitions are checked before permissions for early exit on denial. **Priority-based evaluation:** Both prohibitions and permissions are evaluated; the higher-priority rule wins. At equal or unset priority, prohibition overrides permission.
**Indexed foreign keys:** All foreign key relationships have indexes for fast joins. **Indexed foreign keys:** All foreign key relationships have indexes for fast joins.
@@ -102,11 +105,11 @@ erDiagram
**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 `(senior_activity, junior_activity)`. Permission on a senior activity also grants junior 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 `(senior_view, junior_view)`. Access on a senior view also grants junior 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.
@@ -121,10 +124,10 @@ erDiagram
Temporal role delegation with time bounds. Temporal role delegation with time bounds.
**Key columns:** **Key columns:**
- `delegator_user_id`: User granting the role - `delegator_id`: User granting the role
- `delegate_user_id`: User receiving the role - `delegatee_id`: User receiving the role
- `role_id`, `org_id`: Role being delegated - `role_id`, `org_id`: Role being delegated
- `valid_from`, `valid_until`: Time window (NULL = indefinite) - `valid_from`, `valid_until`: Time window
**Behavior:** Automatically included in `get_comprehensive_roles()` when active. **Behavior:** Automatically included in `get_comprehensive_roles()` when active.
@@ -143,7 +146,7 @@ Explicit role prohibitions with highest precedence.
Separation of Duty constraints enforce mutually exclusive roles. Separation of Duty constraints enforce mutually exclusive roles.
**Key columns:** **Key columns:**
- `role1_id`, `role2_id`: Conflicting role pair - `role_a_id`, `role_b_id`: Conflicting role pair
- `org_id`: Organization scope - `org_id`: Organization scope
- `description`: Explanation of conflict - `description`: Explanation of conflict
@@ -154,8 +157,8 @@ Separation of Duty constraints enforce mutually exclusive roles.
Constrains the number of users per role. Constrains the number of users per role.
**Key columns:** **Key columns:**
- `role_id`, `org_id`: Role to constrain - `role_id`: Role to constrain (primary key)
- `min_users`: Minimum required users (default 0) - `min_users`: Minimum required users (NULL = no minimum)
- `max_users`: Maximum allowed users (NULL = unlimited) - `max_users`: Maximum allowed users (NULL = unlimited)
**Behavior:** Validated via `check_cardinality_violation()` before role assignment. **Behavior:** Validated via `check_cardinality_violation()` before role assignment.
@@ -165,11 +168,11 @@ Constrains the number of users per role.
Dynamically computed roles via custom functions. Dynamically computed roles via custom functions.
**Key columns:** **Key columns:**
- `role_id`, `org_id`: Role to compute - `role_id`: Role to compute (primary key)
- `evaluator`: Function returning `TABLE(user_id UUID, org_id UUID)` - `condition_evaluator`: Function `f(user_id UUID, org_id UUID) RETURNS BOOLEAN`
- `description`: Explanation of computation logic - `description`: Explanation of computation logic
**Behavior:** Function is called at runtime to determine role membership. Included in `get_comprehensive_roles()`. **Behavior:** Function is called at runtime to determine if a user has the role. Included in `get_comprehensive_roles()`.
**morbac.cross_org_rules** **morbac.cross_org_rules**
@@ -179,6 +182,7 @@ Inter-organizational access policies.
- `source_org_id`: Organization where role is held - `source_org_id`: Organization where role is held
- `target_org_id`: Organization where access is granted - `target_org_id`: Organization where access is granted
- `role_id`, `activity`, `view`: Policy specification - `role_id`, `activity`, `view`: Policy specification
- `context_id`: Contextual condition
- `modality`: Permission or prohibition - `modality`: Permission or prohibition
**Behavior:** Allows roles in source organization to access resources in target organization. **Behavior:** Allows roles in source organization to access resources in target organization.
@@ -189,9 +193,9 @@ Administration meta-policies for delegated management.
**Key columns:** **Key columns:**
- `org_id`, `role_id`: Role receiving admin capabilities - `org_id`, `role_id`: Role receiving admin capabilities
- `can_manage_policies`: Can create/modify policies - `admin_activity`: Admin action (e.g., `create_rule`, `assign_role`, `delete_rule`)
- `can_manage_roles`: Can create/modify roles - `admin_target`: What can be administered (e.g., `rules`, `roles`, `users`)
- `can_manage_users`: Can assign/revoke user roles - `context_id`, `modality`: Context condition and permission/prohibition
**Behavior:** Enables organization-scoped administrators without database superuser privileges. **Behavior:** Enables organization-scoped administrators without database superuser privileges.
@@ -201,8 +205,8 @@ Administration meta-policies for delegated management.
Multi-OrBAC implements four modalities: Multi-OrBAC implements four modalities:
1. **Permission**: Allows action (if no prohibition) 1. **Permission**: Allows action (if no higher-or-equal-priority prohibition)
2. **Prohibition**: Denies action (always wins) 2. **Prohibition**: Denies action (wins at equal or unset priority; loses to higher-priority permission)
3. **Obligation**: Must be done (informational only) 3. **Obligation**: Must be done (informational only)
4. **Recommendation**: Should be done (informational only) 4. **Recommendation**: Should be done (informational only)
@@ -212,11 +216,14 @@ Only permissions and prohibitions affect `is_allowed()` decisions.
```mermaid ```mermaid
flowchart LR flowchart LR
A[Check Rule] --> B{Prohibition?} A[Applicable Rules] --> B[Highest-Priority Prohibition]
B -->|Yes| C[DENY] A --> C[Highest-Priority Permission]
B -->|No| D{Permission?} B --> D{Permission priority\n> Prohibition priority?}
C --> D
D -->|Yes| E[ALLOW] D -->|Yes| E[ALLOW]
D -->|No| F[DENY] D -->|No, prohibition exists| F[DENY]
D -->|No prohibition, permission exists| E
D -->|Neither| G[DENY - Default]
``` ```
**Obligations and Recommendations** **Obligations and Recommendations**
@@ -438,7 +445,7 @@ Alice delegates her approver role to Bob for one week:
```sql ```sql
INSERT INTO morbac.delegations ( INSERT INTO morbac.delegations (
delegator_user_id, delegate_user_id, role_id, org_id, valid_until delegator_id, delegatee_id, role_id, org_id, valid_until
) VALUES ( ) VALUES (
alice_id, bob_id, approver_role_id, org_id, NOW() + INTERVAL '7 days' alice_id, bob_id, approver_role_id, org_id, NOW() + INTERVAL '7 days'
); );
@@ -473,12 +480,12 @@ The same person cannot both create and approve invoices:
```sql ```sql
-- Define the conflict -- Define the conflict
INSERT INTO morbac.sod_conflicts (role1_id, role2_id, org_id, description) INSERT INTO morbac.sod_conflicts (role_a_id, role_b_id, org_id, description)
VALUES (creator_role_id, approver_role_id, org_id, 'Cannot create and approve'); VALUES (creator_role_id, approver_role_id, org_id, 'Cannot create and approve');
-- Check before assigning approver role to Alice (who is already a creator) -- Check before assigning approver role to Alice (who is already a creator)
SELECT morbac.check_sod_violation(alice_id, org_id); SELECT morbac.check_sod_violation(alice_id, approver_role_id, org_id);
-- Returns: ['invoice_creator', 'invoice_approver'] if conflict would occur -- Returns: TRUE if conflict would occur, FALSE if safe to assign
``` ```
### Cardinality Constraints ### Cardinality Constraints
@@ -490,11 +497,12 @@ Enforce minimum and maximum users per role.
Require 2-5 administrators at all times: Require 2-5 administrators at all times:
```sql ```sql
INSERT INTO morbac.role_cardinality (role_id, org_id, min_users, max_users) INSERT INTO morbac.role_cardinality (role_id, min_users, max_users)
VALUES (admin_role_id, org_id, 2, 5); VALUES (admin_role_id, 2, 5);
-- Check before adding/removing admins -- Check before adding an admin (pass FALSE when removing)
SELECT morbac.check_cardinality_violation(admin_role_id, org_id); SELECT morbac.check_cardinality_violation(admin_role_id); -- adding (default)
SELECT morbac.check_cardinality_violation(admin_role_id, FALSE); -- removing
-- Returns: error message if would violate constraint, NULL if valid -- Returns: error message if would violate constraint, NULL if valid
``` ```
@@ -534,14 +542,17 @@ Roles computed dynamically from application data.
Automatically grant project_lead role to users leading projects: Automatically grant project_lead role to users leading projects:
```sql ```sql
-- Function returns users who are project leaders -- Function returns TRUE if user is a project leader in the given org
CREATE FUNCTION morbac.eval_project_leads() CREATE FUNCTION morbac.eval_project_lead(p_user_id UUID, p_org_id UUID)
RETURNS TABLE(user_id UUID, org_id UUID) STABLE AS $$ RETURNS BOOLEAN STABLE AS $$
SELECT lead_user_id, org_id FROM app.projects WHERE lead_user_id IS NOT NULL; SELECT EXISTS(
SELECT 1 FROM app.projects
WHERE lead_user_id = p_user_id AND org_id = p_org_id
);
$$ LANGUAGE SQL; $$ LANGUAGE SQL;
INSERT INTO morbac.derived_roles (role_id, org_id, evaluator) INSERT INTO morbac.derived_roles (role_id, condition_evaluator)
VALUES (project_lead_role_id, org_id, 'morbac.eval_project_leads'::regproc); VALUES (project_lead_role_id, 'morbac.eval_project_lead'::regproc);
-- Users automatically get project_lead permissions when they lead a project -- Users automatically get project_lead permissions when they lead a project
``` ```
@@ -556,9 +567,10 @@ Global auditors can review subsidiary financial data:
```sql ```sql
INSERT INTO morbac.cross_org_rules ( INSERT INTO morbac.cross_org_rules (
source_org_id, target_org_id, role_id, activity, view, modality source_org_id, target_org_id, role_id, activity, view, context_id, modality
) VALUES ( ) VALUES (
global_hq_id, subsidiary_id, auditor_role_id, 'read', 'financials', 'permission' global_hq_id, subsidiary_id, auditor_role_id, 'read', 'financials',
(SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission'
); );
-- Auditor from Global HQ can now access Subsidiary data -- Auditor from Global HQ can now access Subsidiary data
@@ -574,12 +586,13 @@ Delegate administrative capabilities to specific roles.
HR can assign users to roles without being a superuser: HR can assign users to roles without being a superuser:
```sql ```sql
-- Grant HR the ability to manage user role assignments -- Grant HR the ability to assign roles
INSERT INTO morbac.admin_rules (org_id, role_id, can_manage_users) INSERT INTO morbac.admin_rules (org_id, role_id, admin_activity, admin_target, context_id, modality)
VALUES (org_id, hr_manager_role_id, true); VALUES (org_id, hr_manager_role_id, 'assign_role', 'roles',
(SELECT id FROM morbac.contexts WHERE name = 'always'), 'permission');
-- Check if HR user can assign roles -- Check if HR user can assign roles
SELECT morbac.is_admin_allowed(hr_user_id, org_id, 'manage_users'); -- TRUE SELECT morbac.is_admin_allowed(hr_user_id, org_id, 'assign_role', 'roles'); -- TRUE
``` ```
### Temporal Constraints ### Temporal Constraints
@@ -616,7 +629,7 @@ morbac.audit_log (
old_data JSONB, -- Record state before change (UPDATE/DELETE) old_data JSONB, -- Record state before change (UPDATE/DELETE)
new_data JSONB, -- Record state after change (INSERT/UPDATE) new_data JSONB, -- Record state after change (INSERT/UPDATE)
changed_fields TEXT[], -- List of modified columns (UPDATE) changed_fields TEXT[], -- List of modified columns (UPDATE)
session_user TEXT, -- PostgreSQL session user session_username TEXT, -- PostgreSQL session user
client_addr INET, -- Client IP address client_addr INET, -- Client IP address
application_name TEXT, -- Application name from connection application_name TEXT, -- Application name from connection
metadata JSONB -- Additional custom metadata metadata JSONB -- Additional custom metadata
@@ -648,7 +661,7 @@ SELECT morbac.disable_audit('user_roles');
SELECT SELECT
timestamp, timestamp,
operation, operation,
session_user, session_username,
old_data->>'role_id' AS old_role, old_data->>'role_id' AS old_role,
new_data->>'role_id' AS new_role new_data->>'role_id' AS new_role
FROM morbac.audit_log FROM morbac.audit_log
@@ -698,7 +711,7 @@ 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)`**: Returns direct + role-hierarchy roles only (no delegations or derived roles). Use `get_comprehensive_roles()` for full resolution.
### Hierarchy Functions ### Hierarchy Functions
@@ -721,17 +734,17 @@ SELECT * FROM morbac.get_org_scope(org_uuid, 'subtree', 2);
### Validation Functions ### Validation Functions
**`check_sod_violation(user_id, org_id)`**: Returns empty array if valid, or array of conflicting role name pairs if violations exist. **`check_sod_violation(user_id, role_id, org_id)`**: Returns TRUE if assigning `role_id` to `user_id` would violate a Separation of Duty constraint, FALSE otherwise.
**`check_cardinality_violation(role_id, org_id)`**: Returns error message if constraint violated, NULL if valid. **`check_cardinality_violation(role_id, adding?)`**: Returns error message if adding/removing a user would violate cardinality constraints, NULL if valid. `adding` defaults to TRUE.
**`detect_rule_conflicts(org_id, role_id, activity, view, context_id, modality, exclude_id?)`**: Returns rows describing rules that directly conflict with the given tuple due to modality precedence. `exclude_id` is used on UPDATE to skip the rule being modified. Also fires automatically as a non-blocking trigger warning on every INSERT or UPDATE to `morbac.rules`. **`detect_rule_conflicts(org_id, role_id, activity, view, context_id, modality, exclude_id?)`**: Returns rows describing rules that directly conflict with the given tuple due to modality precedence. `exclude_id` is used on UPDATE to skip the rule being modified. Also fires automatically as a non-blocking trigger warning on every INSERT or UPDATE to `morbac.rules`.
### Administration Functions ### 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, admin_activity, admin_target)`**: Check if user has administrative permission for the given activity/target combination.
**`eval_derived_role(user_id, org_id, derived_role_id)`**: Evaluate if user has derived role. **`eval_derived_role(evaluator, user_id, org_id)`**: Evaluate a derived role condition function (REGPROC). Returns BOOLEAN.
### Context Functions ### Context Functions
@@ -854,7 +867,7 @@ CREATE POLICY doc_access ON app.documents FOR SELECT USING (
### Optimization Strategies ### Optimization Strategies
Prohibition-first checking provides early exit on denial for both security and performance. Priority-based evaluation compares the best prohibition and best permission in a single pass.
For large hierarchies, use materialized views: For large hierarchies, use materialized views:
@@ -953,7 +966,7 @@ SELECT EXISTS(
| Aspect | Traditional RBAC | Multi-OrBAC (pgmorbac) | | Aspect | Traditional RBAC | Multi-OrBAC (pgmorbac) |
|--------|-----------------|-------------------------| |--------|-----------------|-------------------------|
| **Multi-tenancy** | Single tenant or complex workarounds | Native multi-organization | | **Multi-tenancy** | Single tenant or complex workarounds | Native multi-organization |
| **Prohibition** | No standard support | First-class, always wins | | **Prohibition** | No standard support | First-class, wins at equal or unset priority |
| **Context** | Static permissions | Dynamic, condition-based | | **Context** | Static permissions | Dynamic, condition-based |
| **Hierarchy** | Basic (roles only) | Full (orgs, roles, activities, views) | | **Hierarchy** | Basic (roles only) | Full (orgs, roles, activities, views) |
| **Delegation** | Manual implementation | Native temporal delegation | | **Delegation** | Manual implementation | Native temporal delegation |