docs: cleanup documentation files
This commit is contained in:
@@ -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 |
|
||||
|
||||
+13
-18
@@ -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
|
||||
|
||||
+7
-7
@@ -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
|
||||
|
||||
|
||||
+132
-230
@@ -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
|
||||
EXISTS (
|
||||
SELECT 1 FROM app.document_orgs
|
||||
WHERE document_id = app.documents.id
|
||||
AND org_id = morbac.current_org_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
|
||||
|
||||
+8
-8
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user