docs(documentation): add more examples
This commit is contained in:
+184
-140
@@ -208,83 +208,121 @@ Multi-OrBAC implements four modalities:
|
|||||||
|
|
||||||
Only permissions and prohibitions affect `is_allowed()` decisions.
|
Only permissions and prohibitions affect `is_allowed()` decisions.
|
||||||
|
|
||||||
### Context Evaluation
|
**Evaluation Priority:**
|
||||||
|
|
||||||
Contexts are boolean predicates that determine if a rule applies:
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
```sql
|
A[Check Rule] --> B{Prohibition?}
|
||||||
-- Context function signature
|
B -->|Yes| C[DENY]
|
||||||
CREATE OR REPLACE FUNCTION morbac.my_context()
|
B -->|No| D{Permission?}
|
||||||
RETURNS BOOLEAN
|
D -->|Yes| E[ALLOW]
|
||||||
LANGUAGE plpgsql
|
D -->|No| F[DENY]
|
||||||
STABLE -- Important for RLS performance
|
|
||||||
AS $$
|
|
||||||
BEGIN
|
|
||||||
RETURN <condition>;
|
|
||||||
END;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
-- Register context
|
|
||||||
INSERT INTO morbac.contexts (name, evaluator) VALUES
|
|
||||||
('my_context', 'morbac.my_context'::regproc);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Context functions should:
|
**Example: Employee Document Access**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Employees can read documents
|
||||||
|
INSERT INTO morbac.rules (org_id, role_id, activity, view, modality)
|
||||||
|
VALUES (org_id, employee_role_id, 'read', 'documents', 'permission');
|
||||||
|
|
||||||
|
-- Suspended employees cannot (prohibition overrides permission)
|
||||||
|
INSERT INTO morbac.rules (org_id, role_id, activity, view, modality)
|
||||||
|
VALUES (org_id, suspended_role_id, 'read', 'documents', 'prohibition');
|
||||||
|
|
||||||
|
-- Check access
|
||||||
|
SELECT morbac.is_allowed(employee_id, org_id, 'read', 'documents'); -- TRUE
|
||||||
|
SELECT morbac.is_allowed(suspended_id, org_id, 'read', 'documents'); -- FALSE (prohibited)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context Evaluation
|
||||||
|
|
||||||
|
Contexts are boolean predicates that determine if a rule applies. They enable dynamic, condition-based access control.
|
||||||
|
|
||||||
|
**Context Evaluation Flow:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[Rule Matches] --> B[Evaluate Context]
|
||||||
|
B --> C{Context Returns TRUE?}
|
||||||
|
C -->|Yes| D[Rule Applies]
|
||||||
|
C -->|No| E[Rule Ignored]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example: Business Hours Context**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Create a context for business hours (Monday-Friday, 9am-5pm)
|
||||||
|
CREATE FUNCTION morbac.ctx_business_hours() RETURNS BOOLEAN STABLE AS $$
|
||||||
|
SELECT EXTRACT(DOW FROM now()) BETWEEN 1 AND 5
|
||||||
|
AND EXTRACT(HOUR FROM now()) BETWEEN 9 AND 17;
|
||||||
|
$$ LANGUAGE SQL;
|
||||||
|
|
||||||
|
INSERT INTO morbac.contexts (name, evaluator) VALUES
|
||||||
|
('business_hours', 'morbac.ctx_business_hours'::regproc);
|
||||||
|
|
||||||
|
-- Contractors can only work during business hours
|
||||||
|
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality)
|
||||||
|
SELECT org_id, contractor_role_id, 'read', 'documents', id, 'permission'
|
||||||
|
FROM morbac.contexts WHERE name = 'business_hours';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Context Best Practices:**
|
||||||
|
|
||||||
- Use STABLE (not VOLATILE) for RLS compatibility
|
- Use STABLE (not VOLATILE) for RLS compatibility
|
||||||
- Keep logic simple and fast
|
- Keep logic simple and fast
|
||||||
- Avoid external dependencies
|
- Avoid external dependencies
|
||||||
- Consider caching if computation is expensive
|
- Consider caching if computation is expensive
|
||||||
|
- Return FALSE on errors for safe defaults
|
||||||
|
- Test thoroughly as contexts affect security
|
||||||
|
|
||||||
### Policy DSL
|
### Policy DSL
|
||||||
|
|
||||||
Simplified policy declaration:
|
The Policy DSL provides a simplified way to declare policies using human-readable names instead of UUIDs, making policy management more intuitive.
|
||||||
|
|
||||||
|
**DSL Compilation Flow:**
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[Policy DSL] --> B[compile_policy]
|
||||||
|
B --> C{Resolve Names}
|
||||||
|
C --> D[Create Activities/Views]
|
||||||
|
C --> E[Lookup Orgs/Roles]
|
||||||
|
D --> F[Generate Rules]
|
||||||
|
E --> F
|
||||||
|
F --> G[morbac.rules]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example: Simple Policy Setup**
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Insert policies using names
|
-- Use names instead of UUIDs
|
||||||
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality) VALUES
|
INSERT INTO morbac.policy (org_name, role_name, activity, view, modality) VALUES
|
||||||
('Acme Corp', 'manager', 'approve', 'requests', 'permission');
|
('Acme Corp', 'employee', 'read', 'documents', 'permission');
|
||||||
|
|
||||||
-- Compile (idempotent)
|
-- Compile into actual rules
|
||||||
SELECT * FROM morbac.compile_policy();
|
SELECT * FROM morbac.compile_policy();
|
||||||
```
|
```
|
||||||
|
|
||||||
Compiler behavior:
|
**Compiler Behavior:**
|
||||||
- Creates missing activities/views
|
|
||||||
- Resolves names to UUIDs
|
- Creates missing activities/views automatically
|
||||||
- Creates entries in `morbac.rules`
|
- Resolves names to UUIDs for orgs/roles/contexts
|
||||||
|
- Creates entries in `morbac.rules` table
|
||||||
- Reports errors for missing orgs/roles/contexts
|
- Reports errors for missing orgs/roles/contexts
|
||||||
- Safe to run multiple times
|
- Safe to run multiple times (idempotent)
|
||||||
|
- Returns detailed results for each policy
|
||||||
|
|
||||||
|
**When to Use Policy DSL vs Direct Rules:**
|
||||||
|
|
||||||
|
- **Use Policy DSL**: Initial setup, bulk imports, human-readable policy files
|
||||||
|
- **Use Direct Rules**: Dynamic policies, application-generated rules, when you already have UUIDs
|
||||||
|
|
||||||
## Advanced Features
|
## Advanced Features
|
||||||
|
|
||||||
### Hierarchies
|
### Hierarchies
|
||||||
|
|
||||||
Organizations, roles, activities, and views all support hierarchical relationships with transitive closure.
|
Organizations, roles, activities, and views support hierarchical relationships with transitive closure.
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Organization hierarchy (via parent_id)
|
|
||||||
INSERT INTO morbac.orgs (name, parent_id) VALUES ('EMEA', parent_org_id);
|
|
||||||
SELECT * FROM morbac.get_org_ancestors(org_id);
|
|
||||||
SELECT * FROM morbac.get_org_descendants(org_id);
|
|
||||||
|
|
||||||
-- Role hierarchy (senior inherits from junior)
|
|
||||||
INSERT INTO morbac.role_hierarchy (senior_role_id, junior_role_id)
|
|
||||||
VALUES (manager_role_id, employee_role_id);
|
|
||||||
SELECT * FROM morbac.get_inherited_roles(role_id);
|
|
||||||
|
|
||||||
-- Activity hierarchy (parent includes children)
|
|
||||||
INSERT INTO morbac.activity_hierarchy (parent_activity, child_activity)
|
|
||||||
VALUES ('write', 'create'), ('write', 'update');
|
|
||||||
SELECT * FROM morbac.get_effective_activities('write');
|
|
||||||
|
|
||||||
-- View hierarchy (parent includes children)
|
|
||||||
INSERT INTO morbac.view_hierarchy (parent_view, child_view)
|
|
||||||
VALUES ('documents', 'invoices'), ('documents', 'contracts');
|
|
||||||
SELECT * FROM morbac.get_effective_views('documents');
|
|
||||||
```
|
|
||||||
|
|
||||||
Example hierarchy diagram:
|
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
@@ -293,160 +331,166 @@ graph TD
|
|||||||
A -.->|transitive| C
|
A -.->|transitive| C
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Example: Role Hierarchy**
|
||||||
|
|
||||||
|
Managers inherit all employee permissions:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Create role hierarchy
|
||||||
|
INSERT INTO morbac.role_hierarchy (senior_role_id, junior_role_id)
|
||||||
|
VALUES (manager_role_id, employee_role_id);
|
||||||
|
|
||||||
|
-- Grant permission to employees
|
||||||
|
INSERT INTO morbac.rules (org_id, role_id, activity, view, modality)
|
||||||
|
VALUES (org_id, employee_role_id, 'read', 'documents', 'permission');
|
||||||
|
|
||||||
|
-- Managers automatically get 'read documents' through inheritance
|
||||||
|
SELECT morbac.is_allowed(manager_id, org_id, 'read', 'documents'); -- TRUE
|
||||||
|
```
|
||||||
|
|
||||||
### Delegation
|
### Delegation
|
||||||
|
|
||||||
Temporary role delegation with time bounds:
|
Temporary role delegation with time bounds.
|
||||||
|
|
||||||
|
**Example: Vacation Delegation**
|
||||||
|
|
||||||
|
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,
|
delegator_user_id, delegate_user_id, role_id, org_id, valid_until
|
||||||
valid_from, valid_until
|
|
||||||
) VALUES (
|
) VALUES (
|
||||||
alice_uuid, bob_uuid, approver_role_id, org_id,
|
alice_id, bob_id, approver_role_id, org_id, NOW() + INTERVAL '7 days'
|
||||||
NOW(), NOW() + INTERVAL '7 days'
|
|
||||||
);
|
);
|
||||||
```
|
|
||||||
|
|
||||||
Automatically included in `get_comprehensive_roles()` when active. Set `valid_until` to NULL for indefinite delegation.
|
-- Bob can now approve during this period
|
||||||
|
SELECT morbac.is_allowed(bob_id, org_id, 'approve', 'requests'); -- TRUE (until expires)
|
||||||
|
```
|
||||||
|
|
||||||
### Negative Role Assignments
|
### Negative Role Assignments
|
||||||
|
|
||||||
Explicit prohibitions with highest precedence:
|
Explicitly revoke roles with highest precedence.
|
||||||
|
|
||||||
|
**Example: Suspended Admin**
|
||||||
|
|
||||||
|
Temporarily suspend an admin without removing the role:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
INSERT INTO morbac.negative_role_assignments (user_id, role_id, org_id, reason)
|
INSERT INTO morbac.negative_role_assignments (user_id, role_id, org_id, reason)
|
||||||
VALUES (user_uuid, admin_role_id, org_id, 'Security audit requirement');
|
VALUES (alice_id, admin_role_id, org_id, 'Under investigation');
|
||||||
|
|
||||||
|
-- Alice loses admin access even though role assignment remains
|
||||||
|
SELECT morbac.is_allowed(alice_id, org_id, 'manage', 'users'); -- FALSE
|
||||||
```
|
```
|
||||||
|
|
||||||
### Separation of Duty
|
### Separation of Duty
|
||||||
|
|
||||||
Prevents users from holding conflicting roles that could enable fraud or abuse.
|
Prevents users from holding conflicting roles.
|
||||||
|
|
||||||
**Example: Financial Controls**
|
**Example: Invoice Approval**
|
||||||
|
|
||||||
In an accounting system, the same person should not both create and approve invoices:
|
The same person cannot both create and approve invoices:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Create roles
|
-- Define the conflict
|
||||||
INSERT INTO morbac.roles (org_id, name) VALUES
|
|
||||||
(org_id, 'invoice_creator'),
|
|
||||||
(org_id, 'invoice_approver');
|
|
||||||
|
|
||||||
-- Define conflict
|
|
||||||
INSERT INTO morbac.sod_conflicts (role1_id, role2_id, org_id, description)
|
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');
|
VALUES (creator_role_id, approver_role_id, org_id, 'Cannot create and approve');
|
||||||
|
|
||||||
-- Check before assignment
|
-- Check before assigning approver role to Alice (who is already a creator)
|
||||||
SELECT morbac.check_sod_violation(alice_uuid, org_id);
|
SELECT morbac.check_sod_violation(alice_id, org_id);
|
||||||
-- Returns: empty array if valid, or ['invoice_creator', 'invoice_approver'] if conflict exists
|
-- Returns: ['invoice_creator', 'invoice_approver'] if conflict would occur
|
||||||
```
|
```
|
||||||
|
|
||||||
If Alice already has the `invoice_creator` role, attempting to assign `invoice_approver` will violate the constraint.
|
|
||||||
|
|
||||||
### Cardinality Constraints
|
### Cardinality Constraints
|
||||||
|
|
||||||
Enforce minimum and maximum users per role:
|
Enforce minimum and maximum users per role.
|
||||||
|
|
||||||
|
**Example: Admin Count Limits**
|
||||||
|
|
||||||
|
Require 2-5 administrators at all times:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Require 1-3 administrators
|
|
||||||
INSERT INTO morbac.role_cardinality (role_id, org_id, min_users, max_users)
|
INSERT INTO morbac.role_cardinality (role_id, org_id, min_users, max_users)
|
||||||
VALUES (admin_role_id, org_id, 1, 3);
|
VALUES (admin_role_id, org_id, 2, 5);
|
||||||
|
|
||||||
-- Validate before assignment
|
-- Check before adding/removing admins
|
||||||
SELECT morbac.check_cardinality_violation(admin_role_id, org_id);
|
SELECT morbac.check_cardinality_violation(admin_role_id, org_id);
|
||||||
-- Returns: error message if constraint violated, NULL if valid
|
-- Returns: error message if would violate constraint, NULL if valid
|
||||||
```
|
```
|
||||||
|
|
||||||
Dynamically computed roles via custom functions:
|
### Derived Roles
|
||||||
|
|
||||||
|
Roles computed dynamically from application data.
|
||||||
|
|
||||||
|
**Example: Project Leaders**
|
||||||
|
|
||||||
|
Automatically grant project_lead role to users leading projects:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE OR REPLACE FUNCTION morbac.eval_project_leads()
|
-- Function returns users who are project leaders
|
||||||
RETURNS TABLE(user_id UUID, org_id UUID)
|
CREATE FUNCTION morbac.eval_project_leads()
|
||||||
LANGUAGE sql STABLE AS $$
|
RETURNS TABLE(user_id UUID, org_id UUID) STABLE AS $$
|
||||||
SELECT user_id, org_id FROM app.projects WHERE lead_user_id IS NOT NULL;
|
SELECT lead_user_id, org_id FROM app.projects WHERE lead_user_id IS NOT NULL;
|
||||||
$$;
|
$$ LANGUAGE SQL;
|
||||||
|
|
||||||
INSERT INTO morbac.derived_roles (role_id, org_id, evaluator)
|
INSERT INTO morbac.derived_roles (role_id, org_id, evaluator)
|
||||||
VALUES (project_lead_role_id, org_id, 'morbac.eval_project_leads'::regproc);
|
VALUES (project_lead_role_id, org_id, 'morbac.eval_project_leads'::regproc);
|
||||||
|
|
||||||
|
-- Users automatically get project_lead permissions when they lead a project
|
||||||
```
|
```
|
||||||
|
|
||||||
### Cross-Organizational Rules
|
### Cross-Organizational Rules
|
||||||
|
|
||||||
Inter-organizational access policies:
|
Grant access across organization boundaries.
|
||||||
|
|
||||||
|
**Example: Corporate Auditor**
|
||||||
|
|
||||||
|
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, modality
|
||||||
) VALUES (
|
) VALUES (
|
||||||
global_org_id, subsidiary_org_id, auditor_role_id,
|
global_hq_id, subsidiary_id, auditor_role_id, 'read', 'financials', 'permission'
|
||||||
'read', 'financial_reports', 'permission'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- Auditor from Global HQ can now access Subsidiary data
|
||||||
|
SELECT morbac.is_allowed(auditor_id, subsidiary_id, 'read', 'financials'); -- TRUE
|
||||||
```
|
```
|
||||||
|
|
||||||
### Administration Rules
|
### Administration Rules
|
||||||
|
|
||||||
Meta-policies for policy management:
|
Delegate administrative capabilities to specific roles.
|
||||||
|
|
||||||
|
**Example: HR Manager**
|
||||||
|
|
||||||
|
HR can assign users to roles without being a superuser:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
INSERT INTO morbac.admin_rules (org_id, role_id, can_manage_policies)
|
-- Grant HR the ability to manage user role assignments
|
||||||
VALUES (org_id, sec_admin_role_id, true);
|
INSERT INTO morbac.admin_rules (org_id, role_id, can_manage_users)
|
||||||
|
VALUES (org_id, hr_manager_role_id, true);
|
||||||
|
|
||||||
SELECT morbac.is_admin_allowed(user_uuid, org_id, 'manage_policies');
|
-- Check if HR user can assign roles
|
||||||
|
SELECT morbac.is_admin_allowed(hr_user_id, org_id, 'manage_users'); -- TRUE
|
||||||
```
|
```
|
||||||
|
|
||||||
### Temporal Constraints
|
### Temporal Constraints
|
||||||
|
|
||||||
Rules and cross-organizational rules support optional temporal validity periods. Rules are only active within their specified time window.
|
Rules can have time-based validity periods.
|
||||||
|
|
||||||
**Adding temporal constraints to rules:**
|
**Example: Contractor Access**
|
||||||
|
|
||||||
|
Grant contractor access for Q1 2024 only:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- Rule active only in 2024
|
INSERT INTO morbac.rules (org_id, role_id, activity, view, modality, valid_from, valid_until)
|
||||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from, valid_until)
|
VALUES (org_id, contractor_role_id, 'read', 'documents', 'permission',
|
||||||
VALUES (org_id, contractor_role_id, 'read', 'projects', ctx_id, 'permission',
|
'2024-01-01', '2024-03-31');
|
||||||
'2024-01-01 00:00:00+00', '2024-12-31 23:59:59+00');
|
|
||||||
|
|
||||||
-- Rule active starting from a specific date (no end date)
|
-- Rule automatically inactive after March 31, 2024
|
||||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from)
|
|
||||||
VALUES (org_id, employee_role_id, 'read', 'handbook', ctx_id, 'permission',
|
|
||||||
'2024-06-01 00:00:00+00');
|
|
||||||
|
|
||||||
-- Rule expires at a specific date
|
|
||||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_until)
|
|
||||||
VALUES (org_id, temp_role_id, 'write', 'reports', ctx_id, 'permission',
|
|
||||||
'2024-12-31 23:59:59+00');
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Temporal constraints on cross-org rules:**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
INSERT INTO morbac.cross_org_rules (
|
|
||||||
source_org_id, target_org_id, role_id, activity, view, modality,
|
|
||||||
valid_from, valid_until
|
|
||||||
) VALUES (
|
|
||||||
parent_org_id, subsidiary_org_id, auditor_role_id,
|
|
||||||
'read', 'financial_data', 'permission',
|
|
||||||
'2024-01-01 00:00:00+00', '2024-03-31 23:59:59+00'
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**Checking rule validity manually:**
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT morbac.is_rule_valid(valid_from, valid_until);
|
|
||||||
-- Returns: TRUE if current time is within bounds (or no constraints set)
|
|
||||||
-- FALSE if outside validity period
|
|
||||||
```
|
|
||||||
|
|
||||||
**Behavior:**
|
|
||||||
- Both `valid_from` and `valid_until` are optional (can be NULL)
|
|
||||||
- If both are NULL, rule is always active
|
|
||||||
- If only `valid_from` is set, rule is active from that time onwards
|
|
||||||
- If only `valid_until` is set, rule is active until that time
|
|
||||||
- If both are set, rule is active only during that window
|
|
||||||
- `is_allowed()` automatically checks temporal validity for all rules
|
|
||||||
- Expired rules are effectively inactive but remain in database for audit purposes
|
|
||||||
|
|
||||||
### Audit Logging
|
### Audit Logging
|
||||||
|
|
||||||
Comprehensive audit trail for security-critical operations. The audit system tracks all INSERT, UPDATE, and DELETE operations on specified tables.
|
Comprehensive audit trail for security-critical operations. The audit system tracks all INSERT, UPDATE, and DELETE operations on specified tables.
|
||||||
|
|||||||
Reference in New Issue
Block a user