Skip to content

Commit

Permalink
✨ [#99] Implement IDP availability check
Browse files Browse the repository at this point in the history
Optionally, the OIDC init view can check the identity provider
availability before redirecting the user to it.
  • Loading branch information
sergei-maertens committed May 15, 2024
1 parent ea6f514 commit 10f19f3
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 3 deletions.
30 changes: 27 additions & 3 deletions mozilla_django_oidc_db/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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]:
"""
Expand Down
25 changes: 25 additions & 0 deletions tests/test_init_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

0 comments on commit 10f19f3

Please sign in to comment.