feat: add AuthZ permissions to course creation and outline#38259
Conversation
|
Thanks for the pull request, @dwong2708! This repository is currently maintained by Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review. 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. DetailsWhere can I find more information?If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources: When can I expect my changes to be merged?Our goal is to get community contributions seen and reviewed as efficiently as possible. However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
1936d74 to
610511d
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds AuthZ (openedx-authz) permission enforcement for course authoring/course-visibility-related endpoints (course detail, outline/course index, downstream summary, and legacy content migration), plus new/updated tests and test utilities to validate the new behavior behind the course-authoring AuthZ feature flag.
Changes:
- Enforce
courses.view_coursechecks (with legacy fallbacks where applicable) for several course authoring/read endpoints. - Add an AuthZ-aware course creation permission helper and integration/unit tests for the updated authorization paths.
- Expand AuthZ test mixins to include staff and superuser clients for repeated use in endpoint tests.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| openedx/core/djangoapps/authz/tests/mixins.py | Extends AuthZ test mixin with staff/superuser users and API clients. |
| lms/djangoapps/courseware/courses.py | Adjusts course access flow when AuthZ authoring is enabled (staff-bypass behavior changes). |
| lms/djangoapps/courseware/access.py | Updates see_about_page access logic to use AuthZ permission checks under the flag. |
| lms/djangoapps/course_api/tests/test_api.py | Adds AuthZ-focused tests for course_detail and related access behavior. |
| common/djangoapps/student/auth.py | Introduces AuthZ-backed is_content_creator check for course creation. |
| cms/djangoapps/contentstore/tests/test_course_create_rerun.py | Adds AuthZ integration tests for course creation via course_handler. |
| cms/djangoapps/contentstore/rest_api/v2/views/downstreams.py | Enforces courses.view_course AuthZ permission for downstream summary endpoint. |
| cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstreams.py | Adds AuthZ tests for downstream summary endpoint access control. |
| cms/djangoapps/contentstore/rest_api/v1/views/course_index.py | Enforces courses.view_course AuthZ permission for course outline (course index) endpoint. |
| cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_index.py | Adds AuthZ tests for course index access control. |
| cms/djangoapps/contentstore/api/views/course_validation.py | Moves migrate-legacy-content list endpoint to AuthZ permission decorator and adds DeveloperError mixin. |
| cms/djangoapps/contentstore/api/tests/test_validation.py | Updates/extends tests for migrate-legacy-content endpoint authorization under AuthZ. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
lms/djangoapps/courseware/access.py
Outdated
| """ | ||
| if user and not user.is_anonymous and core_toggles.enable_authz_course_authoring(courselike.id): | ||
| is_authz_allowed = user_has_course_permission(user, COURSES_VIEW_COURSE.identifier, courselike.id) | ||
| return ACCESS_GRANTED if is_authz_allowed else ACCESS_DENIED |
There was a problem hiding this comment.
When AuthZ is enabled, can_see_about_page returns a plain ACCESS_DENIED for unauthorized users instead of returning the typed CatalogVisibilityError that the legacy path returns. This removes the ability for downstream handlers to show the existing meaningful “not visible in catalog” error message for logged-in users under the flag.
| return ACCESS_GRANTED if is_authz_allowed else ACCESS_DENIED | |
| if is_authz_allowed: | |
| return ACCESS_GRANTED | |
| # Preserve the legacy typed CatalogVisibilityError response for denied users | |
| # so downstream handlers can continue showing the existing meaningful message. | |
| return legacy_can_see_about_page() |
There was a problem hiding this comment.
I believe AuthZ should take priority in this behavior when the flag is enabled for the course. I also updated the denied response in the AuthZ flow to return CatalogVisibilityError
610511d to
a774689
Compare
a962558 to
a09b994
Compare
wgu-taylor-payne
left a comment
There was a problem hiding this comment.
Quick first pass leveraging Kiro to surface potential issues — haven't run the tests locally yet, so treat these as things to double-check rather than definitive blockers.
Overall the pattern is clean: consistent use of user_has_course_permission with LegacyAuthoringPermission fallbacks, and the test coverage across authorized/unauthorized/staff/superuser/dynamic-role-assignment is solid.
A few things stood out — one potentially high-impact issue in courses.py, and some smaller items inline.
Review assisted by Kiro
51630f1 to
d562996
Compare
rodmgwgu
left a comment
There was a problem hiding this comment.
Just a couple of clarifications on tests, otherwise looking good, thanks!
666f683 to
75925c3
Compare
rodmgwgu
left a comment
There was a problem hiding this comment.
Did a quick test in my local and worked as expected, thanks!
wgu-taylor-payne
left a comment
There was a problem hiding this comment.
Second pass after the force push — the previous issues are all resolved, nice work. Just a nit and a coverage question.
I cross-referenced the PR against all backend endpoints that frontend-app-authoring calls on the course outline page (/course/:courseId). The xblock write operations (POST/PUT/DELETE /xblock/) are already covered by the hybrid _check_xblock_permission from prior work, and the endpoints this PR updates cover the main course index, downstream summary, and legacy migration list.
Two read endpoints called on every outline page load are still using legacy has_studio_read_access:
GET /xblock/outline/{blockId}—xblock_outline_handlerincms/djangoapps/contentstore/views/block.py— fetches the section/subsection/unit treeGET /api/contentstore/v1/course_details/{courseId}—CourseDetailsView— fetches dates, pacing, course metadata shown on the outline sidebar
Without these, an unauthorized user gets 403 on course_index but could still hit these endpoints directly. Are these intentionally deferred to a follow-up, or should they be part of this PR?
Review assisted by Kiro
|
Not for course creation, but for outline, it sounds like we should also update it to use authz. |
@dwong2708 @rodmgwgu Actually on further analysis, that endpoint can be called through the course outline, but it is done so via the content tags drawer. So maybe it is more appropriate for me to take than one on in my PR. |
Sounds good to me, wdyt @rodmgwgu ? |
Description
Resolves: #38129
This PR introduces new AuthZ permissions for the course list and related course endpoints, aligning them with the
course-v1:*permission model.It ensures that course visibility and creation are consistently enforced using the new authorization system.
Permissions Implemented
The following endpoints were updated to use AuthZ permissions:
Course creation
POST /course/ → courses.create_courseUses Org-scoped authorization, since the course does not yet exist at creation time.
Course read / visibility
The following endpoints now require courses.view_course:
GET /api/courses/v1/courses/<course_id> -> courses.view_courseGET /api/contentstore/v1/course_index/<course_id> -> courses.view_courseGET /api/contentstore/v2/downstreams/<course_id>/summary -> courses.view_courseGET /api/courses/v1/migrate_legacy_content_blocks/<course_id> -> courses.view_courseExcluded Endpoint
The following endpoint was intentionally not updated:
GET /api/content_tagging/v1/object_tag_counts/<course_id>/Reason:
Testing instructions
Added/updated tests to validate:
Deadline
Verawood