fix: OpenID Only allow existing users to log in operate log error

pull/16013/head
wangruidong 2025-09-11 18:56:19 +08:00
parent a732cc614e
commit 5c66b3524a
3 changed files with 41 additions and 7 deletions

View File

@ -134,6 +134,7 @@ class OIDCAuthCallbackView(View, FlashMessageMixin):
log_prompt = "Process GET requests [OIDCAuthCallbackView]: {}" log_prompt = "Process GET requests [OIDCAuthCallbackView]: {}"
logger.debug(log_prompt.format('Start')) logger.debug(log_prompt.format('Start'))
callback_params = request.GET callback_params = request.GET
error_title = _("OpenID Error")
# Retrieve the state value that was previously generated. No state means that we cannot # Retrieve the state value that was previously generated. No state means that we cannot
# authenticate the user (so a failure should be returned). # authenticate the user (so a failure should be returned).
@ -172,10 +173,9 @@ class OIDCAuthCallbackView(View, FlashMessageMixin):
try: try:
user = auth.authenticate(nonce=nonce, request=request, code_verifier=code_verifier) user = auth.authenticate(nonce=nonce, request=request, code_verifier=code_verifier)
except IntegrityError as e: except IntegrityError as e:
title = _("OpenID Error")
msg = _('Please check if a user with the same username or email already exists') msg = _('Please check if a user with the same username or email already exists')
logger.error(e, exc_info=True) logger.error(e, exc_info=True)
response = self.get_failed_response('/', title, msg) response = self.get_failed_response('/', error_title, msg)
return response return response
if user: if user:
logger.debug(log_prompt.format('Login: {}'.format(user))) logger.debug(log_prompt.format('Login: {}'.format(user)))
@ -194,7 +194,6 @@ class OIDCAuthCallbackView(View, FlashMessageMixin):
return HttpResponseRedirect( return HttpResponseRedirect(
next_url or settings.AUTH_OPENID_AUTHENTICATION_REDIRECT_URI next_url or settings.AUTH_OPENID_AUTHENTICATION_REDIRECT_URI
) )
if 'error' in callback_params: if 'error' in callback_params:
logger.debug( logger.debug(
log_prompt.format('Error in callback params: {}'.format(callback_params['error'])) log_prompt.format('Error in callback params: {}'.format(callback_params['error']))
@ -205,9 +204,12 @@ class OIDCAuthCallbackView(View, FlashMessageMixin):
# OpenID Connect Provider authenticate endpoint. # OpenID Connect Provider authenticate endpoint.
logger.debug(log_prompt.format('Logout')) logger.debug(log_prompt.format('Logout'))
auth.logout(request) 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')) logger.debug(log_prompt.format('Redirect'))
return HttpResponseRedirect(settings.AUTH_OPENID_AUTHENTICATION_FAILURE_REDIRECT_URI) return HttpResponseRedirect(redirect_url)
class OIDCAuthCallbackClientView(BaseAuthCallbackClientView): class OIDCAuthCallbackClientView(BaseAuthCallbackClientView):

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import inspect import inspect
import threading
import time import time
import uuid import uuid
from functools import partial from functools import partial
@ -12,6 +13,7 @@ from django.contrib.auth import (
BACKEND_SESSION_KEY, load_backend, BACKEND_SESSION_KEY, load_backend,
PermissionDenied, user_login_failed, _clean_credentials, PermissionDenied, user_login_failed, _clean_credentials,
) )
from django.contrib.auth import get_user_model
from django.core.cache import cache from django.core.cache import cache
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.shortcuts import reverse, redirect, get_object_or_404 from django.shortcuts import reverse, redirect, get_object_or_404
@ -46,6 +48,10 @@ def _get_backends(return_tuples=False):
return backends return backends
class OnlyAllowExistUserAuthError(Exception):
pass
auth._get_backends = _get_backends auth._get_backends = _get_backends
@ -54,6 +60,24 @@ def authenticate(request=None, **credentials):
If the given credentials are valid, return a User object. If the given credentials are valid, return a User object.
之所以 hack 这个 authenticate 之所以 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') username = credentials.get('username')
temp_user = None 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. # This backend doesn't accept these credentials as arguments. Try the next one.
continue continue
try: try:
UserModel.objects.get_or_create = custom_get_or_create.__get__(UserModel.objects)
user = backend.authenticate(request, **credentials) user = backend.authenticate(request, **credentials)
except PermissionDenied: except PermissionDenied:
# This backend says to stop in our tracks - this user should not be allowed in at all. # This backend says to stop in our tracks - this user should not be allowed in at all.
break 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: if user is None:
continue continue

View File

@ -15,7 +15,7 @@ from common.utils import get_logger
from common.utils.common import get_request_ip from common.utils.common import get_request_ip
from common.utils.django import reverse, get_object_or_none from common.utils.django import reverse, get_object_or_none
from users.models import User 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 from .mixins import FlashMessageMixin
logger = get_logger(__file__) logger = get_logger(__file__)
@ -55,7 +55,6 @@ class BaseLoginCallbackView(AuthMixin, FlashMessageMixin, IMClientMixin, View):
) )
if not check_only_allow_exist_user_auth(create): if not check_only_allow_exist_user_auth(create):
user.delete()
return user, (self.msg_client_err, self.request.error_message) return user, (self.msg_client_err, self.request.error_message)
setattr(user, f'{self.user_type}_id', user_id) setattr(user, f'{self.user_type}_id', user_id)