diff --git a/src/govoplan_access/backend/permissions/catalog.py b/src/govoplan_access/backend/permissions/catalog.py index a818e0e..0b0f792 100644 --- a/src/govoplan_access/backend/permissions/catalog.py +++ b/src/govoplan_access/backend/permissions/catalog.py @@ -100,7 +100,12 @@ def expand_scopes(scopes: Iterable[str], *, include_unknown: bool = True) -> lis for alias in compatible_required_scopes(scope): if alias != scope: expanded.add(alias) - if include_unknown and not matched: + # Preserve explicitly granted scopes in the presentation set. A + # canonical module scope can still match its legacy compatibility + # alias when the providing module is not loaded; treating that match + # as proof that the original scope is known would otherwise replace + # the canonical grant with only the deprecated alias. + if include_unknown or scope in catalog: expanded.add(scope) return sorted(expanded) diff --git a/tests/test_permission_catalog_contract.py b/tests/test_permission_catalog_contract.py index 2d2b4c9..4129944 100644 --- a/tests/test_permission_catalog_contract.py +++ b/tests/test_permission_catalog_contract.py @@ -24,6 +24,12 @@ class PermissionCatalogContractTests(unittest.TestCase): self.assertIn("files:upload", scopes) self.assertIn("mail_servers:test", scopes) + def test_expand_scopes_preserves_explicit_canonical_scope_with_legacy_alias(self) -> None: + scopes = access_catalog.expand_scopes(("files:file:read",)) + + self.assertIn("files:file:read", scopes) + self.assertIn("files:read", scopes) + if __name__ == "__main__": unittest.main()