From 0012347763f33df82f22381d8c20ed5934b1cc81 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Mon, 27 Oct 2025 13:03:02 +0100 Subject: [PATCH 1/2] feat: add API function to return scopes IDs for user-role --- openedx_authz/api/roles.py | 20 ++++++++++++++++++++ openedx_authz/api/users.py | 17 +++++++++++++++++ openedx_authz/tests/api/test_roles.py | 19 +++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/openedx_authz/api/roles.py b/openedx_authz/api/roles.py index ca0e67b9..07f6c0ad 100644 --- a/openedx_authz/api/roles.py +++ b/openedx_authz/api/roles.py @@ -37,6 +37,7 @@ "get_subject_role_assignments_for_role_in_scope", "get_all_subject_role_assignments_in_scope", "get_subject_role_assignments", + "get_scopes_for_role_and_subject", ] # TODO: these are the concerns we still have to address: @@ -373,3 +374,22 @@ def get_subjects_for_role(role: RoleData) -> list[SubjectData]: enforcer = AuthzEnforcer.get_enforcer() policies = enforcer.get_filtered_grouping_policy(GroupingPolicyIndex.ROLE.value, role.namespaced_key) return [SubjectData(namespaced_key=policy[GroupingPolicyIndex.SUBJECT.value]) for policy in policies] + + +def get_scopes_for_role_and_subject(role: RoleData, subject: SubjectData) -> list[ScopeData]: + """Get all the scopes where a specific subject is assigned a specific role. + + Args: + role (RoleData): The role to filter scopes. + subject (SubjectData): The subject to filter scopes. + + Returns: + list[ScopeData]: A list of scopes where the subject is assigned the specified role. + """ + enforcer = AuthzEnforcer.get_enforcer() + policies = enforcer.get_filtered_grouping_policy(GroupingPolicyIndex.SUBJECT.value, subject.namespaced_key) + return [ + ScopeData(namespaced_key=policy[GroupingPolicyIndex.SCOPE.value]) + for policy in policies + if policy[GroupingPolicyIndex.ROLE.value] == role.namespaced_key + ] diff --git a/openedx_authz/api/users.py b/openedx_authz/api/users.py index 62c20320..33c52a44 100644 --- a/openedx_authz/api/users.py +++ b/openedx_authz/api/users.py @@ -16,6 +16,7 @@ batch_assign_role_to_subjects_in_scope, batch_unassign_role_from_subjects_in_scope, get_all_subject_role_assignments_in_scope, + get_scopes_for_role_and_subject, get_subject_role_assignments, get_subject_role_assignments_for_role_in_scope, get_subject_role_assignments_in_scope, @@ -198,3 +199,19 @@ def get_users_for_role(role_external_key: str) -> list[UserData]: """ users = get_subjects_for_role(RoleData(external_key=role_external_key)) return [UserData(namespaced_key=user.namespaced_key) for user in users] + + +def get_scopes_for_role_and_user(role_external_key: str, user_external_key: str) -> list[ScopeData]: + """Get all scopes where a specific user has been assigned a specific role. + + Args: + role_external_key (str): The role to filter scopes (e.g., 'instructor'). + user_external_key (str): ID of the user (e.g., 'john_doe'). + + Returns: + list[ScopeData]: A list of scopes where the user has the specified role. + """ + return get_scopes_for_role_and_subject( + RoleData(external_key=role_external_key), + UserData(external_key=user_external_key), + ) diff --git a/openedx_authz/tests/api/test_roles.py b/openedx_authz/tests/api/test_roles.py index 0f4441ac..3741a810 100644 --- a/openedx_authz/tests/api/test_roles.py +++ b/openedx_authz/tests/api/test_roles.py @@ -18,6 +18,7 @@ get_permissions_for_active_roles_in_scope, get_permissions_for_single_role, get_role_definitions_in_scope, + get_scopes_for_role_and_subject, get_subject_role_assignments, get_subject_role_assignments_for_role_in_scope, get_subject_role_assignments_in_scope, @@ -505,6 +506,24 @@ def test_get_role_assignments_in_scope(self, role_name, scope_name, expected_cou self.assertEqual(len(role_assignments), expected_count) + def test_get_scopes_for_role_and_subject(self): + """Test retrieving scopes for a given role and subject. + + Expected result: + - The scopes associated with the specified role and subject are correctly retrieved. + """ + role_name = "library_author" + subject_name = "liam" + expected_scopes = {"lib:Org4:art_101", "lib:Org4:art_201", "lib:Org4:art_301"} + + scopes = get_scopes_for_role_and_subject( + RoleData(external_key=role_name), + SubjectData(external_key=subject_name), + ) + + scope_names = {scope.external_key for scope in scopes} + self.assertEqual(scope_names, expected_scopes) + @ddt class TestRoleAssignmentAPI(RolesTestSetupMixin): From 17aabdc2d72ebf1f27b55d9dbe5d3dc39e136c07 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Mon, 27 Oct 2025 14:48:13 +0100 Subject: [PATCH 2/2] docs: update chagelog --- CHANGELOG.rst | 8 ++++++++ openedx_authz/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4e1283d5..1dbec563 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,14 @@ Unreleased * +0.9.0 - 2025-10-27 +****************** + +Added +===== + +* Function API to retrieve scopes for a given role and subject. + 0.8.0 - 2025-10-24 ****************** diff --git a/openedx_authz/__init__.py b/openedx_authz/__init__.py index 28dd401f..9cabe790 100644 --- a/openedx_authz/__init__.py +++ b/openedx_authz/__init__.py @@ -4,6 +4,6 @@ import os -__version__ = "0.8.0" +__version__ = "0.9.0" ROOT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))