diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e064d4f8..1c269dbd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,14 @@ Change Log Unreleased ********** +0.13.1 - 2025-11-06 +******************** + +Fixed +===== + +* Avoid duplicates when getting scopes for given user and permissions. + 0.13.0 - 2025-11-05 ******************** diff --git a/openedx_authz/__init__.py b/openedx_authz/__init__.py index c39af842..6438c962 100644 --- a/openedx_authz/__init__.py +++ b/openedx_authz/__init__.py @@ -4,6 +4,6 @@ import os -__version__ = "0.13.0" +__version__ = "0.13.1" ROOT_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) diff --git a/openedx_authz/api/roles.py b/openedx_authz/api/roles.py index 2c0f02a7..c4e27e6c 100644 --- a/openedx_authz/api/roles.py +++ b/openedx_authz/api/roles.py @@ -398,6 +398,6 @@ def get_scopes_for_subject_and_permission( scopes = [] for role_assignment in roles_for_subject: for role in role_assignment.roles: - if permission in role.permissions: + if permission in role.permissions and role_assignment.scope not in scopes: scopes.append(role_assignment.scope) return scopes diff --git a/openedx_authz/tests/api/test_roles.py b/openedx_authz/tests/api/test_roles.py index d19497eb..3dec484a 100644 --- a/openedx_authz/tests/api/test_roles.py +++ b/openedx_authz/tests/api/test_roles.py @@ -620,6 +620,41 @@ def test_get_scopes_for_subject_and_permission(self, subject_name, action_name, for expected_scope in expected_scope_names: self.assertIn(expected_scope, actual_scope_names) + def test_get_scopes_for_subject_and_permission_no_duplicates(self): + """Test that get_scopes_for_subject_and_permission returns no duplicate scopes. + + This test verifies that when a subject has multiple roles in the same scope + that grant the same permission, the scope appears only once in the result. + + Expected result: + - Each scope appears exactly once in the returned list + - No duplicate scopes even when multiple roles grant the same permission + """ + test_scope = "lib:TestOrg:duplicate_test" + test_subject = "test_user_duplicates" + + assign_role_to_subject_in_scope( + SubjectData(external_key=test_subject), + RoleData(external_key=roles.LIBRARY_ADMIN.external_key), + ScopeData(external_key=test_scope), + ) + + assign_role_to_subject_in_scope( + SubjectData(external_key=test_subject), + RoleData(external_key=roles.LIBRARY_AUTHOR.external_key), + ScopeData(external_key=test_scope), + ) + + subject = SubjectData(external_key=test_subject) + permission = PermissionData(action=ActionData(external_key="view_library")) + + scopes = get_scopes_for_subject_and_permission(subject, permission) + scope_external_keys = [scope.external_key for scope in scopes] + + self.assertEqual(len(scope_external_keys), 1) + self.assertEqual(scope_external_keys[0], test_scope) + self.assertEqual(len(scope_external_keys), len(set(scope_external_keys))) + @ddt_data( (roles.LIBRARY_AUTHOR.external_key, "lib:Org4:art_101", {"liam"}), (roles.LIBRARY_AUTHOR.external_key, "lib:Org4:art_201", {"liam"}),