fix(docs): update to latest code and functionnalities
This commit is contained in:
+69
-56
@@ -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 |
|
||||||
|
|||||||
Reference in New Issue
Block a user