Skip to content

Commit 0db0991

Browse files
committed
squash!: Fix conflicts
1 parent c6605a5 commit 0db0991

5 files changed

Lines changed: 138 additions & 0 deletions

File tree

openedx_authz/api/roles.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
"batch_unassign_role_from_subjects_in_scope",
3232
"get_all_roles_in_scope",
3333
"get_all_roles_names",
34+
"get_subject_role_assignments_in_scope",
35+
"get_subject_role_assignments_for_role_in_scope",
36+
"get_all_subject_role_assignments",
3437
"get_all_subject_role_assignments_in_scope",
3538
"get_permissions_for_active_roles_in_scope",
3639
"get_permissions_for_roles",
@@ -269,6 +272,28 @@ def batch_unassign_role_from_subjects_in_scope(subjects: list[SubjectData], role
269272
unassign_role_from_subject_in_scope(subject, role, scope)
270273

271274

275+
def get_all_subject_role_assignments() -> list[RoleAssignmentData]:
276+
"""Get all the roles for every subject across all scopes.
277+
278+
Returns:
279+
list[RoleAssignmentData]: A list of role assignments for the subject.
280+
"""
281+
enforcer = AuthzEnforcer.get_enforcer()
282+
role_assignments = []
283+
for policy in enforcer.get_grouping_policy():
284+
subject = SubjectData(namespaced_key=policy[GroupingPolicyIndex.SUBJECT.value])
285+
role = RoleData(namespaced_key=policy[GroupingPolicyIndex.ROLE.value])
286+
role.permissions = get_permissions_for_single_role(role)
287+
288+
role_assignments.append(
289+
RoleAssignmentData(
290+
subject=subject,
291+
roles=[role],
292+
scope=ScopeData(namespaced_key=policy[GroupingPolicyIndex.SCOPE.value]),
293+
)
294+
)
295+
return role_assignments
296+
272297
def get_subject_role_assignments(subject: SubjectData) -> list[RoleAssignmentData]:
273298
"""Get all the roles for a subject across all scopes.
274299

openedx_authz/api/users.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
assign_role_to_subject_in_scope,
2323
batch_assign_role_to_subjects_in_scope,
2424
batch_unassign_role_from_subjects_in_scope,
25+
get_all_subject_role_assignments,
2526
get_all_subject_role_assignments_in_scope,
2627
get_role_assignments,
2728
get_scopes_for_subject_and_permission,
@@ -38,6 +39,7 @@
3839
"batch_assign_role_to_users_in_scope",
3940
"unassign_role_from_user",
4041
"batch_unassign_role_from_users",
42+
"get_all_user_role_assignments",
4143
"get_user_role_assignments",
4244
"get_user_role_assignments_in_scope",
4345
"get_user_role_assignments_for_role_in_scope",
@@ -117,6 +119,13 @@ def batch_unassign_role_from_users(users: list[str], role_external_key: str, sco
117119
ScopeData(external_key=scope_external_key),
118120
)
119121

122+
def get_all_user_role_assignments() -> list[RoleAssignmentData]:
123+
"""Get all roles for all users across all scopes.
124+
125+
Returns:
126+
list[RoleAssignmentData]: A list of role assignments and all their metadata assigned to the user.
127+
"""
128+
return get_all_subject_role_assignments()
120129

121130
def get_user_role_assignments(user_external_key: str) -> list[RoleAssignmentData]:
122131
"""Get all roles for a user across all scopes.

openedx_authz/rest_api/v1/serializers.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,20 @@ def get_email(self, obj) -> str:
203203
def get_roles(self, obj: api.RoleAssignmentData) -> list[str]:
204204
"""Get the roles for the given role assignment."""
205205
return [role.external_key for role in obj.roles]
206+
207+
class ListTeamMembersSerializer(serializers.Serializer): # pylint: disable=abstract-method
208+
"""Serializer for listing team members."""
209+
210+
scopes = CommaSeparatedListField(required=False, default=[])
211+
orgs = CommaSeparatedListField(required=False, default=[])
212+
sort_by = serializers.ChoiceField(
213+
required=False,
214+
choices=[(e.value, e.name) for e in SortField],
215+
default=SortField.USERNAME,
216+
)
217+
order = serializers.ChoiceField(
218+
required=False,
219+
choices=[(e.value, e.name) for e in SortOrder],
220+
default=SortOrder.ASC,
221+
)
222+
search = LowercaseCharField(required=False, default=None)

openedx_authz/rest_api/v1/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212
),
1313
path("roles/", views.RoleListView.as_view(), name="role-list"),
1414
path("roles/users/", views.RoleUserAPIView.as_view(), name="role-user-list"),
15+
path("users/", views.TeamMembersAPIView.as_view(), name="user-list"),
1516
]

openedx_authz/rest_api/v1/views.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
permissions, roles, and user assignments within Open edX platform.
66
"""
77

8+
from dataclasses import dataclass
89
import logging
910

1011
import edx_api_doc_tools as apidocs
@@ -31,6 +32,7 @@
3132
AddUsersToRoleWithScopeSerializer,
3233
ListRolesWithScopeResponseSerializer,
3334
ListRolesWithScopeSerializer,
35+
ListTeamMembersSerializer,
3436
ListUsersInRoleWithScopeSerializer,
3537
PermissionValidationResponseSerializer,
3638
PermissionValidationSerializer,
@@ -449,3 +451,87 @@ def get(self, request: HttpRequest) -> Response:
449451
paginated_response_data = paginator.paginate_queryset(response_data, request)
450452
serialized_data = ListRolesWithScopeResponseSerializer(paginated_response_data, many=True)
451453
return paginator.get_paginated_response(serialized_data.data)
454+
455+
@dataclass
456+
class UserAssignments:
457+
user: "User"
458+
assignments: list[api.RoleAssignmentData]
459+
460+
def get_user_assignment_map(role_assignments: list[api.RoleAssignmentData]) -> list[UserAssignments]:
461+
"""
462+
Group role assignments by user
463+
"""
464+
usernames = {assignment.subject.username for assignment in role_assignments}
465+
user_map = get_user_map(usernames)
466+
467+
users_with_assignments: list[UserAssignments] = []
468+
469+
for username, user in user_map.items():
470+
assignments = [a for a in role_assignments if a.subject.username == username]
471+
users_with_assignments.append(UserAssignments(user=user, assignments=assignments))
472+
473+
return users_with_assignments
474+
475+
476+
477+
@view_auth_classes()
478+
class TeamMembersAPIView(APIView):
479+
"""
480+
API view for listing users in relation to role assignations
481+
"""
482+
483+
pagination_class = AuthZAPIViewPagination
484+
permission_classes = [DynamicScopePermission] # TODO check if this is needed/works
485+
486+
@apidocs.schema(
487+
parameters=[
488+
apidocs.query_parameter("scopes", str, description="The scopes to query assignations for"),
489+
apidocs.query_parameter("orgs", str, description="The orgs to query assignations for"),
490+
apidocs.query_parameter("search", str, description="The search query to filter users by"),
491+
apidocs.query_parameter("sort_by", str, description="The field to sort by"),
492+
apidocs.query_parameter("order", str, description="The order to sort by"),
493+
apidocs.query_parameter("page", int, description="Page number for pagination"),
494+
apidocs.query_parameter("page_size", int, description="Number of items per page"),
495+
],
496+
responses={
497+
status.HTTP_200_OK: ListRolesWithScopeResponseSerializer(many=True),
498+
status.HTTP_400_BAD_REQUEST: "The request parameters are invalid",
499+
status.HTTP_401_UNAUTHORIZED: "The user is not authenticated or does not have the required permissions",
500+
},
501+
)
502+
@authz_permissions([permissions.VIEW_LIBRARY.identifier])
503+
def get(self, request: HttpRequest) -> Response:
504+
"""Retrieve all users that have at least one assignation according to the filtering fields."""
505+
serializer = ListTeamMembersSerializer(data=request.query_params)
506+
serializer.is_valid(raise_exception=True)
507+
query_params = serializer.validated_data
508+
509+
user_role_assignments = api.get_all_user_role_assignments()
510+
# Group assignments by user
511+
users_with_assignments = get_user_assignment_map(user_role_assignments)
512+
513+
if query_params.scopes:
514+
# Filter by scopes
515+
scopes = {s.strip() for s in query_params.scopes.split(",")}
516+
users_with_assignments = [
517+
uwa for uwa in users_with_assignments if any(a.scope.external_key in scopes for a in uwa.assignments)
518+
]
519+
520+
if query_params.orgs:
521+
# Filter by orgs
522+
orgs = {o.strip() for o in query_params.orgs.split(",")}
523+
users_with_assignments = [
524+
uwa for uwa in users_with_assignments if any(a.scope.org in orgs for a in uwa.assignments)
525+
]
526+
527+
528+
logger.info(f">>>>Query params: {query_params}")
529+
logger.info(f">>>>assignments: {user_role_assignments}")
530+
531+
# sort
532+
# paginate
533+
534+
535+
return Response({"results": []}, status=status.HTTP_200_OK)
536+
537+

0 commit comments

Comments
 (0)