DataTalksClub / DataTalksClub/website
Replace global course staff access and plaintext API tokens with scoped capabilities
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Description
Parent epics: #5, #7
Product outcome
Replace the copied course platform's blanket staff and raw-token authorization boundary with one explicit Course/Cohort capability and object-scope foundation that Studio, /api/v1/admin/, and retained compatibility routes can share.
At this issue's frozen integration baseline, every existing course route/action is inventoried and receives an explicit actor, capability, target resolver, object/field policy, and transition disposition. Course-family and Cohort staff assignments become reviewable domain records; copied is_staff/is_superuser gates and plaintext accounts.Token storage stop granting authority. Later course-domain issues register their own actions against this accepted interface rather than inheriting new access automatically.
This is repository/development delivery. It grants no permission to inspect protected production identities or credentials, execute a production mapping, activate a production client, or shorten #60's production-like rehearsal and cutover gates.
Normative authority
_docs/PROCESS.md04 — Course/Cohort staff scope and management06 — Studio/admin API parity, roles, authentication, audit, and safety07 — Identity, authorization, and privacy09 — Expand/contract and migration controls10 — Authorization and management verificationDjango app boundariesandshared command/query boundary- resolved owner decisions #20, #23, and #28
Accepted interface to deliver
Course/Cohort assignment and scope
The courses domain owns revisioned staff assignments referencing the durable local staff user. Each active assignment has exactly one explicit role from owner, instructor, grader, support, or communications, and exactly one scope:
- one Course-family scope, applying to that Course and its Cohorts; or
- one exact Cohort scope whose parent Course must match.
Global function authority remains the accepted #32 role/capability policy; it is not recreated as a magic Course assignment. Every role maps to an explicit versioned allowlist of capability keys available at the frozen baseline. A Course-scoped assignment does not grant a capability absent from that allowlist, and a capability added later is denied until its owning issue explicitly updates the mapping and positive/negative tests. Assignment expiry, revocation, staff disablement, role/permission removal, and API-principal/credential revocation take effect on the next request.
One shared authorization service accepts the current local user or accepted #33 API principal, capability key, Course UUID, optional Cohort UUID, object UUID, and requested fields. It checks function, scope, object, and field policy before lookup and returns a generic denial/missing result without revealing another Course, Cohort, learner, or staff assignment. Public slugs, numeric legacy IDs, group names, is_staff, is_superuser, editorial Person data, token ownership, and an actor/audit snapshot are never authorization inputs.
Assignment management and audit
Assignment list/create/change/revoke uses courses application services shared by Studio and the admin API. The exact registered capabilities are:
courses.staff_assignment.viewcourses.staff_assignment.managecourses.staff_person_link.viewcourses.staff_person_link.managecourses.support_view_as
The default function policy is exact: site_admin may view/manage every assignment; an active in-scope Course owner may view/manage assignments for that Course and its Cohorts; the other four assignment roles may view only their own safe assignment/scope summary and do not manage grants. courses.support_view_as is granted by default only to an in-scope support assignment and site_admin. An accepted #33 API principal additionally needs the exact capability scope and Course/Cohort object grant. No role name alone bypasses the registered permission/policy.
The registered adapter surface is:
GET /studio/courses/<course-uuid>/staff/;POST /studio/courses/<course-uuid>/staff-assignments/;POST /studio/courses/staff-assignments/<uuid>/change/and/revoke/;GET/POST /studio/access/staff-person-links/andPOST /studio/access/staff-person-links/<uuid>/revoke/;GET/POST /api/v1/admin/course-staff-assignments,GET/PATCH /api/v1/admin/course-staff-assignments/<uuid>, andPOST /api/v1/admin/course-staff-assignments/<uuid>/revoke;GET/POST /api/v1/admin/staff-person-links,GET /api/v1/admin/staff-person-links/<uuid>, andPOST /api/v1/admin/staff-person-links/<uuid>/revoke; and- the support-view adapters defined below.
The staff-person-links collection and full detail are site_admin-only because they connect a private account identity to a public editorial identity. A linked user may inspect only their own safe link state through existing account settings if that state is exposed; this issue introduces no searchable staff directory.
All Studio routes are private, CSRF-protected, no-store/noindex and use the established Studio shell. /api/v1/admin/ uses UUID resources and the same service/policy. Mutations require revision/If-Match, idempotency, an allowlisted body, a bounded reason, safe replay/conflict behavior, and redacted audit. Because a role grant/removal is high risk under accepted #28, it also requires the exact accepted #32 reauthentication/API-proof, scope/count/impact preview, and explicit confirmation. Dual approval remains deferred.
The audit records only safe actor/principal reference, action/capability, Course/Cohort/assignment UUIDs, role, revision, reason code, request/correlation/idempotency identifiers, outcome, and timestamps. It never records a token, credential/digest, raw OIDC/session material, email, profile value, learner work, request/response body, or provider payload.
Permission-neutral Person link
After accepted #40 supplies the canonical exact-short Person resolver, one explicit reviewed link may associate a durable staff user with one active canonical editorial Person. The link is presentation/relationship metadata only. Link, unlink, Person removal/alias, public-role changes, account/profile edits, and staff assignment changes never create, copy, broaden, retain, or revoke authority in the other identity. No match by name, email, social link, biography, Course participation, or case-folded/fuzzy key is allowed.
Legacy route and token transition
Freeze a deterministic manifest covering every existing token_required, require_staff_token, staff_required, staff_json_required, authorization-relevant is_staff/is_superuser, copied impersonation, course export, and send-audit use. Each row records route/operation, method, actor class, data/mutation, current control, exact capability, Course/Cohort/object/field resolver, owning downstream issue, compatibility disposition, removal gate, and focused test. A deterministic guard rejects an unapproved new use.
For every course route/action present at the frozen baseline, replace blanket staff authorization with the shared explicit policy. A copied handler may remain a clearly identified compatibility adapter until its owning #53/#55–#59 or #245/#246 service is delivered, but neither adapter nor handler may bypass the policy. New domain actions are denied until their owning issue registers a capability/service/policy row.
Replace plaintext accounts.Token persistence with a digest-backed compatibility credential transition. The existing Authorization: Token scheme may remain only on the manifest's allowlisted legacy routes during the bounded transition; it authenticates one durable actor and then runs the same explicit capability/object/field policy. It never authenticates Studio or /api/v1/admin/, grants authority from is_staff, or becomes a wildcard/future scope. New management credentials use #33's Authorization: Bearer contract. Migration is idempotent, stores no recoverable secret, refuses ambiguous/disabled/unmapped actors, supports explicit expiry/revocation/rotation overlap, and reports only safe counts/checksums. No automatic production mapping, token readback, or protected-data inspection is authorized here.
Support view-as
Replace copied unrestricted impersonation with a typed Course support view-as context. It targets one authorized Enrollment UUID inside the actor's Course/Cohort scope, uses the accepted #288 member_label, requires a bounded reason, and expires after at most 15 minutes or earlier on exit, session expiry/revocation, staff disablement, permission/assignment removal, or target loss.
View-as never changes request.user, issues or swaps credentials, stores a learner session, or grants learner authority. It is GET/HEAD-only, prominently and persistently labeled, private/no-store/noindex, and has an always-visible keyboard-accessible exit. Every unsafe method, form submission, API mutation, network side effect, email/job creation, and out-of-scope lookup is denied. Corrective actions remain separate normal capabilities outside view-as.
Studio starts the bounded context only with POST /studio/courses/enrollments/<uuid>/view-as/start/, renders the accepted learner projection beneath its normal canonical route while the context remains valid, and exits only with POST /studio/courses/view-as/exit/. The parity query is GET /api/v1/admin/course-enrollments/<uuid>/support-view; it returns the same allowlisted read-only projection and never creates a browser/session context. Entry/exit are Studio presentation state, not a domain mutation or API impersonation operation.
Current dependency and dispatch gate
Satisfied decision/foundation inputs: #20, #23, #28, #86, and #87.
Engineering remains blocked until all of these exact interfaces are accepted and integrated on current main:
- #51 — final Course/Cohort UUID, parent, alias, and mechanically retargeted relation contract, consuming #224;
- #40 — canonical exact-
shortPerson resolver for the permission-neutral link; - #32 — effective Studio role/function/object/field and high-risk reauthentication/confirmation/audit policy;
- #33 — human/service API principals, digest-backed credentials, revocation, scope, and API-proof interface; and
- #288 — the owner-approved shared non-PII
member_labelused by view-as.
#61 and the external MFA gate are consumed through #32/#33 for production human identity; they are not silently satisfied here. If a named dependency supplies only a bounded handoff while its issue remains open, that handoff is usable only when its own tester and PM explicitly accepted it, it is integrated and green on current main, and it covers every interface named above. Otherwise the open dependency blocks engineering under _docs/PROCESS.md.
Engineering starts from a clean current-main base only after PM records exact prerequisite merge SHAs, source pin, migration leaves, capability/policy/registry/OpenAPI fingerprints, and the frozen legacy manifest digest. Any drift returns this issue to PM.
Downstream ownership
- #53 consumes the Course/Cohort assignment and Person-link interfaces for lifecycle/teaching-team management.
- #55/#56/#57/#58 register their homework, project, grading, leaderboard/complaint, graduate, and certificate actions and field policies.
- #242–#244 retain their own source, public-account, and learner-ownership authorization and do not depend on #52 merely because they use private data; #245/#246 consume the accepted management read/export and command policies directly.
- #59 completes all remaining service, Studio, admin-API, compatibility, and parity rows and removes retained presentation-layer adapters.
- #60 rehearses protected legacy credential/assignment mapping, compatibility, cutover, rollback, and production-like migration. It receives no production authority from this issue.
- #63 consumes the final legacy-auth/export manifest and no-new-use guard as residual security traceability.
#54 is a coordination epic, not an implementation dependency. #53/#55–#59 and #245/#246 are downstream consumers, not prerequisites for this foundation; they must not be pulled into this issue or create a cycle. #242–#244 remain independent of this management foundation.
Acceptance criteria
- The frozen current-baseline legacy authorization/export/impersonation manifest is complete and reproducible; every row has one explicit owner/capability/scope/field/transition/removal disposition, and the guard rejects every unapproved new use.
- Revisioned Course-family/Cohort assignments enforce exact role and parent/scope invariants, explicit versioned capability allowlists, immediate expiry/revocation/disablement, and no future-capability inheritance.
- One shared policy authorizes Studio users, #33 API principals, and allowlisted compatibility actors identically for function, Course/Cohort, object, and field scope before lookup; cross-scope and no-existence behavior is generic.
- Assignment and Person-link management has registry, Studio, admin-API, service, permission, revision/idempotency, high-risk, audit, and result parity; no adapter writes a model directly.
- Exact Person linking is explicit and permission-neutral across link/unlink, alias/removal, public-role, profile, staff-assignment, and account lifecycle changes; fuzzy/account-derived matching is impossible.
- Plaintext
accounts.Tokenstorage and raw-secret recovery are eliminated. LegacyTokencompatibility is digest-backed, allowlisted, expiring/revocable, never valid for Studio/admin API, and never derives scope from staff/superuser/token ownership. - Every current course route/action uses the new policy or a manifest-declared deny state; no blanket
is_staff,is_superuser, group-name, public Person, legacy-token, or superuser bypass remains. - Copied impersonation is removed. Scoped view-as preserves the staff identity, is visibly read-only, bounded to one authorized Enrollment, expires/revokes correctly, and denies every mutation/side effect.
- Assignment/token migration and reconciliation are repeatable and fail closed for duplicate, ambiguous, inactive, unmapped, malformed, stale, or conflicting input; evidence contains safe aggregate counts/checksums only and no production execution occurs in repository verification.
- Focused model/service/policy/API/OpenAPI/migration/concurrency/security/adoption tests and graph-selected full verification pass on one frozen candidate; an independent tester inspects all required desktop/mobile screenshots and PM accepts before commit.
Required Django, API, migration, and security scenarios
- Exercise every role at Course scope, exact Cohort scope, sibling Cohort, other Course, newly created Cohort, expired/revoked assignment, composed assignment, missing capability, later-added capability, staff disablement, role removal, principal/credential expiry/revocation, and stale policy revision.
- Create/change/revoke assignments and Person links through Studio and admin API with valid/invalid scope, parent mismatch, duplicate/conflicting assignment, stale revision, exact/changed replay, concurrent grant/revoke, fresh/stale/mismatched high-risk proof, confirm/cancel, and audit-redaction canaries.
- Link/unlink exact active Person keys; reject unknown, inactive, alias-chain, case variant, fuzzy name/email/social/account match, duplicate link, and cross-account conflict. Prove zero permission/role/scope/public-profile mutation.
- Reproduce the frozen legacy manifest, reject a new decorated/gated route and an altered disposition, and exercise every current route family with allowed, wrong-scope, wrong-field, inactive, missing, and generic out-of-scope actors.
- Migrate synthetic zero/one/many legacy tokens twice; exercise malformed/duplicate/ambiguous/disabled/unmapped actors, digest/prefix collision, Token-versus-Bearer confusion, exact/expired/revoked/overlap credential, raw-key database/cache/log/audit/error/OpenAPI canaries, and supported reverse/rollback state.
- Start/refresh/exit/expire/revoke view-as; exercise another Course/Cohort/Enrollment, removed target, session/assignment/permission loss, browser back, concurrent exit, every unsafe method, forged context, CSRF, side-effect canaries, and unchanged staff identity/session.
- Prove private/no-store/noindex/zero-TTL, strict JSON/body/query bounds, CSRF for Studio, Bearer-only admin API, safe
400/401/403/404/409/413/422/429, bounded rate/cost behavior, no mass assignment, no object-existence leak, and no token/PII/provider leakage.
Independent browser evidence
At approximately 1440×900 and 390×844 with synthetic .invalid fixtures, the independent tester captures and reads:
- Course-family and exact-Cohort assignment list/create/change/revoke, scope/count/impact preview, explicit confirmation, stale/replay/conflict, and allowed/denied states;
- Person link/unlink with exact public identity and proof that navigation/access does not change;
- view-as start, persistent banner and accepted masked member label, learner page, forbidden mutation, expiry/revocation, exit, browser-back denial, and cross-scope generic denial;
- representative retained compatibility route allowed and denied states plus Studio/admin-API navigation visibility for owner/instructor/grader/support/communications/auditor/site-admin actors; and
- keyboard-only flow, visible focus, 44 CSS px targets, 200% zoom/reflow/320 CSS px, reduced motion, announced errors/status, and JavaScript-disabled assignment confirmation/view-as exit where supported.
Screenshots contain no raw email/name/profile value, learner work, token, credential/digest, OIDC/session data, reason text, provider payload, or production identity/data.
Explicit non-goals
- No choice or implementation of OIDC/provider/MFA/session policy (#61), global role/high-risk policy (#32), API-principal lifecycle (#33), Course/Cohort structure (#51), Person identity (#40), or masked-member presentation (#288).
- No homework/project/grading/leaderboard/certificate/registration/enrollment behavior redesign; no completion of #53/#55–#59 or #242–#246; no direct model adapter replacement for their future shared services.
- No production credential enumeration/readback, identity/assignment inference, protected-data export, production migration/import, provider/AWS action, sender/email side effect, deployment, redirect/DNS cutover, or destructive legacy contraction.
- No wildcard/future capability, implicit Course inheritance outside the explicit Course-scope rule, unrestricted impersonation, identity swap, Person/account field synchronization, public staff directory, Django-admin ordinary workflow, or compatibility token accepted by
/api/v1/admin/. - No source pin change, unrelated UI redesign, commit, push, merge, or lifecycle shortcut during grooming.
Delivery convention
Follow _docs/PROCESS.md. After all prerequisites are integrated, one engineer leaves a frozen uncommitted current-main candidate and complete versioned verification report. A separate tester recomputes the plan, runs all selected gates, and inspects screenshots; PM then performs final acceptance. Only afterward may the engineer commit with Closes #52; the orchestrator locally merges with --no-ff, pushes main, and on-call alone observes CI/deployment. No pull request is created.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with _docs/PROCESS.md and the linked specs for courses, Studio/admin API, identity, migration controls, and verification. Before implementation, confirm that #51, #40, #32, #33, and #288 are integrated on current main and review the frozen legacy-route manifest requirements. Done means the shared scoped policy, assignment management, credential transition, route adapters, view-as controls, audit behavior, and focused verification are delivered without bypasses.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, python
- Domain
- api, authorization, backend-api-design, databases, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 15/100