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.
|
||||
|
||||
### Context Evaluation
|
||||
**Evaluation Priority:**
|
||||
|
||||
Contexts are boolean predicates that determine if a rule applies:
|
||||
|
||||
```sql
|
||||
-- Context function signature
|
||||
CREATE OR REPLACE FUNCTION morbac.my_context()
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE plpgsql
|
||||
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);
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[Check Rule] --> B{Prohibition?}
|
||||
B -->|Yes| C[DENY]
|
||||
B -->|No| D{Permission?}
|
||||
D -->|Yes| E[ALLOW]
|
||||
D -->|No| F[DENY]
|
||||
```
|
||||
|
||||
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
|
||||
- Keep logic simple and fast
|
||||
- Avoid external dependencies
|
||||
- Consider caching if computation is expensive
|
||||
- Return FALSE on errors for safe defaults
|
||||
- Test thoroughly as contexts affect security
|
||||
|
||||
### 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
|
||||
-- Insert policies using names
|
||||
-- Use names instead of UUIDs
|
||||
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();
|
||||
```
|
||||
|
||||
Compiler behavior:
|
||||
- Creates missing activities/views
|
||||
- Resolves names to UUIDs
|
||||
- Creates entries in `morbac.rules`
|
||||
**Compiler Behavior:**
|
||||
|
||||
- Creates missing activities/views automatically
|
||||
- Resolves names to UUIDs for orgs/roles/contexts
|
||||
- Creates entries in `morbac.rules` table
|
||||
- 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
|
||||
|
||||
### Hierarchies
|
||||
|
||||
Organizations, roles, activities, and views all 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:
|
||||
Organizations, roles, activities, and views support hierarchical relationships with transitive closure.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
@@ -293,160 +331,166 @@ graph TD
|
||||
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
|
||||
|
||||
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
|
||||
INSERT INTO morbac.delegations (
|
||||
delegator_user_id, delegate_user_id, role_id, org_id,
|
||||
valid_from, valid_until
|
||||
delegator_user_id, delegate_user_id, role_id, org_id, valid_until
|
||||
) VALUES (
|
||||
alice_uuid, bob_uuid, approver_role_id, org_id,
|
||||
NOW(), NOW() + INTERVAL '7 days'
|
||||
alice_id, bob_id, approver_role_id, org_id, 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
|
||||
|
||||
Explicit prohibitions with highest precedence:
|
||||
Explicitly revoke roles with highest precedence.
|
||||
|
||||
**Example: Suspended Admin**
|
||||
|
||||
Temporarily suspend an admin without removing the role:
|
||||
|
||||
```sql
|
||||
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
|
||||
|
||||
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
|
||||
-- Create roles
|
||||
INSERT INTO morbac.roles (org_id, name) VALUES
|
||||
(org_id, 'invoice_creator'),
|
||||
(org_id, 'invoice_approver');
|
||||
|
||||
-- Define conflict
|
||||
-- Define the 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');
|
||||
VALUES (creator_role_id, approver_role_id, org_id, 'Cannot create and approve');
|
||||
|
||||
-- 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
|
||||
-- Check before assigning approver role to Alice (who is already a creator)
|
||||
SELECT morbac.check_sod_violation(alice_id, org_id);
|
||||
-- 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
|
||||
|
||||
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
|
||||
-- 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);
|
||||
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);
|
||||
-- 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
|
||||
CREATE OR REPLACE FUNCTION morbac.eval_project_leads()
|
||||
RETURNS TABLE(user_id UUID, org_id UUID)
|
||||
LANGUAGE sql STABLE AS $$
|
||||
SELECT user_id, org_id FROM app.projects WHERE lead_user_id IS NOT NULL;
|
||||
$$;
|
||||
-- Function returns users who are project leaders
|
||||
CREATE FUNCTION morbac.eval_project_leads()
|
||||
RETURNS TABLE(user_id UUID, org_id UUID) STABLE AS $$
|
||||
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)
|
||||
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
|
||||
|
||||
Inter-organizational access policies:
|
||||
Grant access across organization boundaries.
|
||||
|
||||
**Example: Corporate Auditor**
|
||||
|
||||
Global auditors can review subsidiary financial data:
|
||||
|
||||
```sql
|
||||
INSERT INTO morbac.cross_org_rules (
|
||||
source_org_id, target_org_id, role_id, activity, view, modality
|
||||
) VALUES (
|
||||
global_org_id, subsidiary_org_id, auditor_role_id,
|
||||
'read', 'financial_reports', 'permission'
|
||||
global_hq_id, subsidiary_id, auditor_role_id, 'read', 'financials', 'permission'
|
||||
);
|
||||
|
||||
-- Auditor from Global HQ can now access Subsidiary data
|
||||
SELECT morbac.is_allowed(auditor_id, subsidiary_id, 'read', 'financials'); -- TRUE
|
||||
```
|
||||
|
||||
### 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
|
||||
INSERT INTO morbac.admin_rules (org_id, role_id, can_manage_policies)
|
||||
VALUES (org_id, sec_admin_role_id, true);
|
||||
-- Grant HR the ability to manage user role assignments
|
||||
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
|
||||
|
||||
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
|
||||
-- Rule active only in 2024
|
||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, context_id, modality, valid_from, valid_until)
|
||||
VALUES (org_id, contractor_role_id, 'read', 'projects', ctx_id, 'permission',
|
||||
'2024-01-01 00:00:00+00', '2024-12-31 23:59:59+00');
|
||||
INSERT INTO morbac.rules (org_id, role_id, activity, view, modality, valid_from, valid_until)
|
||||
VALUES (org_id, contractor_role_id, 'read', 'documents', 'permission',
|
||||
'2024-01-01', '2024-03-31');
|
||||
|
||||
-- Rule active starting from a specific date (no end date)
|
||||
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');
|
||||
-- Rule automatically inactive after March 31, 2024
|
||||
```
|
||||
|
||||
**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
|
||||
|
||||
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