mirror of https://github.com/django/django.git
Fixed #24855 -- Allowed using contrib.auth.login() without credentials.
Added an optional `backend` argument to login().
This commit is contained in:
parent
bd3c2900fc
commit
b643386668
1
AUTHORS
1
AUTHORS
|
@ -567,6 +567,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Paul Lanier <planier@google.com>
|
Paul Lanier <planier@google.com>
|
||||||
Paul McLanahan <paul@mclanahan.net>
|
Paul McLanahan <paul@mclanahan.net>
|
||||||
Paul McMillan <Paul@McMillan.ws>
|
Paul McMillan <Paul@McMillan.ws>
|
||||||
|
Paulo Poiati <paulogpoiati@gmail.com>
|
||||||
Paulo Scardine <paulo@scardine.com.br>
|
Paulo Scardine <paulo@scardine.com.br>
|
||||||
Paul Smith <blinkylights23@gmail.com>
|
Paul Smith <blinkylights23@gmail.com>
|
||||||
pavithran s <pavithran.s@gmail.com>
|
pavithran s <pavithran.s@gmail.com>
|
||||||
|
|
|
@ -86,7 +86,7 @@ def authenticate(**credentials):
|
||||||
credentials=_clean_credentials(credentials))
|
credentials=_clean_credentials(credentials))
|
||||||
|
|
||||||
|
|
||||||
def login(request, user):
|
def login(request, user, backend=None):
|
||||||
"""
|
"""
|
||||||
Persist a user id and a backend in the request. This way a user doesn't
|
Persist a user id and a backend in the request. This way a user doesn't
|
||||||
have to reauthenticate on every request. Note that data set during
|
have to reauthenticate on every request. Note that data set during
|
||||||
|
@ -108,8 +108,22 @@ def login(request, user):
|
||||||
request.session.flush()
|
request.session.flush()
|
||||||
else:
|
else:
|
||||||
request.session.cycle_key()
|
request.session.cycle_key()
|
||||||
|
|
||||||
|
try:
|
||||||
|
backend = backend or user.backend
|
||||||
|
except AttributeError:
|
||||||
|
backends = _get_backends(return_tuples=True)
|
||||||
|
if len(backends) == 1:
|
||||||
|
_, backend = backends[0]
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
'You have multiple authentication backends configured and '
|
||||||
|
'therefore must provide the `backend` argument or set the '
|
||||||
|
'`backend` attribute on the user.'
|
||||||
|
)
|
||||||
|
|
||||||
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
|
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
|
||||||
request.session[BACKEND_SESSION_KEY] = user.backend
|
request.session[BACKEND_SESSION_KEY] = backend
|
||||||
request.session[HASH_SESSION_KEY] = session_auth_hash
|
request.session[HASH_SESSION_KEY] = session_auth_hash
|
||||||
if hasattr(request, 'user'):
|
if hasattr(request, 'user'):
|
||||||
request.user = user
|
request.user = user
|
||||||
|
|
|
@ -603,12 +603,9 @@ class Client(RequestFactory):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def force_login(self, user, backend=None):
|
def force_login(self, user, backend=None):
|
||||||
if backend is None:
|
self._login(user, backend)
|
||||||
backend = settings.AUTHENTICATION_BACKENDS[0]
|
|
||||||
user.backend = backend
|
|
||||||
self._login(user)
|
|
||||||
|
|
||||||
def _login(self, user):
|
def _login(self, user, backend=None):
|
||||||
from django.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
engine = import_module(settings.SESSION_ENGINE)
|
engine = import_module(settings.SESSION_ENGINE)
|
||||||
|
|
||||||
|
@ -619,7 +616,7 @@ class Client(RequestFactory):
|
||||||
request.session = self.session
|
request.session = self.session
|
||||||
else:
|
else:
|
||||||
request.session = engine.SessionStore()
|
request.session = engine.SessionStore()
|
||||||
login(request, user)
|
login(request, user, backend)
|
||||||
|
|
||||||
# Save the session values.
|
# Save the session values.
|
||||||
request.session.save()
|
request.session.save()
|
||||||
|
|
|
@ -68,6 +68,9 @@ Minor features
|
||||||
to prevent an issue where Safari caches redirects and prevents a user from
|
to prevent an issue where Safari caches redirects and prevents a user from
|
||||||
being able to log out.
|
being able to log out.
|
||||||
|
|
||||||
|
* Added the optional ``backend`` argument to :func:`~django.contrib.auth.login`
|
||||||
|
to allowing using it without credentials.
|
||||||
|
|
||||||
:mod:`django.contrib.contenttypes`
|
:mod:`django.contrib.contenttypes`
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -322,7 +322,7 @@ How to log a user in
|
||||||
If you have an authenticated user you want to attach to the current session
|
If you have an authenticated user you want to attach to the current session
|
||||||
- this is done with a :func:`~django.contrib.auth.login` function.
|
- this is done with a :func:`~django.contrib.auth.login` function.
|
||||||
|
|
||||||
.. function:: login(request, user)
|
.. function:: login(request, user, backend=None)
|
||||||
|
|
||||||
To log a user in, from a view, use :func:`~django.contrib.auth.login()`. It
|
To log a user in, from a view, use :func:`~django.contrib.auth.login()`. It
|
||||||
takes an :class:`~django.http.HttpRequest` object and a
|
takes an :class:`~django.http.HttpRequest` object and a
|
||||||
|
@ -354,18 +354,35 @@ If you have an authenticated user you want to attach to the current session
|
||||||
# Return an 'invalid login' error message.
|
# Return an 'invalid login' error message.
|
||||||
...
|
...
|
||||||
|
|
||||||
.. admonition:: Calling ``authenticate()`` first
|
.. versionchanged:: 1.10
|
||||||
|
|
||||||
When you're manually logging a user in, you *must* successfully authenticate
|
In older versions, when you're manually logging a user in, you *must*
|
||||||
the user with :func:`~django.contrib.auth.authenticate()` before you call
|
successfully authenticate the user with
|
||||||
:func:`~django.contrib.auth.login()`.
|
:func:`~django.contrib.auth.authenticate()` before you call
|
||||||
:func:`~django.contrib.auth.authenticate()`
|
:func:`~django.contrib.auth.login()`. Now you can set the backend using
|
||||||
sets an attribute on the :class:`~django.contrib.auth.models.User` noting
|
the new ``backend`` argument.
|
||||||
which authentication backend successfully authenticated that user (see the
|
|
||||||
:ref:`backends documentation <authentication-backends>` for details), and
|
Selecting the :ref:`authentication backend <authentication-backends>`
|
||||||
this information is needed later during the login process. An error will be
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
raised if you try to login a user object retrieved from the database
|
|
||||||
directly.
|
When a user logs in, the user's ID and the backend that was used for
|
||||||
|
authentication are saved in the user's session. This allows the same
|
||||||
|
authentication backend to fetch the user's details on a future request. The
|
||||||
|
authentication backend to save in the session is selected as follows:
|
||||||
|
|
||||||
|
#. Use the value of the optional ``backend`` argument, if provided.
|
||||||
|
#. Use the value of the ``user.backend`` attribute, if present. This allows
|
||||||
|
pairing :func:`~django.contrib.auth.authenticate()` and
|
||||||
|
:func:`~django.contrib.auth.login()`:
|
||||||
|
:func:`~django.contrib.auth.authenticate()`
|
||||||
|
sets the ``user.backend`` attribute on the ``User`` object it returns.
|
||||||
|
#. Use the ``backend`` in :setting:`AUTHENTICATION_BACKENDS`, if there is only
|
||||||
|
one.
|
||||||
|
#. Otherwise, raise an exception.
|
||||||
|
|
||||||
|
In cases 1 and 2, the value of the ``backend`` argument or the ``user.backend``
|
||||||
|
attribute should be a dotted import path string (like that found in
|
||||||
|
:setting:`AUTHENTICATION_BACKENDS`), not the actual backend class.
|
||||||
|
|
||||||
How to log a user out
|
How to log a user out
|
||||||
---------------------
|
---------------------
|
||||||
|
|
|
@ -605,6 +605,14 @@ class ImportedModelBackend(ModelBackend):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CustomModelBackend(ModelBackend):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OtherModelBackend(ModelBackend):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ImportedBackendTests(TestCase):
|
class ImportedBackendTests(TestCase):
|
||||||
"""
|
"""
|
||||||
#23925 - The backend path added to the session should be the same
|
#23925 - The backend path added to the session should be the same
|
||||||
|
@ -622,3 +630,38 @@ class ImportedBackendTests(TestCase):
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
request.session = self.client.session
|
request.session = self.client.session
|
||||||
self.assertEqual(request.session[BACKEND_SESSION_KEY], self.backend)
|
self.assertEqual(request.session[BACKEND_SESSION_KEY], self.backend)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectingBackendTests(TestCase):
|
||||||
|
backend = 'auth_tests.test_auth_backends.CustomModelBackend'
|
||||||
|
other_backend = 'auth_tests.test_auth_backends.OtherModelBackend'
|
||||||
|
username = 'username'
|
||||||
|
password = 'password'
|
||||||
|
|
||||||
|
def assertBackendInSession(self, backend):
|
||||||
|
request = HttpRequest()
|
||||||
|
request.session = self.client.session
|
||||||
|
self.assertEqual(request.session[BACKEND_SESSION_KEY], backend)
|
||||||
|
|
||||||
|
@override_settings(AUTHENTICATION_BACKENDS=[backend])
|
||||||
|
def test_backend_path_login_without_authenticate_single_backend(self):
|
||||||
|
user = User.objects.create_user(self.username, 'email', self.password)
|
||||||
|
self.client._login(user)
|
||||||
|
self.assertBackendInSession(self.backend)
|
||||||
|
|
||||||
|
@override_settings(AUTHENTICATION_BACKENDS=[backend, other_backend])
|
||||||
|
def test_backend_path_login_without_authenticate_multiple_backends(self):
|
||||||
|
user = User.objects.create_user(self.username, 'email', self.password)
|
||||||
|
expected_message = (
|
||||||
|
'You have multiple authentication backends configured and '
|
||||||
|
'therefore must provide the `backend` argument or set the '
|
||||||
|
'`backend` attribute on the user.'
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(ValueError, expected_message):
|
||||||
|
self.client._login(user)
|
||||||
|
|
||||||
|
@override_settings(AUTHENTICATION_BACKENDS=[backend, other_backend])
|
||||||
|
def test_backend_path_login_with_explicit_backends(self):
|
||||||
|
user = User.objects.create_user(self.username, 'email', self.password)
|
||||||
|
self.client._login(user, self.other_backend)
|
||||||
|
self.assertBackendInSession(self.other_backend)
|
||||||
|
|
|
@ -522,7 +522,6 @@ class ClientTest(TestCase):
|
||||||
|
|
||||||
# Log in
|
# Log in
|
||||||
self.client.force_login(self.u1, backend='test_client.auth_backends.TestClientBackend')
|
self.client.force_login(self.u1, backend='test_client.auth_backends.TestClientBackend')
|
||||||
self.assertEqual(self.u1.backend, 'test_client.auth_backends.TestClientBackend')
|
|
||||||
|
|
||||||
# Request a page that requires a login
|
# Request a page that requires a login
|
||||||
response = self.client.get('/login_protected_view/')
|
response = self.client.get('/login_protected_view/')
|
||||||
|
|
Loading…
Reference in New Issue