Skip to content

Commit 152f738

Browse files
refactor: group role assignments for all subjects
1 parent a11161f commit 152f738

6 files changed

Lines changed: 58 additions & 56 deletions

File tree

docs/conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ def get_version(*file_paths):
4747
# If extensions (or modules to document with autodoc) are in another directory,
4848
# add these directories to sys.path here. If the directory is relative to the
4949
# documentation root, use os.path.abspath to make it absolute, like shown here.
50-
#
50+
51+
5152
# import os
5253
# import sys
5354
# sys.path.insert(0, os.path.abspath('.'))

openedx_authz/api/data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,5 +389,5 @@ class RoleAssignmentData(AuthZData):
389389
"""
390390

391391
subject: SubjectData = None # Needs defaults to avoid value error from attrs
392-
role: RoleData = None
392+
roles: list[RoleData] = list()
393393
scope: ScopeData = None

openedx_authz/api/roles.py

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"unassign_role_from_subject_in_scope",
3535
"batch_unassign_role_from_subjects_in_scope",
3636
"get_subject_role_assignments_in_scope",
37-
"get_subjects_role_assignments_for_role_in_scope",
37+
"get_subject_role_assignments_for_role_in_scope",
3838
"get_all_subject_role_assignments_in_scope",
3939
"get_subject_role_assignments",
4040
]
@@ -182,7 +182,7 @@ def get_all_roles_names() -> list[str]:
182182

183183

184184
def get_all_roles_in_scope(scope: ScopeData) -> list[list[str]]:
185-
"""Get all the available roles names in a specific scope.
185+
"""Get all the available role grouping policies in a specific scope.
186186
187187
Args:
188188
scope: The scope to filter roles (e.g., 'lib@*' or '*' for global).
@@ -272,7 +272,7 @@ def get_subject_role_assignments(subject: SubjectData) -> list[RoleAssignmentDat
272272
role_assignments.append(
273273
RoleAssignmentData(
274274
subject=subject,
275-
role=role,
275+
roles=[role],
276276
scope=ScopeData(namespaced_key=policy[GroupingPolicyIndex.SCOPE.value]),
277277
)
278278
)
@@ -300,17 +300,17 @@ def get_subject_role_assignments_in_scope(
300300
role_assignments.append(
301301
RoleAssignmentData(
302302
subject=subject,
303-
role=RoleData(
303+
roles=[RoleData(
304304
namespaced_key=namespaced_key,
305305
permissions=get_permissions_for_single_role(role),
306-
),
306+
)],
307307
scope=scope,
308308
)
309309
)
310310
return role_assignments
311311

312312

313-
def get_subjects_role_assignments_for_role_in_scope(
313+
def get_subject_role_assignments_for_role_in_scope(
314314
role: RoleData, scope: ScopeData
315315
) -> list[RoleAssignmentData]:
316316
"""Get the subjects assigned to a specific role in a specific scope.
@@ -329,43 +329,46 @@ def get_subjects_role_assignments_for_role_in_scope(
329329
if subject.startswith(f"{RoleData.NAMESPACE}{RoleData.SEPARATOR}"):
330330
# Skip roles that are also subjects
331331
continue
332+
332333
role_assignments.append(
333334
RoleAssignmentData(
334335
subject=SubjectData(namespaced_key=subject),
335-
role=RoleData(
336-
external_key=role.external_key,
336+
roles=[RoleData(
337+
namespaced_key=role.namespaced_key,
337338
permissions=get_permissions_for_single_role(role),
338-
),
339+
)],
339340
scope=scope,
340341
)
341342
)
343+
342344
return role_assignments
343345

344346

345-
def get_all_subject_role_assignments_in_scope(
346-
scope: ScopeData,
347-
) -> list[RoleAssignmentData]:
347+
def get_all_subject_role_assignments_in_scope(scope: ScopeData) -> list[RoleAssignmentData]:
348348
"""Get all the subjects assigned to any role in a specific scope.
349349
350350
Args:
351351
scope: The scope to filter subjects (e.g., 'library:123' or '*' for global).
352352
353353
Returns:
354-
list[RoleAssignment]: A list of subjects assigned to roles in the specified scope.
354+
list[RoleAssignment]: A list of role assignments for all subjects in the specified scope.
355355
"""
356-
role_assignments = []
356+
role_assignments_per_subject = {}
357357
roles_in_scope = get_all_roles_in_scope(scope)
358358

359359
for policy in roles_in_scope:
360360
subject = SubjectData(namespaced_key=policy[GroupingPolicyIndex.SUBJECT.value])
361361
role = RoleData(namespaced_key=policy[GroupingPolicyIndex.ROLE.value])
362362
role.permissions = get_permissions_for_single_role(role)
363363

364-
role_assignments.append(
365-
RoleAssignmentData(
366-
subject=subject,
367-
role=role,
368-
scope=scope,
369-
)
364+
if subject.external_key in role_assignments_per_subject:
365+
role_assignments_per_subject[subject.external_key].roles.append(role)
366+
continue
367+
368+
role_assignments_per_subject[subject.external_key] = RoleAssignmentData(
369+
subject=subject,
370+
roles=[role],
371+
scope=scope,
370372
)
371-
return role_assignments
373+
374+
return list(role_assignments_per_subject.values())

openedx_authz/api/users.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
get_all_subject_role_assignments_in_scope,
2525
get_subject_role_assignments,
2626
get_subject_role_assignments_in_scope,
27-
get_subjects_role_assignments_for_role_in_scope,
27+
get_subject_role_assignments_for_role_in_scope,
2828
unassign_role_from_subject_in_scope,
2929
)
3030

@@ -145,11 +145,9 @@ def get_user_role_assignments_for_role_in_scope(
145145
scope (str): Scope in which to retrieve the role assignments.
146146
147147
Returns:
148-
list[RoleAssignmentData]: A list of user names and all their metadata assigned to the role.
148+
list[RoleAssignmentData]: List of users assigned to the specified role in the given scope.
149149
"""
150-
# TODO: this SHOULD definitely be managed in a better way by using class inheritance and factories
151-
# But for now we'll keep it simple and explicit
152-
return get_subjects_role_assignments_for_role_in_scope(
150+
return get_subject_role_assignments_for_role_in_scope(
153151
RoleData(external_key=role_external_key),
154152
ScopeData(external_key=scope_external_key),
155153
)

openedx_authz/tests/api/test_roles.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
get_role_definitions_in_scope,
3030
get_subject_role_assignments,
3131
get_subject_role_assignments_in_scope,
32-
get_subjects_role_assignments_for_role_in_scope,
32+
get_subject_role_assignments_for_role_in_scope,
3333
unassign_role_from_subject_in_scope,
3434
)
3535
from openedx_authz.engine.enforcer import enforcer as global_enforcer
@@ -572,7 +572,7 @@ def test_get_subject_role_assignments_in_scope(
572572
SubjectData(external_key=subject_name), ScopeData(external_key=scope_name)
573573
)
574574

575-
role_names = {assignment.role.external_key for assignment in role_assignments}
575+
role_names = {r.external_key for assignment in role_assignments for r in assignment.roles}
576576
self.assertEqual(role_names, expected_roles)
577577

578578
@ddt_data(
@@ -758,7 +758,7 @@ def test_get_all_role_assignments_scopes(self, subject_name, expected_roles):
758758
for expected_role in expected_roles:
759759
# Compare the role part of the assignment
760760
found = any(
761-
assignment.role == expected_role for assignment in role_assignments
761+
expected_role in assignment.roles for assignment in role_assignments
762762
)
763763
self.assertTrue(
764764
found, f"Expected role {expected_role} not found in assignments"
@@ -797,7 +797,7 @@ def test_get_role_assignments_in_scope(self, role_name, scope_name, expected_cou
797797
Expected result:
798798
- The number of role assignments in the given scope is correctly retrieved.
799799
"""
800-
role_assignments = get_subjects_role_assignments_for_role_in_scope(
800+
role_assignments = get_subject_role_assignments_for_role_in_scope(
801801
RoleData(external_key=role_name), ScopeData(external_key=scope_name)
802802
)
803803

@@ -860,7 +860,7 @@ def test_batch_assign_role_to_subjects_in_scope(
860860
user_roles = get_subject_role_assignments_in_scope(
861861
SubjectData(external_key=subject_name), ScopeData(external_key=scope_name)
862862
)
863-
role_names = {assignment.role.external_key for assignment in user_roles}
863+
role_names = {r.external_key for assignment in user_roles for r in assignment.roles}
864864
self.assertIn(role, role_names)
865865
else:
866866
assign_role_to_subject_in_scope(
@@ -872,7 +872,7 @@ def test_batch_assign_role_to_subjects_in_scope(
872872
SubjectData(external_key=subject_names),
873873
ScopeData(external_key=scope_name),
874874
)
875-
role_names = {assignment.role.external_key for assignment in user_roles}
875+
role_names = {r.external_key for assignment in user_roles for r in assignment.roles}
876876
self.assertIn(role, role_names)
877877

878878
@ddt_data(
@@ -917,7 +917,7 @@ def test_unassign_role_from_subject_in_scope(
917917
SubjectData(external_key=subject),
918918
ScopeData(external_key=scope_name),
919919
)
920-
role_names = {assignment.role.external_key for assignment in user_roles}
920+
role_names = {r.external_key for assignment in user_roles for r in assignment.roles}
921921
self.assertNotIn(role, role_names)
922922
else:
923923
unassign_role_from_subject_in_scope(
@@ -929,7 +929,7 @@ def test_unassign_role_from_subject_in_scope(
929929
SubjectData(external_key=subject_names),
930930
ScopeData(external_key=scope_name),
931931
)
932-
role_names = {assignment.role.external_key for assignment in user_roles}
932+
role_names = {r.external_key for assignment in user_roles for r in assignment.roles}
933933
self.assertNotIn(role, role_names)
934934

935935
@ddt_data(
@@ -938,7 +938,7 @@ def test_unassign_role_from_subject_in_scope(
938938
[
939939
RoleAssignmentData(
940940
subject=SubjectData(external_key="alice"),
941-
role=RoleData(
941+
roles=[RoleData(
942942
external_key="library_admin",
943943
permissions=[
944944
PermissionData(
@@ -986,7 +986,7 @@ def test_unassign_role_from_subject_in_scope(
986986
effect="allow",
987987
),
988988
],
989-
),
989+
)],
990990
scope=ScopeData(external_key="lib:Org1:math_101"),
991991
)
992992
],
@@ -996,7 +996,7 @@ def test_unassign_role_from_subject_in_scope(
996996
[
997997
RoleAssignmentData(
998998
subject=SubjectData(external_key="bob"),
999-
role=RoleData(
999+
roles=[RoleData(
10001000
external_key="library_author",
10011001
permissions=[
10021002
PermissionData(
@@ -1038,7 +1038,7 @@ def test_unassign_role_from_subject_in_scope(
10381038
effect="allow",
10391039
),
10401040
],
1041-
),
1041+
)],
10421042
scope=ScopeData(external_key="lib:Org1:history_201"),
10431043
)
10441044
],
@@ -1048,7 +1048,7 @@ def test_unassign_role_from_subject_in_scope(
10481048
[
10491049
RoleAssignmentData(
10501050
subject=SubjectData(external_key="carol"),
1051-
role=RoleData(
1051+
roles=[RoleData(
10521052
external_key="library_collaborator",
10531053
permissions=[
10541054
PermissionData(
@@ -1084,7 +1084,7 @@ def test_unassign_role_from_subject_in_scope(
10841084
effect="allow",
10851085
),
10861086
],
1087-
),
1087+
)],
10881088
scope=ScopeData(external_key="lib:Org1:science_301"),
10891089
)
10901090
],
@@ -1094,7 +1094,7 @@ def test_unassign_role_from_subject_in_scope(
10941094
[
10951095
RoleAssignmentData(
10961096
subject=SubjectData(external_key="dave"),
1097-
role=RoleData(
1097+
roles=[RoleData(
10981098
external_key="library_user",
10991099
permissions=[
11001100
PermissionData(
@@ -1110,7 +1110,7 @@ def test_unassign_role_from_subject_in_scope(
11101110
effect="allow",
11111111
),
11121112
],
1113-
),
1113+
)],
11141114
scope=ScopeData(external_key="lib:Org1:english_101"),
11151115
)
11161116
],

0 commit comments

Comments
 (0)