Fixed #24855 -- Allowed using contrib.auth.login() without credentials.

Added an optional `backend` argument to login().
This commit is contained in:
Paulo Poiati 2015-07-05 17:54:25 -03:00 committed by Tim Graham
parent bd3c2900fc
commit b643386668
7 changed files with 95 additions and 21 deletions

View File

@ -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>

View File

@ -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

View File

@ -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()

View File

@ -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`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -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
--------------------- ---------------------

View File

@ -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)

View File

@ -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/')