From 664afd32e42c520d1dcdc5929b616078f9f563c3 Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Mon, 22 Jul 2024 20:58:27 +0000 Subject: [PATCH] feat(oauth): handle misconfigured session verifier the case when the session verifier has neither Eligibility API nor IDP config is a system error / configuration error --- benefits/oauth/middleware.py | 19 +++++++++- .../test_middleware_authverifier_required.py | 35 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/benefits/oauth/middleware.py b/benefits/oauth/middleware.py index 69dbe6654..c5ebb272f 100644 --- a/benefits/oauth/middleware.py +++ b/benefits/oauth/middleware.py @@ -1,8 +1,14 @@ import logging +from django.shortcuts import redirect +import sentry_sdk + from benefits.core import session from benefits.core.middleware import VerifierSessionRequired, user_error +from . import analytics +from .redirects import ROUTE_SYSTEM_ERROR + logger = logging.getLogger(__name__) @@ -16,8 +22,19 @@ def process_request(self, request): # from the base middleware class, the session didn't have a verifier return result - if session.verifier(request).uses_auth_verification: + verifier = session.verifier(request) + + if verifier.uses_auth_verification: + # all good, the chosen verifier is configured correctly return None + elif verifier.api_url is None and verifier.form_class is None: + # the chosen verifier doesn't have Eligibility API config OR auth provider config + # this is likely a misconfiguration on the backend, not a user error + message = f"Verifier with no API or IDP config: {verifier.name} (id={verifier.id})" + analytics.error(request, message=message, operation=request.path) + sentry_sdk.capture_exception(Exception(message)) + return redirect(ROUTE_SYSTEM_ERROR) else: + # the chosen verifier was for Eligibility API logger.debug("Session not configured with eligibility verifier that uses auth verification") return user_error(request) diff --git a/tests/pytest/oauth/test_middleware_authverifier_required.py b/tests/pytest/oauth/test_middleware_authverifier_required.py index 6fc479574..d40c765ca 100644 --- a/tests/pytest/oauth/test_middleware_authverifier_required.py +++ b/tests/pytest/oauth/test_middleware_authverifier_required.py @@ -1,9 +1,12 @@ +from django.urls import reverse from django.utils.decorators import decorator_from_middleware import pytest from benefits.core.middleware import TEMPLATE_USER_ERROR from benefits.oauth.middleware import VerifierUsesAuthVerificationSessionRequired +import benefits.oauth.middleware +from benefits.oauth.redirects import ROUTE_SYSTEM_ERROR @pytest.fixture @@ -11,6 +14,16 @@ def decorated_view(mocked_view): return decorator_from_middleware(VerifierUsesAuthVerificationSessionRequired)(mocked_view) +@pytest.fixture +def mocked_analytics_module(mocked_analytics_module): + return mocked_analytics_module(benefits.oauth.middleware) + + +@pytest.fixture +def mocked_sentry_sdk_module(mocker): + return mocker.patch.object(benefits.oauth.middleware, "sentry_sdk") + + @pytest.mark.django_db def test_authverifier_required_no_verifier(app_request, mocked_view, decorated_view): response = decorated_view(app_request) @@ -30,6 +43,28 @@ def test_authverifier_required_no_authverifier(app_request, mocked_view, decorat assert response.template_name == TEMPLATE_USER_ERROR +@pytest.mark.django_db +def test_authverifier_required_misconfigured_verifier( + app_request, + mocked_view, + decorated_view, + mocked_session_verifier_does_not_use_auth_verification, + mocked_analytics_module, + mocked_sentry_sdk_module, +): + # fake a misconfigured verifier + mocked_session_verifier_does_not_use_auth_verification.return_value.api_url = None + mocked_session_verifier_does_not_use_auth_verification.return_value.form_class = None + + response = decorated_view(app_request) + + mocked_view.assert_not_called() + mocked_analytics_module.error.assert_called_once() + mocked_sentry_sdk_module.capture_exception.assert_called_once() + assert response.status_code == 302 + assert response.url == reverse(ROUTE_SYSTEM_ERROR) + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_verifier_oauth") def test_authverifier_required_authverifier(app_request, mocked_view, decorated_view):