Permitheus
A fast, hierarchical permission system for Rust with inheritance, delegation, and conflict resolution. Perfect for building file systems, document management systems, or any application requiring fine-grained access control.
Features
- Hierarchical Permissions: Permissions inherit from parent paths (like Unix file systems)
- Three-tier Model: User, Group, and Public permissions with configurable precedence
- Delegation: Users can grant permissions to others with optional constraints
- Conflict Resolution: Configurable policies for handling group permission conflicts
- High Performance: LRU cache delivers 16-83x speedup (40-50ns per permission check)
- Type-safe: Generic over user and group identifier types
- Zero-copy Iterators: Efficient permission enumeration
- Optional Serde Support: Serialize/deserialize permission state with one line of code
Quick Start
[]
= "0.2"
# Optional: enable serde support for easy serialization
= { = "0.2", = ["serde"] }
use *;
// Bootstrap with a system user
let system_entry = new;
let mut manager: = new;
// Grant permissions
let entry = new;
manager.add_entry.unwrap;
// Check permissions
let result = manager.request_allowed;
assert!;
Core Concepts
Hierarchical Inheritance
Permissions on /documents automatically apply to /documents/file.txt unless a more specific permission exists at a deeper path. This matches how filesystems work:
// Permission at /documents applies to all children
manager.add_entry;
// alice can read /documents/2024/report.pdf (inherited)
assert!;
// More specific permission overrides
manager.add_entry;
// Now alice CANNOT read /documents/private/secret.txt
assert!;
Three Permission Tiers
- User Permissions: Direct grants to specific users (highest priority for that user)
- Group Permissions: Grants to groups that users belong to
- Public Permissions: Available to everyone, including unauthenticated users
// Public: anyone can read
manager.add_entry;
// Group: engineers can write
manager.add_user_to_group;
manager.add_entry;
// User: specific override
manager.add_entry;
Conflict Resolution
When a user belongs to multiple groups with conflicting permissions, the configured policy determines the outcome:
// alice is in both "viewers" (allow read) and "restricted" (deny read)
manager.add_user_to_group;
manager.add_user_to_group;
// With DenyWins (default, most secure):
let manager = new;
// → Deny access if ANY group denies
// With AllowWins (most permissive):
let manager = new;
// → Allow access if ANY group allows
// With ConflictDenies (fail-safe):
let manager = new;
// → Deny access when conflicts detected
All conflicts are logged in the PermissionResult::Conflict variant for auditing.
Delegation
Users can grant permissions to others, with optional constraints:
// alice gets rwx with ability to share read
let entry = new;
manager.add_entry.unwrap;
// alice can now grant read to bob
let grant = new;
manager.add_entry.unwrap; // ✓ Succeeds
// But alice cannot grant write (not in her shareable set)
let invalid = new;
manager.add_entry.unwrap_err; // ✗ Returns GrantorLacksPermission
Performance
Permitheus uses an LRU cache (1000 entries) that is automatically managed:
| Operation | Uncached | Cached | Speedup |
|---|---|---|---|
| Direct user permission | 718ns | 43ns | 16.7x |
| Group permission | 774ns | 44ns | 17.6x |
| Deep path (not found) | 4,160ns | 50ns | 83x |
The cache is automatically cleared when permissions or group memberships change, ensuring consistency while delivering excellent performance for read-heavy workloads (typical in file systems).
Throughput: ~20-25 million permission checks/second (cached), ~1.3 million checks/second (uncached)
Groups
Groups provide an indirection layer for managing permissions across many users. However, they add complexity:
Best Use Cases:
- Simple, stable roles (e.g., "admins", "editors", "viewers")
- Large numbers of users sharing similar access patterns
- Organizational structure maps cleanly to folder hierarchy
Considerations:
- Multiple group memberships can create conflicts (handled by
ConflictResolution) - "Share as group" vs "share as user" - group shares can be revoked by any group member
- If your structure doesn't align with groups, user-based permissions are simpler
The library provides add_user_to_group() and group permission management. Conflicts are detected and resolved at runtime according to your chosen policy.
Persistence
Permitheus is an in-memory permission manager. It does not handle persistence directly - you're responsible for storing and loading the permission data.
What Needs to be Persisted
You need to persist two pieces of state:
- Permission Entries - All
PermissionEntrystructs (user, group, and public permissions) - Group Memberships - All
UserGroupRelationstructs (which users belong to which groups) - Conflict Policy - Your chosen
ConflictResolutionstrategy
Simple Approach: Using dump() with Serde
Enable the serde feature for automatic serialization:
[]
= { = "0.2", = ["serde"] }
= "1.0" # or serde_cbor, bincode, etc.
use *;
use fs;
// Shutdown: export and serialize
let dump = manager.dump;
let json = to_string?;
write?;
// Startup: deserialize and restore
let json = read_to_string?;
let = from_str?;
let manager = new;
Note: This requires your U and G types to implement Serialize and Deserialize. Standard types like String, u64, Uuid all work out of the box.
Advanced Approach: Manual Extraction
Extract the current state and save it to your database/storage:
// Option 1: Manual extraction (if you track changes)
// You already have the entries and groups from your DB
// Option 2: Export current state (if you need to dump everything)
// Note: The library doesn't provide an export method, so you'd track
// entries yourself as you add/remove them
// Save to database
let state = PersistedState ;
// Serialize and save (example with serde_json)
let json = to_string?;
write?;
// Or save to SQL database
for entry in state.entries
Startup Routine
Load state from storage and reconstruct the manager:
// Load from database
let entries: =
db.query?
.into_iter
.map
.collect;
let groups: =
db.query?
.into_iter
.map
.collect;
// Reconstruct the manager
let manager = new;
// Manager is ready to use, cache starts empty
Recommended Pattern: Track Changes
Since the library doesn't provide state export, maintain a parallel list of changes:
Database Schema Example
(
id INTEGER PRIMARY KEY,
path TEXT NOT NULL,
grantee_type TEXT NOT NULL, -- 'user', 'group', or 'public'
grantee_id TEXT, -- NULL for public
mode TEXT NOT NULL, -- 'allow' or 'deny'
read BOOLEAN NOT NULL,
write BOOLEAN NOT NULL,
execute BOOLEAN NOT NULL,
grantor_id TEXT NOT NULL,
grantor_as_group TEXT,
shareable_read BOOLEAN,
shareable_write BOOLEAN,
shareable_execute BOOLEAN,
expiration_secs INTEGER,
entry_id TEXT, -- Application-defined ID
UNIQUE(path, grantee_type, grantee_id)
);
(
user_id TEXT NOT NULL,
group_id TEXT NOT NULL,
PRIMARY KEY (user_id, group_id)
);
(
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
INSERT INTO config (key, value) VALUES ('conflict_policy', 'deny_wins');
The entry_id Field
The entry_id field in PermissionEntry is provided for your use:
Use it to track which database row corresponds to which permission entry.
Why in-memory? Different applications have different persistence needs (SQL, NoSQL, file-based, event sourcing). Permitheus focuses on fast, correct permission logic and lets you choose your storage layer.
Resource Management
Since Permitheus doesn't know about your actual resources (files, documents, etc.), it provides utilities for keeping permissions in sync:
// Delete a folder and all permissions beneath it
manager.delete_resource_recursive;
// Move a resource, preserving its permissions
manager.move_resource;
Moving resources preserves existing permissions and inherits from the new parent path.
API Overview
Permission Checking
// Detailed result with conflict information
let result: PermissionResult = manager.request_allowed;
match result
// Simple boolean check
if manager.user_can_access
// Check public access
if manager.is_public
Permission Management
// Add permission entry
manager.add_entry?; // Returns AddEntryError::GrantorLacksPermission
// Remove permission entry
manager.remove_entry?; // Returns RemoveEntryError
// Modify group membership
manager.add_user_to_group; // Returns bool (was added)
manager.remove_user_from_group; // Returns bool (was removed)
Querying
// List all permission entries for a user (user + group, not public)
for in manager.list_user_permissions
Note: This returns raw entries, not effective permissions after inheritance. Use request_allowed() to check effective permissions for a specific resource.
Error Handling
Permitheus uses type-safe errors:
Examples
See examples/ for:
profile_manual.rs- Performance profiling and benchmarkingprofile_hotpath.rs- Detailed permission checking scenarios
Run with: cargo run --release --example profile_manual
Testing
All tests pass with the cache enabled, ensuring correctness is maintained.
License
MIT OR Apache-2.0 (your choice)
Status
Experimental but functional. Used for personal projects. Breaking changes may occur.
Built for a Google Drive-like clone where permission checking happens on every file access. Optimized for read-heavy workloads with occasional writes.