diff --git a/cms/djangoapps/contentstore/tests/test_course_listing.py b/cms/djangoapps/contentstore/tests/test_course_listing.py index d05bc76b5138..5f8953619c44 100644 --- a/cms/djangoapps/contentstore/tests/test_course_listing.py +++ b/cms/djangoapps/contentstore/tests/test_course_listing.py @@ -976,7 +976,9 @@ def test_get_course_keys_from_scopes_with_platform_scope(self): "is_enabled", side_effect=self._mock_authz_toggle(enabled_keys), ): - course_keys = _get_course_keys_from_scopes([PlatformCourseOverviewGlobData(external_key="course-v1:*")]) + course_keys = _get_course_keys_from_scopes([ + PlatformCourseOverviewGlobData(external_key=PlatformCourseOverviewGlobData.build_external_key()) + ]) assert course_keys == set(authz_keys) | set(legacy_keys) @@ -993,7 +995,9 @@ def test_get_course_keys_from_scopes_with_platform_scope_global_flag_enabled(sel "is_enabled", side_effect=self._mock_authz_toggle(enabled_keys, global_enabled=True), ): - course_keys = _get_course_keys_from_scopes([PlatformCourseOverviewGlobData(external_key="course-v1:*")]) + course_keys = _get_course_keys_from_scopes([ + PlatformCourseOverviewGlobData(external_key=PlatformCourseOverviewGlobData.build_external_key()) + ]) assert course_keys == set(CourseOverview.get_all_courses().values_list("id", flat=True)) @@ -1012,8 +1016,8 @@ def test_get_course_keys_from_scopes_platform_scope_short_circuits(self): ): course_keys = _get_course_keys_from_scopes( [ - OrgCourseOverviewGlobData(external_key="course-v1:Org1+*"), - PlatformCourseOverviewGlobData(external_key="course-v1:*"), + OrgCourseOverviewGlobData(external_key=OrgCourseOverviewGlobData.build_external_key("Org1")), + PlatformCourseOverviewGlobData(external_key=PlatformCourseOverviewGlobData.build_external_key()), ] ) diff --git a/common/djangoapps/student/roles.py b/common/djangoapps/student/roles.py index 408797194a49..c628e7db3b73 100644 --- a/common/djangoapps/student/roles.py +++ b/common/djangoapps/student/roles.py @@ -645,6 +645,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 f7d5b0fc75ae..de576ede58f0 100644 --- a/common/djangoapps/student/tests/test_roles.py +++ b/common/djangoapps/student/tests/test_roles.py @@ -24,6 +24,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 organizations.tests.factories import OrganizationFactory from common.djangoapps.student.admin import CourseAccessRoleHistoryAdmin from common.djangoapps.student.models import CourseAccessRoleHistory, User @@ -316,6 +317,61 @@ 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: + OrganizationFactory(short_name=org, name=org) + + assign_role_to_user_in_scope( + self.student.username, + COURSE_STAFF.external_key, + PlatformCourseOverviewGlobData.build_external_key(), + ) + AuthzEnforcer.get_enforcer().load_policy() + + result = role.get_orgs_for_user(self.student) + assert sorted(result) == sorted(self.orgs) + assert role.has_org_for_user(self.student) + assert role.has_org_for_user(self.student, org=self.orgs[0]) + + @override_waffle_flag(AUTHZ_COURSE_AUTHORING_FLAG, active=True) + def test_get_orgs_for_user_authz_platform_glob_vs_org_scoped(self): + """ + Side-by-side check that the platform-glob branch (return every registered org) + and the regular branch (return only the orgs with a concrete assignment) produce + the same list[str] shape, over the same pool of registered orgs: an org-scoped + grant returns a subset, a platform-wide grant returns all of them. + """ + role = CourseStaffRole(self.course_key) + third_org = "Universal" + all_orgs = [*self.orgs, third_org] + + for org in all_orgs: + OrganizationFactory(short_name=org, name=org) + + subset_user = UserFactory() + assign_role_to_user_in_scope( + subset_user.username, + COURSE_STAFF.external_key, + OrgCourseOverviewGlobData.build_external_key(self.orgs[0]), + ) + assign_role_to_user_in_scope( + self.student.username, + COURSE_STAFF.external_key, + PlatformCourseOverviewGlobData.build_external_key(), + ) + AuthzEnforcer.get_enforcer().load_policy() + + assert sorted(role.get_orgs_for_user(subset_user)) == [self.orgs[0]] + assert sorted(role.get_orgs_for_user(self.student)) == sorted(all_orgs) + 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