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 888cce0ba06..8aaea4079bb 100644 --- a/dojo/pipeline.py +++ b/dojo/pipeline.py @@ -183,5 +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 - details["username"] = sanitize_username(details.get("username")) + 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" 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=(