diff --git a/mozilla_django_oidc_db/views.py b/mozilla_django_oidc_db/views.py index bdbaf6a..72637f5 100644 --- a/mozilla_django_oidc_db/views.py +++ b/mozilla_django_oidc_db/views.py @@ -10,12 +10,14 @@ from django.utils.http import url_has_allowed_host_and_scheme from django.views.generic import TemplateView +import requests from mozilla_django_oidc.views import ( OIDCAuthenticationCallbackView, OIDCAuthenticationRequestView as _OIDCAuthenticationRequestView, ) from .config import get_setting_from_config, store_config +from .exceptions import OIDCProviderOutage from .models import OpenIDConnectConfig, OpenIDConnectConfigBase logger = logging.getLogger(__name__) @@ -181,7 +183,8 @@ def get( if not self.allow_next_from_query: self._validate_return_url(request, return_url=return_url) - self.check_idp_availability() + if self.get_settings("OIDCDB_CHECK_IDP_AVAILABILITY", False): + self.check_idp_availability() response = super().get(request, *args, **kwargs) @@ -241,9 +244,30 @@ def check_idp_availability(self) -> None: """ Hook for subclasses. - Raise :class:`OIDCProviderOutage` if the Identity Provider is not available. + Raise :class:`OIDCProviderOutage` if the Identity Provider is not available, + which your application code needs to handle. + + The default implementation checks if the endpoint has a status code < 401. """ - pass + endpoint = self.OIDC_OP_AUTH_ENDPOINT + try: + # Verify that the identity provider endpoint can be reached. This is where + # the user ultimately gets redirected to. + # + # 5 seconds wait time is probably already too long for a good user + # experience, but we don't want to be *too* aggressive. + response = requests.get(endpoint, timeout=5.0) + # some IDPs have been observed to return HTTP 400 because of the missing + # query params + if response.status_code > 400: + response.raise_for_status() + except requests.RequestException as exc: + logger.info( + "OIDC provider endpoint '%s' could not be retrieved", + endpoint, + exc_info=exc, + ) + raise OIDCProviderOutage("Identity provider appears to be down.") from exc def get_extra_params(self, request: HttpRequest) -> dict[str, str]: """ diff --git a/tests/test_init_flow.py b/tests/test_init_flow.py index 2aac164..f4447f2 100644 --- a/tests/test_init_flow.py +++ b/tests/test_init_flow.py @@ -8,6 +8,8 @@ import pytest +from mozilla_django_oidc_db.exceptions import OIDCProviderOutage + @pytest.mark.oidcconfig( oidc_op_authorization_endpoint="http://localhost:8080/openid-connect/auth" @@ -46,3 +48,26 @@ def test_keycloak_idp_hint_via_settings(dummy_config, settings, client): query = parse_qs(parsed_url.query) assert query["kc_idp_hint"] == ["keycloak-idp1"] + + +def test_check_idp_availability_not_available( + dummy_config, settings, client, requests_mock +): + settings.OIDCDB_CHECK_IDP_AVAILABILITY = True + requests_mock.get("https://mock-oidc-provider:9999/oidc/auth", status_code=503) + start_url = reverse("oidc_authentication_init") + + with pytest.raises(OIDCProviderOutage): + client.get(start_url) + + +def test_check_idp_availability_available( + dummy_config, settings, client, requests_mock +): + settings.OIDCDB_CHECK_IDP_AVAILABILITY = True + requests_mock.get("https://mock-oidc-provider:9999/oidc/auth", status_code=400) + start_url = reverse("oidc_authentication_init") + + response = client.get(start_url) + + assert response.status_code == 302 diff --git a/tests/test_init_flow_custom_config.py b/tests/test_init_flow_custom_config.py index 46c4cce..16ab62a 100644 --- a/tests/test_init_flow_custom_config.py +++ b/tests/test_init_flow_custom_config.py @@ -63,6 +63,8 @@ def check_idp_availability(self) -> None: ) -def test_idp_check_mechanism(auth_request): +def test_idp_check_mechanism(auth_request, settings): + settings.OIDCDB_CHECK_IDP_AVAILABILITY = True + with pytest.raises(OIDCProviderOutage): oidc_init_with_idp_check(auth_request)