Skip to content

feat: AuthZ for course authoring compatibility layer - #38013

Merged
wgu-taylor-payne merged 10 commits into
openedx:masterfrom
WGU-Open-edX:rod/authz-compat-layer
Mar 6, 2026
Merged

feat: AuthZ for course authoring compatibility layer#38013
wgu-taylor-payne merged 10 commits into
openedx:masterfrom
WGU-Open-edX:rod/authz-compat-layer

Conversation

@rodmgwgu

@rodmgwgu rodmgwgu commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

Description

Part of the AuthZ for Course Authoring project.

Implemented a compatibility layer on legacy permission role checking for Course Authoring in studio.

This also covers legacy endpoints that are used for configuring course authoring permissions, so legacy views (like the one in instructor-dashboard) continue to work.

The objective of this task is to keep compatibility with existing codepaths that use the existing endpoints to manage roles related with course authoring.

Implementation details:

Supporting information

Closes: #37909
Related technical discovery documents:

Testing instructions

  1. Select a course to use for testing, for example course-v1:OpenedX+DemoX+DemoCourse
  2. In the Django admin, under /admin/waffle_utils/waffleflagcourseoverridemodel/, enable the flag authz.enable_course_authoring for the chosen course
  3. In Studio, as an admin user, go to the course > Course Team and add another non-admin user to the course team
  4. In MySQL, check the casbin_rule table, you should see an entry for that user as course_staff for the course
# Detailed steps
tutor dev run cms python manage.py cms dbshell
SELECT * FROM casbin_rule WHERE ptype='g';

## You should see an output like:
+-----+-------+------------------+-------------------+----------------------------------------------+----+----+----+
| id  | ptype | v0               | v1                | v2                                           | v3 | v4 | v5 |
+-----+-------+------------------+-------------------+----------------------------------------------+----+----+----+
| 142 | g     | user^contributor | role^course_staff | course-v1^course-v1:OpenedX+DemoX+DemoCourse |    |    |    |
+-----+-------+------------------+-------------------+----------------------------------------------+----+----+----+
  1. In an incognito window, go to studio and login as the non-admin user, you should be able to see and navigate the course in studio normally
  2. On the Studio Course Team page, click on the "Delete user" icon (thrash icon)
  3. Check that the role assignation was removed from the authz table
# Detailed steps
tutor dev run cms python manage.py cms dbshell
SELECT * FROM casbin_rule WHERE ptype='g';

## You should see an output like:
+-----+-------+------------------+-------------------+----------------------------------------------+----+----+----+
| id  | ptype | v0               | v1                | v2                                           | v3 | v4 | v5 |
+-----+-------+------------------+-------------------+----------------------------------------------+----+----+----+
+-----+-------+------------------+-------------------+----------------------------------------------+----+----+----+
  1. In an incognito window, go to studio and login as the non-admin user, you should no longer have access to the course.

Running relevant unit tests manually

Inside a cms container (tutor dev run lms bash):

pytest -p no:randomly --ds=cms.envs.test \
cms/djangoapps/contentstore/tests/test_course_listing.py \
common/djangoapps/student/tests/test_roles.py \
common/djangoapps/student/role_helpers.py \
common/djangoapps/student/roles.py \
openedx/core/djangoapps/content_tagging/rest_api/v1/tests/test_views.py \
openedx/core/djangoapps/embargo/tests/test_api.py \
openedx/core/djangoapps/enrollments/tests/test_data.py \
openedx/core/djangoapps/schedules/tests/test_resolvers.py \
openedx/core/djangolib/testing/utils.py \
openedx/features/content_type_gating/tests/test_models.py \
openedx/features/course_duration_limits/tests/test_models.py


pytest -p no:randomly --ds=lms.envs.test \
common/djangoapps/student/tests/test_roles.py \
common/djangoapps/student/role_helpers.py \
common/djangoapps/student/roles.py \
lms/djangoapps/ccx/tests/test_field_override_performance.py \
lms/djangoapps/course_api/blocks/tests/test_api.py \
lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py \
lms/djangoapps/courseware/tests/test_access.py \
lms/djangoapps/courseware/tests/test_model_data.py \
lms/djangoapps/courseware/tests/test_views.py \
lms/djangoapps/grades/tests/test_tasks.py \
lms/djangoapps/instructor_task/tests/test_tasks_helper.py \
lms/djangoapps/teams/tests/test_serializers.py \
openedx/core/djangoapps/content_tagging/rest_api/v1/tests/test_views.py \
openedx/core/djangoapps/embargo/tests/test_api.py \
openedx/core/djangoapps/enrollments/tests/test_data.py \
openedx/core/djangoapps/schedules/tests/test_resolvers.py \
openedx/core/djangolib/testing/utils.py \
openedx/features/content_type_gating/tests/test_models.py \
openedx/features/course_duration_limits/tests/test_models.py \
openedx/features/course_experience/tests/views/test_course_updates.py

Deadline

Verawood release.

Other information

Depends on: openedx/openedx-authz#217

Performance: I'm seeing that in average the change adds 2 database queries on related codepaths that read course data related to roles, this is mainly because of some api calls that previously returned a CourseAccessRole QuerySet now return AuthzCompatCourseAccessRole which is a data object, so we cannot optimize queries on code like this: https://github.com/WGU-Open-edX/edx-platform/blob/d28972c1f0ac26f94fcbd055ef95c899e5d9e837/cms/djangoapps/contentstore/views/course.py#L540-L541

@openedx-webhooks openedx-webhooks added open-source-contribution PR author is not from Axim or 2U core contributor PR author is a Core Contributor (who may or may not have write access to this repo). labels Feb 16, 2026
@openedx-webhooks

openedx-webhooks commented Feb 16, 2026

Copy link
Copy Markdown

Thanks for the pull request, @rodmgwgu!

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 Feb 16, 2026
@rodmgwgu
rodmgwgu force-pushed the rod/authz-compat-layer branch from b2b2bee to 6d842f9 Compare February 16, 2026 22:40
@mphilbrick211 mphilbrick211 added the mao-onboarding Reviewing this will help onboard devs from an Axim mission-aligned organization (MAO). label Feb 18, 2026
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Feb 18, 2026
@rodmgwgu
rodmgwgu force-pushed the rod/authz-compat-layer branch from c915425 to 5dc65a2 Compare February 18, 2026 22:54
@rodmgwgu
rodmgwgu force-pushed the rod/authz-compat-layer branch from d28972c to 9477f62 Compare February 20, 2026 16:13
@rodmgwgu
rodmgwgu marked this pull request as ready for review February 20, 2026 16:14
@rodmgwgu
rodmgwgu requested review from a team as code owners February 20, 2026 16:14
@bmtcril

bmtcril commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

I finally got my environment working on main again and was able to successfully test this going in both directions, it works great. I'll finish reviewing the actual code tomorrow

@bmtcril bmtcril left a comment

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 looking good, just a few comments

Comment thread cms/djangoapps/contentstore/views/library.py Outdated
Comment thread common/djangoapps/student/roles.py Outdated
Comment thread common/djangoapps/student/roles.py Outdated
@mphilbrick211 mphilbrick211 moved this from Waiting on Author to In Eng Review in Contributions Feb 25, 2026
Comment thread common/djangoapps/student/roles.py Outdated
Comment thread common/djangoapps/student/roles.py

@dwong2708 dwong2708 left a comment

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 looks good overall! just a couple of suggestions and questions.

Comment thread cms/djangoapps/contentstore/views/library.py Outdated
Comment thread cms/djangoapps/contentstore/views/library.py Outdated
Comment thread cms/djangoapps/contentstore/views/library.py Outdated
Comment thread common/djangoapps/student/roles.py Outdated
@rodmgwgu
rodmgwgu force-pushed the rod/authz-compat-layer branch from 0bd07e0 to 7638824 Compare March 4, 2026 22:57

@bmtcril bmtcril left a comment

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.

I think once the other feedback is addressed this is good to go 👍

@rodmgwgu
rodmgwgu force-pushed the rod/authz-compat-layer branch from be3398d to 50c8a13 Compare March 5, 2026 17:55
Comment thread cms/djangoapps/contentstore/views/library.py
Comment thread common/djangoapps/student/roles.py

@dwong2708 dwong2708 left a comment

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.

LGTM! Thank you

@wgu-taylor-payne
wgu-taylor-payne merged commit 12a46e6 into openedx:master Mar 6, 2026
65 checks passed
@github-project-automation github-project-automation Bot moved this from In Eng Review to Done in Contributions Mar 6, 2026
farhan added a commit that referenced this pull request Mar 10, 2026
@farhan

farhan commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

@rodmgwgu Thanks for the PR! After it was merged, we started seeing the following exception on the master branch. Reverting the PR reverting the issue.

Steps to reproduce:
Open the LMS home page, and the following logs should appear.

2026-03-10 04:32:36,306 INFO 166 [tracking] [user 4] [ip 172.18.0.1] logger.py:41 - {"name": "/api/learner_home/init", "context": {"user_id": 4, "path": "/api/learner_home/init", "course_id": "", "org_id": "", "enterprise_uuid": ""}, "username": "edx", "session": "036fc95ce131136bd8ee0a3a2a73af56", "ip": "172.18.0.1", "agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36", "host": "local.openedx.io:8000", "referer": "http://apps.local.openedx.io:1996/", "accept_language": "en-US,en;q=0.9", "event": "{\"GET\": {}, \"POST\": {}}", "time": "2026-03-10T04:32:36.306663+00:00", "event_type": "/api/learner_home/init", "event_source": "server", "page": null}
[10/Mar/2026 04:32:36] "GET /theming/asset/images/logo.png HTTP/1.1" 302 0
2026-03-10 04:32:36,346 ERROR 166 [root] [user None] [ip None] signals.py:22 - Uncaught exception from None
Traceback (most recent call last):
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 234, in __init__
    self._roles_by_course_id = BulkRoleCache.get_user_roles(user)
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 215, in get_user_roles
    return get_cache(cls.CACHE_NAMESPACE)[cls.CACHE_KEY][user.id]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
KeyError: 'roles_by_user'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 238, in get_namespace_plugin
    return drivers[namespace].plugin
           ~~~~~~~^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/stevedore/extension.py", line 452, in __getitem__
    return self._extensions_by_name[name]
           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
KeyError: 'lib'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 190, in from_string
    key_class = cls.get_namespace_plugin(namespace)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 243, in get_namespace_plugin
    raise InvalidKeyError(cls, f'{namespace}:*') from error
opaque_keys.InvalidKeyError: <class 'opaque_keys.edx.keys.CourseKey'>: lib:*

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/openedx/venv/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pyenv/versions/3.12.12/lib/python3.12/contextlib.py", line 81, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper
    return view_func(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/django/views/generic/base.py", line 105, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 515, in dispatch
    response = self.handle_exception(exc)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 475, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception
    raise exc
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 512, in dispatch
    response = handler(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/lms/djangoapps/learner_home/views.py", line 475, in get
    return self._initialize(request.user)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/lms/djangoapps/learner_home/views.py", line 513, in _initialize
    audit_access_deadlines = get_audit_access_deadlines(user, course_enrollments)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pyenv/versions/3.12.12/lib/python3.12/contextlib.py", line 81, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/lms/djangoapps/learner_home/views.py", line 419, in get_audit_access_deadlines
    course_enrollment.course_id: get_user_course_expiration_date(
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/course_duration_limits/access.py", line 80, in get_user_course_expiration_date
    access_duration = get_user_course_duration(user, course)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/course_duration_limits/access.py", line 57, in get_user_course_duration
    if not CourseDurationLimitConfig.enabled_for_enrollment(user, course):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/course_duration_limits/models.py", line 59, in enabled_for_enrollment
    target_datetime = enrollment_date_for_fbe(user, course=course)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/content_type_gating/helpers.py", line 117, in enrollment_date_for_fbe
    if user and user.id and has_staff_roles(user, course_key):
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/core/lib/cache_utils.py", line 74, in decorator
    result = wrapped(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/role_helpers.py", line 39, in has_staff_roles
    is_staff = CourseStaffRole(course_key).has_user(user)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 385, in has_user
    user._roles = RoleCache(user)
                  ^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 239, in __init__
    compat_roles = get_authz_compat_course_access_roles_for_user(user)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 144, in get_authz_compat_course_access_roles_for_user
    parsed_key = CourseKey.from_string(course_key)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 198, in from_string
    return cls.deprecated_fallback._from_deprecated_string(serialized)  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/edx/locator.py", line 403, in _from_deprecated_string
    raise InvalidKeyError(cls, serialized)
opaque_keys.InvalidKeyError: <class 'opaque_keys.edx.locator.CourseLocator'>: lib:axim:1
Internal Server Error: /api/learner_home/init
Traceback (most recent call last):
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 234, in __init__
    self._roles_by_course_id = BulkRoleCache.get_user_roles(user)
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 215, in get_user_roles
    return get_cache(cls.CACHE_NAMESPACE)[cls.CACHE_KEY][user.id]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
KeyError: 'roles_by_user'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 238, in get_namespace_plugin
    return drivers[namespace].plugin
           ~~~~~~~^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/stevedore/extension.py", line 452, in __getitem__
    return self._extensions_by_name[name]
           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
KeyError: 'lib'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 190, in from_string
    key_class = cls.get_namespace_plugin(namespace)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 243, in get_namespace_plugin
    raise InvalidKeyError(cls, f'{namespace}:*') from error
opaque_keys.InvalidKeyError: <class 'opaque_keys.edx.keys.CourseKey'>: lib:*

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/openedx/venv/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pyenv/versions/3.12.12/lib/python3.12/contextlib.py", line 81, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper
    return view_func(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/django/views/generic/base.py", line 105, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 515, in dispatch
    response = self.handle_exception(exc)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 475, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception
    raise exc
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 512, in dispatch
    response = handler(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/lms/djangoapps/learner_home/views.py", line 475, in get
    return self._initialize(request.user)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/lms/djangoapps/learner_home/views.py", line 513, in _initialize
    audit_access_deadlines = get_audit_access_deadlines(user, course_enrollments)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pyenv/versions/3.12.12/lib/python3.12/contextlib.py", line 81, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/lms/djangoapps/learner_home/views.py", line 419, in get_audit_access_deadlines
    course_enrollment.course_id: get_user_course_expiration_date(
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/course_duration_limits/access.py", line 80, in get_user_course_expiration_date
    access_duration = get_user_course_duration(user, course)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/course_duration_limits/access.py", line 57, in get_user_course_duration
    if not CourseDurationLimitConfig.enabled_for_enrollment(user, course):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/course_duration_limits/models.py", line 59, in enabled_for_enrollment
    target_datetime = enrollment_date_for_fbe(user, course=course)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/content_type_gating/helpers.py", line 117, in enrollment_date_for_fbe
    if user and user.id and has_staff_roles(user, course_key):
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/core/lib/cache_utils.py", line 74, in decorator
    result = wrapped(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/role_helpers.py", line 39, in has_staff_roles
    is_staff = CourseStaffRole(course_key).has_user(user)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 385, in has_user
    user._roles = RoleCache(user)
                  ^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 239, in __init__
    compat_roles = get_authz_compat_course_access_roles_for_user(user)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 144, in get_authz_compat_course_access_roles_for_user
    parsed_key = CourseKey.from_string(course_key)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 198, in from_string
    return cls.deprecated_fallback._from_deprecated_string(serialized)  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/edx/locator.py", line 403, in _from_deprecated_string
    raise InvalidKeyError(cls, serialized)
opaque_keys.InvalidKeyError: <class 'opaque_keys.edx.locator.CourseLocator'>: lib:axim:1
2026-03-10 04:32:36,448 ERROR 166 [django.request] [user None] [ip None] log.py:253 - Internal Server Error: /api/learner_home/init
Traceback (most recent call last):
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 234, in __init__
    self._roles_by_course_id = BulkRoleCache.get_user_roles(user)
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 215, in get_user_roles
    return get_cache(cls.CACHE_NAMESPACE)[cls.CACHE_KEY][user.id]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
KeyError: 'roles_by_user'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 238, in get_namespace_plugin
    return drivers[namespace].plugin
           ~~~~~~~^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/stevedore/extension.py", line 452, in __getitem__
    return self._extensions_by_name[name]
           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
KeyError: 'lib'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 190, in from_string
    key_class = cls.get_namespace_plugin(namespace)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 243, in get_namespace_plugin
    raise InvalidKeyError(cls, f'{namespace}:*') from error
opaque_keys.InvalidKeyError: <class 'opaque_keys.edx.keys.CourseKey'>: lib:*

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/openedx/venv/lib/python3.12/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pyenv/versions/3.12.12/lib/python3.12/contextlib.py", line 81, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper
    return view_func(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/django/views/generic/base.py", line 105, in view
    return self.dispatch(request, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 515, in dispatch
    response = self.handle_exception(exc)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 475, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception
    raise exc
  File "/openedx/venv/lib/python3.12/site-packages/rest_framework/views.py", line 512, in dispatch
    response = handler(request, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/lms/djangoapps/learner_home/views.py", line 475, in get
    return self._initialize(request.user)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/lms/djangoapps/learner_home/views.py", line 513, in _initialize
    audit_access_deadlines = get_audit_access_deadlines(user, course_enrollments)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/pyenv/versions/3.12.12/lib/python3.12/contextlib.py", line 81, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/lms/djangoapps/learner_home/views.py", line 419, in get_audit_access_deadlines
    course_enrollment.course_id: get_user_course_expiration_date(
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/course_duration_limits/access.py", line 80, in get_user_course_expiration_date
    access_duration = get_user_course_duration(user, course)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/course_duration_limits/access.py", line 57, in get_user_course_duration
    if not CourseDurationLimitConfig.enabled_for_enrollment(user, course):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/course_duration_limits/models.py", line 59, in enabled_for_enrollment
    target_datetime = enrollment_date_for_fbe(user, course=course)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/features/content_type_gating/helpers.py", line 117, in enrollment_date_for_fbe
    if user and user.id and has_staff_roles(user, course_key):
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/openedx/core/lib/cache_utils.py", line 74, in decorator
    result = wrapped(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/role_helpers.py", line 39, in has_staff_roles
    is_staff = CourseStaffRole(course_key).has_user(user)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 385, in has_user
    user._roles = RoleCache(user)
                  ^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 239, in __init__
    compat_roles = get_authz_compat_course_access_roles_for_user(user)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/edx-platform/common/djangoapps/student/roles.py", line 144, in get_authz_compat_course_access_roles_for_user
    parsed_key = CourseKey.from_string(course_key)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/__init__.py", line 198, in from_string
    return cls.deprecated_fallback._from_deprecated_string(serialized)  # type: ignore
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/openedx/venv/lib/python3.12/site-packages/opaque_keys/edx/locator.py", line 403, in _from_deprecated_string
    raise InvalidKeyError(cls, serialized)
opaque_keys.InvalidKeyError: <class 'opaque_keys.edx.locator.CourseLocator'>: lib:axim:1
[10/Mar/2026 04:32:36] "GET /api/learner_home/init HTTP/1.1" 500 459267

farhan added a commit that referenced this pull request Mar 10, 2026
farhan added a commit that referenced this pull request Mar 10, 2026
@rodmgwgu

Copy link
Copy Markdown
Contributor Author

@farhan Thanks for reporting, this PR should fix the issue, however I don't currently have the setup to try to replicate the issue: #38131

farhan added a commit that referenced this pull request Mar 11, 2026
farhan added a commit that referenced this pull request Mar 11, 2026
farhan added a commit that referenced this pull request Mar 11, 2026
farhan added a commit that referenced this pull request Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core contributor PR author is a Core Contributor (who may or may not have write access to this repo). 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

Archived in project

Development

Successfully merging this pull request may close these issues.

Task - RBAC AuthZ - Implement compatibility layer on legacy endpoints that are used for configuring permissions

7 participants