Fixed #20916 -- Added Client.force_login() to bypass authentication.
This commit is contained in:
parent
39ec59d6d0
commit
b44dee16e6
|
@ -592,40 +592,49 @@ 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()
|
||||
|
||||
if self.session:
|
||||
request.session = self.session
|
||||
else:
|
||||
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)
|
||||
|
||||
self._login(user)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def force_login(self, user, backend=None):
|
||||
if backend is None:
|
||||
backend = settings.AUTHENTICATION_BACKENDS[0]
|
||||
user.backend = backend
|
||||
self._login(user)
|
||||
|
||||
def _login(self, user):
|
||||
from django.contrib.auth import login
|
||||
engine = import_module(settings.SESSION_ENGINE)
|
||||
|
||||
# Create a fake request to store login details.
|
||||
request = HttpRequest()
|
||||
|
||||
if self.session:
|
||||
request.session = self.session
|
||||
else:
|
||||
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):
|
||||
"""
|
||||
Removes the authenticated user's cookies and session object.
|
||||
|
|
|
@ -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
|
||||
^^^^
|
||||
|
||||
|
|
|
@ -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
|
||||
---------------------
|
||||
|
||||
|
|
|
@ -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>`,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
|
||||
class TestClientBackend(ModelBackend):
|
||||
pass
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue