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 McLanahan <paul@mclanahan.net>
Paul McMillan <Paul@McMillan.ws>
Paulo Poiati <paulogpoiati@gmail.com>
Paulo Scardine <paulo@scardine.com.br>
Paul Smith <blinkylights23@gmail.com>
pavithran s <pavithran.s@gmail.com>

View File

@ -86,7 +86,7 @@ def authenticate(**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
have to reauthenticate on every request. Note that data set during
@ -108,8 +108,22 @@ def login(request, user):
request.session.flush()
else:
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[BACKEND_SESSION_KEY] = user.backend
request.session[BACKEND_SESSION_KEY] = backend
request.session[HASH_SESSION_KEY] = session_auth_hash
if hasattr(request, 'user'):
request.user = user

View File

@ -603,12 +603,9 @@ class Client(RequestFactory):
return False
def force_login(self, user, backend=None):
if backend is None:
backend = settings.AUTHENTICATION_BACKENDS[0]
user.backend = backend
self._login(user)
self._login(user, backend)
def _login(self, user):
def _login(self, user, backend=None):
from django.contrib.auth import login
engine = import_module(settings.SESSION_ENGINE)
@ -619,7 +616,7 @@ class Client(RequestFactory):
request.session = self.session
else:
request.session = engine.SessionStore()
login(request, user)
login(request, user, backend)
# Save the session values.
request.session.save()

View File

@ -68,6 +68,9 @@ Minor features
to prevent an issue where Safari caches redirects and prevents a user from
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`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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
- 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
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.
...
.. admonition:: Calling ``authenticate()`` first
.. versionchanged:: 1.10
When you're manually logging a user in, you *must* successfully authenticate
the user with :func:`~django.contrib.auth.authenticate()` before you call
:func:`~django.contrib.auth.login()`.
:func:`~django.contrib.auth.authenticate()`
sets an attribute on the :class:`~django.contrib.auth.models.User` noting
which authentication backend successfully authenticated that user (see the
:ref:`backends documentation <authentication-backends>` for details), and
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.
In older versions, when you're manually logging a user in, you *must*
successfully authenticate the user with
:func:`~django.contrib.auth.authenticate()` before you call
:func:`~django.contrib.auth.login()`. Now you can set the backend using
the new ``backend`` argument.
Selecting the :ref:`authentication backend <authentication-backends>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
---------------------

View File

@ -605,6 +605,14 @@ class ImportedModelBackend(ModelBackend):
pass
class CustomModelBackend(ModelBackend):
pass
class OtherModelBackend(ModelBackend):
pass
class ImportedBackendTests(TestCase):
"""
#23925 - The backend path added to the session should be the same
@ -622,3 +630,38 @@ class ImportedBackendTests(TestCase):
request = HttpRequest()
request.session = self.client.session
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
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
response = self.client.get('/login_protected_view/')