Skip to content

Commit 2b8af69

Browse files
authored
feat: add get_user_role_assignments_per_scope_type api function (#339)
* feat: add get_user_role_assignments_per_scope_type api function * chore: bump version to 1.19.0
1 parent 0041ac3 commit 2b8af69

4 files changed

Lines changed: 115 additions & 2 deletions

File tree

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ Change Log
1414
Unreleased
1515
**********
1616

17+
1.19.0 - 2026-06-17
18+
*******************
19+
20+
Added
21+
=====
22+
23+
* Add ``get_user_role_assignments_per_scope_type`` API function to fetch a user's role assignments filtered by scope type.
24+
1725
1.18.0 - 2026-06-09
1826
*******************
1927

openedx_authz/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44

55
import os
66

7-
__version__ = "1.18.0"
7+
__version__ = "1.19.0"
88

99
ROOT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))

openedx_authz/api/users.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"unassign_role_from_user",
5555
"batch_unassign_role_from_users",
5656
"get_user_role_assignments",
57+
"get_user_role_assignments_per_scope_type",
5758
"get_user_role_assignments_in_scope",
5859
"get_user_role_assignments_for_role_in_scope",
5960
"get_user_role_assignments_filtered",
@@ -149,6 +150,29 @@ def get_user_role_assignments(user_external_key: str) -> list[RoleAssignmentData
149150
return get_subject_role_assignments(UserData(external_key=user_external_key))
150151

151152

153+
def get_user_role_assignments_per_scope_type(
154+
user_external_key: str,
155+
scope_types: tuple[type[ScopeData], ...],
156+
) -> list[RoleAssignmentData]:
157+
"""Get role assignments for a user matching any of the given scope types.
158+
159+
Casbin policies store full scope keys (e.g., 'course-v1^course-v1:Org+Course+Run'), so there is no
160+
way to query by scope type directly; filtering happens here after fetching the user's assignments.
161+
162+
Args:
163+
user_external_key: ID of the user (e.g., 'john_doe').
164+
scope_types: ScopeData subclasses (not instances). Assignments matching any of the given types are returned.
165+
166+
Returns:
167+
list[RoleAssignmentData]: The user's assignments whose scope is an instance of any of the given scope types.
168+
"""
169+
return [
170+
assignment
171+
for assignment in get_user_role_assignments(user_external_key=user_external_key)
172+
if isinstance(assignment.scope, scope_types)
173+
]
174+
175+
152176
def get_user_role_assignments_in_scope(user_external_key: str, scope_external_key: str) -> list[RoleAssignmentData]:
153177
"""Get the roles assigned to a user in a specific scope.
154178

openedx_authz/tests/api/test_users.py

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,17 @@
55
from ddt import data, ddt, unpack
66
from django.contrib.auth import get_user_model
77

8-
from openedx_authz.api.data import ContentLibraryData, RoleAssignmentData, RoleData, UserData
8+
from openedx_authz.api.data import (
9+
ContentLibraryData,
10+
CourseOverviewData,
11+
OrgContentLibraryGlobData,
12+
OrgCourseOverviewGlobData,
13+
PlatformContentLibraryGlobData,
14+
PlatformCourseOverviewGlobData,
15+
RoleAssignmentData,
16+
RoleData,
17+
UserData,
18+
)
919
from openedx_authz.api.users import (
1020
_filter_allowed_assignments,
1121
_filter_candidate_assignments_by_params,
@@ -17,6 +27,7 @@
1727
get_user_role_assignments_filtered,
1828
get_user_role_assignments_for_role_in_scope,
1929
get_user_role_assignments_in_scope,
30+
get_user_role_assignments_per_scope_type,
2031
get_visible_role_assignments_for_user,
2132
get_visible_user_role_assignments_filtered_by_current_user,
2233
is_user_allowed,
@@ -57,6 +68,76 @@ def _assign_roles_to_users(
5768
)
5869

5970

71+
@ddt
72+
class TestUserRoleAssignmentsPerScopeType(UserAssignmentsSetupMixin):
73+
"""Tests for get_user_role_assignments_per_scope_type including glob scope types."""
74+
75+
GLOB_SCOPE_TEST_ASSIGNMENTS = [
76+
{
77+
"subject_name": "nina",
78+
"role_name": roles.LIBRARY_ADMIN.external_key,
79+
"scope_name": OrgContentLibraryGlobData.build_external_key("GlobTest"),
80+
},
81+
{
82+
"subject_name": "nina",
83+
"role_name": roles.COURSE_STAFF.external_key,
84+
"scope_name": OrgCourseOverviewGlobData.build_external_key("GlobTest"),
85+
},
86+
{
87+
"subject_name": "nina",
88+
"role_name": roles.LIBRARY_ADMIN.external_key,
89+
"scope_name": PlatformContentLibraryGlobData.build_external_key(),
90+
},
91+
{
92+
"subject_name": "nina",
93+
"role_name": roles.COURSE_STAFF.external_key,
94+
"scope_name": PlatformCourseOverviewGlobData.build_external_key(),
95+
},
96+
]
97+
98+
@classmethod
99+
def setUpClass(cls):
100+
super().setUpClass()
101+
cls._assign_roles_to_users(assignments=cls.GLOB_SCOPE_TEST_ASSIGNMENTS)
102+
103+
@data(
104+
("eve", (ContentLibraryData,), 3),
105+
("eve", (CourseOverviewData,), 0),
106+
("carlos", (CourseOverviewData,), 3),
107+
("carlos", (ContentLibraryData,), 0),
108+
("carlos", (CourseOverviewData, ContentLibraryData), 3),
109+
("nina", (OrgContentLibraryGlobData,), 1),
110+
("nina", (OrgCourseOverviewGlobData,), 1),
111+
("nina", (PlatformContentLibraryGlobData,), 1),
112+
("nina", (PlatformCourseOverviewGlobData,), 1),
113+
("nina", (OrgContentLibraryGlobData, OrgCourseOverviewGlobData), 2),
114+
("nina", (PlatformContentLibraryGlobData, PlatformCourseOverviewGlobData), 2),
115+
(
116+
"nina",
117+
(
118+
OrgContentLibraryGlobData,
119+
OrgCourseOverviewGlobData,
120+
PlatformContentLibraryGlobData,
121+
PlatformCourseOverviewGlobData,
122+
),
123+
4,
124+
),
125+
("nina", (ContentLibraryData,), 0),
126+
("nina", (CourseOverviewData,), 0),
127+
)
128+
@unpack
129+
def test_get_user_role_assignments_per_scope_type(self, username, scope_types, expected_count):
130+
"""Test retrieving role assignments for a user filtered by scope type."""
131+
role_assignments = get_user_role_assignments_per_scope_type(
132+
user_external_key=username,
133+
scope_types=scope_types,
134+
)
135+
136+
self.assertEqual(len(role_assignments), expected_count)
137+
for assignment in role_assignments:
138+
self.assertIsInstance(assignment.scope, scope_types)
139+
140+
60141
@ddt
61142
class TestUserRoleAssignments(UserAssignmentsSetupMixin):
62143
"""Test suite for user-role assignment API functions."""

0 commit comments

Comments
 (0)