# RBAC & Permission Model **Status:** Stable draft for MVP implementation **Related:** [02-data-model.md](02-data-model.md), [06-review-workflow.md](06-review-workflow.md), [08-audit-log.md](08-audit-log.md), ADR-009 --- ## 1. Design principles 1. **Deny by default.** Every endpoint and mutation declares the permission it requires; absence of a declaration fails CI (a test enumerates all DRF views and asserts each has an explicit permission class). 2. **Roles are floors, not ceilings.** Roles grant capability sets; *ownership*, *resource state*, and *reputation gates* further constrain what a capability can act on. 3. **Server-side only.** The frontend uses the same permission descriptors (shipped in the session payload) purely to hide UI affordances; every check is re-evaluated server-side. 4. **Auditable.** Every permission-denied event on a mutating endpoint and every privileged action is written to the audit log. 5. **Separation of duties.** Authors cannot review their own submissions; reviewers cannot approve content they materially co-authored; moderators moderate but do not gain review-approval power by virtue of moderation alone. ## 2. Roles Roles are cumulative in spirit but modeled as **independent grants** (a user holds a set of roles; `learner` is implicit for every authenticated user). This avoids the classic trap where "admin" silently loses learner-specific behaviors. | Role | How acquired | Summary | |---|---|---| | **Learner** | Any registered account | Consume published content, attempt problems, track progress, discuss, report, bookmark | | **Contributor** | Self-serve after email verification + accepting the contributor agreement (CC BY-SA grant) | Everything a learner can, plus author drafts, submit for review, fork, maintain own published entities | | **Reviewer** | Granted per-topic by moderators/admins; soft requirement of reputation β‰₯ R_review (default 200) in that topic | Claim and review submissions, request changes, approve/reject within their topic grants | | **Moderator** | Appointed by admins | Handle reports/flags, hide/retract content, suspend users, manage discussions, manage reviewer grants | | **Admin** | Instance operators | Everything, including role administration, instance configuration, data export, irreversible actions | **Topic-scoped reviewing.** `Reviewer` grants carry an optional topic scope (`reviewer:math.number-theory`, `reviewer:*`). A reviewer may only claim submissions whose primary topic falls under a granted scope. This keeps review quality high without requiring global trust. **Anonymous (unauthenticated) users** can read published content, search, and view discussions. Nothing else. ## 3. Permission matrix Legend: βœ… allowed Β· πŸ”Ά allowed with condition (footnoted) Β· ❌ denied. "Own" = entity where user is owner or listed maintainer. ### 3.1 Content (Problems & Courses) | Capability | Anon | Learner | Contributor | Reviewer | Moderator | Admin | |---|---|---|---|---|---|---| | Read published version | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | | Read superseded versions | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | | Read others' drafts | ❌ | ❌ | ❌ | πŸ”ΆΒΉ | βœ… | βœ… | | Create draft | ❌ | ❌ | βœ… | βœ… | βœ… | βœ… | | Edit draft | ❌ | ❌ | πŸ”Ά own | πŸ”Ά own | πŸ”ΆΒ² | βœ… | | Submit for review | ❌ | ❌ | πŸ”Ά own | πŸ”Ά own | πŸ”Ά own | βœ… | | Withdraw submission | ❌ | ❌ | πŸ”Ά own | πŸ”Ά own | βœ… | βœ… | | Fork published entity | ❌ | ❌ | βœ… | βœ… | βœ… | βœ… | | Publish accepted version | ❌ | ❌ | πŸ”ΆΒ³ | πŸ”ΆΒ³ | βœ… | βœ… | | Rollback published version | ❌ | ❌ | πŸ”Άβ΄ | πŸ”Άβ΄ | βœ… | βœ… | | Retract published version | ❌ | ❌ | ❌ | ❌ | βœ… | βœ… | | Delete draft (own, never submitted) | ❌ | ❌ | βœ… | βœ… | βœ… | βœ… | | Hard-delete entity | ❌ | ❌ | ❌ | ❌ | ❌ | πŸ”Άβ΅ | | Add/remove maintainers | ❌ | ❌ | πŸ”Ά own | πŸ”Ά own | βœ… | βœ… | | Export OER bundle | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | | Import OER bundle (creates drafts) | ❌ | ❌ | βœ… | βœ… | βœ… | βœ… | ΒΉ Only drafts attached to a submission the reviewer has claimed. Β² Moderators may edit others' drafts only to redact policy-violating material; such edits are flagged in the audit log and version history as moderation edits. Β³ Publishing is automatic on review acceptance by default; maintainers may enable "manual publish" to batch releases. Either way, only `accepted` versions are publishable (Invariant V1). ⁴ Fast-track rollback (Β§04, hash-identical to a previously accepted version): maintainer alone. Otherwise requires moderator co-sign. ⁡ Hard delete only for content with zero attempts, zero forks, and zero discussion, or under legal compulsion; otherwise retraction is used. Always audit-logged with reason. ### 3.2 Review system | Capability | Learner | Contributor | Reviewer | Moderator | Admin | |---|---|---|---|---|---| | View review queue (own topic scopes) | ❌ | ❌ | βœ… | βœ… | βœ… | | Claim submission | ❌ | ❌ | πŸ”ΆΒΉ | ❌² | πŸ”ΆΒΉ | | Comment on a review | ❌ | πŸ”Ά own submission | βœ… claimed | βœ… | βœ… | | Approve / reject / request changes | ❌ | ❌ | πŸ”ΆΒΉ | ❌² | πŸ”ΆΒΉ | | Override review outcome | ❌ | ❌ | ❌ | ❌ | πŸ”ΆΒ³ | | Reassign / unclaim stale reviews | ❌ | ❌ | self only | βœ… | βœ… | | Grant/revoke reviewer scope | ❌ | ❌ | ❌ | βœ… | βœ… | ΒΉ Must hold a matching topic scope; must not be author/maintainer of the submission; must not have authored > 25 % of the diff under review (conflict-of-interest check, computed from version authorship). Β² Moderators do not approve content unless they *also* hold a reviewer grant β€” separation of duties. Β³ Admin override exists for deadlock/abuse situations; it requires a written justification β‰₯ 50 chars and emits a high-severity audit event. ### 3.3 Discussions, votes, reports | Capability | Anon | Learner | Contributor | Reviewer | Moderator | Admin | |---|---|---|---|---|---|---| | Read discussions | βœ… | βœ… | βœ… | βœ… | βœ… | βœ… | | Create thread / comment | ❌ | πŸ”ΆΒΉ | βœ… | βœ… | βœ… | βœ… | | Edit own comment (≀ 24 h, pre-reply) | ❌ | βœ… | βœ… | βœ… | βœ… | βœ… | | Delete own comment | ❌ | βœ… tombstone | βœ… tombstone | βœ… tombstone | βœ… | βœ… | | Vote (content, comments) | ❌ | πŸ”ΆΒΉ | βœ… | βœ… | βœ… | βœ… | | Report content/user | ❌ | βœ… | βœ… | βœ… | βœ… | βœ… | | Hide/lock/move threads | ❌ | ❌ | ❌ | ❌ | βœ… | βœ… | | View report queue & resolve | ❌ | ❌ | ❌ | ❌ | βœ… | βœ… | | Mark spoiler-protected solution discussions visible to self | β€” | πŸ”ΆΒ² | πŸ”ΆΒ² | βœ… | βœ… | βœ… | ΒΉ Rate-limited and gated for brand-new accounts (< 24 h or unverified email): may not post links, max 3 comments/hour β€” spam mitigation. Β² Solution-discussion threads are hidden until the learner has solved the problem or explicitly reveals (recorded for spaced-repetition purposes). ### 3.4 Users & administration | Capability | Learner | Contributor | Reviewer | Moderator | Admin | |---|---|---|---|---|---| | Edit own profile, privacy settings | βœ… | βœ… | βœ… | βœ… | βœ… | | Export own data (GDPR) | βœ… | βœ… | βœ… | βœ… | βœ… | | Delete own account | βœ… | βœ… | βœ… | βœ… | βœ… | | View non-public profile fields of others | ❌ | ❌ | ❌ | πŸ”ΆΒΉ | βœ… | | Warn / temporarily suspend user | ❌ | ❌ | ❌ | βœ… | βœ… | | Permanently ban user | ❌ | ❌ | ❌ | ❌ | βœ… | | Grant/revoke moderator | ❌ | ❌ | ❌ | ❌ | βœ… | | Read audit log | ❌ | ❌ | ❌ | πŸ”ΆΒ² | βœ… | | Instance settings, feature flags | ❌ | ❌ | ❌ | ❌ | βœ… | | API token management (own) | βœ… | βœ… | βœ… | βœ… | βœ… | ΒΉ Only fields relevant to an open report (e.g., registration IP for ban-evasion checks), and each access is itself audit-logged. Β² Moderators see moderation-relevant audit streams only; admin and auth events are admin-visible only. ## 4. Reputation gates (orthogonal to roles) Reputation never *replaces* a role check; it gates rate limits and a few self-serve capabilities: | Threshold (default, instance-configurable) | Unlocks | |---|---| | 0 | Attempt, bookmark, report | | 15 | Voting | | 50 | Posting links in discussions | | 100 | Suggesting tag edits on others' content (queued for maintainer approval) | | 200 (per topic) | Eligible for reviewer grant in that topic | | 500 | Eligible to be added as course maintainer by others without moderator sign-off | Reputation sources and weights are specified in the review-workflow doc (Β§06.7) since most reputation flows from reviewing and being reviewed. ## 5. Implementation ### 5.1 Policy layer We implement a thin policy layer (plain Python, no heavyweight engine for MVP) with one module per resource: ```python # policies/problems.py def can_edit_draft(actor: Actor, problem: Problem, version: ProblemVersion) -> Decision: if version.state != VersionState.DRAFT: return Decision.deny("version_not_draft") if actor.is_admin: return Decision.allow("admin") if problem.is_maintainer(actor.user_id): return Decision.allow("maintainer") if actor.is_moderator: return Decision.allow("moderation_edit", flags={"moderation": True}) return Decision.deny("not_maintainer") ``` - `Decision` carries the machine-readable reason; **denials on mutations and all flagged allows are written to the audit log** with the reason code. - DRF views call policies via a `@requires(policy)` decorator; a CI test introspects the router and fails on any unguarded mutating view. - The same reason codes are returned in `403` bodies (`{"error": {"code": "not_maintainer", …}}`) so the frontend can render precise messaging. ### 5.2 Role storage - `roles` table (slug, description) seeded by migration; `user_roles` join table with optional `scope` (for topic-scoped reviewer grants), `granted_by`, `granted_at`, `expires_at` (suspensions and probationary grants use expiry). - Role grants/revocations are audit events with actor + justification. - Sessions embed a signed, short-lived capability summary; role changes bump a per-user `permissions_version` so stale sessions re-fetch (no need to wait for token expiry to revoke). ### 5.3 Object-level checks & query scoping Permission filtering must also apply to **list endpoints**: e.g., `GET /api/v1/problems?status=draft` returns only the actor's own drafts (or claimed submissions for reviewers). Each policy module exposes a `visible_queryset(actor)` companion used by list views, keeping read-path filtering and write-path checks in one reviewed module. ## 6. Threat notes - **Privilege escalation via role UI bugs:** mitigated by server-side re-checks + CI guard (Β§5.1). - **Reviewer collusion / sockpuppet approval:** conflict-of-interest check (Β§3.2ΒΉ), per-topic grants requiring human appointment, and review quorum rules (Β§06). - **Moderator overreach:** moderation edits are visibly flagged in version history; moderators cannot approve content; audit log of moderator actions is admin-reviewable. - **Mass-deletion by compromised admin:** hard deletes restricted (Β§3.1⁡), audit log is append-only and shipped off-box (Β§08), and destructive admin endpoints require recent re-authentication (sudo mode, 10-minute window).