summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaulo Poiati <paulogpoiati@gmail.com>2015-07-05 17:54:25 -0300
committerTim Graham <timograham@gmail.com>2016-01-07 08:56:07 -0500
commitb6433866682baac35a953e59298dff7f399ac49b (patch)
treeb0a6a9ce9edd6099b1b79744c5087b5783d994ff
parentbd3c2900fc0e6863924d3b27f0713aa3022c061d (diff)
Fixed #24855 -- Allowed using contrib.auth.login() without credentials.
Added an optional `backend` argument to login().
-rw-r--r--AUTHORS1
-rw-r--r--django/contrib/auth/__init__.py18
-rw-r--r--django/test/client.py9
-rw-r--r--docs/releases/1.10.txt3
-rw-r--r--docs/topics/auth/default.txt41
-rw-r--r--tests/auth_tests/test_auth_backends.py43
-rw-r--r--tests/test_client/tests.py1
7 files changed, 95 insertions, 21 deletions
diff --git a/AUTHORS b/AUTHORS
index eeeca06e52..b391c00eb7 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -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>
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index 7e663f545b..63d59398f5 100644
--- a/django/contrib/auth/__init__.py
+++ b/django/contrib/auth/__init__.py
@@ -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
diff --git a/django/test/client.py b/django/test/client.py
index bd92664548..78a5df5c52 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -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()
diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt
index 0eec66faee..64a755bdfb 100644
--- a/docs/releases/1.10.txt
+++ b/docs/releases/1.10.txt
@@ -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`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt
index 046b7731c0..e0740a201d 100644
--- a/docs/topics/auth/default.txt
+++ b/docs/topics/auth/default.txt
@@ -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
---------------------
diff --git a/tests/auth_tests/test_auth_backends.py b/tests/auth_tests/test_auth_backends.py
index 6ded9f2eb8..c0ccbb3971 100644
--- a/tests/auth_tests/test_auth_backends.py
+++ b/tests/auth_tests/test_auth_backends.py
@@ -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)
diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py
index c47107a676..ccddf76cf1 100644
--- a/tests/test_client/tests.py
+++ b/tests/test_client/tests.py
@@ -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/')