Skip to content
12 changes: 8 additions & 4 deletions cms/djangoapps/contentstore/tests/test_course_listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -936,7 +936,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)

Expand All @@ -953,7 +955,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))

Expand All @@ -972,8 +976,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()),
]
)

Expand Down
6 changes: 6 additions & 0 deletions common/djangoapps/student/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()]
Comment thread
efortish marked this conversation as resolved.
orgs = {assignment.scope.org for assignment in assignments if assignment.scope.org is not None}
return list(orgs)

Expand Down
58 changes: 58 additions & 0 deletions common/djangoapps/student/tests/test_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
ContentLibraryData,
CourseOverviewData,
OrgCourseOverviewGlobData,
PlatformCourseOverviewGlobData,
RoleAssignmentData,
RoleData,
ScopeData,
UserData,
)
from openedx_authz.api.users import assign_role_to_user_in_scope
from openedx_authz.constants.roles import COURSE_ADMIN, COURSE_STAFF
from openedx_authz.engine.enforcer import AuthzEnforcer
from organizations.tests.factories import OrganizationFactory

from common.djangoapps.student.admin import CourseAccessRoleHistoryAdmin
from common.djangoapps.student.models import CourseAccessRoleHistory, User
Expand Down Expand Up @@ -313,6 +316,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()
Comment thread
efortish marked this conversation as resolved.

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
Expand Down
Loading