diff --git a/CHANGELOG.md b/CHANGELOG.md index 3289e0be..6178a07d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to ## [Unreleased] +## Fixed + +- 🐛 (backend) gitlab oicd userinfo endpoint #232 + ## [1.4.0] - 2024-09-17 diff --git a/src/backend/core/authentication/backends.py b/src/backend/core/authentication/backends.py index bfa2c107..efada966 100644 --- a/src/backend/core/authentication/backends.py +++ b/src/backend/core/authentication/backends.py @@ -45,7 +45,17 @@ class OIDCAuthenticationBackend(MozillaOIDCAuthenticationBackend): proxies=self.get_settings("OIDC_PROXY", None), ) user_response.raise_for_status() - userinfo = self.verify_token(user_response.text) + + try: + userinfo = user_response.json() + except ValueError: + try: + userinfo = self.verify_token(user_response.text) + except Exception as e: + raise SuspiciousOperation( + _("Invalid response format or token verification failed") + ) from e + return userinfo def get_or_create_user(self, access_token, id_token, payload): diff --git a/src/backend/core/tests/authentication/test_backends.py b/src/backend/core/tests/authentication/test_backends.py index 6e216c2d..f3b9e093 100644 --- a/src/backend/core/tests/authentication/test_backends.py +++ b/src/backend/core/tests/authentication/test_backends.py @@ -1,8 +1,12 @@ """Unit tests for the Authentication Backends.""" +import re + from django.core.exceptions import SuspiciousOperation +from django.test.utils import override_settings import pytest +import responses from core import models from core.authentication.backends import OIDCAuthenticationBackend @@ -81,7 +85,7 @@ def test_authentication_getter_new_user_with_email(monkeypatch): assert models.User.objects.count() == 1 -def test_models_oidc_user_getter_invalid_token(django_assert_num_queries, monkeypatch): +def test_authentication_getter_invalid_token(django_assert_num_queries, monkeypatch): """The user's info doesn't contain a sub.""" klass = OIDCAuthenticationBackend() @@ -102,3 +106,64 @@ def test_models_oidc_user_getter_invalid_token(django_assert_num_queries, monkey klass.get_or_create_user(access_token="test-token", id_token=None, payload=None) assert models.User.objects.exists() is False + + +@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo") +@responses.activate +def test_authentication_get_userinfo_json_response(): + """Test get_userinfo method with a JSON response.""" + + responses.add( + responses.GET, + re.compile(r".*/userinfo"), + json={"name": "John Doe", "email": "john.doe@example.com"}, + status=200, + ) + + oidc_backend = OIDCAuthenticationBackend() + result = oidc_backend.get_userinfo("fake_access_token", None, None) + + assert result["name"] == "John Doe" + assert result["email"] == "john.doe@example.com" + + +@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo") +@responses.activate +def test_authentication_get_userinfo_token_response(monkeypatch): + """Test get_userinfo method with a token response.""" + + responses.add( + responses.GET, re.compile(r".*/userinfo"), body="fake.jwt.token", status=200 + ) + + def mock_verify_token(self, token): # pylint: disable=unused-argument + return {"name": "Jane Doe", "email": "jane.doe@example.com"} + + monkeypatch.setattr(OIDCAuthenticationBackend, "verify_token", mock_verify_token) + + oidc_backend = OIDCAuthenticationBackend() + result = oidc_backend.get_userinfo("fake_access_token", None, None) + + assert result["name"] == "Jane Doe" + assert result["email"] == "jane.doe@example.com" + + +@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo") +@responses.activate +def test_authentication_get_userinfo_invalid_response(): + """ + Test get_userinfo method with an invalid JWT response that + causes verify_token to raise an error. + """ + + responses.add( + responses.GET, re.compile(r".*/userinfo"), body="fake.jwt.token", status=200 + ) + + oidc_backend = OIDCAuthenticationBackend() + + with pytest.raises( + SuspiciousOperation, + match="Invalid response format or token verification failed", + ): + oidc_backend.get_userinfo("fake_access_token", None, None)