Permission System
Cloudillo’s permission system has two pillars that work together to control access to all resources:
- ABAC Policies (profile/community-level) — Configurable TOP and BOTTOM policy rules that define hard constraints and guarantees
- Discretionary Access Control — The content creator’s own choices: visibility levels, explicit audience, file shares, and access grants
These two pillars combine in a layered evaluation:
Pillar 1: ABAC Policies
Attribute-Based Access Control evaluates rules based on attributes of users, resources, and context. In Cloudillo, ABAC is used for profile-level (community/company) policies that set boundaries around the discretionary access decisions.
The Four-Object Model
ABAC decisions involve four types of objects:
1. Subject (Who)
The user or entity requesting access.
Roles come from a fixed hierarchy — public, follower, supporter, contributor,
moderator, leader (each inherits the lower ones) — plus the site-admin role SADM,
which is checked separately by require_admin rather than through ABAC.
2. Action (What)
The operation being attempted, in resource:operation format:
The operation string maps directly to the middleware on the route — for example
PATCH /api/files/{id} and DELETE /api/files/{id} both run check_perm_file("write"),
so deletion is gated by the same file:write check rather than a separate file:delete.
Action bodies are immutable; status is not
An action’s signed payload (type, audience, visibility) is content-addressed and fixed at
creation. What action:write controls is the mutable status — accepting, rejecting,
dismissing, deleting, or publishing a scheduled draft — never the payload itself.
3. Object (Resource)
The resource being accessed. Must implement the AttrSet trait:
4. Environment (Context)
Contextual factors for the request:
TOP Policy (Constraints)
Defines maximum permissions — what is never allowed, regardless of discretionary settings. Evaluated first; if a rule matches with Deny effect, access is immediately denied.
Use case: Community or company-wide restrictions.
Examples:
BOTTOM Policy (Guarantees)
Defines minimum permissions — what is always allowed, regardless of other rules. Evaluated second; if a rule matches with Allow effect, access is immediately granted.
Use case: Platform guarantees and special role privileges.
Examples:
Policy Operators
A rule holds a list of conditions; all conditions must match for the rule to fire (implicit AND — there is no separate And/Or operator). Each condition uses one of:
Comparison: Equals, NotEquals, GreaterThan, LessThan
Set/string: Contains, NotContains, In
Role: HasRole — checks if the subject has a specific role
Pillar 2: Discretionary Access Control
Between the TOP and BOTTOM policies, discretionary access control determines access based on the content creator’s own choices. This is the primary day-to-day access mechanism.
Ownership
The simplest check: owners always have full access to their own resources. The tenant is treated as owner-equivalent, so a community profile owns the resources stored on it.
Separately, the leader role short-circuits to ALLOW for any operation (see the evaluation order below), so community leaders effectively have owner-level reach.
Visibility Levels
Content creators set visibility when creating resources. This is a discretionary choice stored as a single character in the database (or NULL for direct).
Hierarchy (most to least permissive):
| Code | Level | Who can access |
|---|---|---|
P |
Public | Anyone, including unauthenticated users |
V |
Verified | Any authenticated user from any federated instance |
2 |
SecondDegree | Friend of friend (reserved for voucher token system) |
F |
Follower | Authenticated users who follow the owner |
C |
Connected | Authenticated users with mutual connection |
NULL |
Direct | Only owner + explicit audience |
The system computes the subject’s access level based on their relationship with the resource owner, then checks if it meets the visibility requirement:
| Subject Access Level | Description |
|---|---|
Owner |
Is the resource owner (highest) |
Connected |
Has mutual CONN with owner |
Follower |
Has FLLW to owner |
SecondDegree |
Friend of friend (future) |
Verified |
Authenticated user |
Public |
Unauthenticated (lowest) |
These levels form an ordered enum (using Rust’s PartialOrd derive), where higher levels grant access to all visibility settings that lower levels can access.
Access check: subject_access_level.can_access(resource_visibility)
For Direct visibility, the system also checks explicit audience membership — if the subject’s identity is listed in the resource’s audience field, access is granted.
Explicit Access Grants
Beyond visibility, access can be granted explicitly through several mechanisms:
File Shares (FSHR Actions)
When a user shares a file, the system creates a share entry in the database (file → recipient with a permission char) and an FSHR action token for federation. The FSHR subType maps to an access level: WRITE → Write, COMMENT → Comment, otherwise → Read. Shares attached to a folder are inherited by descendant files (the parent chain is walked, bounded to 64 levels).
The recipient gets Confirmation status and must accept the share, after which the file appears in their listing with the granted level.
Scoped Access Tokens
Access tokens can carry a scope that limits them to a single resource (used by share links):
- File scope format:
file:{file_id}:{R|C|W}→ Read / Comment / Write on that file - A scope for a folder extends to files nested under it; a scope for a document root extends to its child files
apkg:publishis the only non-file scope (package upload), and grants no file access
Role-Based File Access (Communities)
For files owned by a community (a file whose owner is the tenant), member roles determine access:
- leader, moderator, contributor → Write access
- Any other community role → Read access
This applies only to files owned by the community profile, not files owned by individual users.
Discretionary Evaluation Order
When neither TOP nor BOTTOM policy matches, the discretionary layer evaluates by operation:
The single pre-computed access_level attribute is what carries share, scoped-token,
ownership, and community-role grants into the check — the read and write branches above
just compare against it.
Complete Evaluation Flow
When a permission check is requested, the full flow is:
Evaluation Example
Request: Bob wants to read Alice’s connected-only file
Integration with Routes
Cloudillo uses permission middleware to enforce access control on HTTP routes:
Object-bound middleware (check_perm_file, check_perm_action, check_perm_profile)
loads the resource, computes the subject’s relationship to the owner, and runs the full
evaluation flow. check_perm_create evaluates the collection policy (role/quota) before
the object exists. require_admin is a plain SADM-role gate, not an ABAC check.
Examples
Example 1: Public File Access
Alice uploads a public file:
Bob reads the file (no authentication needed):
Example 2: Connected-Only File
Alice uploads a connected-only file:
Bob tries to read (not connected):
Charlie reads (connected to Alice):
Example 3: File Share Override
Alice has a Connected-only file, but wants to share it with Dave (who is not connected):
Dave can now read the file despite not being connected:
Attribute Set Implementations
Cloudillo implements the AttrSet trait for different resource types, providing consistent attribute access for permission evaluation.
The attribute keys are what policy conditions reference. Visibility is exposed as its
lowercase string form (public, verified, second_degree, follower, connected,
direct), not the single-char DB code.
File Attributes (FileAttrs)
Action Attributes (ActionAttrs)
Profile Attributes (ProfileAttrs)
Subject Attributes for CREATE (SubjectAttrs)
Security Best Practices
Default Deny
The system defaults to denying access unless explicitly allowed. Unknown visibility values are parsed as Direct (most restrictive). Unknown access levels default to None.
Validate Server-Side
Client-side visibility checks are for UX only (show/hide UI elements). The server always validates permissions before serving resources, regardless of client-side checks.
Audit Permission Denials
All permission denials are logged with debug-level tracing, including subject identity, action attempted, visibility level, access level, and relationship status.
Immutable Actions
Action tokens are cryptographically signed and content-addressed. They cannot be modified after creation. Visibility and audience are set at creation time and cannot be changed.
See Also
- Access Control — Token-based authentication
- Actions — Action tokens for relationships (CONN, FLLW, FSHR)
- System Architecture — Overall system design
- Identity System — User identity and authentication