Skip to content

Commit 9a3c9b8

Browse files
committed
🎉 add middleware to handle social auth provider unavailability gracefully
1 parent a008a00 commit 9a3c9b8

2 files changed

Lines changed: 88 additions & 1 deletion

File tree

dojo/middleware.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
from urllib.parse import quote
77

88
import pghistory.middleware
9+
import requests
910
from auditlog.context import set_actor
1011
from auditlog.middleware import AuditlogMiddleware as _AuditlogMiddleware
1112
from django.conf import settings
13+
from django.contrib import messages
1214
from django.db import models
1315
from django.http import HttpResponseRedirect
16+
from django.shortcuts import redirect
1417
from django.urls import reverse
1518
from django.utils.functional import SimpleLazyObject
1619
from watson.middleware import SearchContextMiddleware
@@ -75,6 +78,87 @@ def __call__(self, request):
7578
return self.get_response(request)
7679

7780

81+
class AuthProviderHealthCheckMiddleware:
82+
def __init__(self, get_response):
83+
self.get_response = get_response
84+
self.providers = [
85+
{
86+
"name": "OIDC",
87+
"enabled": getattr(settings, "OIDC_AUTH_ENABLED", False),
88+
"endpoint": getattr(settings, "SOCIAL_AUTH_OIDC_OIDC_ENDPOINT", None),
89+
"path_prefix": "/login/oidc/",
90+
"check_path": "/.well-known/openid-configuration",
91+
},
92+
{
93+
"name": "GitLab",
94+
"enabled": getattr(settings, "GITLAB_OAUTH2_ENABLED", False),
95+
"endpoint": getattr(settings, "SOCIAL_AUTH_GITLAB_API_URL", None),
96+
"path_prefix": "/login/gitlab/",
97+
"check_path": "",
98+
},
99+
{
100+
"name": "GitHub Enterprise",
101+
"enabled": getattr(settings, "GITHUB_ENTERPRISE_OAUTH2_ENABLED", False),
102+
"endpoint": getattr(settings, "SOCIAL_AUTH_GITHUB_ENTERPRISE_URL", None),
103+
"path_prefix": "/login/github/",
104+
"check_path": "",
105+
},
106+
{
107+
"name": "Google",
108+
"enabled": getattr(settings, "GOOGLE_OAUTH_ENABLED", False),
109+
"endpoint": "https://accounts.google.com",
110+
"path_prefix": "/login/google-oauth2/",
111+
"check_path": "/.well-known/openid-configuration",
112+
},
113+
{
114+
"name": "Azure AD",
115+
"enabled": getattr(settings, "AZUREAD_TENANT_OAUTH2_ENABLED", False),
116+
"endpoint": f"https://login.microsoftonline.com/{getattr(settings, 'SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID', '')}",
117+
"path_prefix": "/login/azuread-tenant-oauth2/",
118+
"check_path": "/v2.0/.well-known/openid-configuration",
119+
},
120+
{
121+
"name": "Okta",
122+
"enabled": getattr(settings, "OKTA_OAUTH_ENABLED", False),
123+
"endpoint": getattr(settings, "SOCIAL_AUTH_OKTA_OAUTH2_API_URL", None),
124+
"path_prefix": "/login/okta-oauth2/",
125+
"check_path": "/.well-known/openid-configuration",
126+
},
127+
{
128+
"name": "Keycloak",
129+
"enabled": getattr(settings, "KEYCLOAK_OAUTH2_ENABLED", False),
130+
"endpoint": getattr(settings, "SOCIAL_AUTH_KEYCLOAK_OAUTH2_API_URL", None),
131+
"path_prefix": "/login/keycloak-oauth2/",
132+
"check_path": "/.well-known/openid-configuration",
133+
},
134+
{
135+
"name": "Auth0",
136+
"enabled": getattr(settings, "AUTH0_OAUTH2_ENABLED", False),
137+
"endpoint": getattr(settings, "SOCIAL_AUTH_AUTH0_DOMAIN", None),
138+
"path_prefix": "/login/auth0/",
139+
"check_path": "/.well-known/openid-configuration",
140+
},
141+
]
142+
143+
def __call__(self, request):
144+
for provider in self.providers:
145+
if provider["enabled"] and provider["endpoint"] and request.path.startswith(provider["path_prefix"]):
146+
check_url = provider["endpoint"] + provider["check_path"]
147+
try:
148+
response = requests.get(check_url, timeout=3, allow_redirects=False)
149+
if response.status_code >= 500:
150+
raise requests.exceptions.RequestException(provider["name"] + " returned " + str(response.status_code))
151+
except requests.exceptions.RequestException as e:
152+
logger.warning(f"{provider['name']} provider unavailable: {e!s}")
153+
messages.error(
154+
request,
155+
f"{provider['name']} login is temporarily unavailable. Please use the standard login below.",
156+
)
157+
return redirect("/login")
158+
159+
return self.get_response(request)
160+
161+
78162
class DojoSytemSettingsMiddleware:
79163
_thread_local = local()
80164

dojo/settings/settings.dist.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -924,15 +924,17 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param
924924
# ------------------------------------------------------------------------------
925925
# MIDDLEWARE
926926
# ------------------------------------------------------------------------------
927+
927928
DJANGO_MIDDLEWARE_CLASSES = [
928929
"django.middleware.common.CommonMiddleware",
929930
"dojo.middleware.APITrailingSlashMiddleware",
930931
"dojo.middleware.DojoSytemSettingsMiddleware",
931932
"django.contrib.sessions.middleware.SessionMiddleware",
933+
"django.contrib.messages.middleware.MessageMiddleware",
934+
"dojo.middleware.AuthProviderHealthCheckMiddleware",
932935
"django.middleware.csrf.CsrfViewMiddleware",
933936
"django.middleware.security.SecurityMiddleware",
934937
"django.contrib.auth.middleware.AuthenticationMiddleware",
935-
"django.contrib.messages.middleware.MessageMiddleware",
936938
"django.middleware.clickjacking.XFrameOptionsMiddleware",
937939
"dojo.middleware.LoginRequiredMiddleware",
938940
"dojo.middleware.AdditionalHeaderMiddleware",
@@ -944,6 +946,7 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param
944946
"dojo.middleware.LongRunningRequestAlertMiddleware",
945947
]
946948

949+
947950
MIDDLEWARE = DJANGO_MIDDLEWARE_CLASSES
948951

949952
# WhiteNoise allows your web app to serve its own static files,

0 commit comments

Comments
 (0)