Skip to content

feat: apply OEP-66 queryset-scoping pattern to Enrollments v2 API - #39037

Open
Abdul-Muqadim-Arbisoft wants to merge 1 commit into
openedx:masterfrom
edly-io:feat/apply-queryset-scoping-to-enrollments-v2
Open

feat: apply OEP-66 queryset-scoping pattern to Enrollments v2 API#39037
Abdul-Muqadim-Arbisoft wants to merge 1 commit into
openedx:masterfrom
edly-io:feat/apply-queryset-scoping-to-enrollments-v2

Conversation

@Abdul-Muqadim-Arbisoft

Copy link
Copy Markdown
Contributor

Adopt the OEP-66 "Separating Authorization Concerns in List Endpoints" record-visibility layer on the admin enrollment list, using the shared building blocks now published in edx-drf-extensions.

EnrollmentsAdminListView is an ORM-backed ListAPIView, so it wires the three authorization concerns separately:

  • Endpoint access: permission_classes = (IsAdminUser,).
  • Record visibility: ScopedQuerysetMixin applies AdminEnrollmentScopingPolicy to the base queryset in get_queryset(). The policy is an intentional pass-through for platform admins today, documented as the single seam where an openedx-authz scope-set filter (e.g. restricting a delegated, org-scoped admin to their organization's enrollments) will plug in 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 already-authorized queryset.

Bumps edx-drf-extensions 10.6.0 -> 10.7.0, the release that adds the reusable ScopingPolicy (a typing.Protocol) and ScopedQuerysetMixin. Adds TestEnrollmentsAdminListView regression tests covering endpoint access (401/403), pass-through scoping (admin sees all rows), the course_key/username filters, the 400-on-invalid-params path, the ADR 0033 Deprecation header, and the scoping-policy pass-through.

Follows up the closed PR #38847: per review, the shared ScopingPolicy / ScopedQuerysetMixin tooling moved to edx-drf-extensions (#569) as a subject-based typing.Protocol with a duck-typed mixin check, and this change consumes it rather than defining it locally.

@Abdul-Muqadim-Arbisoft
Abdul-Muqadim-Arbisoft requested a review from a team as a code owner August 27, 2026 13:28
@Abdul-Muqadim-Arbisoft
Abdul-Muqadim-Arbisoft force-pushed the feat/apply-queryset-scoping-to-enrollments-v2 branch from 78d3f46 to 3678f4c Compare August 30, 2026 10:04
def test_filter_by_username_narrows(self):
"""User-driven filter (filter_queryset) still narrows by username."""
self.client.force_authenticate(user=self.admin)
response = self.client.get(self.url, {"username": self.learner_b.username})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only work for admins correct? we should have the inverted test validating that users can't see eachothers enrollments by passing eachothers names in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup that should be admin only. Just added test_non_admin_cannot_list_another_users_enrollments for it

Comment thread openedx/core/djangoapps/enrollments/v2/views.py Outdated

def scope(self, queryset, subject):
# Platform admins (IsAdminUser) see all enrollments; nothing to narrow yet.
return queryset

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of boilerplate for essentially a no-op on scoping, can we simplify this? Maybe we have some sort of a "FullScopePolicy" that can be used in many places for things like this?

@Abdul-Muqadim-Arbisoft Abdul-Muqadim-Arbisoft Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think FullScopePolicy would be a good to add since any admin only list that adopts the scoping layer would otherwise end up hand writing the same pass through class, and since its a reusable DRF tooling it should be placed next to ScopingPolicy / ScopedQuerysetMixin so plugins can use it too.

I've opened openedx/edx-drf-extensions#578 adding it there, and this PR now imports it from the library. CI here will stay red until that's released; once you approve, merge and publish 10.9.0, I'll bump the pin in this PR and it should be good to merge.

Comment thread openedx/core/djangoapps/enrollments/v2/views.py Outdated
Wire EnrollmentsAdminListView through ScopedQuerysetMixin from
edx-drf-extensions so endpoint access, record visibility and user-driven
filtering are kept separate. Platform admins may see every enrollment, so
the view uses the library's FullScopePolicy for now; a narrower policy can
replace it later without touching the view. The form-based filtering moves
from get_queryset() into filter_queryset(), so it only narrows the scoped
queryset. No behavior change.

Adds regression tests for the admin list: 401/403, a regular user filtering
by someone else's username gets 403 with no data, admins see all rows, the
course_key/username filters, the 400 on invalid params, and the ADR 0033
Deprecation header.

Requires edx-drf-extensions 10.9.0 (edx-drf-extensions#578). Follows up
openedx#38847, whose shared scoping tooling moved to edx-drf-extensions in #569.
@Abdul-Muqadim-Arbisoft
Abdul-Muqadim-Arbisoft force-pushed the feat/apply-queryset-scoping-to-enrollments-v2 branch from 3678f4c to b106b61 Compare September 12, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants