Skip to content

Commit e627dd7

Browse files
committed
fixup! feat: add course authoring migration and rollback scripts
1 parent ac7acef commit e627dd7

5 files changed

Lines changed: 34 additions & 20 deletions

File tree

CHANGELOG.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ Change Log
1414
Unreleased
1515
**********
1616

17+
0.23.0 - 2026-02-18
18+
********************
19+
20+
Added
21+
=====
22+
23+
* Add authz_migrate_course_authoring command to migrate legacy CourseAccessRole data to the new Authz (Casbin-based) system
24+
* Add authz_rollback_course_authoring command to rollback Authz roles back to legacy CourseAccessRole
25+
* Support optional --delete flag for controlled cleanup of source permissions after successful migration
26+
* Add migrate_legacy_course_roles_to_authz and migrate_authz_to_legacy_course_roles service functions
27+
* Add unit tests to verify migration and command behavior
28+
1729
Added
1830
=====
1931

openedx_authz/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44

55
import os
66

7-
__version__ = "0.22.0"
7+
__version__ = "0.23.0"
88

99
ROOT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))

openedx_authz/constants/roles.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,6 @@
160160
permissions.COURSES_EXPORT_TAGS,
161161
]
162162

163-
COURSE_LIMITED_STAFF_PERMISSIONS = []
164-
165-
COURSE_DATA_RESEARCHER_PERMISSIONS = []
166-
167-
COURSE_ADMIN = RoleData(external_key="course_admin", permissions=COURSE_ADMIN_PERMISSIONS)
168163
COURSE_STAFF = RoleData(external_key="course_staff", permissions=COURSE_STAFF_PERMISSIONS)
169164

170165
COURSE_LIMITED_STAFF_PERMISSIONS = [

openedx_authz/engine/utils.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
COURSE_DATA_RESEARCHER,
2020
COURSE_LIMITED_STAFF,
2121
COURSE_STAFF,
22+
LEGACY_COURSE_ROLE_EQUIVALENCES,
2223
LIBRARY_ADMIN,
2324
LIBRARY_AUTHOR,
2425
LIBRARY_USER,
@@ -29,13 +30,8 @@
2930
GROUPING_POLICY_PTYPES = ["g", "g2", "g3", "g4", "g5", "g6"]
3031

3132

32-
# Map new roles back to legacy roles
33-
role_to_legacy_role = {
34-
COURSE_ADMIN.external_key: "instructor",
35-
COURSE_STAFF.external_key: "staff",
36-
COURSE_LIMITED_STAFF.external_key: "limited_staff",
37-
COURSE_DATA_RESEARCHER.external_key: "data_researcher",
38-
}
33+
# Map new roles back to legacy roles for rollback purposes
34+
COURSE_ROLE_EQUIVALENCES = {v: k for k, v in LEGACY_COURSE_ROLE_EQUIVALENCES.items()}
3935

4036

4137
def migrate_policy_between_enforcers(
@@ -273,7 +269,7 @@ def migrate_authz_to_legacy_course_roles(CourseAccessRole, UserSubject, delete_a
273269
course_overview = assignment.scope.get_object()
274270

275271
for role in assignment.roles:
276-
legacy_role = role_to_legacy_role.get(role.external_key)
272+
legacy_role = COURSE_ROLE_EQUIVALENCES.get(role.external_key)
277273
if legacy_role is None:
278274
logger.error(f"Unknown role: {role} for User: {user_external_key}")
279275
roles_with_errors.append((user_external_key, role.external_key, scope))

openedx_authz/tests/test_migrations.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
COURSE_DATA_RESEARCHER,
1414
COURSE_LIMITED_STAFF,
1515
COURSE_STAFF,
16+
LEGACY_COURSE_ROLE_EQUIVALENCES,
1617
LIBRARY_ADMIN,
1718
LIBRARY_USER,
1819
)
@@ -305,6 +306,16 @@ def tearDown(self):
305306
scope_external_key=self.course_id,
306307
)
307308

309+
def test_legacy_course_role_equivalences_mapping(self):
310+
"""Test that the LEGACY_COURSE_ROLE_EQUIVALENCES mapping contains no duplicate values."""
311+
legacy_roles = LEGACY_COURSE_ROLE_EQUIVALENCES.keys()
312+
new_roles = LEGACY_COURSE_ROLE_EQUIVALENCES.values()
313+
314+
# Check that there are no duplicate values in the mapping
315+
self.assertEqual(
316+
len(legacy_roles), len(set(new_roles)), "LEGACY_COURSE_ROLE_EQUIVALENCES contains duplicate values"
317+
)
318+
308319
@patch("openedx_authz.api.data.CourseOverview", CourseOverview)
309320
def test_migrate_legacy_course_roles_to_authz_and_rollback_no_deletion(self):
310321
"""Test the migration of legacy permissions from CourseAccessRole to the new Casbin-based model
@@ -613,12 +624,12 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_no_new_role_equi
613624
CourseAccessRole.objects.all().order_by("id").values("id", "user_id", "org", "course_id", "role")
614625
)
615626

616-
# Mock the role_to_legacy_role mapping to only include a mapping
627+
# Mock the COURSE_ROLE_EQUIVALENCES mapping to only include a mapping
617628
# for COURSE_ADMIN to simulate the scenario where the staff, limited_staff
618629
# and data_researcher roles do not have a legacy role equivalent and
619630
# therefore cannot be migrated back to legacy roles during the rollback.
620631
with patch(
621-
"openedx_authz.engine.utils.role_to_legacy_role",
632+
"openedx_authz.engine.utils.COURSE_ROLE_EQUIVALENCES",
622633
{COURSE_ADMIN.external_key: "instructor"},
623634
):
624635
permissions_with_errors = migrate_authz_to_legacy_course_roles(
@@ -636,21 +647,21 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_no_new_role_equi
636647
assignments = get_user_role_assignments_in_scope(
637648
user_external_key=user.username, scope_external_key=self.course_id
638649
)
639-
# Since we are mocking the role_to_legacy_role mapping to only include a mapping for COURSE_ADMIN,
650+
# Since we are mocking the COURSE_ROLE_EQUIVALENCES mapping to only include a mapping for COURSE_ADMIN,
640651
# the staff role will not have a legacy role equivalent and therefore should not be migrated back
641652
self.assertEqual(len(assignments), 1)
642653
for user in self.limited_staff:
643654
assignments = get_user_role_assignments_in_scope(
644655
user_external_key=user.username, scope_external_key=self.course_id
645656
)
646-
# Since we are mocking the role_to_legacy_role mapping to only include a mapping for COURSE_ADMIN,
657+
# Since we are mocking the COURSE_ROLE_EQUIVALENCES mapping to only include a mapping for COURSE_ADMIN,
647658
# the limited_staff role will not have a legacy role equivalent and therefore should not be migrated back
648659
self.assertEqual(len(assignments), 1)
649660
for user in self.data_researcher:
650661
assignments = get_user_role_assignments_in_scope(
651662
user_external_key=user.username, scope_external_key=self.course_id
652663
)
653-
# Since we are mocking the role_to_legacy_role mapping to only include a mapping for COURSE_ADMIN,
664+
# Since we are mocking the COURSE_ROLE_EQUIVALENCES mapping to only include a mapping for COURSE_ADMIN,
654665
# the data_researcher role will not have a legacy role equivalent and therefore should not be migrated back
655666
self.assertEqual(len(assignments), 1)
656667

@@ -680,7 +691,7 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_no_new_role_equi
680691

681692
# After rollback, we should have 9 UserSubjects related to the course permissions
682693
# since the users with staff, limited_staff and data_researcher roles will not be
683-
# migrated back to legacy roles due to our mocked role_to_legacy_role mapping.
694+
# migrated back to legacy roles due to our mocked COURSE_ROLE_EQUIVALENCES mapping.
684695
self.assertEqual(len(state_after_migration_user_subjects), 9)
685696

686697
@patch("openedx_authz.management.commands.authz_migrate_course_authoring.CourseAccessRole", CourseAccessRole)

0 commit comments

Comments
 (0)