diff --git a/src/backend/core/external_api/permissions.py b/src/backend/core/external_api/permissions.py index 82315b6e..1bf29e81 100644 --- a/src/backend/core/external_api/permissions.py +++ b/src/backend/core/external_api/permissions.py @@ -57,6 +57,9 @@ class BaseScopePermission(permissions.BasePermission): if isinstance(token_scopes, str): token_scopes = token_scopes.split() + # Ensure scopes is a deduplicated list (preserving order) and lowercase all scopes + token_scopes = list(dict.fromkeys(scope.lower() for scope in token_scopes)) + if settings.OIDC_RS_SCOPES_PREFIX: token_scopes = [ scope.removeprefix(f"{settings.OIDC_RS_SCOPES_PREFIX}:") diff --git a/src/backend/core/tests/test_external_api_rooms.py b/src/backend/core/tests/test_external_api_rooms.py index 00071585..eaf69ea1 100644 --- a/src/backend/core/tests/test_external_api_rooms.py +++ b/src/backend/core/tests/test_external_api_rooms.py @@ -660,6 +660,37 @@ def test_api_rooms_response_no_telephony(settings): assert response.data["id"] == str(room.id) +def test_api_rooms_token_scope_case_insensitive(settings): + """Token's scope should be case-insensitive.""" + settings.APPLICATION_JWT_SECRET_KEY = "devKey" + user = UserFactory() + + # Generate token with mixed-case scope "Rooms:List" to verify that scope + # validation is case-insensitive (should match "rooms:list") + now = datetime.now(timezone.utc) + payload = { + "iss": settings.APPLICATION_JWT_ISSUER, + "aud": settings.APPLICATION_JWT_AUDIENCE, + "iat": now, + "exp": now + timedelta(hours=1), + "client_id": "test-client", + "scope": "Rooms:List", # Mixed case - should be accepted as "rooms:list" + "user_id": str(user.id), + "delegated": True, + } + token = jwt.encode( + payload, + settings.APPLICATION_JWT_SECRET_KEY, + algorithm=settings.APPLICATION_JWT_ALG, + ) + + client = APIClient() + client.credentials(HTTP_AUTHORIZATION=f"Bearer {token}") + response = client.get("/external-api/v1.0/rooms/") + + assert response.status_code == 200 + + def test_api_rooms_token_without_delegated_flag(settings): """Token without delegated flag should be rejected.""" settings.APPLICATION_JWT_SECRET_KEY = "devKey"