Skip to content

Commit d003279

Browse files
feat: implement function to get all role assignments within a scope
1 parent 147d533 commit d003279

6 files changed

Lines changed: 413 additions & 39 deletions

File tree

openedx_authz/api/permissions.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,7 @@
55
are not explicitly defined, but are inferred from the policy rules.
66
"""
77

8-
from openedx_authz.api.data import (
9-
ActionData,
10-
PermissionData,
11-
PolicyIndex,
12-
ScopeData,
13-
SubjectData,
14-
)
8+
from openedx_authz.api.data import ActionData, PermissionData, PolicyIndex, ScopeData, SubjectData
159
from openedx_authz.engine.enforcer import enforcer
1610

1711
__all__ = [

openedx_authz/api/roles.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
__all__ = [
2828
"get_permissions_for_roles",
2929
"get_all_roles_names",
30+
"get_all_roles_in_scope",
3031
"get_permissions_for_active_roles_in_scope",
3132
"get_role_definitions_in_scope",
3233
"assign_role_to_subject_in_scope",
@@ -35,6 +36,7 @@
3536
"batch_unassign_role_from_subjects_in_scope",
3637
"get_subject_role_assignments_in_scope",
3738
"get_subjects_role_assignments_for_role_in_scope",
39+
"get_all_subject_role_assignments_in_scope",
3840
"get_subject_role_assignments",
3941
]
4042

@@ -170,6 +172,20 @@ def get_all_roles_names() -> list[str]:
170172
return enforcer.get_all_subjects()
171173

172174

175+
def get_all_roles_in_scope(scope: ScopeData) -> list[list[str]]:
176+
"""Get all the available roles names in a specific scope.
177+
178+
Args:
179+
scope: The scope to filter roles (e.g., 'lib@*' or '*' for global).
180+
181+
Returns:
182+
list[list[str]]: A list of policies in the specified scope.
183+
"""
184+
return enforcer.get_filtered_grouping_policy(
185+
GroupingPolicyIndex.SCOPE.value, scope.scope_id
186+
)
187+
188+
173189
def assign_role_to_subject_in_scope(
174190
subject: SubjectData, role: RoleData, scope: ScopeData
175191
) -> None:
@@ -321,3 +337,34 @@ def get_subjects_role_assignments_for_role_in_scope(
321337
)
322338
)
323339
return role_assignments
340+
341+
342+
def get_all_subject_role_assignments_in_scope(
343+
scope: ScopeData,
344+
) -> list[RoleAssignmentData]:
345+
"""Get all the subjects assigned to any role in a specific scope.
346+
347+
Args:
348+
scope: The scope to filter subjects (e.g., 'library:123' or '*' for global).
349+
350+
Returns:
351+
list[RoleAssignment]: A list of subjects assigned to roles in the specified scope.
352+
"""
353+
role_assignments = []
354+
roles_in_scope = get_all_roles_in_scope(scope)
355+
356+
for policy in roles_in_scope:
357+
subject = SubjectData(subject_id=policy[GroupingPolicyIndex.SUBJECT.value])
358+
role = RoleData(role_id=policy[GroupingPolicyIndex.ROLE.value])
359+
role.permissions = get_permissions_for_roles(role)[role.name][
360+
"permissions"
361+
] # Index by role name for easy lookup
362+
363+
role_assignments.append(
364+
RoleAssignmentData(
365+
subject=subject,
366+
role=role,
367+
scope=scope,
368+
)
369+
)
370+
return role_assignments

openedx_authz/api/users.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,12 @@
99
(e.g., 'user@john_doe').
1010
"""
1111

12-
from openedx_authz.api.data import (
13-
RoleAssignmentData,
14-
RoleData,
15-
ScopeData,
16-
SubjectData,
17-
UserData,
18-
)
12+
from openedx_authz.api.data import RoleAssignmentData, RoleData, ScopeData, SubjectData, UserData
1913
from openedx_authz.api.roles import (
2014
assign_role_to_subject_in_scope,
2115
batch_assign_role_to_subjects_in_scope,
2216
batch_unassign_role_from_subjects_in_scope,
17+
get_all_subject_role_assignments_in_scope,
2318
get_subject_role_assignments,
2419
get_subject_role_assignments_in_scope,
2520
get_subjects_role_assignments_for_role_in_scope,
@@ -33,6 +28,8 @@
3328
"batch_unassign_role_from_users",
3429
"get_user_role_assignments",
3530
"get_user_role_assignments_in_scope",
31+
"get_user_role_assignments_for_role_in_scope",
32+
"get_all_user_role_assignments_in_scope",
3633
]
3734

3835

@@ -142,6 +139,7 @@ def get_user_role_assignments_for_role_in_scope(
142139
# TODO: this SHOULD definitely be managed in a better way by using class inheritance and factories
143140
# But for now we'll keep it simple and explicit
144141
user_role_assignments = []
142+
145143
for role_assignment in get_subjects_role_assignments_for_role_in_scope(
146144
RoleData(name=role_name), ScopeData(name=scope)
147145
):
@@ -154,4 +152,29 @@ def get_user_role_assignments_for_role_in_scope(
154152
scope=role_assignment.scope,
155153
)
156154
)
155+
156+
return user_role_assignments
157+
158+
159+
def get_all_user_role_assignments_in_scope(scope: str) -> list[RoleAssignmentData]:
160+
"""Get all user role assignments in a specific scope.
161+
162+
Args:
163+
scope (str): Scope in which to retrieve the user role assignments.
164+
165+
Returns:
166+
list[dict]: A list of user role assignments and all their metadata in the specified scope.
167+
"""
168+
user_role_assignments = []
169+
role_assignments = get_all_subject_role_assignments_in_scope(ScopeData(name=scope))
170+
171+
for role_assignment in role_assignments:
172+
user_role_assignments.append(
173+
RoleAssignmentData(
174+
subject=UserData(subject_id=role_assignment.subject.subject_id),
175+
role=role_assignment.role,
176+
scope=role_assignment.scope,
177+
)
178+
)
179+
157180
return user_role_assignments

openedx_authz/tests/api/test_data.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,7 @@
33
from ddt import data, ddt, unpack
44
from django.test import TestCase
55

6-
from openedx_authz.api.data import (
7-
ActionData,
8-
ContentLibraryData,
9-
RoleData,
10-
ScopeData,
11-
UserData,
12-
)
6+
from openedx_authz.api.data import ActionData, ContentLibraryData, RoleData, ScopeData, UserData
137

148

159
@ddt

openedx_authz/tests/api/test_roles.py

Lines changed: 176 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@
1111
from django.test import TestCase
1212

1313
from openedx_authz.api import *
14-
from openedx_authz.api.data import (
15-
ActionData,
16-
PermissionData,
17-
RoleData,
18-
ScopeData,
19-
SubjectData,
20-
)
14+
from openedx_authz.api.data import ActionData, PermissionData, RoleData, ScopeData, SubjectData
2115
from openedx_authz.engine.enforcer import enforcer as global_enforcer
2216
from openedx_authz.engine.utils import migrate_policy_from_file_to_db
2317

@@ -117,6 +111,11 @@ def setUpClass(cls):
117111
"role_name": "library_collaborator",
118112
"scope_name": "math_advanced",
119113
},
114+
{
115+
"subject_name": "Heidi",
116+
"role_name": "library_collaborator",
117+
"scope_name": "math_advanced",
118+
},
120119
# Hierarchical scope_id assignments - different specificity levels
121120
{
122121
"subject_name": "Ivy",
@@ -971,3 +970,173 @@ def test_unassign_role_from_subject_in_scope(
971970
)
972971
role_names = {assignment.role.name for assignment in user_roles}
973972
self.assertNotIn(role, role_names)
973+
974+
@ddt_data(
975+
(
976+
"math_101",
977+
[
978+
RoleAssignmentData(
979+
subject=SubjectData(name="alice"),
980+
role=RoleData(
981+
name="library_admin",
982+
permissions=[
983+
PermissionData(
984+
action=ActionData(name="delete_library"), effect="allow"
985+
),
986+
PermissionData(
987+
action=ActionData(name="publish_library"),
988+
effect="allow",
989+
),
990+
PermissionData(
991+
action=ActionData(name="manage_library_team"),
992+
effect="allow",
993+
),
994+
PermissionData(
995+
action=ActionData(name="manage_library_tags"),
996+
effect="allow",
997+
),
998+
PermissionData(
999+
action=ActionData(name="delete_library_content"),
1000+
effect="allow",
1001+
),
1002+
PermissionData(
1003+
action=ActionData(name="publish_library_content"),
1004+
effect="allow",
1005+
),
1006+
PermissionData(
1007+
action=ActionData(name="delete_library_collection"),
1008+
effect="allow",
1009+
),
1010+
PermissionData(
1011+
action=ActionData(name="create_library"), effect="allow"
1012+
),
1013+
PermissionData(
1014+
action=ActionData(name="create_library_collection"),
1015+
effect="allow",
1016+
),
1017+
],
1018+
),
1019+
scope=ScopeData(name="math_101"),
1020+
)
1021+
],
1022+
),
1023+
(
1024+
"history_201",
1025+
[
1026+
RoleAssignmentData(
1027+
subject=SubjectData(name="bob"),
1028+
role=RoleData(
1029+
name="library_author",
1030+
permissions=[
1031+
PermissionData(
1032+
action=ActionData(name="delete_library_content"),
1033+
effect="allow",
1034+
),
1035+
PermissionData(
1036+
action=ActionData(name="publish_library_content"),
1037+
effect="allow",
1038+
),
1039+
PermissionData(
1040+
action=ActionData(name="edit_library"), effect="allow"
1041+
),
1042+
PermissionData(
1043+
action=ActionData(name="manage_library_tags"),
1044+
effect="allow",
1045+
),
1046+
PermissionData(
1047+
action=ActionData(name="create_library_collection"),
1048+
effect="allow",
1049+
),
1050+
PermissionData(
1051+
action=ActionData(name="edit_library_collection"),
1052+
effect="allow",
1053+
),
1054+
PermissionData(
1055+
action=ActionData(name="delete_library_collection"),
1056+
effect="allow",
1057+
),
1058+
],
1059+
),
1060+
scope=ScopeData(name="history_201"),
1061+
)
1062+
],
1063+
),
1064+
(
1065+
"science_301",
1066+
[
1067+
RoleAssignmentData(
1068+
subject=SubjectData(name="carol"),
1069+
role=RoleData(
1070+
name="library_collaborator",
1071+
permissions=[
1072+
PermissionData(
1073+
action=ActionData(name="edit_library"), effect="allow"
1074+
),
1075+
PermissionData(
1076+
action=ActionData(name="delete_library_content"),
1077+
effect="allow",
1078+
),
1079+
PermissionData(
1080+
action=ActionData(name="manage_library_tags"),
1081+
effect="allow",
1082+
),
1083+
PermissionData(
1084+
action=ActionData(name="create_library_collection"),
1085+
effect="allow",
1086+
),
1087+
PermissionData(
1088+
action=ActionData(name="edit_library_collection"),
1089+
effect="allow",
1090+
),
1091+
PermissionData(
1092+
action=ActionData(name="delete_library_collection"),
1093+
effect="allow",
1094+
),
1095+
],
1096+
),
1097+
scope=ScopeData(name="science_301"),
1098+
)
1099+
],
1100+
),
1101+
(
1102+
"english_101",
1103+
[
1104+
RoleAssignmentData(
1105+
subject=SubjectData(name="dave"),
1106+
role=RoleData(
1107+
name="library_user",
1108+
permissions=[
1109+
PermissionData(
1110+
action=ActionData(name="view_library"), effect="allow"
1111+
),
1112+
PermissionData(
1113+
action=ActionData(name="view_library_team"),
1114+
effect="allow",
1115+
),
1116+
PermissionData(
1117+
action=ActionData(name="reuse_library_content"),
1118+
effect="allow",
1119+
),
1120+
],
1121+
),
1122+
scope=ScopeData(name="english_101"),
1123+
)
1124+
],
1125+
),
1126+
("non_existent_scope", []),
1127+
)
1128+
@unpack
1129+
def test_get_all_role_assignments_in_scope(self, scope_name, expected_assignments):
1130+
"""Test retrieving all role assignments in a specific scope.
1131+
1132+
Expected result:
1133+
- All role assignments in the specified scope are correctly retrieved.
1134+
- Each assignment includes the subject, role, and scope information with permissions.
1135+
"""
1136+
role_assignments = get_all_subject_role_assignments_in_scope(
1137+
ScopeData(name=scope_name)
1138+
)
1139+
1140+
self.assertEqual(len(role_assignments), len(expected_assignments))
1141+
for assignment in role_assignments:
1142+
self.assertIn(assignment, expected_assignments)

0 commit comments

Comments
 (0)