Skip to content

Commit 6934876

Browse files
Maffoochclaude
andcommitted
Isolate all SSO code into dojo/sso/ as a plain Python package
Move all SSO functionality (OAuth2, SAML2, OIDC, remote user auth) into dojo/sso/ so that removing SSO requires only deleting the directory and removing packages from requirements.txt. All hooks in shared files use try/except ImportError guards that gracefully degrade when dojo/sso/ is absent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3699b76 commit 6934876

22 files changed

Lines changed: 660 additions & 582 deletions

.dryrunsecurity.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ sensitiveCodepaths:
4040
- 'dojo/middleware.py'
4141
- 'dojo/models.py'
4242
- 'dojo/okta.py'
43-
- 'dojo/pipeline.py'
44-
- 'dojo/remote_user.py'
43+
- 'dojo/sso/pipeline.py'
44+
- 'dojo/sso/remote_user.py'
4545
- 'dojo/tasks.py'
4646
- 'dojo/urls.py'
4747
- 'dojo/utils.py'

dojo/context_processors.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,6 @@ def globalize_vars(request):
1616
"FORGOT_PASSWORD": settings.FORGOT_PASSWORD,
1717
"FORGOT_USERNAME": settings.FORGOT_USERNAME,
1818
"CLASSIC_AUTH_ENABLED": settings.CLASSIC_AUTH_ENABLED,
19-
"OIDC_ENABLED": settings.OIDC_AUTH_ENABLED,
20-
"SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT": settings.SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT,
21-
"AUTH0_ENABLED": settings.AUTH0_OAUTH2_ENABLED,
22-
"GOOGLE_ENABLED": settings.GOOGLE_OAUTH_ENABLED,
23-
"OKTA_ENABLED": settings.OKTA_OAUTH_ENABLED,
24-
"GITLAB_ENABLED": settings.GITLAB_OAUTH2_ENABLED,
25-
"AZUREAD_TENANT_OAUTH2_ENABLED": settings.AZUREAD_TENANT_OAUTH2_ENABLED,
26-
"AZUREAD_TENANT_OAUTH2_GET_GROUPS": settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS,
27-
"AZUREAD_TENANT_OAUTH2_GROUPS_FILTER": settings.AZUREAD_TENANT_OAUTH2_GROUPS_FILTER,
28-
"AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS": settings.AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS,
29-
"KEYCLOAK_ENABLED": settings.KEYCLOAK_OAUTH2_ENABLED,
30-
"SOCIAL_AUTH_KEYCLOAK_LOGIN_BUTTON_TEXT": settings.SOCIAL_AUTH_KEYCLOAK_LOGIN_BUTTON_TEXT,
31-
"GITHUB_ENTERPRISE_ENABLED": settings.GITHUB_ENTERPRISE_OAUTH2_ENABLED,
32-
"SAML2_ENABLED": settings.SAML2_ENABLED,
33-
"SAML2_LOGIN_BUTTON_TEXT": settings.SAML2_LOGIN_BUTTON_TEXT,
34-
"SAML2_LOGOUT_URL": settings.SAML2_LOGOUT_URL,
3519
"DOCUMENTATION_URL": settings.DOCUMENTATION_URL,
3620
"API_TOKENS_ENABLED": settings.API_TOKENS_ENABLED,
3721
"API_TOKEN_AUTH_ENDPOINT_ENABLED": settings.API_TOKEN_AUTH_ENDPOINT_ENABLED,

dojo/middleware.py

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

88
import pghistory.middleware
9-
import requests
109
from django.conf import settings
11-
from django.contrib import messages
1210
from django.db import models
1311
from django.http import HttpResponseRedirect
14-
from django.shortcuts import redirect
1512
from django.urls import reverse
16-
from social_core.exceptions import AuthCanceled, AuthFailed, AuthForbidden, AuthTokenError
17-
from social_django.middleware import SocialAuthExceptionMiddleware
1813
from watson.middleware import SearchContextMiddleware
1914
from watson.search import search_context_manager
2015

@@ -79,31 +74,6 @@ def __call__(self, request):
7974
return response
8075

8176

82-
class CustomSocialAuthExceptionMiddleware(SocialAuthExceptionMiddleware):
83-
def process_exception(self, request, exception):
84-
if isinstance(exception, requests.exceptions.RequestException):
85-
messages.error(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_REQUEST_EXCEPTION)
86-
return redirect("/login?force_login_form")
87-
if isinstance(exception, AuthCanceled):
88-
messages.warning(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_AUTH_CANCELED)
89-
return redirect("/login?force_login_form")
90-
if isinstance(exception, AuthFailed):
91-
messages.error(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_AUTH_FAILED)
92-
return redirect("/login?force_login_form")
93-
if isinstance(exception, AuthForbidden):
94-
messages.error(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_AUTH_FORBIDDEN)
95-
return redirect("/login?force_login_form")
96-
if isinstance(exception, AuthTokenError):
97-
messages.error(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_AUTH_TOKEN_ERROR)
98-
return redirect("/login?force_login_form")
99-
if isinstance(exception, TypeError) and "'NoneType' object is not iterable" in str(exception):
100-
logger.warning("OIDC login error: NoneType is not iterable")
101-
messages.error(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_NONE_TYPE)
102-
return redirect("/login?force_login_form")
103-
logger.error(f"Unhandled exception during social login: {exception}")
104-
return super().process_exception(request, exception)
105-
106-
10777
class DojoSytemSettingsMiddleware:
10878
_thread_local = local()
10979

dojo/settings/settings.dist.py

Lines changed: 17 additions & 430 deletions
Large diffs are not rendered by default.

dojo/sso/attribute_maps/__init__.py

Whitespace-only changes.
File renamed without changes.

dojo/sso/context_processors.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from django.conf import settings
2+
3+
4+
def sso_context(request):
5+
return {
6+
"OIDC_ENABLED": settings.OIDC_AUTH_ENABLED,
7+
"SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT": settings.SOCIAL_AUTH_OIDC_LOGIN_BUTTON_TEXT,
8+
"AUTH0_ENABLED": settings.AUTH0_OAUTH2_ENABLED,
9+
"GOOGLE_ENABLED": settings.GOOGLE_OAUTH_ENABLED,
10+
"OKTA_ENABLED": settings.OKTA_OAUTH_ENABLED,
11+
"GITLAB_ENABLED": settings.GITLAB_OAUTH2_ENABLED,
12+
"AZUREAD_TENANT_OAUTH2_ENABLED": settings.AZUREAD_TENANT_OAUTH2_ENABLED,
13+
"AZUREAD_TENANT_OAUTH2_GET_GROUPS": settings.AZUREAD_TENANT_OAUTH2_GET_GROUPS,
14+
"AZUREAD_TENANT_OAUTH2_GROUPS_FILTER": settings.AZUREAD_TENANT_OAUTH2_GROUPS_FILTER,
15+
"AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS": settings.AZUREAD_TENANT_OAUTH2_CLEANUP_GROUPS,
16+
"KEYCLOAK_ENABLED": settings.KEYCLOAK_OAUTH2_ENABLED,
17+
"SOCIAL_AUTH_KEYCLOAK_LOGIN_BUTTON_TEXT": settings.SOCIAL_AUTH_KEYCLOAK_LOGIN_BUTTON_TEXT,
18+
"GITHUB_ENTERPRISE_ENABLED": settings.GITHUB_ENTERPRISE_OAUTH2_ENABLED,
19+
"SAML2_ENABLED": settings.SAML2_ENABLED,
20+
"SAML2_LOGIN_BUTTON_TEXT": settings.SAML2_LOGIN_BUTTON_TEXT,
21+
"SAML2_LOGOUT_URL": settings.SAML2_LOGOUT_URL,
22+
}

dojo/sso/middleware.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import logging
2+
3+
import requests
4+
from django.conf import settings
5+
from django.contrib import messages
6+
from django.shortcuts import redirect
7+
from social_core.exceptions import AuthCanceled, AuthFailed, AuthForbidden, AuthTokenError
8+
from social_django.middleware import SocialAuthExceptionMiddleware
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
class CustomSocialAuthExceptionMiddleware(SocialAuthExceptionMiddleware):
14+
def process_exception(self, request, exception):
15+
if isinstance(exception, requests.exceptions.RequestException):
16+
messages.error(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_REQUEST_EXCEPTION)
17+
return redirect("/login?force_login_form")
18+
if isinstance(exception, AuthCanceled):
19+
messages.warning(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_AUTH_CANCELED)
20+
return redirect("/login?force_login_form")
21+
if isinstance(exception, AuthFailed):
22+
messages.error(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_AUTH_FAILED)
23+
return redirect("/login?force_login_form")
24+
if isinstance(exception, AuthForbidden):
25+
messages.error(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_AUTH_FORBIDDEN)
26+
return redirect("/login?force_login_form")
27+
if isinstance(exception, AuthTokenError):
28+
messages.error(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_AUTH_TOKEN_ERROR)
29+
return redirect("/login?force_login_form")
30+
if isinstance(exception, TypeError) and "'NoneType' object is not iterable" in str(exception):
31+
logger.warning("OIDC login error: NoneType is not iterable")
32+
messages.error(request, settings.SOCIAL_AUTH_EXCEPTION_MESSAGE_NONE_TYPE)
33+
return redirect("/login?force_login_form")
34+
logger.error(f"Unhandled exception during social login: {exception}")
35+
return super().process_exception(request, exception)

0 commit comments

Comments
 (0)