Skip to content

Commit a17748e

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

3 files changed

Lines changed: 102 additions & 64 deletions

File tree

openedx_authz/engine/utils.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@
2929
GROUPING_POLICY_PTYPES = ["g", "g2", "g3", "g4", "g5", "g6"]
3030

3131

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+
}
39+
40+
3241
def migrate_policy_between_enforcers(
3342
source_enforcer: Enforcer,
3443
target_enforcer: Enforcer,
@@ -268,14 +277,6 @@ def migrate_authz_to_legacy_course_roles(CourseAccessRole, UserSubject, delete_a
268277
if not scope.startswith("course-v1:"):
269278
continue
270279

271-
# Map new roles back to legacy roles
272-
role_to_legacy_role = {
273-
COURSE_ADMIN.external_key: "instructor",
274-
COURSE_STAFF.external_key: "staff",
275-
COURSE_LIMITED_STAFF.external_key: "limited_staff",
276-
COURSE_DATA_RESEARCHER.external_key: "data_researcher",
277-
}
278-
279280
legacy_role = role_to_legacy_role.get(role.external_key)
280281
if legacy_role is None:
281282
logger.error(f"Unknown role: {role} for User: {user_external_key}")

openedx_authz/migrations/0008_migrate_course_roles_to_authz.py

Lines changed: 0 additions & 56 deletions
This file was deleted.

openedx_authz/tests/test_migrations.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,99 @@ def test_migrate_legacy_course_roles_to_authz_and_rollback_with_deletion(self):
590590
# After rollback, we should have 0 UserSubjects related to the course permissions
591591
self.assertEqual(len(state_after_migration_user_subjects), 0)
592592

593+
@patch("openedx_authz.api.data.CourseOverview", CourseOverview)
594+
def test_migrate_legacy_course_roles_to_authz_and_rollback_with_no_new_role_equivalent(self):
595+
"""Test the migration of legacy course roles to the new Casbin-based model
596+
and the rollback when there is no equivalent new role.
597+
"""
598+
599+
# Migrate from legacy CourseAccessRole to new Casbin-based model
600+
permissions_with_errors = migrate_legacy_course_roles_to_authz(CourseAccessRole, delete_after_migration=True)
601+
AuthzEnforcer.get_enforcer().load_policy()
602+
603+
# Now let's rollback
604+
605+
# Capture the state of permissions before rollback to verify that rollback restores the original state
606+
original_state_user_subjects = list(
607+
UserSubject.objects.filter(casbin_rules__scope__coursescope__course_overview__isnull=False)
608+
.distinct()
609+
.order_by("id")
610+
.values("id", "user_id")
611+
)
612+
original_state_access_roles = list(
613+
CourseAccessRole.objects.all().order_by("id").values("id", "user_id", "org", "course_id", "role")
614+
)
615+
616+
# Mock the role_to_legacy_role mapping to only include a mapping
617+
# for COURSE_ADMIN to simulate the scenario where the staff, limited_staff
618+
# and data_researcher roles do not have a legacy role equivalent and
619+
# therefore cannot be migrated back to legacy roles during the rollback.
620+
with patch(
621+
"openedx_authz.engine.utils.role_to_legacy_role",
622+
{COURSE_ADMIN.external_key: "instructor"},
623+
):
624+
permissions_with_errors = migrate_authz_to_legacy_course_roles(
625+
CourseAccessRole, UserSubject, delete_after_migration=True
626+
)
627+
628+
# Check that each user has the expected legacy role after rollback
629+
# and that errors are logged for any permissions that could not be rolled back
630+
for user in self.admin_users:
631+
assignments = get_user_role_assignments_in_scope(
632+
user_external_key=user.username, scope_external_key=self.course_id
633+
)
634+
self.assertEqual(len(assignments), 0)
635+
for user in self.staff_users:
636+
assignments = get_user_role_assignments_in_scope(
637+
user_external_key=user.username, scope_external_key=self.course_id
638+
)
639+
# Since we are mocking the role_to_legacy_role mapping to only include a mapping for COURSE_ADMIN,
640+
# the staff role will not have a legacy role equivalent and therefore should not be migrated back
641+
self.assertEqual(len(assignments), 1)
642+
for user in self.limited_staff:
643+
assignments = get_user_role_assignments_in_scope(
644+
user_external_key=user.username, scope_external_key=self.course_id
645+
)
646+
# Since we are mocking the role_to_legacy_role mapping to only include a mapping for COURSE_ADMIN,
647+
# the limited_staff role will not have a legacy role equivalent and therefore should not be migrated back
648+
self.assertEqual(len(assignments), 1)
649+
for user in self.data_researcher:
650+
assignments = get_user_role_assignments_in_scope(
651+
user_external_key=user.username, scope_external_key=self.course_id
652+
)
653+
# Since we are mocking the role_to_legacy_role mapping to only include a mapping for COURSE_ADMIN,
654+
# the data_researcher role will not have a legacy role equivalent and therefore should not be migrated back
655+
self.assertEqual(len(assignments), 1)
656+
657+
# 3 staff + 3 limited_staff + 3 data_researcher = 9 entries with no legacy role equivalent
658+
self.assertEqual(len(permissions_with_errors), 9)
659+
660+
state_after_migration_user_subjects = list(
661+
UserSubject.objects.filter(casbin_rules__scope__coursescope__course_overview__isnull=False)
662+
.distinct()
663+
.order_by("id")
664+
.values("id", "user_id")
665+
)
666+
after_migrate_state_access_roles = list(
667+
CourseAccessRole.objects.all().order_by("id").values("id", "user_id", "org", "course_id", "role")
668+
)
669+
670+
# Before the rollback, we should only have the 1 invalid role entry
671+
# since we set delete_after_migration to True in the migration.
672+
self.assertEqual(len(original_state_access_roles), 1)
673+
674+
# All original entries (1) + 3 users * 1 roles = 4
675+
self.assertEqual(len(after_migrate_state_access_roles), 1 + 3)
676+
677+
# Before the rollback, we should have the 12 UserSubjects related to the course permissions
678+
# since we had 3 users with 4 roles each in the original state.
679+
self.assertEqual(len(original_state_user_subjects), 12)
680+
681+
# After rollback, we should have 9 UserSubjects related to the course permissions
682+
# 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.
684+
self.assertEqual(len(state_after_migration_user_subjects), 9)
685+
593686
@patch("openedx_authz.management.commands.authz_migrate_course_authoring.CourseAccessRole", CourseAccessRole)
594687
@patch("openedx_authz.management.commands.authz_migrate_course_authoring.migrate_legacy_course_roles_to_authz")
595688
def test_authz_migrate_course_authoring_command(self, mock_migrate):

0 commit comments

Comments
 (0)