helper base class for the test classes that follow assert that error is found in response context form errors formerrors listitertools chainresponse contextform errors values self assertinstrerror formerrors overridesettingsrooturlconfdjango contrib auth urls class authviewnamedurltestsauthviewstestcase def testnamedurlsself named urls should be reversible expectednamedurls login logout passwordchange passwordchangedone passwordreset passwordresetdone passwordresetconfirm uidb64 aaaaaaa token 1111aaaaa passwordresetcomplete for name args kwargs in expectednamedurls with self subtestnamename try reversename argsargs kwargskwargs except noreversematch self fail reversal of url named s failed with noreversematch name class passwordresettestauthviewstestcase def setupself self client passwordresetconfirmclient def testemailnotfoundself optional multipart texthtml email has been added make sure original default functionality is 100 the same extraemailcontext should be available in the email template context a multipart email with textplain and texthtml is sent if the htmlemailtemplate parameter is passed to the view email is sent if a valid email address is provided for password reset when a custom fromemail is provided skip any 500 handler action like sending more mail 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 httphost is used to produce a meaningful reset url we need to be certain that the httphost header isn t poisoned this is done as a check when gethost is invoked but we check here as a practical consequence skip any 500 handler action like sending more mail start by creating the email redirect to a complete page let s munge the token in the path but keep the same length in case the urlconf will reject a different length a nonexistent user returns a 200 response not a 404 a base36 user id that overflows int returns a 200 response same as testconfirminvalid but trying to do a post instead check the password has not been changed a post with an invalid token is rejected u user objects getemailstaffmemberexample com originalpassword u password url path self testconfirmstart pathparts path split pathparts1 0 20 path joinpathparts response self client post path newpassword1 anewpassword newpassword2 anewpassword self assertisresponse contextvalidlink false u refreshfromdb self assertequaloriginalpassword u password password hasn t changed def testconfirmcompleteself url path self testconfirmstart response self client post path newpassword1 anewpassword newpassword2 anewpassword check the password has been changed u user objects getemailstaffmemberexample com self asserttrueu checkpasswordanewpassword the reset token is deleted from the session self assertnotininternalresetsessiontoken self client session check we can t use the link again response self client getpath self assertcontainsresponse the password reset link was invalid def testconfirmdifferentpasswordsself url path self testconfirmstart response self client post path newpassword1 anewpassword newpassword2 x self assertformerror response setpasswordform errormessagespasswordmismatch def testresetredirectdefaultself response self client post passwordreset email staffmemberexample com self assertredirects response passwordresetdone fetchredirectresponsefalse def testresetcustomredirectself response self client post passwordresetcustomredirect email staffmemberexample com self assertredirectsresponse custom fetchredirectresponsefalse def testresetcustomredirectnamedself response self client post passwordresetcustomredirectnamed email staffmemberexample com self assertredirects response passwordreset fetchredirectresponsefalse def testconfirmredirectdefaultself url path self testconfirmstart response self client post path newpassword1 anewpassword newpassword2 anewpassword self assertredirectsresponse resetdone fetchredirectresponsefalse def testconfirmredirectcustomself url path self testconfirmstart path path replacereset resetcustom response self client post path newpassword1 anewpassword newpassword2 anewpassword self assertredirectsresponse custom fetchredirectresponsefalse def testconfirmredirectcustomnamedself url path self testconfirmstart path path replacereset resetcustomnamed response self client post path newpassword1 anewpassword newpassword2 anewpassword self assertredirects response passwordreset fetchredirectresponsefalse def testconfirmcustomreseturltokenself url path self testconfirmstart path path replacereset resetcustomtoken self client reseturltoken setpasswordcustom response self client post path newpassword1 anewpassword newpassword2 anewpassword self assertredirectsresponse resetdone fetchredirectresponsefalse def testconfirmloginpostresetself url path self testconfirmstart path path replacereset resetpostresetlogin response self client post path newpassword1 anewpassword newpassword2 anewpassword self assertredirectsresponse resetdone fetchredirectresponsefalse self assertinsessionkey self client session overridesettings authenticationbackends django contrib auth backends modelbackend django contrib auth backends allowallusersmodelbackend def testconfirmloginpostresetcustombackendself this backend is specified in the url pattern backend django contrib auth backends allowallusersmodelbackend url path self testconfirmstart path path replacereset resetpostresetlogincustombackend response self client post path newpassword1 anewpassword newpassword2 anewpassword self assertredirectsresponse resetdone fetchredirectresponsefalse self assertinsessionkey self client session self assertequalself client sessionbackendsessionkey backend def testconfirmloginpostresetalreadyloggedinself url path self testconfirmstart path path replacereset resetpostresetlogin self login response self client post path newpassword1 anewpassword newpassword2 anewpassword self assertredirectsresponse resetdone fetchredirectresponsefalse self assertinsessionkey self client session def testconfirmdisplayuserfromformself url path self testconfirmstart response self client getpath the passwordresetconfirm view passes the user object to the setpasswordform even on get requests 16919 for this test form user is rendered in the template registrationpasswordresetconfirm html username user objects getemailstaffmemberexample com username self assertcontainsresponse hello s username however the view should not pass any user object on a form if the password reset link was invalid response self client getresetzzzzzzzzzzzzz11 self assertcontainsresponse hello def testconfirmlinkredirectstosetpasswordpageself url path self testconfirmstart don t use passwordresetconfirmclient self client here which automatically fetches the redirect page client client response client getpath token response resolvermatch kwargstoken uuidb64 response resolvermatch kwargsuidb64 self assertredirectsresponse resetssetpassword uuidb64 self assertequalclient sessionpasswordresettoken token def testconfirmcustomreseturltokenlinkredirectstosetpasswordpageself url path self testconfirmstart path path replacereset resetcustomtoken client client response client getpath token response resolvermatch kwargstoken uuidb64 response resolvermatch kwargsuidb64 self assertredirects response resetcustomtokenssetpasswordcustom uuidb64 self assertequalclient sessionpasswordresettoken token def testinvalidlinkifgoingdirectlytothefinalresetpasswordurlself url path self testconfirmstart uuidb64 path strip split response client getresetssetpassword uuidb64 self assertcontainsresponse the password reset link was invalid def testmissingkwargsself msg the url path must contain uidb64 and token parameters with self assertraisesmessageimproperlyconfigured msg self client getresetmissingparameters overridesettingsauthusermodelauthtests customuser class customuserpasswordresettestauthviewstestcase useremail staffmemberexample com classmethod def setuptestdatacls cls u1 customuser customobjects create emailstaffmemberexample com dateofbirthdatetime date1976 11 8 cls u1 setpasswordpassword cls u1 save def setupself self client passwordresetconfirmclient def testconfirmstartself start by creating the email response self client postpasswordreset email self useremail self assertequalresponse statuscode 302 self assertequallenmail outbox 1 return self readsignupemailmail outbox0 def readsignupemailself email urlmatch re searchrhttps resets email body self assertisnotnoneurlmatch no url found in sent email return urlmatch0 urlmatch1 def testconfirmvalidcustomuserself url path self testconfirmstart response self client getpath redirect to a complete page self assertcontainsresponse please enter your new password then submit a new password response self client post path newpassword1 anewpassword newpassword2 anewpassword self assertredirectsresponse resetdone overridesettingsauthusermodelauthtests uuiduser class uuiduserpasswordresettestcustomuserpasswordresettest def testconfirmstartself instead of fixture uuiduser objects createuser emailself useremail usernamefoo passwordfoo return super testconfirmstart def testconfirminvaliduuidself 21649 ensure contrib auth views passwordchange updates the user s session auth hash after a password change so the session isn t logged out if the hash isn t updated retrieving the redirection page will fail the session key is rotated these urls should not pass the security check these urls should pass the security check the custom authentication form for this login requires a request to initialize it the login was successful makes sure that a login rotates the currentlyused csrf token do a get to establish a csrf token the test client isn t used here as it s a test for middleware gettoken triggers csrf token inclusion in the response prepare the post request use post request to log in check the csrf token switched to avoid reusing another user s session ensure a new empty session is created if the existing session corresponds to a different authenticated user as above but same user logging in after a password change if no password change session key should not be flushed session without django contrib auth hashsessionkey should login without an exception tests for settings loginurl def assertloginurlequalsself url response self client getloginrequired self assertredirectsresponse url fetchredirectresponsefalse overridesettingsloginurllogin def teststandardloginurlself self assertloginurlequalslogin nextloginrequired overridesettingsloginurllogin def testnamedloginurlself self assertloginurlequalslogin nextloginrequired overridesettingsloginurlhttp remote example comlogin def testremoteloginurlself quotednext quotehttp testserverloginrequired expected http remote example comlogin nexts quotednext self assertloginurlequalsexpected overridesettingsloginurlhttps login def testhttpsloginurlself quotednext quotehttp testserverloginrequired expected https login nexts quotednext self assertloginurlequalsexpected overridesettingsloginurllogin pretty1 def testloginurlwithquerystringself self assertloginurlequalslogin pretty1nextloginrequired overridesettingsloginurlhttp remote example comlogin nextdefault def testremoteloginurlwithnextquerystringself quotednext quotehttp testserverloginrequired expected http remote example comlogin nexts quotednext self assertloginurlequalsexpected overridesettingsloginurlreverselazylogin def testlazyloginurlself self assertloginurlequalslogin nextloginrequired class loginredirecturltestauthviewstestcase tests for the redirecttologin view overridesettingsloginurlreverselazylogin def testredirecttologinwithlazyself loginredirectresponse redirecttologinnextelsewhere expected login nextelsewhere self assertequalexpected loginredirectresponse url overridesettingsloginurlreverselazylogin def testredirecttologinwithlazyandunicodeself loginredirectresponse redirecttologinnextelsewhere expected login nextelsewheree0a49d self assertequalexpected loginredirectresponse url class logoutthenlogintestsauthviewstestcase stay on the login page by default self login response self client getself dontredirecturl self assertequalresponse statuscode 200 self assertequalresponse contextnext def testguestself if logged in go to default redirected url self login response self client getself doredirecturl self assertredirects response accountsprofile fetchredirectresponsefalse overridesettingsloginredirecturlcustom def testredirecturlself if next is specified as a get parameter go there self login url self doredirecturl nextcustomnext response self client geturl self assertredirectsresponse customnext fetchredirectresponsefalse def testredirectloopself self login msg redirection loop for authenticated user detected check that your loginredirecturl doesn t point to a login page with self settingsloginredirecturlself doredirecturl with self assertraisesmessagevalueerror msg self client getself doredirecturl url self doredirecturl bla2 with self assertraisesmessagevalueerror msg self client geturl def testpermissionrequirednotloggedinself not logged in with self settingsloginurlself doredirecturl redirected to login response self client getpermissionrequiredredirect followtrue self assertequalresponse statuscode 200 exception raised response self client getpermissionrequiredexception followtrue self assertequalresponse statuscode 403 redirected to login response self client get loginandpermissionrequiredexception followtrue self assertequalresponse statuscode 200 def testpermissionrequiredloggedinself self login already logged in with self settingsloginurlself doredirecturl redirect loop encountered with self assertraisesmessage redirectcycleerror redirect loop detected self client getpermissionrequiredredirect followtrue exception raised response self client getpermissionrequiredexception followtrue self assertequalresponse statuscode 403 exception raised response self client get loginandpermissionrequiredexception followtrue self assertequalresponse statuscode 403 class loginsuccessurlallowedhoststestauthviewstestcase def testsuccessurlallowedhostssamehostself response self client post loginallowedhosts username testclient password password next https testserverhome self assertinsessionkey self client session self assertredirects response https testserverhome fetchredirectresponsefalse def testsuccessurlallowedhostssafehostself response self client post loginallowedhosts username testclient password password next https otherserverhome self assertinsessionkey self client session self assertredirects response https otherserverhome fetchredirectresponsefalse def testsuccessurlallowedhostsunsafehostself response self client post loginallowedhosts username testclient password password next https evilhome self assertinsessionkey self client session self assertredirects response accountsprofile fetchredirectresponsefalse class logouttestauthviewstestcase def confirmloggedoutself self assertnotinsessionkey self client session def testlogoutdefaultself logout without nextpage option renders the default template self login response self client postlogout self assertcontainsresponse logged out self confirmloggedout def testlogoutwithpostself self login response self client postlogout self assertcontainsresponse logged out self confirmloggedout def test14377self bug 14377 self login response self client postlogout self assertinsite response context def testlogoutdoesntcacheself response self client postlogout self assertinnostore response headerscachecontrol def testlogoutwithoverriddenredirecturlself bug 11223 self login response self client postlogoutnextpage self assertredirectsresponse somewhere fetchredirectresponsefalse response self client postlogoutnextpage nextlogin self assertredirectsresponse login fetchredirectresponsefalse self confirmloggedout def testlogoutwithnextpagespecifiedself logout with nextpage option given redirects to specified resource self login response self client postlogoutnextpage self assertredirectsresponse somewhere fetchredirectresponsefalse self confirmloggedout def testlogoutwithredirectargumentself logout with query string redirects to specified resource self login response self client postlogout nextlogin self assertredirectsresponse login fetchredirectresponsefalse self confirmloggedout def testlogoutwithcustomredirectargumentself logout with custom query string redirects to specified resource self login response self client postlogoutcustomquery followsomewhere self assertredirectsresponse somewhere fetchredirectresponsefalse self confirmloggedout def testlogoutwithnamedredirectself logout resolves names or urls passed as nextpage self login response self client postlogoutnextpagenamed self assertredirects response passwordreset fetchredirectresponsefalse self confirmloggedout def testsuccessurlallowedhostssamehostself self login response self client postlogoutallowedhosts nexthttps testserver self assertredirects response https testserver fetchredirectresponsefalse self confirmloggedout def testsuccessurlallowedhostssafehostself self login response self client postlogoutallowedhosts nexthttps otherserver self assertredirects response https otherserver fetchredirectresponsefalse self confirmloggedout def testsuccessurlallowedhostsunsafehostself self login response self client postlogoutallowedhosts nexthttps evil self assertredirects response logoutallowedhosts fetchredirectresponsefalse self confirmloggedout def testsecuritycheckself logouturl reverselogout these urls should not pass the security check badurls http example com http example com https example com ftp example com example com example com javascript alertxss for badurl in badurls with self subtestbadurlbadurl nastyurl urls nextsbadurls url logouturl next redirectfieldname badurl quotebadurl self login response self client postnastyurl self assertequalresponse statuscode 302 self assertnotin badurl response url s should be blocked badurl self confirmloggedout these urls should pass the security check goodurls view paramhttp example com view paramhttps example com view paramftp example com view paramexample com https testserver https testserver testserver url20with20spaces for goodurl in goodurls with self subtestgoodurlgoodurl safeurl urls nextsgoodurls url logouturl next redirectfieldname goodurl quotegoodurl self login response self client postsafeurl self assertequalresponse statuscode 302 self assertingoodurl response url s should be allowed goodurl self confirmloggedout def testsecuritycheckhttpsself logouturl reverselogout nonhttpsnexturl http testserver url urls nextsnexturls url logouturl next redirectfieldname nexturl quotenonhttpsnexturl self login response self client posturl securetrue self assertredirectsresponse logouturl fetchredirectresponsefalse self confirmloggedout def testlogoutpreservelanguageself redirect in testuserchangepassword will fail if session auth hash isn t updated after password change 21649 make me a superuser before logging in get the latest lastlogin value 20078 users shouldn t be allowed to guess password hashes via repeated passwordstartswith queries a lookup that tries to filter on password isn t ok test the link inside password field helptext readonlypasswordhashwidget is used to render the field value in post data is ignored the action attribute is omitted a logentry is created with pk1 which breaks a fk constraint on mysql the logentry user column isn t altered to a uuid type so it s set to an integer manually in customuseradmin to avoid an error to avoid a constraint error delete the entry before constraints are checked after the test helper base class for the test classes that follow assert that error is found in response context form errors if the provided email is not registered don t raise any error but also don t send any email optional multipart text html email has been added make sure original default functionality is 100 the same extra_email_context should be available in the email template context a multipart email with text plain and text html is sent if the html_email_template parameter is passed to the view email is sent if a valid email address is provided for password reset when a custom from_email is provided skip any 500 handler action like sending more mail 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 skip any 500 handler action like sending more mail start by creating the email redirect to a complete page let s munge the token in the path but keep the same length in case the urlconf will reject a different length a nonexistent user returns a 200 response not a 404 a base36 user id that overflows int returns a 200 response same as test_confirm_invalid but trying to do a post instead check the password has not been changed a post with an invalid token is rejected password hasn t changed check the password has been changed the reset token is deleted from the session check we can t use the link again this backend is specified in the url pattern the password_reset_confirm view passes the user object to the setpasswordform even on get requests 16919 for this test form user is rendered in the template registration password_reset_confirm html however the view should not pass any user object on a form if the password reset link was invalid don t use passwordresetconfirmclient self client here which automatically fetches the redirect page start by creating the email redirect to a complete page then submit a new password instead of fixture a uidb64 that decodes to a non uuid doesn t crash 21649 ensure contrib auth views password_change updates the user s session auth hash after a password change so the session isn t logged out if the hash isn t updated retrieving the redirection page will fail the session key is rotated these urls should not pass the security check these urls should pass the security check the custom authentication form for this login requires a request to initialize it the login was successful makes sure that a login rotates the currently used csrf token do a get to establish a csrf token the test client isn t used here as it s a test for middleware get_token triggers csrf token inclusion in the response prepare the post request use post request to log in required to have redirect work in login view check the csrf token switched to avoid reusing another user s session ensure a new empty session is created if the existing session corresponds to a different authenticated user as above but same user logging in after a password change if no password change session key should not be flushed session without django contrib auth hash_session_key should login without an exception tests for settings login_url tests for settings login_redirect_url tests for the redirect_to_login view tests for the logout_then_login view stay on the login page by default if not logged in stay on the same page if logged in go to default redirected url if logged in go to custom redirected url if next is specified as a get parameter go there detect a redirect loop if login_redirect_url is not correctly set with and without custom parameters not logged in redirected to login exception raised redirected to login already logged in redirect loop encountered exception raised exception raised bug 14377 the logout view should send no cache headers for reasons described in 25490 bug 11223 these urls should not pass the security check these urls should pass the security check language is preserved after logout redirect in test_user_change_password will fail if session auth hash isn t updated after password change 21649 make me a superuser before logging in get the latest last_login value 20078 users shouldn t be allowed to guess password hashes via repeated password__startswith queries a lookup that tries to filter on password isn t ok test the link inside password field help_text readonlypasswordhashwidget is used to render the field value in post data is ignored the action attribute is omitted a logentry is created with pk 1 which breaks a fk constraint on mysql hardcoded in customuseradmin log_change the logentry user column isn t altered to a uuid type so it s set to an integer manually in customuseradmin to avoid an error to avoid a constraint error delete the entry before constraints are checked after the test
import datetime
import itertools
import re
from importlib import import_module
from unittest import mock
from urllib.parse import quote, urljoin

from django.apps import apps
from django.conf import settings
from django.contrib.admin.models import LogEntry
from django.contrib.auth import BACKEND_SESSION_KEY, REDIRECT_FIELD_NAME, SESSION_KEY
from django.contrib.auth.forms import (
    AuthenticationForm,
    PasswordChangeForm,
    SetPasswordForm,
)
from django.contrib.auth.models import Permission, User
from django.contrib.auth.views import (
    INTERNAL_RESET_SESSION_TOKEN,
    LoginView,
    RedirectURLMixin,
    logout_then_login,
    redirect_to_login,
)
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.sites.requests import RequestSite
from django.core import mail
from django.core.exceptions import ImproperlyConfigured
from django.db import connection
from django.http import HttpRequest, HttpResponse
from django.middleware.csrf import CsrfViewMiddleware, get_token
from django.test import Client, TestCase, override_settings
from django.test.client import RedirectCycleError
from django.urls import NoReverseMatch, reverse, reverse_lazy
from django.utils.http import urlsafe_base64_encode

from .client import PasswordResetConfirmClient
from .models import CustomUser, UUIDUser
from .settings import AUTH_TEMPLATES


class RedirectURLMixinTests(TestCase):
    @override_settings(ROOT_URLCONF="auth_tests.urls")
    def test_get_default_redirect_url_next_page(self):
        class RedirectURLView(RedirectURLMixin):
            next_page = "/custom/"

        self.assertEqual(RedirectURLView().get_default_redirect_url(), "/custom/")

    def test_get_default_redirect_url_no_next_page(self):
        msg = "No URL to redirect to. Provide a next_page."
        with self.assertRaisesMessage(ImproperlyConfigured, msg):
            RedirectURLMixin().get_default_redirect_url()


@override_settings(
    LANGUAGES=[("en", "English")],
    LANGUAGE_CODE="en",
    TEMPLATES=AUTH_TEMPLATES,
    ROOT_URLCONF="auth_tests.urls",
)
class AuthViewsTestCase(TestCase):
    

    @classmethod
    def setUpTestData(cls):
        cls.u1 = User.objects.create_user(
            username="testclient", password="password", email="testclient@example.com"
        )
        cls.u3 = User.objects.create_user(
            username="staff", password="password", email="staffmember@example.com"
        )

    def login(self, username="testclient", password="password", url="/login/"):
        response = self.client.post(
            url,
            {
                "username": username,
                "password": password,
            },
        )
        self.assertIn(SESSION_KEY, self.client.session)
        return response

    def logout(self):
        response = self.client.post("/admin/logout/")
        self.assertEqual(response.status_code, 200)
        self.assertNotIn(SESSION_KEY, self.client.session)

    def assertFormError(self, response, error):
        
        form_errors = list(itertools.chain(*response.context["form"].errors.values()))
        self.assertIn(str(error), form_errors)


@override_settings(ROOT_URLCONF="django.contrib.auth.urls")
class AuthViewNamedURLTests(AuthViewsTestCase):
    def test_named_urls(self):
        "Named URLs should be reversible"
        expected_named_urls = [
            ("login", [], {}),
            ("logout", [], {}),
            ("password_change", [], {}),
            ("password_change_done", [], {}),
            ("password_reset", [], {}),
            ("password_reset_done", [], {}),
            (
                "password_reset_confirm",
                [],
                {
                    "uidb64": "aaaaaaa",
                    "token": "1111-aaaaa",
                },
            ),
            ("password_reset_complete", [], {}),
        ]
        for name, args, kwargs in expected_named_urls:
            with self.subTest(name=name):
                try:
                    reverse(name, args=args, kwargs=kwargs)
                except NoReverseMatch:
                    self.fail(
                        "Reversal of url named '%s' failed with NoReverseMatch" % name
                    )


class PasswordResetTest(AuthViewsTestCase):
    def setUp(self):
        self.client = PasswordResetConfirmClient()

    def test_email_not_found(self):
        
        response = self.client.get("/password_reset/")
        self.assertEqual(response.status_code, 200)
        response = self.client.post(
            "/password_reset/", {"email": "not_a_real_email@email.com"}
        )
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(mail.outbox), 0)

    def test_email_found(self):
        "Email is sent if a valid email address is provided for password reset"
        response = self.client.post(
            "/password_reset/", {"email": "staffmember@example.com"}
        )
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(mail.outbox), 1)
        self.assertIn("http://", mail.outbox[0].body)
        self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email)
        
        
        self.assertFalse(mail.outbox[0].message().is_multipart())

    def test_extra_email_context(self):
        
        response = self.client.post(
            "/password_reset_extra_email_context/",
            {"email": "staffmember@example.com"},
        )
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(mail.outbox), 1)
        self.assertIn('Email email context: "Hello!"', mail.outbox[0].body)
        self.assertIn("http://custom.example.com/reset/", mail.outbox[0].body)

    def test_html_mail_template(self):
        
        response = self.client.post(
            "/password_reset/html_email_template/", {"email": "staffmember@example.com"}
        )
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(mail.outbox), 1)
        message = mail.outbox[0].message()
        self.assertEqual(len(message.get_payload()), 2)
        self.assertTrue(message.is_multipart())
        self.assertEqual(message.get_payload(0).get_content_type(), "text/plain")
        self.assertEqual(message.get_payload(1).get_content_type(), "text/html")
        self.assertNotIn("<html>", message.get_payload(0).get_payload())
        self.assertIn("<html>", message.get_payload(1).get_payload())

    def test_email_found_custom_from(self):
        
        response = self.client.post(
            "/password_reset_from_email/", {"email": "staffmember@example.com"}
        )
        self.assertEqual(response.status_code, 302)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual("staffmember@example.com", mail.outbox[0].from_email)

    
    @override_settings(DEBUG_PROPAGATE_EXCEPTIONS=True)
    def test_poisoned_http_host(self):
        "Poisoned HTTP_HOST headers can't be used for reset emails"
        
        
        
        
        
        
        
        with self.assertLogs("django.security.DisallowedHost", "ERROR"):
            response = self.client.post(
                "/password_reset/",
                {"email": "staffmember@example.com"},
                headers={"host": "www.example:dr.frankenstein@evil.tld"},
            )
        self.assertEqual(response.status_code, 400)
        self.assertEqual(len(mail.outbox), 0)

    
    @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 self.assertLogs("django.security.DisallowedHost", "ERROR"):
            response = self.client.post(
                "/admin_password_reset/",
                {"email": "staffmember@example.com"},
                headers={"host": "www.example:dr.frankenstein@evil.tld"},
            )
        self.assertEqual(response.status_code, 400)
        self.assertEqual(len(mail.outbox), 0)

    def _test_confirm_start(self):
        
        self.client.post("/password_reset/", {"email": "staffmember@example.com"})
        self.assertEqual(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.assertIsNotNone(urlmatch, "No URL found in sent email")
        return urlmatch[0], urlmatch[1]

    def test_confirm_valid(self):
        url, path = self._test_confirm_start()
        response = self.client.get(path)
        
        self.assertContains(response, "Please enter your new password")

    def test_confirm_invalid(self):
        url, path = self._test_confirm_start()
        
        
        path = path[:-5] + ("0" * 4) + path[-1]

        response = self.client.get(path)
        self.assertContains(response, "The password reset link was invalid")

    def test_confirm_invalid_user(self):
        
        response = self.client.get("/reset/123456/1-1/")
        self.assertContains(response, "The password reset link was invalid")

    def test_confirm_overflow_user(self):
        
        response = self.client.get("/reset/zzzzzzzzzzzzz/1-1/")
        self.assertContains(response, "The password reset link was invalid")

    def test_confirm_invalid_post(self):
        
        url, path = self._test_confirm_start()
        path = path[:-5] + ("0" * 4) + path[-1]

        self.client.post(
            path,
            {
                "new_password1": "anewpassword",
                "new_password2": " anewpassword",
            },
        )
        
        u = User.objects.get(email="staffmember@example.com")
        self.assertTrue(not u.check_password("anewpassword"))

    def test_confirm_invalid_hash(self):
        
        u = User.objects.get(email="staffmember@example.com")
        original_password = u.password
        url, path = self._test_confirm_start()
        path_parts = path.split("-")
        path_parts[-1] = ("0") * 20 + "/"
        path = "-".join(path_parts)

        response = self.client.post(
            path,
            {
                "new_password1": "anewpassword",
                "new_password2": "anewpassword",
            },
        )
        self.assertIs(response.context["validlink"], False)
        u.refresh_from_db()
        self.assertEqual(original_password, u.password)  

    def test_confirm_complete(self):
        url, path = self._test_confirm_start()
        response = self.client.post(
            path, {"new_password1": "anewpassword", "new_password2": "anewpassword"}
        )
        
        u = User.objects.get(email="staffmember@example.com")
        self.assertTrue(u.check_password("anewpassword"))
        
        self.assertNotIn(INTERNAL_RESET_SESSION_TOKEN, self.client.session)

        
        response = self.client.get(path)
        self.assertContains(response, "The password reset link was invalid")

    def test_confirm_different_passwords(self):
        url, path = self._test_confirm_start()
        response = self.client.post(
            path, {"new_password1": "anewpassword", "new_password2": "x"}
        )
        self.assertFormError(
            response, SetPasswordForm.error_messages["password_mismatch"]
        )

    def test_reset_redirect_default(self):
        response = self.client.post(
            "/password_reset/", {"email": "staffmember@example.com"}
        )
        self.assertRedirects(
            response, "/password_reset/done/", fetch_redirect_response=False
        )

    def test_reset_custom_redirect(self):
        response = self.client.post(
            "/password_reset/custom_redirect/", {"email": "staffmember@example.com"}
        )
        self.assertRedirects(response, "/custom/", fetch_redirect_response=False)

    def test_reset_custom_redirect_named(self):
        response = self.client.post(
            "/password_reset/custom_redirect/named/",
            {"email": "staffmember@example.com"},
        )
        self.assertRedirects(
            response, "/password_reset/", fetch_redirect_response=False
        )

    def test_confirm_redirect_default(self):
        url, path = self._test_confirm_start()
        response = self.client.post(
            path, {"new_password1": "anewpassword", "new_password2": "anewpassword"}
        )
        self.assertRedirects(response, "/reset/done/", fetch_redirect_response=False)

    def test_confirm_redirect_custom(self):
        url, path = self._test_confirm_start()
        path = path.replace("/reset/", "/reset/custom/")
        response = self.client.post(
            path, {"new_password1": "anewpassword", "new_password2": "anewpassword"}
        )
        self.assertRedirects(response, "/custom/", fetch_redirect_response=False)

    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_password1": "anewpassword", "new_password2": "anewpassword"}
        )
        self.assertRedirects(
            response, "/password_reset/", fetch_redirect_response=False
        )

    def test_confirm_custom_reset_url_token(self):
        url, path = self._test_confirm_start()
        path = path.replace("/reset/", "/reset/custom/token/")
        self.client.reset_url_token = "set-passwordcustom"
        response = self.client.post(
            path,
            {"new_password1": "anewpassword", "new_password2": "anewpassword"},
        )
        self.assertRedirects(response, "/reset/done/", fetch_redirect_response=False)

    def test_confirm_login_post_reset(self):
        url, path = self._test_confirm_start()
        path = path.replace("/reset/", "/reset/post_reset_login/")
        response = self.client.post(
            path, {"new_password1": "anewpassword", "new_password2": "anewpassword"}
        )
        self.assertRedirects(response, "/reset/done/", fetch_redirect_response=False)
        self.assertIn(SESSION_KEY, self.client.session)

    @override_settings(
        AUTHENTICATION_BACKENDS=[
            "django.contrib.auth.backends.ModelBackend",
            "django.contrib.auth.backends.AllowAllUsersModelBackend",
        ]
    )
    def test_confirm_login_post_reset_custom_backend(self):
        
        backend = "django.contrib.auth.backends.AllowAllUsersModelBackend"
        url, path = self._test_confirm_start()
        path = path.replace("/reset/", "/reset/post_reset_login_custom_backend/")
        response = self.client.post(
            path, {"new_password1": "anewpassword", "new_password2": "anewpassword"}
        )
        self.assertRedirects(response, "/reset/done/", fetch_redirect_response=False)
        self.assertIn(SESSION_KEY, self.client.session)
        self.assertEqual(self.client.session[BACKEND_SESSION_KEY], backend)

    def test_confirm_login_post_reset_already_logged_in(self):
        url, path = self._test_confirm_start()
        path = path.replace("/reset/", "/reset/post_reset_login/")
        self.login()
        response = self.client.post(
            path, {"new_password1": "anewpassword", "new_password2": "anewpassword"}
        )
        self.assertRedirects(response, "/reset/done/", fetch_redirect_response=False)
        self.assertIn(SESSION_KEY, self.client.session)

    def test_confirm_display_user_from_form(self):
        url, path = self._test_confirm_start()
        response = self.client.get(path)
        
        
        
        
        username = User.objects.get(email="staffmember@example.com").username
        self.assertContains(response, "Hello, %s." % username)
        
        
        response = self.client.get("/reset/zzzzzzzzzzzzz/1-1/")
        self.assertContains(response, "Hello, .")

    def test_confirm_link_redirects_to_set_password_page(self):
        url, path = self._test_confirm_start()
        
        
        client = Client()
        response = client.get(path)
        token = response.resolver_match.kwargs["token"]
        uuidb64 = response.resolver_match.kwargs["uidb64"]
        self.assertRedirects(response, "/reset/%s/set-password/" % uuidb64)
        self.assertEqual(client.session["_password_reset_token"], token)

    def test_confirm_custom_reset_url_token_link_redirects_to_set_password_page(self):
        url, path = self._test_confirm_start()
        path = path.replace("/reset/", "/reset/custom/token/")
        client = Client()
        response = client.get(path)
        token = response.resolver_match.kwargs["token"]
        uuidb64 = response.resolver_match.kwargs["uidb64"]
        self.assertRedirects(
            response, "/reset/custom/token/%s/set-passwordcustom/" % uuidb64
        )
        self.assertEqual(client.session["_password_reset_token"], token)

    def test_invalid_link_if_going_directly_to_the_final_reset_password_url(self):
        url, path = self._test_confirm_start()
        _, uuidb64, _ = path.strip("/").split("/")
        response = Client().get("/reset/%s/set-password/" % uuidb64)
        self.assertContains(response, "The password reset link was invalid")

    def test_missing_kwargs(self):
        msg = "The URL path must contain 'uidb64' and 'token' parameters."
        with self.assertRaisesMessage(ImproperlyConfigured, msg):
            self.client.get("/reset/missing_parameters/")


@override_settings(AUTH_USER_MODEL="auth_tests.CustomUser")
class CustomUserPasswordResetTest(AuthViewsTestCase):
    user_email = "staffmember@example.com"

    @classmethod
    def setUpTestData(cls):
        cls.u1 = CustomUser.custom_objects.create(
            email="staffmember@example.com",
            date_of_birth=datetime.date(1976, 11, 8),
        )
        cls.u1.set_password("password")
        cls.u1.save()

    def setUp(self):
        self.client = PasswordResetConfirmClient()

    def _test_confirm_start(self):
        
        response = self.client.post("/password_reset/", {"email": self.user_email})
        self.assertEqual(response.status_code, 302)
        self.assertEqual(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.assertIsNotNone(urlmatch, "No URL found in sent email")
        return urlmatch[0], urlmatch[1]

    def test_confirm_valid_custom_user(self):
        url, path = self._test_confirm_start()
        response = self.client.get(path)
        
        self.assertContains(response, "Please enter your new password")
        
        response = self.client.post(
            path,
            {
                "new_password1": "anewpassword",
                "new_password2": "anewpassword",
            },
        )
        self.assertRedirects(response, "/reset/done/")


@override_settings(AUTH_USER_MODEL="auth_tests.UUIDUser")
class UUIDUserPasswordResetTest(CustomUserPasswordResetTest):
    def _test_confirm_start(self):
        
        UUIDUser.objects.create_user(
            email=self.user_email,
            username="foo",
            password="foo",
        )
        return super()._test_confirm_start()

    def test_confirm_invalid_uuid(self):
        
        _, path = self._test_confirm_start()
        invalid_uidb64 = urlsafe_base64_encode(b"INVALID_UUID")
        first, _uuidb64_, second = path.strip("/").split("/")
        response = self.client.get(
            "/" + "/".join((first, invalid_uidb64, second)) + "/"
        )
        self.assertContains(response, "The password reset link was invalid")


class ChangePasswordTest(AuthViewsTestCase):
    def fail_login(self):
        response = self.client.post(
            "/login/",
            {
                "username": "testclient",
                "password": "password",
            },
        )
        self.assertFormError(
            response,
            AuthenticationForm.error_messages["invalid_login"]
            % {"username": User._meta.get_field("username").verbose_name},
        )

    def logout(self):
        self.client.post("/logout/")

    def test_password_change_fails_with_invalid_old_password(self):
        self.login()
        response = self.client.post(
            "/password_change/",
            {
                "old_password": "donuts",
                "new_password1": "password1",
                "new_password2": "password1",
            },
        )
        self.assertFormError(
            response, PasswordChangeForm.error_messages["password_incorrect"]
        )

    def test_password_change_fails_with_mismatched_passwords(self):
        self.login()
        response = self.client.post(
            "/password_change/",
            {
                "old_password": "password",
                "new_password1": "password1",
                "new_password2": "donuts",
            },
        )
        self.assertFormError(
            response, SetPasswordForm.error_messages["password_mismatch"]
        )

    def test_password_change_succeeds(self):
        self.login()
        self.client.post(
            "/password_change/",
            {
                "old_password": "password",
                "new_password1": "password1",
                "new_password2": "password1",
            },
        )
        self.fail_login()
        self.login(password="password1")

    def test_password_change_done_succeeds(self):
        self.login()
        response = self.client.post(
            "/password_change/",
            {
                "old_password": "password",
                "new_password1": "password1",
                "new_password2": "password1",
            },
        )
        self.assertRedirects(
            response, "/password_change/done/", fetch_redirect_response=False
        )

    @override_settings(LOGIN_URL="/login/")
    def test_password_change_done_fails(self):
        response = self.client.get("/password_change/done/")
        self.assertRedirects(
            response,
            "/login/?next=/password_change/done/",
            fetch_redirect_response=False,
        )

    def test_password_change_redirect_default(self):
        self.login()
        response = self.client.post(
            "/password_change/",
            {
                "old_password": "password",
                "new_password1": "password1",
                "new_password2": "password1",
            },
        )
        self.assertRedirects(
            response, "/password_change/done/", fetch_redirect_response=False
        )

    def test_password_change_redirect_custom(self):
        self.login()
        response = self.client.post(
            "/password_change/custom/",
            {
                "old_password": "password",
                "new_password1": "password1",
                "new_password2": "password1",
            },
        )
        self.assertRedirects(response, "/custom/", fetch_redirect_response=False)

    def test_password_change_redirect_custom_named(self):
        self.login()
        response = self.client.post(
            "/password_change/custom/named/",
            {
                "old_password": "password",
                "new_password1": "password1",
                "new_password2": "password1",
            },
        )
        self.assertRedirects(
            response, "/password_reset/", fetch_redirect_response=False
        )


class SessionAuthenticationTests(AuthViewsTestCase):
    def test_user_password_change_updates_session(self):
        
        self.login()
        original_session_key = self.client.session.session_key
        response = self.client.post(
            "/password_change/",
            {
                "old_password": "password",
                "new_password1": "password1",
                "new_password2": "password1",
            },
        )
        
        self.assertRedirects(response, "/password_change/done/")
        
        self.assertNotEqual(original_session_key, self.client.session.session_key)


class LoginTest(AuthViewsTestCase):
    def test_current_site_in_context_after_login(self):
        response = self.client.get(reverse("login"))
        self.assertEqual(response.status_code, 200)
        if apps.is_installed("django.contrib.sites"):
            Site = apps.get_model("sites.Site")
            site = Site.objects.get_current()
            self.assertEqual(response.context["site"], site)
            self.assertEqual(response.context["site_name"], site.name)
        else:
            self.assertIsInstance(response.context["site"], RequestSite)
        self.assertIsInstance(response.context["form"], AuthenticationForm)

    def test_security_check(self):
        login_url = reverse("login")

        
        bad_urls = (
            "http://example.com",
            "http:///example.com",
            "https://example.com",
            "ftp://example.com",
            "///example.com",
            "//example.com",
            'javascript:alert("XSS")',
        )
        for bad_url in bad_urls:
            with self.subTest(bad_url=bad_url):
                nasty_url = "%(url)s?%(next)s=%(bad_url)s" % {
                    "url": login_url,
                    "next": REDIRECT_FIELD_NAME,
                    "bad_url": quote(bad_url),
                }
                response = self.client.post(
                    nasty_url,
                    {
                        "username": "testclient",
                        "password": "password",
                    },
                )
                self.assertEqual(response.status_code, 302)
                self.assertNotIn(
                    bad_url, response.url, "%s should be blocked" % bad_url
                )

        
        good_urls = (
            "/view/?param=http://example.com",
            "/view/?param=https://example.com",
            "/view?param=ftp://example.com",
            "view/?param=//example.com",
            "https://testserver/",
            "HTTPS://testserver/",
            "//testserver/",
            "/url%20with%20spaces/",
        )
        for good_url in good_urls:
            with self.subTest(good_url=good_url):
                safe_url = "%(url)s?%(next)s=%(good_url)s" % {
                    "url": login_url,
                    "next": REDIRECT_FIELD_NAME,
                    "good_url": quote(good_url),
                }
                response = self.client.post(
                    safe_url,
                    {
                        "username": "testclient",
                        "password": "password",
                    },
                )
                self.assertEqual(response.status_code, 302)
                self.assertIn(good_url, response.url, "%s should be allowed" % good_url)

    def test_security_check_https(self):
        login_url = reverse("login")
        non_https_next_url = "http://testserver/path"
        not_secured_url = "%(url)s?%(next)s=%(next_url)s" % {
            "url": login_url,
            "next": REDIRECT_FIELD_NAME,
            "next_url": quote(non_https_next_url),
        }
        post_data = {
            "username": "testclient",
            "password": "password",
        }
        response = self.client.post(not_secured_url, post_data, secure=True)
        self.assertEqual(response.status_code, 302)
        self.assertNotEqual(response.url, non_https_next_url)
        self.assertEqual(response.url, settings.LOGIN_REDIRECT_URL)

    def test_login_form_contains_request(self):
        
        
        response = self.client.post(
            "/custom_request_auth_login/",
            {
                "username": "testclient",
                "password": "password",
            },
        )
        
        self.assertRedirects(
            response, settings.LOGIN_REDIRECT_URL, fetch_redirect_response=False
        )

    def test_login_csrf_rotate(self):
        

        def get_response(request):
            return HttpResponse()

        
        
        req = HttpRequest()
        CsrfViewMiddleware(get_response).process_view(req, LoginView.as_view(), (), {})
        
        get_token(req)
        resp = CsrfViewMiddleware(LoginView.as_view())(req)
        csrf_cookie = resp.cookies.get(settings.CSRF_COOKIE_NAME, None)
        token1 = csrf_cookie.coded_value

        
        req = HttpRequest()
        req.COOKIES[settings.CSRF_COOKIE_NAME] = token1
        req.method = "POST"
        req.POST = {
            "username": "testclient",
            "password": "password",
            "csrfmiddlewaretoken": token1,
        }

        
        SessionMiddleware(get_response).process_request(req)
        CsrfViewMiddleware(get_response).process_view(req, LoginView.as_view(), (), {})
        req.META[
            "SERVER_NAME"
        ] = "testserver"  
        req.META["SERVER_PORT"] = 80
        resp = CsrfViewMiddleware(LoginView.as_view())(req)
        csrf_cookie = resp.cookies.get(settings.CSRF_COOKIE_NAME, None)
        token2 = csrf_cookie.coded_value

        
        self.assertNotEqual(token1, token2)

    def test_session_key_flushed_on_login(self):
        
        self.login()
        original_session_key = self.client.session.session_key

        self.login(username="staff")
        self.assertNotEqual(original_session_key, self.client.session.session_key)

    def test_session_key_flushed_on_login_after_password_change(self):
        
        self.login()
        original_session_key = self.client.session.session_key

        
        self.login()
        self.assertEqual(original_session_key, self.client.session.session_key)

        user = User.objects.get(username="testclient")
        user.set_password("foobar")
        user.save()

        self.login(password="foobar")
        self.assertNotEqual(original_session_key, self.client.session.session_key)

    def test_login_session_without_hash_session_key(self):
        
        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.assertNotEqual(original_session_key, self.client.session.session_key)

    def test_login_get_default_redirect_url(self):
        response = self.login(url="/login/get_default_redirect_url/")
        self.assertRedirects(response, "/custom/", fetch_redirect_response=False)

    def test_login_next_page(self):
        response = self.login(url="/login/next_page/")
        self.assertRedirects(response, "/somewhere/", fetch_redirect_response=False)

    def test_login_named_next_page_named(self):
        response = self.login(url="/login/next_page/named/")
        self.assertRedirects(
            response, "/password_reset/", fetch_redirect_response=False
        )

    @override_settings(LOGIN_REDIRECT_URL="/custom/")
    def test_login_next_page_overrides_login_redirect_url_setting(self):
        response = self.login(url="/login/next_page/")
        self.assertRedirects(response, "/somewhere/", fetch_redirect_response=False)

    def test_login_redirect_url_overrides_next_page(self):
        response = self.login(url="/login/next_page/?next=/test/")
        self.assertRedirects(response, "/test/", fetch_redirect_response=False)

    def test_login_redirect_url_overrides_get_default_redirect_url(self):
        response = self.login(url="/login/get_default_redirect_url/?next=/test/")
        self.assertRedirects(response, "/test/", fetch_redirect_response=False)


class LoginURLSettings(AuthViewsTestCase):
    

    def assertLoginURLEquals(self, url):
        response = self.client.get("/login_required/")
        self.assertRedirects(response, url, fetch_redirect_response=False)

    @override_settings(LOGIN_URL="/login/")
    def test_standard_login_url(self):
        self.assertLoginURLEquals("/login/?next=/login_required/")

    @override_settings(LOGIN_URL="login")
    def test_named_login_url(self):
        self.assertLoginURLEquals("/login/?next=/login_required/")

    @override_settings(LOGIN_URL="http://remote.example.com/login")
    def test_remote_login_url(self):
        quoted_next = quote("http://testserver/login_required/")
        expected = "http://remote.example.com/login?next=%s" % quoted_next
        self.assertLoginURLEquals(expected)

    @override_settings(LOGIN_URL="https:///login/")
    def test_https_login_url(self):
        quoted_next = quote("http://testserver/login_required/")
        expected = "https:///login/?next=%s" % quoted_next
        self.assertLoginURLEquals(expected)

    @override_settings(LOGIN_URL="/login/?pretty=1")
    def test_login_url_with_querystring(self):
        self.assertLoginURLEquals("/login/?pretty=1&next=/login_required/")

    @override_settings(LOGIN_URL="http://remote.example.com/login/?next=/default/")
    def test_remote_login_url_with_next_querystring(self):
        quoted_next = quote("http://testserver/login_required/")
        expected = "http://remote.example.com/login/?next=%s" % quoted_next
        self.assertLoginURLEquals(expected)

    @override_settings(LOGIN_URL=reverse_lazy("login"))
    def test_lazy_login_url(self):
        self.assertLoginURLEquals("/login/?next=/login_required/")


class LoginRedirectUrlTest(AuthViewsTestCase):
    

    def assertLoginRedirectURLEqual(self, url):
        response = self.login()
        self.assertRedirects(response, url, fetch_redirect_response=False)

    def test_default(self):
        self.assertLoginRedirectURLEqual("/accounts/profile/")

    @override_settings(LOGIN_REDIRECT_URL="/custom/")
    def test_custom(self):
        self.assertLoginRedirectURLEqual("/custom/")

    @override_settings(LOGIN_REDIRECT_URL="password_reset")
    def test_named(self):
        self.assertLoginRedirectURLEqual("/password_reset/")

    @override_settings(LOGIN_REDIRECT_URL="http://remote.example.com/welcome/")
    def test_remote(self):
        self.assertLoginRedirectURLEqual("http://remote.example.com/welcome/")


class RedirectToLoginTests(AuthViewsTestCase):
    

    @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.assertEqual(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.assertEqual(expected, login_redirect_response.url)


class LogoutThenLoginTests(AuthViewsTestCase):
    

    def confirm_logged_out(self):
        self.assertNotIn(SESSION_KEY, self.client.session)

    @override_settings(LOGIN_URL="/login/")
    def test_default_logout_then_login(self):
        self.login()
        req = HttpRequest()
        req.method = "POST"
        csrf_token = get_token(req)
        req.COOKIES[settings.CSRF_COOKIE_NAME] = csrf_token
        req.POST = {"csrfmiddlewaretoken": csrf_token}
        req.META["SERVER_NAME"] = "testserver"
        req.META["SERVER_PORT"] = 80
        req.session = self.client.session
        response = logout_then_login(req)
        self.confirm_logged_out()
        self.assertRedirects(response, "/login/", fetch_redirect_response=False)

    def test_logout_then_login_with_custom_login(self):
        self.login()
        req = HttpRequest()
        req.method = "POST"
        csrf_token = get_token(req)
        req.COOKIES[settings.CSRF_COOKIE_NAME] = csrf_token
        req.POST = {"csrfmiddlewaretoken": csrf_token}
        req.META["SERVER_NAME"] = "testserver"
        req.META["SERVER_PORT"] = 80
        req.session = self.client.session
        response = logout_then_login(req, login_url="/custom/")
        self.confirm_logged_out()
        self.assertRedirects(response, "/custom/", fetch_redirect_response=False)

    @override_settings(LOGIN_URL="/login/")
    def test_default_logout_then_login_get(self):
        self.login()
        req = HttpRequest()
        req.method = "GET"
        req.META["SERVER_NAME"] = "testserver"
        req.META["SERVER_PORT"] = 80
        req.session = self.client.session
        response = logout_then_login(req)
        self.assertEqual(response.status_code, 405)


class LoginRedirectAuthenticatedUser(AuthViewsTestCase):
    dont_redirect_url = "/login/redirect_authenticated_user_default/"
    do_redirect_url = "/login/redirect_authenticated_user/"

    def test_default(self):
        
        self.login()
        response = self.client.get(self.dont_redirect_url)
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context["next"], "")

    def test_guest(self):
        
        response = self.client.get(self.do_redirect_url)
        self.assertEqual(response.status_code, 200)

    def test_redirect(self):
        
        self.login()
        response = self.client.get(self.do_redirect_url)
        self.assertRedirects(
            response, "/accounts/profile/", fetch_redirect_response=False
        )

    @override_settings(LOGIN_REDIRECT_URL="/custom/")
    def test_redirect_url(self):
        
        self.login()
        response = self.client.get(self.do_redirect_url)
        self.assertRedirects(response, "/custom/", fetch_redirect_response=False)

    def test_redirect_param(self):
        
        self.login()
        url = self.do_redirect_url + "?next=/custom_next/"
        response = self.client.get(url)
        self.assertRedirects(response, "/custom_next/", fetch_redirect_response=False)

    def test_redirect_loop(self):
        
        self.login()
        msg = (
            "Redirection loop for authenticated user detected. Check that "
            "your LOGIN_REDIRECT_URL doesn't point to a login page."
        )
        with self.settings(LOGIN_REDIRECT_URL=self.do_redirect_url):
            with self.assertRaisesMessage(ValueError, msg):
                self.client.get(self.do_redirect_url)

            url = self.do_redirect_url + "?bla=2"
            with self.assertRaisesMessage(ValueError, msg):
                self.client.get(url)

    def test_permission_required_not_logged_in(self):
        
        with self.settings(LOGIN_URL=self.do_redirect_url):
            
            response = self.client.get("/permission_required_redirect/", follow=True)
            self.assertEqual(response.status_code, 200)
            
            response = self.client.get("/permission_required_exception/", follow=True)
            self.assertEqual(response.status_code, 403)
            
            response = self.client.get(
                "/login_and_permission_required_exception/", follow=True
            )
            self.assertEqual(response.status_code, 200)

    def test_permission_required_logged_in(self):
        self.login()
        
        with self.settings(LOGIN_URL=self.do_redirect_url):
            
            with self.assertRaisesMessage(
                RedirectCycleError, "Redirect loop detected."
            ):
                self.client.get("/permission_required_redirect/", follow=True)
            
            response = self.client.get("/permission_required_exception/", follow=True)
            self.assertEqual(response.status_code, 403)
            
            response = self.client.get(
                "/login_and_permission_required_exception/", follow=True
            )
            self.assertEqual(response.status_code, 403)


class LoginSuccessURLAllowedHostsTest(AuthViewsTestCase):
    def test_success_url_allowed_hosts_same_host(self):
        response = self.client.post(
            "/login/allowed_hosts/",
            {
                "username": "testclient",
                "password": "password",
                "next": "https://testserver/home",
            },
        )
        self.assertIn(SESSION_KEY, self.client.session)
        self.assertRedirects(
            response, "https://testserver/home", fetch_redirect_response=False
        )

    def test_success_url_allowed_hosts_safe_host(self):
        response = self.client.post(
            "/login/allowed_hosts/",
            {
                "username": "testclient",
                "password": "password",
                "next": "https://otherserver/home",
            },
        )
        self.assertIn(SESSION_KEY, self.client.session)
        self.assertRedirects(
            response, "https://otherserver/home", fetch_redirect_response=False
        )

    def test_success_url_allowed_hosts_unsafe_host(self):
        response = self.client.post(
            "/login/allowed_hosts/",
            {
                "username": "testclient",
                "password": "password",
                "next": "https://evil/home",
            },
        )
        self.assertIn(SESSION_KEY, self.client.session)
        self.assertRedirects(
            response, "/accounts/profile/", fetch_redirect_response=False
        )


class LogoutTest(AuthViewsTestCase):
    def confirm_logged_out(self):
        self.assertNotIn(SESSION_KEY, self.client.session)

    def test_logout_default(self):
        "Logout without next_page option renders the default template"
        self.login()
        response = self.client.post("/logout/")
        self.assertContains(response, "Logged out")
        self.confirm_logged_out()

    def test_logout_with_post(self):
        self.login()
        response = self.client.post("/logout/")
        self.assertContains(response, "Logged out")
        self.confirm_logged_out()

    def test_14377(self):
        
        self.login()
        response = self.client.post("/logout/")
        self.assertIn("site", response.context)

    def test_logout_doesnt_cache(self):
        
        response = self.client.post("/logout/")
        self.assertIn("no-store", response.headers["Cache-Control"])

    def test_logout_with_overridden_redirect_url(self):
        
        self.login()
        response = self.client.post("/logout/next_page/")
        self.assertRedirects(response, "/somewhere/", fetch_redirect_response=False)

        response = self.client.post("/logout/next_page/?next=/login/")
        self.assertRedirects(response, "/login/", fetch_redirect_response=False)

        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.post("/logout/next_page/")
        self.assertRedirects(response, "/somewhere/", fetch_redirect_response=False)
        self.confirm_logged_out()

    def test_logout_with_redirect_argument(self):
        "Logout with query string redirects to specified resource"
        self.login()
        response = self.client.post("/logout/?next=/login/")
        self.assertRedirects(response, "/login/", fetch_redirect_response=False)
        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.post("/logout/custom_query/?follow=/somewhere/")
        self.assertRedirects(response, "/somewhere/", fetch_redirect_response=False)
        self.confirm_logged_out()

    def test_logout_with_named_redirect(self):
        "Logout resolves names or URLs passed as next_page."
        self.login()
        response = self.client.post("/logout/next_page/named/")
        self.assertRedirects(
            response, "/password_reset/", fetch_redirect_response=False
        )
        self.confirm_logged_out()

    def test_success_url_allowed_hosts_same_host(self):
        self.login()
        response = self.client.post("/logout/allowed_hosts/?next=https://testserver/")
        self.assertRedirects(
            response, "https://testserver/", fetch_redirect_response=False
        )
        self.confirm_logged_out()

    def test_success_url_allowed_hosts_safe_host(self):
        self.login()
        response = self.client.post("/logout/allowed_hosts/?next=https://otherserver/")
        self.assertRedirects(
            response, "https://otherserver/", fetch_redirect_response=False
        )
        self.confirm_logged_out()

    def test_success_url_allowed_hosts_unsafe_host(self):
        self.login()
        response = self.client.post("/logout/allowed_hosts/?next=https://evil/")
        self.assertRedirects(
            response, "/logout/allowed_hosts/", fetch_redirect_response=False
        )
        self.confirm_logged_out()

    def test_security_check(self):
        logout_url = reverse("logout")

        
        bad_urls = (
            "http://example.com",
            "http:///example.com",
            "https://example.com",
            "ftp://example.com",
            "///example.com",
            "//example.com",
            'javascript:alert("XSS")',
        )
        for bad_url in bad_urls:
            with self.subTest(bad_url=bad_url):
                nasty_url = "%(url)s?%(next)s=%(bad_url)s" % {
                    "url": logout_url,
                    "next": REDIRECT_FIELD_NAME,
                    "bad_url": quote(bad_url),
                }
                self.login()
                response = self.client.post(nasty_url)
                self.assertEqual(response.status_code, 302)
                self.assertNotIn(
                    bad_url, response.url, "%s should be blocked" % bad_url
                )
                self.confirm_logged_out()

        
        good_urls = (
            "/view/?param=http://example.com",
            "/view/?param=https://example.com",
            "/view?param=ftp://example.com",
            "view/?param=//example.com",
            "https://testserver/",
            "HTTPS://testserver/",
            "//testserver/",
            "/url%20with%20spaces/",
        )
        for good_url in good_urls:
            with self.subTest(good_url=good_url):
                safe_url = "%(url)s?%(next)s=%(good_url)s" % {
                    "url": logout_url,
                    "next": REDIRECT_FIELD_NAME,
                    "good_url": quote(good_url),
                }
                self.login()
                response = self.client.post(safe_url)
                self.assertEqual(response.status_code, 302)
                self.assertIn(good_url, response.url, "%s should be allowed" % good_url)
                self.confirm_logged_out()

    def test_security_check_https(self):
        logout_url = reverse("logout")
        non_https_next_url = "http://testserver/"
        url = "%(url)s?%(next)s=%(next_url)s" % {
            "url": logout_url,
            "next": REDIRECT_FIELD_NAME,
            "next_url": quote(non_https_next_url),
        }
        self.login()
        response = self.client.post(url, secure=True)
        self.assertRedirects(response, logout_url, fetch_redirect_response=False)
        self.confirm_logged_out()

    def test_logout_preserve_language(self):
        
        self.login()
        self.client.post("/setlang/", {"language": "pl"})
        self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, "pl")
        self.client.post("/logout/")
        self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, "pl")

    @override_settings(LOGOUT_REDIRECT_URL="/custom/")
    def test_logout_redirect_url_setting(self):
        self.login()
        response = self.client.post("/logout/")
        self.assertRedirects(response, "/custom/", fetch_redirect_response=False)

    @override_settings(LOGOUT_REDIRECT_URL="/custom/")
    def test_logout_redirect_url_setting_allowed_hosts_unsafe_host(self):
        self.login()
        response = self.client.post("/logout/allowed_hosts/?next=https://evil/")
        self.assertRedirects(response, "/custom/", fetch_redirect_response=False)

    @override_settings(LOGOUT_REDIRECT_URL="logout")
    def test_logout_redirect_url_named_setting(self):
        self.login()
        response = self.client.post("/logout/")
        self.assertContains(response, "Logged out")
        self.confirm_logged_out()


def get_perm(Model, perm):
    ct = ContentType.objects.get_for_model(Model)
    return Permission.objects.get(content_type=ct, codename=perm)




@override_settings(
    ROOT_URLCONF="auth_tests.urls_admin",
    PASSWORD_HASHERS=["django.contrib.auth.hashers.MD5PasswordHasher"],
)
class ChangelistTests(AuthViewsTestCase):
    @classmethod
    def setUpTestData(cls):
        super().setUpTestData()
        
        User.objects.filter(username="testclient").update(
            is_staff=True, is_superuser=True
        )

    def setUp(self):
        self.login()
        
        self.admin = User.objects.get(pk=self.u1.pk)

    def get_user_data(self, user):
        return {
            "username": user.username,
            "password": user.password,
            "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,
        }

    
    
    def test_changelist_disallows_password_lookups(self):
        
        with self.assertLogs("django.security.DisallowedModelAdminLookup", "ERROR"):
            response = self.client.get(
                reverse("auth_test_admin:auth_user_changelist")
                + "?password__startswith=sha1$"
            )
        self.assertEqual(response.status_code, 400)

    def test_user_change_email(self):
        data = self.get_user_data(self.admin)
        data["email"] = "new_" + data["email"]
        response = self.client.post(
            reverse("auth_test_admin:auth_user_change", args=(self.admin.pk,)), data
        )
        self.assertRedirects(response, reverse("auth_test_admin:auth_user_changelist"))
        row = LogEntry.objects.latest("id")
        self.assertEqual(row.get_change_message(), "Changed Email address.")

    def test_user_not_change(self):
        response = self.client.post(
            reverse("auth_test_admin:auth_user_change", args=(self.admin.pk,)),
            self.get_user_data(self.admin),
        )
        self.assertRedirects(response, reverse("auth_test_admin:auth_user_changelist"))
        row = LogEntry.objects.latest("id")
        self.assertEqual(row.get_change_message(), "No fields changed.")

    def test_user_change_password(self):
        user_change_url = reverse(
            "auth_test_admin:auth_user_change", args=(self.admin.pk,)
        )
        password_change_url = reverse(
            "auth_test_admin:auth_user_password_change", args=(self.admin.pk,)
        )

        response = self.client.get(user_change_url)
        
        rel_link = re.search(
            r'you can change the password using <a href="([^"]*)">this form</a>',
            response.content.decode(),
        )[1]
        self.assertEqual(urljoin(user_change_url, rel_link), password_change_url)

        response = self.client.post(
            password_change_url,
            {
                "password1": "password1",
                "password2": "password1",
            },
        )
        self.assertRedirects(response, user_change_url)
        row = LogEntry.objects.latest("id")
        self.assertEqual(row.get_change_message(), "Changed password.")
        self.logout()
        self.login(password="password1")

    def test_user_change_different_user_password(self):
        u = User.objects.get(email="staffmember@example.com")
        response = self.client.post(
            reverse("auth_test_admin:auth_user_password_change", args=(u.pk,)),
            {
                "password1": "password1",
                "password2": "password1",
            },
        )
        self.assertRedirects(
            response, reverse("auth_test_admin:auth_user_change", args=(u.pk,))
        )
        row = LogEntry.objects.latest("id")
        self.assertEqual(row.user_id, self.admin.pk)
        self.assertEqual(row.object_id, str(u.pk))
        self.assertEqual(row.get_change_message(), "Changed password.")

    def test_password_change_bad_url(self):
        response = self.client.get(
            reverse("auth_test_admin:auth_user_password_change", args=("foobar",))
        )
        self.assertEqual(response.status_code, 404)

    @mock.patch("django.contrib.auth.admin.UserAdmin.has_change_permission")
    def test_user_change_password_passes_user_to_has_change_permission(
        self, has_change_permission
    ):
        url = reverse(
            "auth_test_admin:auth_user_password_change", args=(self.admin.pk,)
        )
        self.client.post(url, {"password1": "password1", "password2": "password1"})
        (_request, user), _kwargs = has_change_permission.call_args
        self.assertEqual(user.pk, self.admin.pk)

    def test_view_user_password_is_readonly(self):
        u = User.objects.get(username="testclient")
        u.is_superuser = False
        u.save()
        original_password = u.password
        u.user_permissions.add(get_perm(User, "view_user"))
        response = self.client.get(
            reverse("auth_test_admin:auth_user_change", args=(u.pk,)),
        )
        algo, salt, hash_string = u.password.split("$")
        self.assertContains(response, '<div class="readonly">testclient</div>')
        
        self.assertContains(
            response,
            "<strong>algorithm</strong>: <bdi>%s</bdi>\n\n"
            "<strong>salt</strong>: <bdi>%s********************</bdi>\n\n"
            "<strong>hash</strong>: <bdi>%s**************************</bdi>\n\n"
            % (
                algo,
                salt[:2],
                hash_string[:6],
            ),
            html=True,
        )
        
        data = self.get_user_data(u)
        data["password"] = "shouldnotchange"
        change_url = reverse("auth_test_admin:auth_user_change", args=(u.pk,))
        response = self.client.post(change_url, data)
        self.assertEqual(response.status_code, 403)
        u.refresh_from_db()
        self.assertEqual(u.password, original_password)


@override_settings(
    AUTH_USER_MODEL="auth_tests.UUIDUser",
    ROOT_URLCONF="auth_tests.urls_custom_user_admin",
)
class UUIDUserTests(TestCase):
    def test_admin_password_change(self):
        u = UUIDUser.objects.create_superuser(
            username="uuid", email="foo@bar.com", password="test"
        )
        self.assertTrue(self.client.login(username="uuid", password="test"))

        user_change_url = reverse(
            "custom_user_admin:auth_tests_uuiduser_change", args=(u.pk,)
        )
        response = self.client.get(user_change_url)
        self.assertEqual(response.status_code, 200)

        password_change_url = reverse(
            "custom_user_admin:auth_user_password_change", args=(u.pk,)
        )
        response = self.client.get(password_change_url)
        
        self.assertContains(response, '<form method="post" id="uuiduser_form">')

        
        with connection.constraint_checks_disabled():
            response = self.client.post(
                password_change_url,
                {
                    "password1": "password1",
                    "password2": "password1",
                },
            )
        self.assertRedirects(response, user_change_url)
        row = LogEntry.objects.latest("id")
        self.assertEqual(row.user_id, 1)  
        self.assertEqual(row.object_id, str(u.pk))
        self.assertEqual(row.get_change_message(), "Changed password.")

        
        
        
        
        row.delete()