Fixed #33561 -- Allowed synchronization of user attributes in RemoteUserBackend.
This commit is contained in:
parent
67b5f506a6
commit
d90e34c61b
1
AUTHORS
1
AUTHORS
|
@ -23,6 +23,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Adiyat Mubarak <adiyatmubarak@gmail.com>
|
Adiyat Mubarak <adiyatmubarak@gmail.com>
|
||||||
Adnan Umer <u.adnan@outlook.com>
|
Adnan Umer <u.adnan@outlook.com>
|
||||||
Adrian Holovaty <adrian@holovaty.com>
|
Adrian Holovaty <adrian@holovaty.com>
|
||||||
|
Adrian Torres <atorresj@redhat.com>
|
||||||
Adrien Lemaire <lemaire.adrien@gmail.com>
|
Adrien Lemaire <lemaire.adrien@gmail.com>
|
||||||
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
|
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
|
||||||
AgarFu <heaven@croasanaso.sytes.net>
|
AgarFu <heaven@croasanaso.sytes.net>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.db.models import Exists, OuterRef, Q
|
from django.db.models import Exists, OuterRef, Q
|
||||||
|
from django.utils.deprecation import RemovedInDjango50Warning
|
||||||
|
from django.utils.inspect import func_supports_parameter
|
||||||
|
|
||||||
UserModel = get_user_model()
|
UserModel = get_user_model()
|
||||||
|
|
||||||
|
@ -192,6 +196,7 @@ class RemoteUserBackend(ModelBackend):
|
||||||
"""
|
"""
|
||||||
if not remote_user:
|
if not remote_user:
|
||||||
return
|
return
|
||||||
|
created = False
|
||||||
user = None
|
user = None
|
||||||
username = self.clean_username(remote_user)
|
username = self.clean_username(remote_user)
|
||||||
|
|
||||||
|
@ -202,13 +207,24 @@ class RemoteUserBackend(ModelBackend):
|
||||||
user, created = UserModel._default_manager.get_or_create(
|
user, created = UserModel._default_manager.get_or_create(
|
||||||
**{UserModel.USERNAME_FIELD: username}
|
**{UserModel.USERNAME_FIELD: username}
|
||||||
)
|
)
|
||||||
if created:
|
|
||||||
user = self.configure_user(request, user)
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
user = UserModel._default_manager.get_by_natural_key(username)
|
user = UserModel._default_manager.get_by_natural_key(username)
|
||||||
except UserModel.DoesNotExist:
|
except UserModel.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# RemovedInDjango50Warning: When the deprecation ends, replace with:
|
||||||
|
# user = self.configure_user(request, user, created=created)
|
||||||
|
if func_supports_parameter(self.configure_user, "created"):
|
||||||
|
user = self.configure_user(request, user, created=created)
|
||||||
|
else:
|
||||||
|
warnings.warn(
|
||||||
|
f"`created=True` must be added to the signature of "
|
||||||
|
f"{self.__class__.__qualname__}.configure_user().",
|
||||||
|
category=RemovedInDjango50Warning,
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
user = self.configure_user(request, user)
|
||||||
return user if self.user_can_authenticate(user) else None
|
return user if self.user_can_authenticate(user) else None
|
||||||
|
|
||||||
def clean_username(self, username):
|
def clean_username(self, username):
|
||||||
|
@ -220,9 +236,9 @@ class RemoteUserBackend(ModelBackend):
|
||||||
"""
|
"""
|
||||||
return username
|
return username
|
||||||
|
|
||||||
def configure_user(self, request, user):
|
def configure_user(self, request, user, created=True):
|
||||||
"""
|
"""
|
||||||
Configure a user after creation and return the updated user.
|
Configure a user and return the updated user.
|
||||||
|
|
||||||
By default, return the user unmodified.
|
By default, return the user unmodified.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -87,6 +87,9 @@ details on these changes.
|
||||||
|
|
||||||
* Passing unsaved model instances to related filters will no longer be allowed.
|
* Passing unsaved model instances to related filters will no longer be allowed.
|
||||||
|
|
||||||
|
* ``created=True`` will be required in the signature of
|
||||||
|
``RemoteUserBackend.configure_user()`` subclasses.
|
||||||
|
|
||||||
.. _deprecation-removed-in-4.1:
|
.. _deprecation-removed-in-4.1:
|
||||||
|
|
||||||
4.1
|
4.1
|
||||||
|
|
|
@ -649,17 +649,27 @@ The following backends are available in :mod:`django.contrib.auth.backends`:
|
||||||
information) prior to using it to get or create a user object. Returns
|
information) prior to using it to get or create a user object. Returns
|
||||||
the cleaned username.
|
the cleaned username.
|
||||||
|
|
||||||
.. method:: configure_user(request, user)
|
.. method:: configure_user(request, user, created=True)
|
||||||
|
|
||||||
Configures a newly created user. This method is called immediately
|
Configures the user on each authentication attempt. This method is
|
||||||
after a new user is created, and can be used to perform custom setup
|
called immediately after fetching or creating the user being
|
||||||
actions, such as setting the user's groups based on attributes in an
|
authenticated, and can be used to perform custom setup actions, such as
|
||||||
LDAP directory. Returns the user object.
|
setting the user's groups based on attributes in an LDAP directory.
|
||||||
|
Returns the user object.
|
||||||
|
|
||||||
|
The setup can be performed either once when the user is created
|
||||||
|
(``created`` is ``True``) or on existing users (``created`` is
|
||||||
|
``False``) as a way of synchronizing attributes between the remote and
|
||||||
|
the local systems.
|
||||||
|
|
||||||
``request`` is an :class:`~django.http.HttpRequest` and may be ``None``
|
``request`` is an :class:`~django.http.HttpRequest` and may be ``None``
|
||||||
if it wasn't provided to :func:`~django.contrib.auth.authenticate`
|
if it wasn't provided to :func:`~django.contrib.auth.authenticate`
|
||||||
(which passes it on to the backend).
|
(which passes it on to the backend).
|
||||||
|
|
||||||
|
.. versionchanged:: 4.1
|
||||||
|
|
||||||
|
The ``created`` argument was added.
|
||||||
|
|
||||||
.. method:: user_can_authenticate()
|
.. method:: user_can_authenticate()
|
||||||
|
|
||||||
Returns whether the user is allowed to authenticate. This method
|
Returns whether the user is allowed to authenticate. This method
|
||||||
|
|
|
@ -74,6 +74,9 @@ Minor features
|
||||||
* The default iteration count for the PBKDF2 password hasher is increased from
|
* The default iteration count for the PBKDF2 password hasher is increased from
|
||||||
320,000 to 390,000.
|
320,000 to 390,000.
|
||||||
|
|
||||||
|
* The :meth:`.RemoteUserBackend.configure_user` method now allows synchronizing
|
||||||
|
user attributes with attributes in a remote system such as an LDAP directory.
|
||||||
|
|
||||||
:mod:`django.contrib.contenttypes`
|
:mod:`django.contrib.contenttypes`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -493,6 +496,10 @@ Miscellaneous
|
||||||
* Passing unsaved model instances to related filters is deprecated. In Django
|
* Passing unsaved model instances to related filters is deprecated. In Django
|
||||||
5.0, the exception will be raised.
|
5.0, the exception will be raised.
|
||||||
|
|
||||||
|
* ``created=True`` is added to the signature of
|
||||||
|
:meth:`.RemoteUserBackend.configure_user`. Support for ``RemoteUserBackend``
|
||||||
|
subclasses that do not accept this argument is deprecated.
|
||||||
|
|
||||||
Features removed in 4.1
|
Features removed in 4.1
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,15 @@ from django.contrib.auth.backends import RemoteUserBackend
|
||||||
from django.contrib.auth.middleware import RemoteUserMiddleware
|
from django.contrib.auth.middleware import RemoteUserMiddleware
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.middleware.csrf import _get_new_csrf_string, _mask_cipher_secret
|
from django.middleware.csrf import _get_new_csrf_string, _mask_cipher_secret
|
||||||
from django.test import Client, TestCase, modify_settings, override_settings
|
from django.test import (
|
||||||
|
Client,
|
||||||
|
TestCase,
|
||||||
|
ignore_warnings,
|
||||||
|
modify_settings,
|
||||||
|
override_settings,
|
||||||
|
)
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.deprecation import RemovedInDjango50Warning
|
||||||
|
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF="auth_tests.urls")
|
@override_settings(ROOT_URLCONF="auth_tests.urls")
|
||||||
|
@ -215,11 +222,14 @@ class CustomRemoteUserBackend(RemoteUserBackend):
|
||||||
"""
|
"""
|
||||||
return username.split("@")[0]
|
return username.split("@")[0]
|
||||||
|
|
||||||
def configure_user(self, request, user):
|
def configure_user(self, request, user, created=True):
|
||||||
"""
|
"""
|
||||||
Sets user's email address using the email specified in an HTTP header.
|
Sets user's email address using the email specified in an HTTP header.
|
||||||
|
Sets user's last name for existing users.
|
||||||
"""
|
"""
|
||||||
user.email = request.META.get(RemoteUserTest.email_header, "")
|
user.email = request.META.get(RemoteUserTest.email_header, "")
|
||||||
|
if not created:
|
||||||
|
user.last_name = user.username
|
||||||
user.save()
|
user.save()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
@ -242,8 +252,12 @@ class RemoteUserCustomTest(RemoteUserTest):
|
||||||
should not have been configured with an email address.
|
should not have been configured with an email address.
|
||||||
"""
|
"""
|
||||||
super().test_known_user()
|
super().test_known_user()
|
||||||
self.assertEqual(User.objects.get(username="knownuser").email, "")
|
knownuser = User.objects.get(username="knownuser")
|
||||||
self.assertEqual(User.objects.get(username="knownuser2").email, "")
|
knownuser2 = User.objects.get(username="knownuser2")
|
||||||
|
self.assertEqual(knownuser.email, "")
|
||||||
|
self.assertEqual(knownuser2.email, "")
|
||||||
|
self.assertEqual(knownuser.last_name, "knownuser")
|
||||||
|
self.assertEqual(knownuser2.last_name, "knownuser2")
|
||||||
|
|
||||||
def test_unknown_user(self):
|
def test_unknown_user(self):
|
||||||
"""
|
"""
|
||||||
|
@ -260,11 +274,40 @@ class RemoteUserCustomTest(RemoteUserTest):
|
||||||
)
|
)
|
||||||
self.assertEqual(response.context["user"].username, "newuser")
|
self.assertEqual(response.context["user"].username, "newuser")
|
||||||
self.assertEqual(response.context["user"].email, "user@example.com")
|
self.assertEqual(response.context["user"].email, "user@example.com")
|
||||||
|
self.assertEqual(response.context["user"].last_name, "")
|
||||||
self.assertEqual(User.objects.count(), num_users + 1)
|
self.assertEqual(User.objects.count(), num_users + 1)
|
||||||
newuser = User.objects.get(username="newuser")
|
newuser = User.objects.get(username="newuser")
|
||||||
self.assertEqual(newuser.email, "user@example.com")
|
self.assertEqual(newuser.email, "user@example.com")
|
||||||
|
|
||||||
|
|
||||||
|
# RemovedInDjango50Warning.
|
||||||
|
class CustomRemoteUserNoCreatedArgumentBackend(CustomRemoteUserBackend):
|
||||||
|
def configure_user(self, request, user):
|
||||||
|
return super().configure_user(request, user)
|
||||||
|
|
||||||
|
|
||||||
|
@ignore_warnings(category=RemovedInDjango50Warning)
|
||||||
|
class RemoteUserCustomNoCreatedArgumentTest(RemoteUserTest):
|
||||||
|
backend = "auth_tests.test_remote_user.CustomRemoteUserNoCreatedArgumentBackend"
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF="auth_tests.urls")
|
||||||
|
@modify_settings(
|
||||||
|
AUTHENTICATION_BACKENDS={
|
||||||
|
"append": "auth_tests.test_remote_user.CustomRemoteUserNoCreatedArgumentBackend"
|
||||||
|
},
|
||||||
|
MIDDLEWARE={"append": "django.contrib.auth.middleware.RemoteUserMiddleware"},
|
||||||
|
)
|
||||||
|
class RemoteUserCustomNoCreatedArgumentDeprecationTest(TestCase):
|
||||||
|
def test_known_user_sync(self):
|
||||||
|
msg = (
|
||||||
|
"`created=True` must be added to the signature of "
|
||||||
|
"CustomRemoteUserNoCreatedArgumentBackend.configure_user()."
|
||||||
|
)
|
||||||
|
with self.assertWarnsMessage(RemovedInDjango50Warning, msg):
|
||||||
|
self.client.get("/remote_user/", **{RemoteUserTest.header: "newuser"})
|
||||||
|
|
||||||
|
|
||||||
class CustomHeaderMiddleware(RemoteUserMiddleware):
|
class CustomHeaderMiddleware(RemoteUserMiddleware):
|
||||||
"""
|
"""
|
||||||
Middleware that overrides custom HTTP auth user header.
|
Middleware that overrides custom HTTP auth user header.
|
||||||
|
|
Loading…
Reference in New Issue