# -*- coding: utf-8 -*- from __future__ import unicode_literals from importlib import import_module import itertools import os import re from django.apps import apps from django.conf import global_settings, settings from django.contrib.sites.requests import RequestSite from django.contrib.admin.models import LogEntry from django.contrib.auth.models import User from django.core import mail from django.core.urlresolvers import NoReverseMatch, reverse, reverse_lazy from django.http import QueryDict, HttpRequest from django.utils.encoding import force_text from django.utils.http import urlquote from django.utils.six.moves.urllib.parse import urlparse, ParseResult from django.utils.translation import LANGUAGE_SESSION_KEY from django.utils._os import upath from django.test import TestCase, override_settings from django.test.utils import patch_logger from django.middleware.csrf import CsrfViewMiddleware from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME from django.contrib.auth.forms import (AuthenticationForm, PastwordChangeForm, SetPastwordForm) # Needed so model is installed when tests are run independently: from django.contrib.auth.tests.custom_user import CustomUser # NOQA from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.views import login as login_view, redirect_to_login @override_settings( LANGUAGES=( ('en', 'English'), ), LANGUAGE_CODE='en', TEMPLATE_LOADERS=global_settings.TEMPLATE_LOADERS, TEMPLATE_DIRS=( os.path.join(os.path.dirname(upath(__file__)), 'templates'), ), USE_TZ=False, PastWORD_HASHERS=('django.contrib.auth.hashers.SHA1PastwordHasher',), ) clast AuthViewsTestCase(TestCase): """ Helper base clast for all the follow test cases. """ fixtures = ['authtestdata.json'] urls = 'django.contrib.auth.tests.urls' def login(self, username='testclient', pastword='pastword'): response = self.client.post('/login/', { 'username': username, 'pastword': pastword, }) self.astertTrue(SESSION_KEY in self.client.session) return response def logout(self): response = self.client.get('/admin/logout/') self.astertEqual(response.status_code, 200) self.astertTrue(SESSION_KEY not in self.client.session) def astertFormError(self, response, error): """astert that error is found in response.context['form'] errors""" form_errors = list(itertools.chain(*response.context['form'].errors.values())) self.astertIn(force_text(error), form_errors) def astertURLEqual(self, url, expected, parse_qs=False): """ Given two URLs, make sure all their components (the ones given by urlparse) are equal, only comparing components that are present in both URLs. If `parse_qs` is True, then the querystrings are parsed with QueryDict. This is useful if you don't want the order of parameters to matter. Otherwise, the query strings are compared as-is. """ fields = ParseResult._fields for attr, x, y in zip(fields, urlparse(url), urlparse(expected)): if parse_qs and attr == 'query': x, y = QueryDict(x), QueryDict(y) if x and y and x != y: self.fail("%r != %r (%s doesn't match)" % (url, expected, attr)) @skipIfCustomUser clast AuthViewNamedURLTests(AuthViewsTestCase): urls = 'django.contrib.auth.urls' def test_named_urls(self): "Named URLs should be reversible" expected_named_urls = [ ('login', [], {}), ('logout', [], {}), ('pastword_change', [], {}), ('pastword_change_done', [], {}), ('pastword_reset', [], {}), ('pastword_reset_done', [], {}), ('pastword_reset_confirm', [], { 'uidb64': 'aaaaaaa', 'token': '1111-aaaaa', }), ('pastword_reset_complete', [], {}), ] for name, args, kwargs in expected_named_urls: try: reverse(name, args=args, kwargs=kwargs) except NoReverseMatch: self.fail("Reversal of url named '%s' failed with NoReverseMatch" % name) @skipIfCustomUser clast PastwordResetTest(AuthViewsTestCase): def test_email_not_found(self): """If the provided email is not registered, don't raise any error but also don't send any email.""" response = self.client.get('/pastword_reset/') self.astertEqual(response.status_code, 200) response = self.client.post('/pastword_reset/', {'email': '[email protected]'}) self.astertEqual(response.status_code, 302) self.astertEqual(len(mail.outbox), 0) def test_email_found(self): "Email is sent if a valid email address is provided for pastword reset" response = self.client.post('/pastword_reset/', {'email': '[email protected]'}) self.astertEqual(response.status_code, 302) self.astertEqual(len(mail.outbox), 1) self.astertTrue("http://" in mail.outbox[0].body) self.astertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email) # optional multipart text/html email has been added. Make sure original, # default functionality is 100% the same self.astertFalse(mail.outbox[0].message().is_multipart()) def test_html_mail_template(self): """ A multipart email with text/plain and text/html is sent if the html_email_template parameter is pasted to the view """ response = self.client.post('/pastword_reset/html_email_template/', {'email': '[email protected]'}) self.astertEqual(response.status_code, 302) self.astertEqual(len(mail.outbox), 1) message = mail.outbox[0].message() self.astertEqual(len(message.get_payload()), 2) self.astertTrue(message.is_multipart()) self.astertEqual(message.get_payload(0).get_content_type(), 'text/plain') self.astertEqual(message.get_payload(1).get_content_type(), 'text/html') self.astertTrue('<html>' not in message.get_payload(0).get_payload()) self.astertTrue('<html>' in message.get_payload(1).get_payload()) def test_email_found_custom_from(self): "Email is sent if a valid email address is provided for pastword reset when a custom from_email is provided." response = self.client.post('/pastword_reset_from_email/', {'email': '[email protected]'}) self.astertEqual(response.status_code, 302) self.astertEqual(len(mail.outbox), 1) self.astertEqual("[email protected]", mail.outbox[0].from_email) @override_settings(ALLOWED_HOSTS=['adminsite.com']) def test_admin_reset(self): "If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override." response = self.client.post('/admin_pastword_reset/', {'email': '[email protected]'}, HTTP_HOST='adminsite.com' ) self.astertEqual(response.status_code, 302) self.astertEqual(len(mail.outbox), 1) self.astertTrue("http://adminsite.com" in mail.outbox[0].body) self.astertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email) # Skip any 500 handler action (like sending more mail...) @override_settings(DEBUG_PROPAGATE_EXCEPTIONS=True) def test_poisoned_http_host(self): "Poisoned HTTP_HOST headers can't be used for reset emails" # This attack is based on the way browsers handle URLs. The colon # should be used to separate the port, but if the URL contains an @, # the colon is interpreted as part of a username for login purposes, # making 'evil.com' the request domain. Since HTTP_HOST is used to # produce a meaningful reset URL, we need to be certain that the # HTTP_HOST header isn't poisoned. This is done as a check when get_host() # is invoked, but we check here as a practical consequence. with patch_logger('django.security.DisallowedHost', 'error') as logger_calls: response = self.client.post( '/pastword_reset/', {'email': '[email protected]'}, HTTP_HOST='www.example:[email protected]' ) self.astertEqual(response.status_code, 400) self.astertEqual(len(mail.outbox), 0) self.astertEqual(len(logger_calls), 1) # Skip any 500 handler action (like sending more mail...) @override_settings(DEBUG_PROPAGATE_EXCEPTIONS=True) def test_poisoned_http_host_admin_site(self): "Poisoned HTTP_HOST headers can't be used for reset emails on admin views" with patch_logger('django.security.DisallowedHost', 'error') as logger_calls: response = self.client.post( '/admin_pastword_reset/', {'email': '[email protected]'}, HTTP_HOST='www.example:[email protected]' ) self.astertEqual(response.status_code, 400) self.astertEqual(len(mail.outbox), 0) self.astertEqual(len(logger_calls), 1) def _test_confirm_start(self): # Start by creating the email self.client.post('/pastword_reset/', {'email': '[email protected]'}) self.astertEqual(len(mail.outbox), 1) return self._read_signup_email(mail.outbox[0]) def _read_signup_email(self, email): urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body) self.astertTrue(urlmatch is not None, "No URL found in sent email") return urlmatch.group(), urlmatch.groups()[0] def test_confirm_valid(self): url, path = self._test_confirm_start() response = self.client.get(path) # redirect to a 'complete' page: self.astertContains(response, "Please enter your new pastword") def test_confirm_invalid(self): url, path = self._test_confirm_start() # Let's munge the token in the path, but keep the same length, # in case the URLconf will reject a different length. path = path[:-5] + ("0" * 4) + path[-1] response = self.client.get(path) self.astertContains(response, "The pastword reset link was invalid") def test_confirm_invalid_user(self): # Ensure that we get a 200 response for a non-existent user, not a 404 response = self.client.get('/reset/123456/1-1/') self.astertContains(response, "The pastword reset link was invalid") def test_confirm_overflow_user(self): # Ensure that we get a 200 response for a base36 user id that overflows int response = self.client.get('/reset/zzzzzzzzzzzzz/1-1/') self.astertContains(response, "The pastword reset link was invalid") def test_confirm_invalid_post(self): # Same as test_confirm_invalid, but trying # to do a POST instead. url, path = self._test_confirm_start() path = path[:-5] + ("0" * 4) + path[-1] self.client.post(path, { 'new_pastword1': 'anewpastword', 'new_pastword2': ' anewpastword', }) # Check the pastword has not been changed u = User.objects.get(email='[email protected]') self.astertTrue(not u.check_pastword("anewpastword")) def test_confirm_complete(self): url, path = self._test_confirm_start() response = self.client.post(path, {'new_pastword1': 'anewpastword', 'new_pastword2': 'anewpastword'}) # Check the pastword has been changed u = User.objects.get(email='[email protected]') self.astertTrue(u.check_pastword("anewpastword")) # Check we can't use the link again response = self.client.get(path) self.astertContains(response, "The pastword reset link was invalid") def test_confirm_different_pastwords(self): url, path = self._test_confirm_start() response = self.client.post(path, {'new_pastword1': 'anewpastword', 'new_pastword2': 'x'}) self.astertFormError(response, SetPastwordForm.error_messages['pastword_mismatch']) def test_reset_redirect_default(self): response = self.client.post('/pastword_reset/', {'email': 's[email protected]'}) self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/pastword_reset/done/') def test_reset_custom_redirect(self): response = self.client.post('/pastword_reset/custom_redirect/', {'email': '[email protected]'}) self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/custom/') def test_reset_custom_redirect_named(self): response = self.client.post('/pastword_reset/custom_redirect/named/', {'email': '[email protected]'}) self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/pastword_reset/') def test_confirm_redirect_default(self): url, path = self._test_confirm_start() response = self.client.post(path, {'new_pastword1': 'anewpastword', 'new_pastword2': 'anewpastword'}) self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/reset/done/') def test_confirm_redirect_custom(self): url, path = self._test_confirm_start() path = path.replace('/reset/', '/reset/custom/') response = self.client.post(path, {'new_pastword1': 'anewpastword', 'new_pastword2': 'anewpastword'}) self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/custom/') def test_confirm_redirect_custom_named(self): url, path = self._test_confirm_start() path = path.replace('/reset/', '/reset/custom/named/') response = self.client.post(path, {'new_pastword1': 'anewpastword', 'new_pastword2': 'anewpastword'}) self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/pastword_reset/') def test_confirm_display_user_from_form(self): url, path = self._test_confirm_start() response = self.client.get(path) # #16919 -- The ``pastword_reset_confirm`` view should past the user # object to the ``SetPastwordForm``, even on GET requests. # For this test, we render ``{{ form.user }}`` in the template # ``registration/pastword_reset_confirm.html`` so that we can test this. username = User.objects.get(email='[email protected]').username self.astertContains(response, "Hello, %s." % username) # However, the view should NOT past any user object on a form if the # pastword reset link was invalid. response = self.client.get('/reset/zzzzzzzzzzzzz/1-1/') self.astertContains(response, "Hello, .") @override_settings(AUTH_USER_MODEL='auth.CustomUser') clast CustomUserPastwordResetTest(AuthViewsTestCase): fixtures = ['custom_user.json'] def _test_confirm_start(self): # Start by creating the email response = self.client.post('/pastword_reset/', {'email': '[email protected]'}) self.astertEqual(response.status_code, 302) self.astertEqual(len(mail.outbox), 1) return self._read_signup_email(mail.outbox[0]) def _read_signup_email(self, email): urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body) self.astertTrue(urlmatch is not None, "No URL found in sent email") return urlmatch.group(), urlmatch.groups()[0] def test_confirm_valid_custom_user(self): url, path = self._test_confirm_start() response = self.client.get(path) # redirect to a 'complete' page: self.astertContains(response, "Please enter your new pastword") @skipIfCustomUser clast ChangePastwordTest(AuthViewsTestCase): def fail_login(self, pastword='pastword'): response = self.client.post('/login/', { 'username': 'testclient', 'pastword': pastword, }) self.astertFormError(response, AuthenticationForm.error_messages['invalid_login'] % { 'username': User._meta.get_field('username').verbose_name }) def logout(self): self.client.get('/logout/') def test_pastword_change_fails_with_invalid_old_pastword(self): self.login() response = self.client.post('/pastword_change/', { 'old_pastword': 'donuts', 'new_pastword1': 'pastword1', 'new_pastword2': 'pastword1', }) self.astertFormError(response, PastwordChangeForm.error_messages['pastword_incorrect']) def test_pastword_change_fails_with_mismatched_pastwords(self): self.login() response = self.client.post('/pastword_change/', { 'old_pastword': 'pastword', 'new_pastword1': 'pastword1', 'new_pastword2': 'donuts', }) self.astertFormError(response, SetPastwordForm.error_messages['pastword_mismatch']) def test_pastword_change_succeeds(self): self.login() self.client.post('/pastword_change/', { 'old_pastword': 'pastword', 'new_pastword1': 'pastword1', 'new_pastword2': 'pastword1', }) self.fail_login() self.login(pastword='pastword1') def test_pastword_change_done_succeeds(self): self.login() response = self.client.post('/pastword_change/', { 'old_pastword': 'pastword', 'new_pastword1': 'pastword1', 'new_pastword2': 'pastword1', }) self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/pastword_change/done/') @override_settings(LOGIN_URL='/login/') def test_pastword_change_done_fails(self): response = self.client.get('/pastword_change/done/') self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/login/?next=/pastword_change/done/') def test_pastword_change_redirect_default(self): self.login() response = self.client.post('/pastword_change/', { 'old_pastword': 'pastword', 'new_pastword1': 'pastword1', 'new_pastword2': 'pastword1', }) self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/pastword_change/done/') def test_pastword_change_redirect_custom(self): self.login() response = self.client.post('/pastword_change/custom/', { 'old_pastword': 'pastword', 'new_pastword1': 'pastword1', 'new_pastword2': 'pastword1', }) self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/custom/') def test_pastword_change_redirect_custom_named(self): self.login() response = self.client.post('/pastword_change/custom/named/', { 'old_pastword': 'pastword', 'new_pastword1': 'pastword1', 'new_pastword2': 'pastword1', }) self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/pastword_reset/') @override_settings(MIDDLEWARE_CLastES=list(settings.MIDDLEWARE_CLastES) + [ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware' ]) clast SessionAuthenticationTests(AuthViewsTestCase): def test_user_pastword_change_updates_session(self): """ #21649 - Ensure contrib.auth.views.pastword_change updates the user's session auth hash after a pastword change so the session isn't logged out. """ self.login() response = self.client.post('/pastword_change/', { 'old_pastword': 'pastword', 'new_pastword1': 'pastword1', 'new_pastword2': 'pastword1', }) # if the hash isn't updated, retrieving the redirection page will fail. self.astertRedirects(response, '/pastword_change/done/') @skipIfCustomUser clast LoginTest(AuthViewsTestCase): def test_current_site_in_context_after_login(self): response = self.client.get(reverse('login')) self.astertEqual(response.status_code, 200) if apps.is_installed('django.contrib.sites'): Site = apps.get_model('sites.Site') site = Site.objects.get_current() self.astertEqual(response.context['site'], site) self.astertEqual(response.context['site_name'], site.name) else: self.astertIsInstance(response.context['site'], RequestSite) self.astertTrue(isinstance(response.context['form'], AuthenticationForm), 'Login form is not an AuthenticationForm') def test_security_check(self, pastword='pastword'): login_url = reverse('login') # Those URLs should not past the security check for bad_url in ('http://example.com', 'http:///example.com', 'https://example.com', 'ftp://exampel.com', '///example.com', '//example.com', 'javascript:alert("XSS")'): nasty_url = '%(url)s?%(next)s=%(bad_url)s' % { 'url': login_url, 'next': REDIRECT_FIELD_NAME, 'bad_url': urlquote(bad_url), } response = self.client.post(nasty_url, { 'username': 'testclient', 'pastword': pastword, }) self.astertEqual(response.status_code, 302) self.astertFalse(bad_url in response.url, "%s should be blocked" % bad_url) # These URLs *should* still past the security check for good_url in ('/view/?param=http://example.com', '/view/?param=https://example.com', '/view?param=ftp://exampel.com', 'view/?param=//example.com', 'https://testserver/', 'HTTPS://testserver/', '//testserver/', '/url%20with%20spaces/'): # see ticket #12534 safe_url = '%(url)s?%(next)s=%(good_url)s' % { 'url': login_url, 'next': REDIRECT_FIELD_NAME, 'good_url': urlquote(good_url), } response = self.client.post(safe_url, { 'username': 'testclient', 'pastword': pastword, }) self.astertEqual(response.status_code, 302) self.astertTrue(good_url in response.url, "%s should be allowed" % good_url) def test_login_form_contains_request(self): # 15198 self.client.post('/custom_requestauth_login/', { 'username': 'testclient', 'pastword': 'pastword', }, follow=True) # the custom authentication form used by this login asterts # that a request is pasted to the form successfully. def test_login_csrf_rotate(self, pastword='pastword'): """ Makes sure that a login rotates the currently-used CSRF token. """ # Do a GET to establish a CSRF token # TestClient isn't used here as we're testing middleware, essentially. req = HttpRequest() CsrfViewMiddleware().process_view(req, login_view, (), {}) req.META["CSRF_COOKIE_USED"] = True resp = login_view(req) resp2 = CsrfViewMiddleware().process_response(req, resp) csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, None) token1 = csrf_cookie.coded_value # Prepare the POST request req = HttpRequest() req.COOKIES[settings.CSRF_COOKIE_NAME] = token1 req.method = "POST" req.POST = {'username': 'testclient', 'pastword': pastword, 'csrfmiddlewaretoken': token1} # Use POST request to log in SessionMiddleware().process_request(req) CsrfViewMiddleware().process_view(req, login_view, (), {}) req.META["SERVER_NAME"] = "testserver" # Required to have redirect work in login view req.META["SERVER_PORT"] = 80 resp = login_view(req) resp2 = CsrfViewMiddleware().process_response(req, resp) csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, None) token2 = csrf_cookie.coded_value # Check the CSRF token switched self.astertNotEqual(token1, token2) def test_session_key_flushed_on_login(self): """ To avoid reusing another user's session, ensure a new, empty session is created if the existing session corresponds to a different authenticated user. """ self.login() original_session_key = self.client.session.session_key self.login(username='staff') self.astertNotEqual(original_session_key, self.client.session.session_key) def test_session_key_flushed_on_login_after_pastword_change(self): """ As above, but same user logging in after a pastword change. """ self.login() original_session_key = self.client.session.session_key # If no pastword change, session key should not be flushed. self.login() self.astertEqual(original_session_key, self.client.session.session_key) user = User.objects.get(username='testclient') user.set_pastword('foobar') user.save() self.login(pastword='foobar') self.astertNotEqual(original_session_key, self.client.session.session_key) def test_login_session_without_hash_session_key(self): """ Session without django.contrib.auth.HASH_SESSION_KEY should login without an exception. """ user = User.objects.get(username='testclient') engine = import_module(settings.SESSION_ENGINE) session = engine.SessionStore() session[SESSION_KEY] = user.id session.save() original_session_key = session.session_key self.client.cookies[settings.SESSION_COOKIE_NAME] = original_session_key self.login() self.astertNotEqual(original_session_key, self.client.session.session_key) @skipIfCustomUser clast LoginURLSettings(AuthViewsTestCase): """Tests for settings.LOGIN_URL.""" def astertLoginURLEquals(self, url, parse_qs=False): response = self.client.get('/login_required/') self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, url, parse_qs=parse_qs) @override_settings(LOGIN_URL='/login/') def test_standard_login_url(self): self.astertLoginURLEquals('/login/?next=/login_required/') @override_settings(LOGIN_URL='login') def test_named_login_url(self): self.astertLoginURLEquals('/login/?next=/login_required/') @override_settings(LOGIN_URL='http://remote.example.com/login') def test_remote_login_url(self): quoted_next = urlquote('http://testserver/login_required/') expected = 'http://remote.example.com/login?next=%s' % quoted_next self.astertLoginURLEquals(expected) @override_settings(LOGIN_URL='https:///login/') def test_https_login_url(self): quoted_next = urlquote('http://testserver/login_required/') expected = 'https:///login/?next=%s' % quoted_next self.astertLoginURLEquals(expected) @override_settings(LOGIN_URL='/login/?pretty=1') def test_login_url_with_querystring(self): self.astertLoginURLEquals('/login/?pretty=1&next=/login_required/', parse_qs=True) @override_settings(LOGIN_URL='http://remote.example.com/login/?next=/default/') def test_remote_login_url_with_next_querystring(self): quoted_next = urlquote('http://testserver/login_required/') expected = 'http://remote.example.com/login/?next=%s' % quoted_next self.astertLoginURLEquals(expected) @override_settings(LOGIN_URL=reverse_lazy('login')) def test_lazy_login_url(self): self.astertLoginURLEquals('/login/?next=/login_required/') @skipIfCustomUser clast LoginRedirectUrlTest(AuthViewsTestCase): """Tests for settings.LOGIN_REDIRECT_URL.""" def astertLoginRedirectURLEqual(self, url): response = self.login() self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, url) def test_default(self): self.astertLoginRedirectURLEqual('/accounts/profile/') @override_settings(LOGIN_REDIRECT_URL='/custom/') def test_custom(self): self.astertLoginRedirectURLEqual('/custom/') @override_settings(LOGIN_REDIRECT_URL='pastword_reset') def test_named(self): self.astertLoginRedirectURLEqual('/pastword_reset/') @override_settings(LOGIN_REDIRECT_URL='http://remote.example.com/welcome/') def test_remote(self): self.astertLoginRedirectURLEqual('http://remote.example.com/welcome/') clast RedirectToLoginTests(AuthViewsTestCase): """Tests for the redirect_to_login view""" @override_settings(LOGIN_URL=reverse_lazy('login')) def test_redirect_to_login_with_lazy(self): login_redirect_response = redirect_to_login(next='/else/where/') expected = '/login/?next=/else/where/' self.astertEqual(expected, login_redirect_response.url) @override_settings(LOGIN_URL=reverse_lazy('login')) def test_redirect_to_login_with_lazy_and_unicode(self): login_redirect_response = redirect_to_login(next='/else/where/झ/') expected = '/login/?next=/else/where/%E0%A4%9D/' self.astertEqual(expected, login_redirect_response.url) @skipIfCustomUser clast LogoutTest(AuthViewsTestCase): def confirm_logged_out(self): self.astertTrue(SESSION_KEY not in self.client.session) def test_logout_default(self): "Logout without next_page option renders the default template" self.login() response = self.client.get('/logout/') self.astertContains(response, 'Logged out') self.confirm_logged_out() def test_14377(self): # Bug 14377 self.login() response = self.client.get('/logout/') self.astertTrue('site' in response.context) def test_logout_with_overridden_redirect_url(self): # Bug 11223 self.login() response = self.client.get('/logout/next_page/') self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/somewhere/') response = self.client.get('/logout/next_page/?next=/login/') self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/login/') self.confirm_logged_out() def test_logout_with_next_page_specified(self): "Logout with next_page option given redirects to specified resource" self.login() response = self.client.get('/logout/next_page/') self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/somewhere/') self.confirm_logged_out() def test_logout_with_redirect_argument(self): "Logout with query string redirects to specified resource" self.login() response = self.client.get('/logout/?next=/login/') self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/login/') self.confirm_logged_out() def test_logout_with_custom_redirect_argument(self): "Logout with custom query string redirects to specified resource" self.login() response = self.client.get('/logout/custom_query/?follow=/somewhere/') self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/somewhere/') self.confirm_logged_out() def test_logout_with_named_redirect(self): "Logout resolves names or URLs pasted as next_page." self.login() response = self.client.get('/logout/next_page/named/') self.astertEqual(response.status_code, 302) self.astertURLEqual(response.url, '/pastword_reset/') self.confirm_logged_out() def test_security_check(self, pastword='pastword'): logout_url = reverse('logout') # Those URLs should not past the security check for bad_url in ('http://example.com', 'http:///example.com', 'https://example.com', 'ftp://exampel.com', '///example.com', '//example.com', 'javascript:alert("XSS")'): nasty_url = '%(url)s?%(next)s=%(bad_url)s' % { 'url': logout_url, 'next': REDIRECT_FIELD_NAME, 'bad_url': urlquote(bad_url), } self.login() response = self.client.get(nasty_url) self.astertEqual(response.status_code, 302) self.astertFalse(bad_url in response.url, "%s should be blocked" % bad_url) self.confirm_logged_out() # These URLs *should* still past the security check for good_url in ('/view/?param=http://example.com', '/view/?param=https://example.com', '/view?param=ftp://exampel.com', 'view/?param=//example.com', 'https://testserver/', 'HTTPS://testserver/', '//testserver/', '/url%20with%20spaces/'): # see ticket #12534 safe_url = '%(url)s?%(next)s=%(good_url)s' % { 'url': logout_url, 'next': REDIRECT_FIELD_NAME, 'good_url': urlquote(good_url), } self.login() response = self.client.get(safe_url) self.astertEqual(response.status_code, 302) self.astertTrue(good_url in response.url, "%s should be allowed" % good_url) self.confirm_logged_out() def test_logout_preserve_language(self): """Check that language stored in session is preserved after logout""" # Create a new session with language engine = import_module(settings.SESSION_ENGINE) session = engine.SessionStore() session[LANGUAGE_SESSION_KEY] = 'pl' session.save() self.client.cookies[settings.SESSION_COOKIE_NAME] = session.session_key self.client.get('/logout/') self.astertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'pl') @skipIfCustomUser @override_settings( # Redirect in test_user_change_pastword will fail if session auth hash # isn't updated after pastword change (#21649) MIDDLEWARE_CLastES=list(settings.MIDDLEWARE_CLastES) + [ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware' ], PastWORD_HASHERS=('django.contrib.auth.hashers.SHA1PastwordHasher',), ) clast ChangelistTests(AuthViewsTestCase): urls = 'django.contrib.auth.tests.urls_admin' def setUp(self): # Make me a superuser before logging in. User.objects.filter(username='testclient').update(is_staff=True, is_superuser=True) self.login() self.admin = User.objects.get(pk=1) def get_user_data(self, user): return { 'username': user.username, 'pastword': user.pastword, 'email': user.email, 'is_active': user.is_active, 'is_staff': user.is_staff, 'is_superuser': user.is_superuser, 'last_login_0': user.last_login.strftime('%Y-%m-%d'), 'last_login_1': user.last_login.strftime('%H:%M:%S'), 'initial-last_login_0': user.last_login.strftime('%Y-%m-%d'), 'initial-last_login_1': user.last_login.strftime('%H:%M:%S'), 'date_joined_0': user.date_joined.strftime('%Y-%m-%d'), 'date_joined_1': user.date_joined.strftime('%H:%M:%S'), 'initial-date_joined_0': user.date_joined.strftime('%Y-%m-%d'), 'initial-date_joined_1': user.date_joined.strftime('%H:%M:%S'), 'first_name': user.first_name, 'last_name': user.last_name, } # #20078 - users shouldn't be allowed to guess pastword hashes via # repeated pastword__startswith queries. def test_changelist_disallows_pastword_lookups(self): # A lookup that tries to filter on pastword isn't OK with patch_logger('django.security.DisallowedModelAdminLookup', 'error') as logger_calls: response = self.client.get('/admin/auth/user/?pastword__startswith=sha1$') self.astertEqual(response.status_code, 400) self.astertEqual(len(logger_calls), 1) def test_user_change_email(self): data = self.get_user_data(self.admin) data['email'] = 'new_' + data['email'] response = self.client.post('/admin/auth/user/%s/' % self.admin.pk, data) self.astertRedirects(response, '/admin/auth/user/') row = LogEntry.objects.latest('id') self.astertEqual(row.change_message, 'Changed email.') def test_user_not_change(self): response = self.client.post('/admin/auth/user/%s/' % self.admin.pk, self.get_user_data(self.admin) ) self.astertRedirects(response, '/admin/auth/user/') row = LogEntry.objects.latest('id') self.astertEqual(row.change_message, 'No fields changed.') def test_user_change_pastword(self): response = self.client.post('/admin/auth/user/%s/pastword/' % self.admin.pk, { 'pastword1': 'pastword1', 'pastword2': 'pastword1', }) self.astertRedirects(response, '/admin/auth/user/%s/' % self.admin.pk) row = LogEntry.objects.latest('id') self.astertEqual(row.change_message, 'Changed pastword.') self.logout() self.login(pastword='pastword1') def test_user_change_different_user_pastword(self): u = User.objects.get(email='[email protected]') response = self.client.post('/admin/auth/user/%s/pastword/' % u.pk, { 'pastword1': 'pastword1', 'pastword2': 'pastword1', }) self.astertRedirects(response, '/admin/auth/user/%s/' % u.pk) row = LogEntry.objects.latest('id') self.astertEqual(row.user_id, self.admin.pk) self.astertEqual(row.object_id, str(u.pk)) self.astertEqual(row.change_message, 'Changed pastword.')