summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Dufresne <jon.dufresne@gmail.com>2015-06-15 21:35:19 -0700
committerTim Graham <timograham@gmail.com>2015-07-01 13:01:08 -0400
commitb44dee16e60ff2600fee90576e5bf0083c266104 (patch)
tree591b026b6cc77f6a16373093f7d4bbc3caa8032a
parent39ec59d6d0f136778d483a96f15b6806a2d50407 (diff)
Fixed #20916 -- Added Client.force_login() to bypass authentication.
-rw-r--r--django/test/client.py59
-rw-r--r--docs/releases/1.9.txt5
-rw-r--r--docs/topics/testing/overview.txt2
-rw-r--r--docs/topics/testing/tools.txt26
-rw-r--r--tests/test_client/auth_backends.py5
-rw-r--r--tests/test_client/tests.py101
6 files changed, 173 insertions, 25 deletions
diff --git a/django/test/client.py b/django/test/client.py
index f8c2705f98..945fa13281 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -592,39 +592,48 @@ class Client(RequestFactory):
are incorrect, or the user is inactive, or if the sessions framework is
not available.
"""
- from django.contrib.auth import authenticate, login
+ from django.contrib.auth import authenticate
user = authenticate(**credentials)
if (user and user.is_active and
apps.is_installed('django.contrib.sessions')):
- engine = import_module(settings.SESSION_ENGINE)
-
- # Create a fake request to store login details.
- request = HttpRequest()
+ self._login(user)
+ return True
+ else:
+ return False
- if self.session:
- request.session = self.session
- else:
- request.session = engine.SessionStore()
- login(request, user)
+ def force_login(self, user, backend=None):
+ if backend is None:
+ backend = settings.AUTHENTICATION_BACKENDS[0]
+ user.backend = backend
+ self._login(user)
- # Save the session values.
- request.session.save()
+ def _login(self, user):
+ from django.contrib.auth import login
+ engine = import_module(settings.SESSION_ENGINE)
- # Set the cookie to represent the session.
- session_cookie = settings.SESSION_COOKIE_NAME
- self.cookies[session_cookie] = request.session.session_key
- cookie_data = {
- 'max-age': None,
- 'path': '/',
- 'domain': settings.SESSION_COOKIE_DOMAIN,
- 'secure': settings.SESSION_COOKIE_SECURE or None,
- 'expires': None,
- }
- self.cookies[session_cookie].update(cookie_data)
+ # Create a fake request to store login details.
+ request = HttpRequest()
- return True
+ if self.session:
+ request.session = self.session
else:
- return False
+ request.session = engine.SessionStore()
+ login(request, user)
+
+ # Save the session values.
+ request.session.save()
+
+ # Set the cookie to represent the session.
+ session_cookie = settings.SESSION_COOKIE_NAME
+ self.cookies[session_cookie] = request.session.session_key
+ cookie_data = {
+ 'max-age': None,
+ 'path': '/',
+ 'domain': settings.SESSION_COOKIE_DOMAIN,
+ 'secure': settings.SESSION_COOKIE_SECURE or None,
+ 'expires': None,
+ }
+ self.cookies[session_cookie].update(cookie_data)
def logout(self):
"""
diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt
index b80d14f985..48c850f9a7 100644
--- a/docs/releases/1.9.txt
+++ b/docs/releases/1.9.txt
@@ -484,6 +484,11 @@ Tests
* Added the :meth:`json() <django.test.Response.json>` method to test client
responses to give access to the response body as JSON.
+* Added the :meth:`~django.test.Client.force_login()` method to the test
+ client. Use this method to simulate the effect of a user logging into the
+ site while skipping the authentication and verification steps of
+ :meth:`~django.test.Client.login()`.
+
URLs
^^^^
diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt
index cee2f2c1fe..d345e9c36d 100644
--- a/docs/topics/testing/overview.txt
+++ b/docs/topics/testing/overview.txt
@@ -312,6 +312,8 @@ failed and erroneous tests. If all the tests pass, the return code is 0. This
feature is useful if you're using the test-runner script in a shell script and
need to test for success or failure at that level.
+.. _speeding-up-tests-auth-hashers:
+
Speeding up the tests
---------------------
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index 2ebe98eda4..fef718c7c6 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -384,6 +384,32 @@ Use the ``django.test.Client`` class to make requests.
:meth:`~django.contrib.auth.models.UserManager.create_user` helper
method to create a new user with a correctly hashed password.
+ .. method:: Client.force_login(user, backend=None)
+
+ .. versionadded:: 1.9
+
+ If your site uses Django's :doc:`authentication
+ system</topics/auth/index>`, you can use the ``force_login()`` method
+ to simulate the effect of a user logging into the site. Use this method
+ instead of :meth:`login` when a test requires a user be logged in and
+ the details of how a user logged in aren't important.
+
+ Unlike ``login()``, this method skips the authentication and
+ verification steps: inactive users (:attr:`is_active=False
+ <django.contrib.auth.models.User.is_active>`) are permitted to login
+ and the user's credentials don't need to be provided.
+
+ The user will have its ``backend`` attribute set to the value of the
+ ``backend`` argument (which should be a dotted Python path string), or
+ to ``settings.AUTHENTICATION_BACKENDS[0]`` if a value isn't provided.
+ The :func:`~django.contrib.auth.authenticate` function called by
+ :meth:`login` normally annotates the user like this.
+
+ This method is faster than ``login()`` since the expensive
+ password hashing algorithms are bypassed. Also, you can speed up
+ ``login()`` by :ref:`using a weaker hasher while testing
+ <speeding-up-tests-auth-hashers>`.
+
.. method:: Client.logout()
If your site uses Django's :doc:`authentication system</topics/auth/index>`,
diff --git a/tests/test_client/auth_backends.py b/tests/test_client/auth_backends.py
new file mode 100644
index 0000000000..c886dce336
--- /dev/null
+++ b/tests/test_client/auth_backends.py
@@ -0,0 +1,5 @@
+from django.contrib.auth.backends import ModelBackend
+
+
+class TestClientBackend(ModelBackend):
+ pass
diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py
index bef1146172..ae70f5c494 100644
--- a/tests/test_client/tests.py
+++ b/tests/test_client/tests.py
@@ -364,6 +364,20 @@ class ClientTest(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient')
+ def test_view_with_force_login(self):
+ "Request a page that is protected with @login_required"
+ # Get the page without logging in. Should result in 302.
+ response = self.client.get('/login_protected_view/')
+ self.assertRedirects(response, '/accounts/login/?next=/login_protected_view/')
+
+ # Log in
+ self.client.force_login(self.u1)
+
+ # Request a page that requires a login
+ response = self.client.get('/login_protected_view/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['user'].username, 'testclient')
+
def test_view_with_method_login(self):
"Request a page that is protected with a @login_required method"
@@ -380,6 +394,20 @@ class ClientTest(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient')
+ def test_view_with_method_force_login(self):
+ "Request a page that is protected with a @login_required method"
+ # Get the page without logging in. Should result in 302.
+ response = self.client.get('/login_protected_method_view/')
+ self.assertRedirects(response, '/accounts/login/?next=/login_protected_method_view/')
+
+ # Log in
+ self.client.force_login(self.u1)
+
+ # Request a page that requires a login
+ response = self.client.get('/login_protected_method_view/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['user'].username, 'testclient')
+
def test_view_with_login_and_custom_redirect(self):
"Request a page that is protected with @login_required(redirect_field_name='redirect_to')"
@@ -396,6 +424,23 @@ class ClientTest(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['user'].username, 'testclient')
+ def test_view_with_force_login_and_custom_redirect(self):
+ """
+ Request a page that is protected with
+ @login_required(redirect_field_name='redirect_to')
+ """
+ # Get the page without logging in. Should result in 302.
+ response = self.client.get('/login_protected_view_custom_redirect/')
+ self.assertRedirects(response, '/accounts/login/?redirect_to=/login_protected_view_custom_redirect/')
+
+ # Log in
+ self.client.force_login(self.u1)
+
+ # Request a page that requires a login
+ response = self.client.get('/login_protected_view_custom_redirect/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['user'].username, 'testclient')
+
def test_view_with_bad_login(self):
"Request a page that is protected with @login, but use bad credentials"
@@ -408,6 +453,21 @@ class ClientTest(TestCase):
login = self.client.login(username='inactive', password='password')
self.assertFalse(login)
+ def test_view_with_inactive_force_login(self):
+ "Request a page that is protected with @login, but use an inactive login"
+
+ # Get the page without logging in. Should result in 302.
+ response = self.client.get('/login_protected_view/')
+ self.assertRedirects(response, '/accounts/login/?next=/login_protected_view/')
+
+ # Log in
+ self.client.force_login(self.u2)
+
+ # Request a page that requires a login
+ response = self.client.get('/login_protected_view/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['user'].username, 'inactive')
+
def test_logout(self):
"Request a logout after logging in"
# Log in
@@ -425,6 +485,47 @@ class ClientTest(TestCase):
response = self.client.get('/login_protected_view/')
self.assertRedirects(response, '/accounts/login/?next=/login_protected_view/')
+ def test_logout_with_force_login(self):
+ "Request a logout after logging in"
+ # Log in
+ self.client.force_login(self.u1)
+
+ # Request a page that requires a login
+ response = self.client.get('/login_protected_view/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['user'].username, 'testclient')
+
+ # Log out
+ self.client.logout()
+
+ # Request a page that requires a login
+ response = self.client.get('/login_protected_view/')
+ self.assertRedirects(response, '/accounts/login/?next=/login_protected_view/')
+
+ @override_settings(
+ AUTHENTICATION_BACKENDS=[
+ 'django.contrib.auth.backends.ModelBackend',
+ 'test_client.auth_backends.TestClientBackend',
+ ],
+ )
+ def test_force_login_with_backend(self):
+ """
+ Request a page that is protected with @login_required when using
+ force_login() and passing a backend.
+ """
+ # Get the page without logging in. Should result in 302.
+ response = self.client.get('/login_protected_view/')
+ self.assertRedirects(response, '/accounts/login/?next=/login_protected_view/')
+
+ # 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/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.context['user'].username, 'testclient')
+
@override_settings(SESSION_ENGINE="django.contrib.sessions.backends.signed_cookies")
def test_logout_cookie_sessions(self):
self.test_logout()