1616 get_user_role_assignments ,
1717)
1818from openedx_authz .constants .roles import (
19- COURSE_ADMIN ,
20- COURSE_DATA_RESEARCHER ,
21- COURSE_LIMITED_STAFF ,
22- COURSE_STAFF ,
2319 LEGACY_COURSE_ROLE_EQUIVALENCES ,
2420 LIBRARY_ADMIN ,
2521 LIBRARY_AUTHOR ,
@@ -172,7 +168,7 @@ def migrate_legacy_permissions(ContentLibraryPermission):
172168 return permissions_with_errors
173169
174170
175- def migrate_legacy_course_roles_to_authz (CourseAccessRole , delete_after_migration ):
171+ def migrate_legacy_course_roles_to_authz (CourseAccessRole , course_id_list , org_id , delete_after_migration ):
176172 """
177173 Migrate legacy course role data to the new Casbin-based authorization model.
178174 This function reads legacy permissions from the CourseAccessRole model
@@ -193,10 +189,23 @@ def migrate_legacy_course_roles_to_authz(CourseAccessRole, delete_after_migratio
193189
194190 param CourseAccessRole: The CourseAccessRole model to use.
195191 """
192+ if not course_id_list and not org_id :
193+ raise ValueError (
194+ "At least one of course_id_list or org_id must be provided to limit the scope of the rollback migration."
195+ )
196+ course_access_role_filter = {
197+ "course_id__startswith" : "course-v1:" ,
198+ }
199+
200+ if org_id :
201+ course_access_role_filter ["org" ] = org_id
202+
203+ if course_id_list and not org_id :
204+ # Only filter by course_id if org_id is not provided,
205+ # otherwise we will filter by org_id which is more efficient
206+ course_access_role_filter ["course_id__in" ] = course_id_list
196207
197- legacy_permissions = (
198- CourseAccessRole .objects .filter (course_id__startswith = "course-v1:" ).select_related ("user" ).all ()
199- )
208+ legacy_permissions = CourseAccessRole .objects .filter (** course_access_role_filter ).select_related ("user" ).all ()
200209
201210 # List to keep track of any permissions that could not be migrated
202211 permissions_with_errors = []
@@ -205,15 +214,7 @@ def migrate_legacy_course_roles_to_authz(CourseAccessRole, delete_after_migratio
205214 for permission in legacy_permissions :
206215 # Migrate the permission to the new model
207216
208- # Derive equivalent role based on access level
209- map_legacy_role = {
210- "instructor" : COURSE_ADMIN ,
211- "staff" : COURSE_STAFF ,
212- "limited_staff" : COURSE_LIMITED_STAFF ,
213- "data_researcher" : COURSE_DATA_RESEARCHER ,
214- }
215-
216- role = map_legacy_role .get (permission .role )
217+ role = LEGACY_COURSE_ROLE_EQUIVALENCES .get (permission .role )
217218 if role is None :
218219 # This should not happen as there are no more access_levels defined
219220 # in CourseAccessRole, log and skip
@@ -224,32 +225,33 @@ def migrate_legacy_course_roles_to_authz(CourseAccessRole, delete_after_migratio
224225 # Permission applied to individual user
225226 logger .info (
226227 f"Migrating permission for User: { permission .user .username } "
227- f"to Role: { role . external_key } in Scope: { permission .course_id } "
228+ f"to Role: { role } in Scope: { permission .course_id } "
228229 )
229230
230231 is_user_added = assign_role_to_user_in_scope (
231232 user_external_key = permission .user .username ,
232- role_external_key = role . external_key ,
233+ role_external_key = role ,
233234 scope_external_key = str (permission .course_id ),
234235 )
235236
236237 if not is_user_added :
237238 logger .error (
238239 f"Failed to migrate permission for User: { permission .user .username } "
239- f"to Role: { role . external_key } in Scope: { permission .course_id } "
240+ f"to Role: { role } in Scope: { permission .course_id } "
240241 )
241242 permissions_with_errors .append (permission )
242243 continue
243244
244245 permissions_with_no_errors .append (permission )
245246
246247 if delete_after_migration :
248+ # Only delete permissions that were successfully migrated to avoid data loss.
247249 CourseAccessRole .objects .filter (id__in = [p .id for p in permissions_with_no_errors ]).delete ()
248250
249- return permissions_with_errors
251+ return permissions_with_errors , permissions_with_no_errors
250252
251253
252- def migrate_authz_to_legacy_course_roles (CourseAccessRole , UserSubject , delete_after_migration ):
254+ def migrate_authz_to_legacy_course_roles (CourseAccessRole , UserSubject , course_id_list , org_id , delete_after_migration ):
253255 """
254256 Migrate permissions from the new Casbin-based authorization model back to the legacy CourseAccessRole model.
255257 This function reads permissions from the Casbin enforcer and creates equivalent entries in the
@@ -258,15 +260,29 @@ def migrate_authz_to_legacy_course_roles(CourseAccessRole, UserSubject, delete_a
258260 This is essentially the reverse of migrate_legacy_course_roles_to_authz and is intended
259261 for rollback purposes in case of migration issues.
260262 """
263+ if not course_id_list and not org_id :
264+ raise ValueError (
265+ "At least one of course_id_list or org_id must be provided to limit the scope of the rollback migration."
266+ )
267+
261268 # 1. Get all users with course-related permissions in the new model by filtering
262269 # UserSubjects that are linked to CourseScopes with a valid course overview.
263- course_subjects = (
264- UserSubject .objects .filter (casbin_rules__scope__coursescope__course_overview__isnull = False )
265- .select_related ("user" )
266- .distinct ()
267- )
270+ course_subject_filter = {
271+ "casbin_rules__scope__coursescope__course_overview__isnull" : False ,
272+ }
273+
274+ if org_id :
275+ course_subject_filter ["casbin_rules__scope__coursescope__course_overview__org" ] = org_id
276+
277+ if course_id_list and not org_id :
278+ # Only filter by course_id if org_id is not provided,
279+ # otherwise we will filter by org_id which is more efficient
280+ course_subject_filter ["casbin_rules__scope__coursescope__course_overview__id__in" ] = course_id_list
281+
282+ course_subjects = UserSubject .objects .filter (** course_subject_filter ).select_related ("user" ).distinct ()
268283
269284 roles_with_errors = []
285+ roles_with_no_errors = []
270286
271287 for course_subject in course_subjects :
272288 user = course_subject .user
@@ -299,6 +315,7 @@ def migrate_authz_to_legacy_course_roles(CourseAccessRole, UserSubject, delete_a
299315 course_id = scope ,
300316 role = legacy_role ,
301317 )
318+ roles_with_no_errors .append ((user_external_key , role .external_key , scope ))
302319 except Exception as e : # pylint: disable=broad-exception-caught
303320 logger .error (
304321 f"Error creating CourseAccessRole for User: "
@@ -314,4 +331,4 @@ def migrate_authz_to_legacy_course_roles(CourseAccessRole, UserSubject, delete_a
314331 role_external_key = role .external_key ,
315332 scope_external_key = scope ,
316333 )
317- return roles_with_errors
334+ return roles_with_errors , roles_with_no_errors
0 commit comments