mirror of https://github.com/django/django.git
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
|
are incorrect, or the user is inactive, or if the sessions framework is
|
||||||
not available.
|
not available.
|
||||||
"""
|
"""
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate
|
||||||
user = authenticate(**credentials)
|
user = authenticate(**credentials)
|
||||||
if (user and user.is_active and
|
if (user and user.is_active and
|
||||||
apps.is_installed('django.contrib.sessions')):
|
apps.is_installed('django.contrib.sessions')):
|
||||||
engine = import_module(settings.SESSION_ENGINE)
|
self._login(user)
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
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):
|
def logout(self):
|
||||||
"""
|
"""
|
||||||
Removes the authenticated user's cookies and session object.
|
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
|
* Added the :meth:`json() <django.test.Response.json>` method to test client
|
||||||
responses to give access to the response body as JSON.
|
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
|
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
|
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.
|
need to test for success or failure at that level.
|
||||||
|
|
||||||
|
.. _speeding-up-tests-auth-hashers:
|
||||||
|
|
||||||
Speeding up the tests
|
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
|
:meth:`~django.contrib.auth.models.UserManager.create_user` helper
|
||||||
method to create a new user with a correctly hashed password.
|
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()
|
.. method:: Client.logout()
|
||||||
|
|
||||||
If your site uses Django's :doc:`authentication system</topics/auth/index>`,
|
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.status_code, 200)
|
||||||
self.assertEqual(response.context['user'].username, 'testclient')
|
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):
|
def test_view_with_method_login(self):
|
||||||
"Request a page that is protected with a @login_required method"
|
"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.status_code, 200)
|
||||||
self.assertEqual(response.context['user'].username, 'testclient')
|
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):
|
def test_view_with_login_and_custom_redirect(self):
|
||||||
"Request a page that is protected with @login_required(redirect_field_name='redirect_to')"
|
"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.status_code, 200)
|
||||||
self.assertEqual(response.context['user'].username, 'testclient')
|
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):
|
def test_view_with_bad_login(self):
|
||||||
"Request a page that is protected with @login, but use bad credentials"
|
"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')
|
login = self.client.login(username='inactive', password='password')
|
||||||
self.assertFalse(login)
|
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):
|
def test_logout(self):
|
||||||
"Request a logout after logging in"
|
"Request a logout after logging in"
|
||||||
# Log in
|
# Log in
|
||||||
|
@ -425,6 +485,47 @@ class ClientTest(TestCase):
|
||||||
response = self.client.get('/login_protected_view/')
|
response = self.client.get('/login_protected_view/')
|
||||||
self.assertRedirects(response, '/accounts/login/?next=/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")
|
@override_settings(SESSION_ENGINE="django.contrib.sessions.backends.signed_cookies")
|
||||||
def test_logout_cookie_sessions(self):
|
def test_logout_cookie_sessions(self):
|
||||||
self.test_logout()
|
self.test_logout()
|
||||||
|
|
Loading…
Reference in New Issue