feat: apply OEP-66 (queryset-scoping pattern) across 6 standardized APIs#38847
Open
Abdul-Muqadim-Arbisoft wants to merge 2 commits into
Open
Conversation
e1b225a to
ebf36d7
Compare
added 2 commits
July 6, 2026 23:17
Add openedx/core/lib/api/scoping.py providing the reusable OEP-66
(openedx-proposals#802) record-visibility primitives for DRF list endpoints:
* ScopingPolicy — an abstract policy that filters a queryset to the rows a
user may see, delegating scope resolution to the platform authorization
engine (openedx-authz) in one bulk lookup rather than per-row checks.
* ScopedQuerysetMixin — applies a view's ``scoping_policy`` on top of
``get_queryset()``, raising ``ImproperlyConfigured`` when it is unset.
Includes unit tests (openedx/core/lib/api/tests/test_scoping.py) covering the
mixin's policy application and the ImproperlyConfigured guard.
Wire the OEP-66 (openedx-proposals#802) three-layer separation into the
ORM-backed admin enrollment list (EnrollmentsAdminListView):
* Endpoint access — permission_classes = (IsAdminUser,).
* Record visibility — ScopedQuerysetMixin applies AdminEnrollmentScopingPolicy
to a base ``queryset`` in get_queryset(). The policy is a pass-through for
platform admins (who see all enrollments), documented as the seam for a
narrower openedx-authz scope-set filter later; behavior is unchanged.
* User-driven filtering — the form-based course_key/course_keys/username/
email/ordering filtering moves from get_queryset() into filter_queryset(),
so it runs after scoping and only narrows the authorized queryset.
Adds TestEnrollmentsAdminListView regression tests guarding the
get_queryset->filter_queryset split: endpoint access (401/403), pass-through
scoping (admin sees all rows), course_key/username filters still narrow, the
400-on-invalid-params path, the ADR 0033 Deprecation header, and the
scoping-policy pass-through.
ebf36d7 to
33137f8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Applies the OEP-66 "Separating Authorization Concerns in List Endpoints"
queryset-scoping pattern (added in openedx-proposals#802)
across the 6 standardized FC-0118 APIs.
OEP-66 keeps three authorization concerns separate on a list endpoint:
permission_classes(may this user call the endpoint?)ScopingPolicyapplied inget_queryset()viaScopedQuerysetMixin, resolving the user's accessible scopes in one bulklookup (openedx-authz
get_scopes_for_*_and_permission) rather than per-row checksEach API is applied and committed on its own (one commit per API). Single-object
endpoints get the ADR's detail-path treatment (a direct point check, documented);
list endpoints get the record-visibility layer, using the ORM mixin where an ORM
queryset exists and the ADR's documented in-memory fallback where the source is the
modulestore.
Commits
HasCourseAuthorAccesspoint check)scoping.py+ list-endpoint compliance docsScopedQuerysetMixin+AdminEnrollmentScopingPolicyon the admin listWhat OEP-66 layers were added
(
partial_update/ CRUD-by-key /retrieve+update). Nolistaction, noqueryset, so the list-scoping layers are out of scope. Each already implements
OEP-66's single-object point check (inline
user_has_course_permission, orthe
HasCourseAuthorAccesspermission class), now documented and annotated.(not a Django ORM queryset). Record visibility is already delegated to openedx-authz
via
get_courses_accessible_to_user→get_scopes_for_user_and_permission(COURSES_VIEW_COURSE)(one bulk scope-set lookup, merged with legacy roles). Home v3's
librariesactionis scoped by a per-object
has_studio_read_accesscheck; itslistaction carriesno records. Per OEP-66's "Data sources without an ORM" note, the scope decision is
applied in memory. Documented per action; the reusable ORM abstractions can't attach
here (no
QuerySet).EnrollmentsAdminListViewis a realListAPIViewover an ORMqueryset, so it adopts the pattern literally:
ScopedQuerysetMixin+AdminEnrollmentScopingPolicyapplied inget_queryset(), with the form-based userfiltering moved into
filter_queryset(). The policy is a pass-through for platformadmins (
IsAdminUser), documented as the single seam for a narrower authz scope-setfilter later — no behavior change today.
EnrollmentViewSet.list(service-layerscoped) and
UserRolesView(self-scoped) get compliance notes.Shared utilities
New reusable module
openedx/core/lib/api/scoping.py—ScopingPolicy(ABC) andScopedQuerysetMixin— so any ORM-backed DRF list view can wire the OEP-66record-visibility layer without re-implementing it. Adopted by Enrollments v2's admin
list; ready for future ORM-backed endpoints.
Tests added
ScopingPolicyTests,ScopedQuerysetMixinTestsopenedx/core/lib/api/tests/test_scoping.pyTestEnrollmentsAdminListViewopenedx/core/djangoapps/enrollments/v2/tests/test_views.py12 new tests total, all mocked + MongoDB-free. The admin-list tests guard the
get_queryset()→filter_queryset()split: endpoint access (401/403), pass-throughscoping (admin sees all rows),
course_key/usernamefilters still narrow, the400-on-invalid-params path, and the ADR 0033
Deprecationheader. Commits 1-3 and 5are documentation-only (no runtime change) and add no tests, consistent with prior
declaration-only ADR commits (e.g. ADR 0034, #38796).
Backward compatibility
No default response or behavior changes. Commits 1-5 are docstring/comment
additions only. Commit 6 restructures
EnrollmentsAdminListViewinternally(
get_queryset→ base queryset +filter_queryset) but preserves every filter, theform-validation 400 path, ordering, and the ADR 0033 header — the scoping policy is a
pass-through, so the same rows are returned. Guarded by the new regression tests above.
Out of scope
Details) — no list/queryset to scope; documented as the OEP-66 detail-path point check.
AdminEnrollmentScopingPolicyintentionallyreturns all rows for platform admins; introducing an org-scoped authz filter is a
future, opt-in change flagged in the policy docstring.
data-source change; the ORM mixin is the sibling path for future ORM-backed endpoints.
Test plan