Skip to content

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

Draft
dwong2708 wants to merge 4 commits intoopenedx:masterfrom
WGU-Open-edX:dwong2708/authz-perms-course-creation-outline
Draft

feat: add AuthZ permissions to course creation and outline#38259
dwong2708 wants to merge 4 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>
  • GET /api/contentstore/v1/course_index/<course_id>
  • GET /api/contentstore/v2/downstreams/<course_id>/summary
  • GET /api/courses/v1/migrate_legacy_content_blocks/<course_id>

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

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.

🔘 Update the status of your PR

Your PR is currently marked as a draft. After completing the steps above, update its status by clicking "Ready for Review", or removing "WIP" from the title, as appropriate.


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 from 0445013 to 03241cc Compare April 7, 2026 02:12
@dwong2708 dwong2708 force-pushed the dwong2708/authz-perms-course-creation-outline branch 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.

Comment on lines +219 to +221
# If AuthZ is enabled for this course, it checks already
# permissions for staff.
return non_staff_access_response
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.

The AuthZ toggle branch has inconsistent indentation (extra leading space) compared to the surrounding block. This makes the code harder to read and may trip style/lint checks; align the comment and return indentation with the if core_toggles... block’s standard 4-space indentation level.

Suggested change
# If AuthZ is enabled for this course, it checks already
# permissions for staff.
return non_staff_access_response
# If AuthZ is enabled for this course, it checks already
# permissions for staff.
return non_staff_access_response

Copilot uses AI. Check for mistakes.
"""
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.
Comment on lines +248 to +261
if core_toggles.AUTHZ_COURSE_AUTHORING_FLAG.is_enabled():
return _has_content_creator_access(user, org)
return _has_legacy_content_creator_access(user, org)


def _has_content_creator_access(user, org):
if settings.FEATURES.get('DISABLE_COURSE_CREATION', False):
return False
org_scope_key = f"course-v1:{org}+*"
return authz_api.is_user_allowed(
user.username,
COURSES_EDIT_COURSE_CONTENT.identifier,
org_scope_key
)
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.

This AuthZ branch for course creation does not preserve the legacy GlobalStaff/staff bypass: user_has_role grants Django staff users access, but _has_content_creator_access only checks authz_api.is_user_allowed(...). If AuthZ is enabled without explicit role assignment, Django staff/superusers may lose the ability to create/rerun courses.

Copilot uses AI. Check for mistakes.
Comment on lines +253 to +261
def _has_content_creator_access(user, org):
if settings.FEATURES.get('DISABLE_COURSE_CREATION', False):
return False
org_scope_key = f"course-v1:{org}+*"
return authz_api.is_user_allowed(
user.username,
COURSES_EDIT_COURSE_CONTENT.identifier,
org_scope_key
)
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.

_has_content_creator_access is used for course creation but checks COURSES_EDIT_COURSE_CONTENT. This appears inconsistent with the PR’s stated permission model (courses.create_course) and makes it unclear which permission is actually required to create a course. Consider using the dedicated create-course permission constant (or renaming the helper to reflect that it is checking edit-content permission).

Copilot uses AI. Check for mistakes.
Comment on lines +553 to +568
@override_settings(FEATURES={"DISABLE_COURSE_CREATION": False})
def test_create_course_staff(self):
"""
Staff user can create course.
"""
response = self.authorized_staff_client.ajax_post(self.url, {
"org": self.org,
"number": "CS101",
"display_name": "Authz Course",
"run": "2026_T1",
})

# At the moment of implement new permissions for course creation,
# the staff user has no role and thus is unauthorized.
self.assertEqual(response.status_code, 403)

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.

The test docstring says “Staff user can create course”, but the assertion expects a 403. Either the expected behavior or the docstring/test name should be updated so the test intent matches the assertion (and aligns with the rest of the AuthZ tests that treat staff/superusers as elevated users).

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +156
cls.authorized_user = cls.create_user('authorized', is_staff=False)
cls.unauthorized_user = cls.create_user('unauthorized', is_staff=False)
cls.staff_user = cls.create_user('staff', is_staff=True)

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.

CourseAuthoringAuthzTestMixin.setUp() already creates self.authorized_user, self.unauthorized_user, and self.staff_user for each test. The additional user objects created in this class’s setUpClass() are unused (they’re overwritten in setUp()), which adds unnecessary DB setup and makes it harder to understand which users are actually used in assertions.

Suggested change
cls.authorized_user = cls.create_user('authorized', is_staff=False)
cls.unauthorized_user = cls.create_user('unauthorized', is_staff=False)
cls.staff_user = cls.create_user('staff', is_staff=True)

Copilot uses AI. Check for mistakes.
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: Waiting on Author

Development

Successfully merging this pull request may close these issues.

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

4 participants