1515from opaque_keys .edx .keys import CourseKey
1616from opaque_keys .edx .locator import CourseLocator
1717from openedx_authz .api import users as authz_api
18- from openedx_authz .api .data import CourseOverviewData , OrgCourseOverviewGlobData , RoleAssignmentData
18+ from openedx_authz .api .data import (
19+ CourseOverviewData ,
20+ OrgCourseOverviewGlobData ,
21+ PlatformCourseOverviewGlobData ,
22+ RoleAssignmentData ,
23+ )
1924from openedx_authz .constants import roles as authz_roles
2025from organizations .api import get_organizations
2126
@@ -160,44 +165,55 @@ class AuthzCompatCourseAccessRole:
160165 role : str
161166
162167
163- def _get_org_and_course_id_from_authz_scope (
164- scope : CourseOverviewData | OrgCourseOverviewGlobData ,
165- ) -> tuple [str , str | None ] | None :
168+ def _get_orgs_and_course_ids_from_authz_scope (
169+ scope : CourseOverviewData | OrgCourseOverviewGlobData | PlatformCourseOverviewGlobData ,
170+ ) -> list [ tuple [str , str | None ]] :
166171 """
167- Extract the org and course key from an AuthZ course assignment scope.
172+ Extract the ( org, course_id) pairs an AuthZ course assignment scope maps to .
168173
169- Course-scoped assignments return ``(org, course_external_key)``.
170- Org-wide assignments return ``(org, None)``.
174+ Course-scoped assignments map to a single ``(org, course_external_key)`` pair .
175+ Org-wide assignments map to a single ``(org, None)`` pair .
171176
172- Returns ``None`` when the org cannot be determined. For org-wide scopes,
173- ``OrgGlobData.org`` is typed as ``str | None`` because it is parsed from
174- ``external_key`` and returns ``None`` for malformed glob patterns.
177+ Platform-wide assignments (``course-v1:*``) apply to every org, not just one, so
178+ they map to ``(org, None)`` for *every registered org* — the same shape an org-wide
179+ grant already produces, just repeated per org. This lets a platform-wide grant be
180+ picked up by the existing OrgRole-based legacy checks (e.g. ``has_staff_roles``,
181+ ``get_user_permissions``, which already check org-level and course-level access
182+ separately) with no changes to ``RoleCache``/``OrgRole``/``CourseRole``.
183+
184+ Returns an empty list when the org cannot be determined (e.g. a malformed org-glob
185+ external_key, where ``OrgGlobData.org`` is ``None``) or the scope type isn't one of
186+ the above.
175187 """
176188 if isinstance (scope , CourseOverviewData ):
177189 course_id = scope .external_key
178- return get_org_from_key (course_id ), course_id
179- if isinstance (scope , OrgCourseOverviewGlobData ):
180- return scope .org , None
181- return None
190+ return [(get_org_from_key (course_id ), course_id )]
191+ if isinstance (scope , PlatformCourseOverviewGlobData ):
192+ return [(org ["short_name" ], None ) for org in get_organizations ()]
193+ if isinstance (scope , OrgCourseOverviewGlobData ) and scope .org is not None :
194+ return [(scope .org , None )]
195+ return []
182196
183197
184198def authz_get_all_course_assignments_for_user (user : User ) -> list [RoleAssignmentData ]:
185199 """
186200 Return AuthZ role assignments for a user that apply to courses.
187201
188- Includes assignments scoped to a specific course (``CourseOverviewData``) and
189- assignments scoped to all courses in an organization (``OrgCourseOverviewGlobData``).
190- Assignments for other resource types, such as content libraries, are excluded.
202+ Includes assignments scoped to a specific course (``CourseOverviewData``), to all
203+ courses in an organization (``OrgCourseOverviewGlobData``), and to all courses on
204+ the platform (``PlatformCourseOverviewGlobData``). Assignments for other resource
205+ types, such as content libraries, are excluded.
191206
192207 Args:
193208 user (User): The user whose AuthZ role assignments should be retrieved.
194209
195210 Returns:
196- list[RoleAssignmentData]: Role assignments whose scope is course-level or org-wide
211+ list[RoleAssignmentData]: Role assignments whose scope is course-level,
212+ org-wide, or platform-wide.
197213 """
198214 return authz_api .get_user_role_assignments_per_scope_type (
199215 user_external_key = user .username ,
200- scope_types = (CourseOverviewData , OrgCourseOverviewGlobData ),
216+ scope_types = (CourseOverviewData , OrgCourseOverviewGlobData , PlatformCourseOverviewGlobData ),
201217 )
202218
203219
@@ -208,9 +224,10 @@ def _compat_roles_from_authz_assignment(
208224 """
209225 Convert an AuthZ role assignment into legacy-compatible course access roles.
210226
211- Course-scoped assignments produce roles tied to a specific course key.
212- Org-wide assignments produce org-level roles with no course key (``course_id``
213- is ``None``), matching legacy ``OrgStaffRole`` / ``OrgInstructorRole`` behavior.
227+ Course-scoped assignments produce roles tied to a specific course key. Org-wide
228+ and platform-wide assignments produce org-level roles with no course key
229+ (``course_id`` is ``None``), matching legacy ``OrgStaffRole`` / ``OrgInstructorRole``
230+ behavior — a platform-wide assignment produces one such role per registered org.
214231 AuthZ roles without a legacy mapping are skipped.
215232
216233 Args:
@@ -223,25 +240,21 @@ def _compat_roles_from_authz_assignment(
223240 assignment. Returns an empty set if the org cannot be determined from
224241 the scope or no roles could be mapped.
225242 """
226- org_and_course_id = _get_org_and_course_id_from_authz_scope (assignment .scope )
227- if org_and_course_id is None :
228- return set ()
229- org , course_id = org_and_course_id
230-
231243 compat_roles = set ()
232- for role in assignment .roles :
233- legacy_role = get_legacy_role_from_authz_role (authz_role = role .external_key )
234- if legacy_role is None :
235- continue
236- compat_roles .add (
237- AuthzCompatCourseAccessRole (
238- user_id = user .id ,
239- username = user .username ,
240- org = org ,
241- course_id = course_id ,
242- role = legacy_role ,
244+ for org , course_id in _get_orgs_and_course_ids_from_authz_scope (assignment .scope ):
245+ for role in assignment .roles :
246+ legacy_role = get_legacy_role_from_authz_role (authz_role = role .external_key )
247+ if legacy_role is None :
248+ continue
249+ compat_roles .add (
250+ AuthzCompatCourseAccessRole (
251+ user_id = user .id ,
252+ username = user .username ,
253+ org = org ,
254+ course_id = course_id ,
255+ role = legacy_role ,
256+ )
243257 )
244- )
245258 return compat_roles
246259
247260
0 commit comments