diff --git a/common/djangoapps/student/roles.py b/common/djangoapps/student/roles.py index 81a28773950f..58e91bf96b68 100644 --- a/common/djangoapps/student/roles.py +++ b/common/djangoapps/student/roles.py @@ -17,6 +17,7 @@ from openedx_authz.api import users as authz_api from openedx_authz.api.data import CourseOverviewData, OrgCourseOverviewGlobData, RoleAssignmentData from openedx_authz.constants import roles as authz_roles +from organizations.api import get_organizations from common.djangoapps.student.models import CourseAccessRole from common.djangoapps.student.signals.signals import emit_course_access_role_added, emit_course_access_role_removed @@ -632,6 +633,11 @@ def _authz_get_orgs_for_user(self, user) -> list[str]: user_external_key=user.username, role_external_key=role, ) + # A platform-wide grant (course-v1:*, lib:*) covers every org, not just the ones + # with a concrete assignment. Platform-glob scopes have no .org attribute at all + # (unlike org-glob/course/library scopes, where it's a real field that can be None). + if any(assignment.scope.IS_PLATFORM_GLOB for assignment in assignments): + return [org["short_name"] for org in get_organizations()] orgs = {assignment.scope.org for assignment in assignments if assignment.scope.org is not None} return list(orgs) diff --git a/common/djangoapps/student/tests/test_roles.py b/common/djangoapps/student/tests/test_roles.py index 1979bc6687fd..975339ea67dd 100644 --- a/common/djangoapps/student/tests/test_roles.py +++ b/common/djangoapps/student/tests/test_roles.py @@ -14,6 +14,7 @@ ContentLibraryData, CourseOverviewData, OrgCourseOverviewGlobData, + PlatformCourseOverviewGlobData, RoleAssignmentData, RoleData, ScopeData, @@ -21,6 +22,7 @@ ) from openedx_authz.constants.roles import COURSE_ADMIN, COURSE_STAFF from openedx_authz.engine.enforcer import AuthzEnforcer +from organizations.api import add_organization from common.djangoapps.student.admin import CourseAccessRoleHistoryAdmin from common.djangoapps.student.models import CourseAccessRoleHistory, User @@ -313,6 +315,33 @@ def test_get_orgs_for_user_authz(self): result = role.get_orgs_for_user(self.student) self.assertCountEqual(result, [self.course_key.org, other_org]) # noqa: PT009 + @override_waffle_flag(AUTHZ_COURSE_AUTHORING_FLAG, active=True) + def test_get_orgs_for_user_authz_platform_glob(self): + """ + A platform-wide glob assignment (course-v1:*) has no `.org` attribute, unlike + course/org-glob scopes. get_orgs_for_user must special-case it and return every + registered org instead of crashing with an AttributeError. + """ + role = CourseStaffRole(self.course_key) + + for org in self.orgs: + add_organization({"name": org, "short_name": org, "description": ""}) + + staff_authz_role = RoleData(external_key=COURSE_STAFF) + assignments = [ + RoleAssignmentData( + subject=UserData(external_key=self.student.username), + roles=[staff_authz_role], + scope=PlatformCourseOverviewGlobData(external_key="course-v1:*"), + ), + ] + + with patch("openedx_authz.api.users.get_user_role_assignments_filtered", return_value=assignments): + result = role.get_orgs_for_user(self.student) + self.assertCountEqual(result, self.orgs) # noqa: PT009 + assert role.has_org_for_user(self.student) + assert role.has_org_for_user(self.student, org=self.orgs[0]) + def test_get_authz_compat_course_access_roles_for_user(self): """ Test that get_authz_compat_course_access_roles_for_user doesn't crash when the user