From 5c66b3524af633736852addd6d9914b3bcb517be Mon Sep 17 00:00:00 2001 From: wangruidong <940853815@qq.com> Date: Thu, 11 Sep 2025 18:56:19 +0800 Subject: [PATCH] fix: OpenID Only allow existing users to log in operate log error --- apps/authentication/backends/oidc/views.py | 12 ++++---- apps/authentication/mixins.py | 33 ++++++++++++++++++++++ apps/authentication/views/base.py | 3 +- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/apps/authentication/backends/oidc/views.py b/apps/authentication/backends/oidc/views.py index 844c3a4c1..c6554fb20 100644 --- a/apps/authentication/backends/oidc/views.py +++ b/apps/authentication/backends/oidc/views.py @@ -134,6 +134,7 @@ class OIDCAuthCallbackView(View, FlashMessageMixin): log_prompt = "Process GET requests [OIDCAuthCallbackView]: {}" logger.debug(log_prompt.format('Start')) callback_params = request.GET + error_title = _("OpenID Error") # Retrieve the state value that was previously generated. No state means that we cannot # authenticate the user (so a failure should be returned). @@ -172,10 +173,9 @@ class OIDCAuthCallbackView(View, FlashMessageMixin): try: user = auth.authenticate(nonce=nonce, request=request, code_verifier=code_verifier) except IntegrityError as e: - title = _("OpenID Error") msg = _('Please check if a user with the same username or email already exists') logger.error(e, exc_info=True) - response = self.get_failed_response('/', title, msg) + response = self.get_failed_response('/', error_title, msg) return response if user: logger.debug(log_prompt.format('Login: {}'.format(user))) @@ -194,7 +194,6 @@ class OIDCAuthCallbackView(View, FlashMessageMixin): return HttpResponseRedirect( next_url or settings.AUTH_OPENID_AUTHENTICATION_REDIRECT_URI ) - if 'error' in callback_params: logger.debug( log_prompt.format('Error in callback params: {}'.format(callback_params['error'])) @@ -205,9 +204,12 @@ class OIDCAuthCallbackView(View, FlashMessageMixin): # OpenID Connect Provider authenticate endpoint. logger.debug(log_prompt.format('Logout')) auth.logout(request) - + redirect_url = settings.AUTH_OPENID_AUTHENTICATION_FAILURE_REDIRECT_URI + if not user and getattr(request, 'error_message', ''): + response = self.get_failed_response(redirect_url, title=error_title, msg=request.error_message) + return response logger.debug(log_prompt.format('Redirect')) - return HttpResponseRedirect(settings.AUTH_OPENID_AUTHENTICATION_FAILURE_REDIRECT_URI) + return HttpResponseRedirect(redirect_url) class OIDCAuthCallbackClientView(BaseAuthCallbackClientView): diff --git a/apps/authentication/mixins.py b/apps/authentication/mixins.py index dbaa3bb77..84813f002 100644 --- a/apps/authentication/mixins.py +++ b/apps/authentication/mixins.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # import inspect +import threading import time import uuid from functools import partial @@ -12,6 +13,7 @@ from django.contrib.auth import ( BACKEND_SESSION_KEY, load_backend, PermissionDenied, user_login_failed, _clean_credentials, ) +from django.contrib.auth import get_user_model from django.core.cache import cache from django.core.exceptions import ImproperlyConfigured from django.shortcuts import reverse, redirect, get_object_or_404 @@ -46,6 +48,10 @@ def _get_backends(return_tuples=False): return backends +class OnlyAllowExistUserAuthError(Exception): + pass + + auth._get_backends = _get_backends @@ -54,6 +60,24 @@ def authenticate(request=None, **credentials): If the given credentials are valid, return a User object. 之所以 hack 这个 authenticate """ + + UserModel = get_user_model() + original_get_or_create = UserModel.objects.get_or_create + + thread_local = threading.local() + thread_local.thread_id = threading.get_ident() + + def custom_get_or_create(self, *args, **kwargs): + logger.debug(f"get_or_create: thread_id={threading.get_ident()}, username={username}") + if threading.get_ident() != thread_local.thread_id or not settings.ONLY_ALLOW_EXIST_USER_AUTH: + return original_get_or_create(*args, **kwargs) + create_username = kwargs.get('username') + try: + UserModel.objects.get(username=create_username) + except UserModel.DoesNotExist: + raise OnlyAllowExistUserAuthError + return original_get_or_create(*args, **kwargs) + username = credentials.get('username') temp_user = None @@ -71,10 +95,19 @@ def authenticate(request=None, **credentials): # This backend doesn't accept these credentials as arguments. Try the next one. continue try: + UserModel.objects.get_or_create = custom_get_or_create.__get__(UserModel.objects) user = backend.authenticate(request, **credentials) except PermissionDenied: # This backend says to stop in our tracks - this user should not be allowed in at all. break + except OnlyAllowExistUserAuthError: + request.error_message = _( + '''The administrator has enabled "Only allow existing users to log in", + and the current user is not in the user list. Please contact the administrator.''' + ) + continue + finally: + UserModel.objects.get_or_create = original_get_or_create if user is None: continue diff --git a/apps/authentication/views/base.py b/apps/authentication/views/base.py index 93e602934..1ac390429 100644 --- a/apps/authentication/views/base.py +++ b/apps/authentication/views/base.py @@ -15,7 +15,7 @@ from common.utils import get_logger from common.utils.common import get_request_ip from common.utils.django import reverse, get_object_or_none from users.models import User -from users.signal_handlers import check_only_allow_exist_user_auth, bind_user_to_org_role +from users.signal_handlers import bind_user_to_org_role, check_only_allow_exist_user_auth from .mixins import FlashMessageMixin logger = get_logger(__file__) @@ -55,7 +55,6 @@ class BaseLoginCallbackView(AuthMixin, FlashMessageMixin, IMClientMixin, View): ) if not check_only_allow_exist_user_auth(create): - user.delete() return user, (self.msg_client_err, self.request.error_message) setattr(user, f'{self.user_type}_id', user_id)