AccessControl & Permission Model
aegis beginner 4 min read
ELI5
A guest-list clipboard with sticky notes: each note says who can do what to which document and optionally when it expires. The door staff check the list, tear off expired notes, and can erase a whole row when a guest is removed — but they can’t erase just one permission from a multi-permission row.
Technical Deep Dive
AccessControl in src/access_control.rs is a flat Vec<AccessEntry> with a hard cap of 10,000 entries (max_entries). There is no secondary index — every lookup is a linear scan.
AccessEntry Fields
| Field | Type | Notes |
|---|---|---|
user_id | String | Caller-supplied; no format validation |
doc_id | String | Caller-supplied; no format validation |
permission | Permission | `Read |
granted_at_ms | u64 | Wall-clock ms at construction |
expires_at_ms | Option<u64> | None = never expires |
is_valid() checks current wall clock against expires_at_ms. Entries with None are always valid.
Permission Enum
Read → "read"Write → "write"Delete → "delete"Admin → "admin"check_permission(user_id, doc_id, action) maps action: &str to the enum. An unrecognised string returns Err("Unknown action: ...").
Entry Lifecycle
stateDiagram-v2 [*] --> Valid : grant_permission() Valid --> Expired : expires_at_ms elapsed Valid --> Revoked : revoke_permission() Expired --> Revoked : revoke_permission() (still removed) Revoked --> [*]revoke_permission(user_id, doc_id) removes ALL entries matching the (user_id, doc_id) pair regardless of permission type or expiry state. It returns true if any were removed, false if the pair was not found.
Capacity Behaviour
When entries.len() >= 10_000, grant_permission returns Err("Access control table full"). There is no LRU eviction — callers must call clear() or revoke_permission() explicitly to free space.
Key Terms
- AccessEntry → Single (user, doc, permission) grant with optional expiry; defined in
src/access_control.rs - Permission → Four-variant enum:
Read,Write,Delete,Admin - expires_at_ms → Unix millisecond timestamp after which
is_valid()returns false - max_entries → Hard cap of 10,000 entries; exceeding it blocks new grants
- revoke_permission → Removes all entries for a
(user_id, doc_id)pair in one call
Q&A
Q: grant_permission appends a new entry even if an identical one already exists — does this cause double-grant?
A: Yes. Two identical entries means check_permission finds the first matching entry and returns true, which is harmless, but get_permissions returns duplicates. Callers are responsible for idempotency.
Q: What does valid_count() return versus entry_count()?
A: entry_count() is the raw Vec length; valid_count() is the subset where is_valid() is true — i.e., non-expired entries. The difference represents stale entries still consuming table capacity.
Q: with_expiry(expires_at_ms) — can it be called after an entry is already inserted?
A: No. with_expiry is a builder method on the newly created AccessEntry; once pushed into entries, there is no mutation API. Expiry must be set before calling grant_permission via the internal AccessEntry::new().with_expiry() builder (not directly exposed on AccessControl).
Examples
Grant a time-limited write permission and verify expiry:
let mut ac = AccessControl::new();// grant with expiry 1 ms in the past → immediately invalidlet expired_ms = 1u64;let entry = AccessEntry::new("u1", "doc1", Permission::Write).with_expiry(expired_ms);ac.entries.push(entry); // direct push bypasses grant_permission for testinglet valid = ac.valid_count(); // 0 — entry is expiredassert_eq!(valid, 0);neighbors on the map
- FNP End-to-End Encryption & Zero Trust Architecture understanding FNP's security layers
- LORE RBAC & Airlock Auth Flow implementing authentication in a new LORE page
- JWT Auth & RBAC Hierarchy adding a new permission to a tool
- Auth-First WebSocket Handshake debugging AUTH_REQUIRED errors on the relay