From 6fb65a2ff5de8c89d9386f0d6b89495d1a4bc850 Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Thu, 30 Oct 2025 15:27:11 +0100 Subject: [PATCH 1/4] :bug: Robustify create_user to handle None value --- dojo/pipeline.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index 888cce0ba06..9c60a440e34 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -183,5 +183,12 @@ def sanitize_username(username): def create_user(strategy, details, backend, user=None, *args, **kwargs): if not settings.SOCIAL_AUTH_CREATE_USER: return None - details["username"] = sanitize_username(details.get("username")) + username = details.get("username") + if not username: + username = details.get("email") + if not username: + username = details.get("fullname") + if not username: + logger.warning("User creation failed: No valid identifier found in details (username, email, fullname).") + details["username"] = sanitize_username(username) return social_core.pipeline.user.create_user(strategy, details, backend, user, args, kwargs) From 5fa87c79ca08147a23f5f83e45f553e5253062db Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Thu, 30 Oct 2025 15:48:20 +0100 Subject: [PATCH 2/4] fix --- dojo/pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index 9c60a440e34..d88020af9e3 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -189,6 +189,6 @@ def create_user(strategy, details, backend, user=None, *args, **kwargs): if not username: username = details.get("fullname") if not username: - logger.warning("User creation failed: No valid identifier found in details (username, email, fullname).") + logger.error("User creation failed: No valid identifier found in details (username, email, fullname).") details["username"] = sanitize_username(username) return social_core.pipeline.user.create_user(strategy, details, backend, user, args, kwargs) From 667edbba6508cef1a1580009f0928a900e81f745 Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Thu, 30 Oct 2025 16:48:17 +0100 Subject: [PATCH 3/4] update --- dojo/middleware.py | 5 +++++ dojo/pipeline.py | 2 -- unittests/test_social_auth_failure_handling.py | 10 ++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/dojo/middleware.py b/dojo/middleware.py index ab89bf5a849..5b50f3cc987 100644 --- a/dojo/middleware.py +++ b/dojo/middleware.py @@ -94,6 +94,11 @@ def process_exception(self, request, exception): if isinstance(exception, AuthForbidden): messages.error(request, "You are not authorized to log in via this method. Please contact support or use the standard login.") return redirect("/login?force_login_form") + if isinstance(exception, TypeError) and "'NoneType' object is not iterable" in str(exception): + logger.warning("OIDC login error: NoneType is not iterable") + messages.error(request, "An unexpected error occurred during social login. Please use the standard login.") + return redirect("/login?force_login_form") + logger.error(f"Unhandled exception during social login: {exception}") return super().process_exception(request, exception) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index d88020af9e3..cdb257e5a0e 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -188,7 +188,5 @@ def create_user(strategy, details, backend, user=None, *args, **kwargs): username = details.get("email") if not username: username = details.get("fullname") - if not username: - logger.error("User creation failed: No valid identifier found in details (username, email, fullname).") details["username"] = sanitize_username(username) return social_core.pipeline.user.create_user(strategy, details, backend, user, args, kwargs) diff --git a/unittests/test_social_auth_failure_handling.py b/unittests/test_social_auth_failure_handling.py index 83f69471a02..0cf55f8d860 100644 --- a/unittests/test_social_auth_failure_handling.py +++ b/unittests/test_social_auth_failure_handling.py @@ -83,6 +83,16 @@ def test_non_social_auth_path_redirects_on_auth_forbidden(self): storage = list(messages.get_messages(request)) self.assertTrue(any("You are not authorized to log in via this method." in str(msg) for msg in storage)) + def test_type_error_none_type_iterable_redirect(self): + """Ensure middleware catches 'NoneType' object is not iterable TypeError and redirects.""" + request = self._prepare_request("/login/oidc/") + exception = TypeError("'NoneType' object is not iterable") + response = self.middleware.process_exception(request, exception) + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, "/login?force_login_form") + storage = list(messages.get_messages(request)) + self.assertTrue(any("An unexpected error occurred during social login." in str(msg) for msg in storage)) + @override_settings( AUTHENTICATION_BACKENDS=( From fe01743765b200000baa3de2b82a42f36d87e928 Mon Sep 17 00:00:00 2001 From: Manuel Sommer Date: Mon, 3 Nov 2025 16:02:26 +0100 Subject: [PATCH 4/4] update according to review --- dojo/pipeline.py | 6 +----- dojo/settings/settings.dist.py | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/dojo/pipeline.py b/dojo/pipeline.py index cdb257e5a0e..8aaea4079bb 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -183,10 +183,6 @@ def sanitize_username(username): def create_user(strategy, details, backend, user=None, *args, **kwargs): if not settings.SOCIAL_AUTH_CREATE_USER: return None - username = details.get("username") - if not username: - username = details.get("email") - if not username: - username = details.get("fullname") + username = details.get(settings.SOCIAL_AUTH_CREATE_USER_MAPPING) details["username"] = sanitize_username(username) return social_core.pipeline.user.create_user(strategy, details, backend, user, args, kwargs) diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index b2be58bc64d..5c2fc7367a1 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -113,6 +113,7 @@ DD_FORGOT_USERNAME=(bool, True), # do we show link "I forgot my username" on login screen DD_SOCIAL_AUTH_SHOW_LOGIN_FORM=(bool, True), # do we show user/pass input DD_SOCIAL_AUTH_CREATE_USER=(bool, True), # if True creates user at first login + DD_SOCIAL_AUTH_CREATE_USER_MAPPING=(str, "username"), # could also be email or fullname DD_SOCIAL_LOGIN_AUTO_REDIRECT=(bool, False), # auto-redirect if there is only one social login method DD_SOCIAL_AUTH_TRAILING_SLASH=(bool, True), DD_SOCIAL_AUTH_OIDC_AUTH_ENABLED=(bool, False), @@ -574,6 +575,7 @@ def generate_url(scheme, double_slashes, user, password, host, port, path, param SHOW_LOGIN_FORM = env("DD_SOCIAL_AUTH_SHOW_LOGIN_FORM") SOCIAL_LOGIN_AUTO_REDIRECT = env("DD_SOCIAL_LOGIN_AUTO_REDIRECT") SOCIAL_AUTH_CREATE_USER = env("DD_SOCIAL_AUTH_CREATE_USER") +SOCIAL_AUTH_CREATE_USER_MAPPING = env("DD_SOCIAL_AUTH_CREATE_USER_MAPPING") SOCIAL_AUTH_STRATEGY = "social_django.strategy.DjangoStrategy" SOCIAL_AUTH_STORAGE = "social_django.models.DjangoStorage"