Skip to content

feat: add AuthZ permissions to course creation and outline#38259

Merged
wgu-taylor-payne merged 13 commits intoopenedx:masterfrom
WGU-Open-edX:dwong2708/authz-perms-course-creation-outline
Apr 10, 2026
Merged

feat: add AuthZ permissions to course creation and outline#38259
wgu-taylor-payne merged 13 commits intoopenedx:masterfrom
WGU-Open-edX:dwong2708/authz-perms-course-creation-outline

Conversation

@dwong2708
Copy link
Copy Markdown
Contributor

@dwong2708 dwong2708 commented Mar 31, 2026

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_course
Uses 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_course
  • GET /api/contentstore/v1/course_index/<course_id> -> courses.view_course
  • GET /api/contentstore/v2/downstreams/<course_id>/summary -> courses.view_course
  • GET /api/courses/v1/migrate_legacy_content_blocks/<course_id> -> courses.view_course

Excluded Endpoint

The following endpoint was intentionally not updated:

  • GET /api/content_tagging/v1/object_tag_counts/<course_id>/

Reason:

  • It belongs to an external app (openedx-tagging)
  • It does not currently implement legacy permissions
  • Modifying it would require changes outside openedx-platform

Testing instructions

Added/updated tests to validate:

  • Authorized users can access course data
  • Unauthorized users receive appropriate errors (e.g., 403)
  • Course creation respects Org-level permissions

Deadline

Verawood

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Mar 31, 2026
@openedx-webhooks
Copy link
Copy Markdown

openedx-webhooks commented Mar 31, 2026

Thanks for the pull request, @dwong2708!

This repository is currently maintained by @openedx/wg-maintenance-openedx-platform.

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 approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To 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:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where 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:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@github-project-automation github-project-automation bot moved this to Needs Triage in Contributions Mar 31, 2026
@mphilbrick211 mphilbrick211 added the mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). label Apr 1, 2026
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Apr 1, 2026
@dwong2708 dwong2708 force-pushed the dwong2708/authz-perms-course-creation-outline branch 2 times, most recently from 1936d74 to 610511d Compare April 7, 2026 23:31
@dwong2708 dwong2708 requested a review from Copilot April 8, 2026 00:57
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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_course checks (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.

"""
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
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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()

Copilot uses AI. Check for mistakes.
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 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

@dwong2708 dwong2708 force-pushed the dwong2708/authz-perms-course-creation-outline branch from 610511d to a774689 Compare April 8, 2026 17:16
@dwong2708 dwong2708 marked this pull request as ready for review April 8, 2026 17:40
@dwong2708 dwong2708 force-pushed the dwong2708/authz-perms-course-creation-outline branch from a962558 to a09b994 Compare April 8, 2026 18:15
Copy link
Copy Markdown
Contributor

@wgu-taylor-payne wgu-taylor-payne left a comment

Choose a reason for hiding this comment

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

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

@dwong2708 dwong2708 force-pushed the dwong2708/authz-perms-course-creation-outline branch from 51630f1 to d562996 Compare April 9, 2026 22:45
Copy link
Copy Markdown
Contributor

@rodmgwgu rodmgwgu left a comment

Choose a reason for hiding this comment

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

Just a couple of clarifications on tests, otherwise looking good, thanks!

@dwong2708 dwong2708 force-pushed the dwong2708/authz-perms-course-creation-outline branch from 666f683 to 75925c3 Compare April 10, 2026 16:37
@dwong2708 dwong2708 requested review from BryanttV and MaferMazu April 10, 2026 16:38
Copy link
Copy Markdown
Contributor

@rodmgwgu rodmgwgu left a comment

Choose a reason for hiding this comment

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

Did a quick test in my local and worked as expected, thanks!

Copy link
Copy Markdown
Contributor

@wgu-taylor-payne wgu-taylor-payne left a comment

Choose a reason for hiding this comment

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

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_handler in cms/djangoapps/contentstore/views/block.py — fetches the section/subsection/unit tree
  • GET /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

@dwong2708
Copy link
Copy Markdown
Contributor Author

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_handler in cms/djangoapps/contentstore/views/block.py — fetches the section/subsection/unit tree
  • GET /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

GET /api/contentstore/v1/course_details/{courseId} is already covered. For GET /xblock/outline/{blockId}, I don’t see it being used during course creation. Is it needed? @rodmgwgu

@rodmgwgu
Copy link
Copy Markdown
Contributor

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_handler in cms/djangoapps/contentstore/views/block.py — fetches the section/subsection/unit tree
  • GET /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

GET /api/contentstore/v1/course_details/{courseId} is already covered. For GET /xblock/outline/{blockId}, I don’t see it being used during course creation. Is it needed? @rodmgwgu

Not for course creation, but for outline, it sounds like we should also update it to use authz.

@wgu-taylor-payne
Copy link
Copy Markdown
Contributor

GET /api/contentstore/v1/course_details/{courseId} is already covered. For GET /xblock/outline/{blockId}, I don’t see it being used during course creation. Is it needed? @rodmgwgu

@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.

@dwong2708
Copy link
Copy Markdown
Contributor Author

GET /api/contentstore/v1/course_details/{courseId} is already covered. For GET /xblock/outline/{blockId}, I don’t see it being used during course creation. Is it needed? @rodmgwgu

@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 ?

@wgu-taylor-payne wgu-taylor-payne merged commit fb04cbc into openedx:master Apr 10, 2026
48 checks passed
@github-project-automation github-project-automation bot moved this from Waiting on Author to Done in Contributions Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). open-source-contribution PR author is not from Axim or 2U

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Task - RBAC AuthZ - Implement new permissions for course creation and outline

7 participants