diff --git a/openedx_authz/api/data.py b/openedx_authz/api/data.py index 859e8e31..52bf09ec 100644 --- a/openedx_authz/api/data.py +++ b/openedx_authz/api/data.py @@ -392,6 +392,15 @@ class ContentLibraryData(ScopeData): NAMESPACE: ClassVar[str] = "lib" + @property + def org(self) -> str: + """Get the organization name from the library key. + + Returns: + str: The organization name (e.g., ``DemoX`` from ``lib:DemoX:CSPROB``). + """ + return self.library_key.org + @property def library_id(self) -> str: """The library identifier as used in Open edX (e.g., 'lib:DemoX:CSPROB'). @@ -494,6 +503,15 @@ class CourseOverviewData(ScopeData): NAMESPACE: ClassVar[str] = "course-v1" + @property + def org(self) -> str: + """Get the organization name from the course key. + + Returns: + str: The organization name (e.g., ``DemoX`` from ``course-v1:DemoX+TestCourse+2024_T1``). + """ + return self.course_key.org + @property def course_id(self) -> str: """The course identifier as used in Open edX (e.g., 'course-v1:TestOrg+TestCourse+2024_T1'). diff --git a/openedx_authz/api/roles.py b/openedx_authz/api/roles.py index c5b8cb78..2a6cb155 100644 --- a/openedx_authz/api/roles.py +++ b/openedx_authz/api/roles.py @@ -37,6 +37,7 @@ "unassign_role_from_subject_in_scope", "batch_unassign_role_from_subjects_in_scope", "get_subject_role_assignments_in_scope", + "get_subject_role_assignments_for_role", "get_subject_role_assignments_for_role_in_scope", "get_all_subject_role_assignments_in_scope", "get_subject_role_assignments", @@ -323,6 +324,23 @@ def get_subject_role_assignments_in_scope(subject: SubjectData, scope: ScopeData return role_assignments +def get_subject_role_assignments_for_role(subject: SubjectData, role: RoleData) -> list[RoleAssignmentData]: + """Get role assignments for a subject filtered by a specific role across all scopes. + + Args: + subject: The SubjectData object representing the subject. + role: The RoleData object representing the role to filter by. + + Returns: + list[RoleAssignmentData]: A list of assignments where the subject has the specified role. + """ + return [ + assignment + for assignment in get_subject_role_assignments(subject) + if any(assigned_role.namespaced_key == role.namespaced_key for assigned_role in assignment.roles) + ] + + def get_subject_role_assignments_for_role_in_scope(role: RoleData, scope: ScopeData) -> list[RoleAssignmentData]: """Get the subjects assigned to a specific role in a specific scope. diff --git a/openedx_authz/api/users.py b/openedx_authz/api/users.py index e07df678..2c71afc7 100644 --- a/openedx_authz/api/users.py +++ b/openedx_authz/api/users.py @@ -9,7 +9,14 @@ (e.g., 'user^john_doe'). """ -from openedx_authz.api.data import ActionData, PermissionData, RoleAssignmentData, RoleData, ScopeData, UserData +from openedx_authz.api.data import ( + ActionData, + PermissionData, + RoleAssignmentData, + RoleData, + ScopeData, + UserData, +) from openedx_authz.api.permissions import is_subject_allowed from openedx_authz.api.roles import ( assign_role_to_subject_in_scope, @@ -18,6 +25,7 @@ get_all_subject_role_assignments_in_scope, get_scopes_for_subject_and_permission, get_subject_role_assignments, + get_subject_role_assignments_for_role, get_subject_role_assignments_for_role_in_scope, get_subject_role_assignments_in_scope, get_subjects_for_role_in_scope, @@ -38,6 +46,7 @@ "get_scopes_for_user_and_permission", "get_users_for_role_in_scope", "unassign_all_roles_from_user", + "get_user_role_assignments_for_role", ] @@ -121,6 +130,22 @@ def get_user_role_assignments(user_external_key: str) -> list[RoleAssignmentData return get_subject_role_assignments(UserData(external_key=user_external_key)) +def get_user_role_assignments_for_role(user_external_key: str, role_external_key: str) -> list[RoleAssignmentData]: + """Get role assignments for a user filtered by a specific role across all scopes. + + Args: + user_external_key (str): ID of the user (e.g., 'john_doe'). + role_external_key (str): Name of the role (e.g., 'instructor'). + + Returns: + list[RoleAssignmentData]: A list of role assignments for the given user and role. + """ + return get_subject_role_assignments_for_role( + UserData(external_key=user_external_key), + RoleData(external_key=role_external_key), + ) + + def get_user_role_assignments_in_scope(user_external_key: str, scope_external_key: str) -> list[RoleAssignmentData]: """Get the roles assigned to a user in a specific scope. diff --git a/openedx_authz/tests/api/test_roles.py b/openedx_authz/tests/api/test_roles.py index 2830db5b..806e8fbb 100644 --- a/openedx_authz/tests/api/test_roles.py +++ b/openedx_authz/tests/api/test_roles.py @@ -31,6 +31,7 @@ get_role_definitions_in_scope, get_scopes_for_subject_and_permission, get_subject_role_assignments, + get_subject_role_assignments_for_role, get_subject_role_assignments_for_role_in_scope, get_subject_role_assignments_in_scope, get_subjects_for_role_in_scope, @@ -603,6 +604,23 @@ def test_get_all_role_assignments_scopes(self, subject_name, expected_roles): found = any(expected_role in assignment.roles for assignment in role_assignments) self.assertTrue(found, f"Expected role {expected_role} not found in assignments") + @ddt_data( + ("liam", roles.LIBRARY_AUTHOR.external_key, 3), + ("eve", roles.LIBRARY_ADMIN.external_key, 1), + ("eve", roles.LIBRARY_AUTHOR.external_key, 1), + ("eve", roles.LIBRARY_USER.external_key, 1), + ("alice", roles.LIBRARY_AUTHOR.external_key, 0), + ("non_existent_user", roles.LIBRARY_ADMIN.external_key, 0), + ) + @unpack + def test_get_subject_role_assignments_for_role(self, subject_name, role_name, expected_count): + """Test retrieving role assignments for a subject filtered by role across all scopes.""" + role_assignments = get_subject_role_assignments_for_role( + SubjectData(external_key=subject_name), + RoleData(external_key=role_name), + ) + self.assertEqual(len(role_assignments), expected_count) + @ddt_data( (roles.LIBRARY_ADMIN.external_key, "lib:Org1:math_101", 1), (roles.LIBRARY_AUTHOR.external_key, "lib:Org1:history_201", 1),