feat: migrate org scope access forward and rollback from/to authz policies#249
feat: migrate org scope access forward and rollback from/to authz policies#249mariajgrimaldi wants to merge 14 commits intomainfrom
Conversation
|
Thanks for the pull request, @mariajgrimaldi! 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. |
cf6f212 to
749754c
Compare
39591db to
2375588
Compare
openedx_authz/engine/utils.py
Outdated
| "course_id__startswith": "course-v1:", | ||
| } | ||
|
|
||
| # TODO: not sure if we should keep the startswith here |
There was a problem hiding this comment.
I think those are legacy libraries, right? In that case, it makes sense to keep that filter
There was a problem hiding this comment.
Very interesting. Thanks for raising it! I'll work on this.
I was thinking about excluding any course_id different than empty (org-wide) or starting with course-v1 (course-level). What do you think?
There was a problem hiding this comment.
Sounds good. I was going to suggest adding another condition to handle NULL values, but since it doesn’t accept them, it’s all good.
rodmgwgu
left a comment
There was a problem hiding this comment.
Code is looking good, apart from the existing comments. Also I see that there is a coverage issue.
|
@mariajgrimaldi just a minor comment, could you add something like the following to the PR description? Resolves: #222 This helps link the task to the PR. |
…lob or specific key)
f099f26 to
7ad67d0
Compare
| course_access_role_model.objects.filter(**course_access_role_filter) | ||
| .filter(Q(course_id=CourseKeyField.Empty) | Q(course_id__startswith=CourseOverviewData.NAMESPACE)) | ||
| .select_related("user") | ||
| .all() |
There was a problem hiding this comment.
It looks like when using select_related on a queryset, the latter operation is not actually needed. Thanks for noticing!
| if course_id_list and not org_id: | ||
| # Only filter by course_id if org_id is not provided, | ||
| # otherwise we will filter by org_id which is more efficient | ||
| course_subject_filter["casbin_rules__scope__coursescope__course_overview__id__in"] = course_id_list | ||
|
|
||
| course_subjects = user_subject_model.objects.filter(**course_subject_filter).select_related("user").distinct() | ||
| role_assignments = [ | ||
| role_assignment | ||
| for role_assignment in role_assignments | ||
| if role_assignment.scope.course_id in course_id_list | ||
| ] |
There was a problem hiding this comment.
I was running some tests, and the previous isinstance check is indeed necessary. For example, when migrating a specific course, there may also be org-level global assignments.
If I run:
python manage.py lms authz_rollback_course_authoring --course-id-list course-v1:SeedOrg+CS101+2024 --deleteWe get the following error:
Rollback failed due to unexpected error: 'OrgCourseOverviewGlobData' object has no attribute 'course_id'
Traceback (most recent call last):
File "/openedx/edx-platform/manage.py", line 93, in <module>
execute_from_command_line([sys.argv[0]] + django_args)
File "/openedx/venv/lib/python3.12/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
utility.execute()
File "/openedx/venv/lib/python3.12/site-packages/django/core/management/__init__.py", line 436, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/openedx/venv/lib/python3.12/site-packages/django/core/management/base.py", line 420, in run_from_argv
self.execute(*args, **cmd_options)
File "/openedx/venv/lib/python3.12/site-packages/django/core/management/base.py", line 464, in execute
output = self.handle(*args, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/openedx/extra-deps/openedx-authz/openedx_authz/management/commands/authz_rollback_course_authoring.py", line 69, in handle
errors, success = migrate_authz_to_legacy_course_roles(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/openedx/extra-deps/openedx-authz/openedx_authz/engine/utils.py", line 345, in migrate_authz_to_legacy_course_roles
if role_assignment.scope.course_id in course_id_list
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'OrgCourseOverviewGlobData' object has no attribute 'course_id'Can we include a unit test to verify this?
There was a problem hiding this comment.
Yes! I completely missed this. Thanks
There was a problem hiding this comment.
It looks this part is untested, adding tests now
dwong2708
left a comment
There was a problem hiding this comment.
LGTM! Just a non-blocking comment
| """ | ||
| return [ | ||
| role_assignment for role_assignment in get_role_assignments() | ||
| if isinstance(role_assignment.scope, tuple(scope_types)) |
There was a problem hiding this comment.
nit: I think the tuple cast could be moved above the return to avoid casting on each iteration. Alternatively, this function could accept a tuple directly, and the caller would pass it as such.
There was a problem hiding this comment.
@mariajgrimaldi, thanks for this PR.
Thanks for the helpers added in the api, and kudos for the script for testing, it makes it easy to try this out quickly. ✨
Migration and rollback results:
=== Summary ===
✅ PASS All checks passed.
And it looks good in the Casbin table and the course role access table.
A minor thing I found is in the migration messages: if the registry already exists, the message shows an error (the command didn't create it because it already exists). I'm not sure we should show an error when the registry is there.
Examples:

Here I was playing, and I got the error because a rollback entry already exists.
Here, I ran the migration script with the delete flag, immediately after running it without it.
... I don't think it is a blocker, but it is something I found.

Description
Resolves: #222
This PR adds org-level support to the forward and rollback migration scripts.
Forward migration
The previous implementation filtered out course access roles with an empty
course_id, which excluded org-level roles entirely. Changes:course_idif non-empty (course-level scope, most restrictive)orgif non-empty (org-glob external key)Rollback migration
The rollback got a bit more complex. The previous approach queried the Casbin table through scope model relationships, but org-level roles have no associated scope, so they'd be excluded.
I went a different route: fetch all valid role assignments (course or org level), filter by
course_idororg_id, then recreate the corresponding course access roles. I found it easier to use API calls here rather than ORM queries, though the tradeoff is that filtering moves from the DB to runtime. Open to suggestions since it's the biggest refactor in the PR.How to test
I put together some scripts to test this. Download them and follow the instructions in the README:
scripts-migrations-testing.zip
Merge checklist:
Check off if complete or not applicable: