Testing Django 1-5 Reset Password Form - how to generate the token for the test?

3.5k views Asked by At

With the following test, the token is not recognised as valid. In my manual test, it's working so I'm missing something in the way the password is generated I guess.

def test_actual_reset_password(self):
    new_password = "myNewPassword012*"
    token_generator = PasswordResetTokenGenerator()
    user = UserFactory.create()
    token = token_generator.make_token(user=user)

    response = self.assert_page_loading(path="/forgot-password/reset/{0}/".format(token))
    print response 
    # That loads the page with the error message mentioning that the token was already used        

    # So I cannot carry on:
    form = response.form
    form['new_password1'] = new_password
    form['new_password2'] = new_password

    response = form.submit()

In the django source code, in the PasswordResetForm, I've found this code; I can't see what the difference is:

def save(self, ..., token_generator=default_token_generator, ...):
    """
    Generates a one-use only link for resetting password and sends to the
    user.
    """
    ...
    for user in self.users_cache:
        ...
        c = {
            ...
            'token': token_generator.make_token(user),
            ...
        }
        ...
        send_mail(subject, email, from_email, [user.email])
3

There are 3 answers

6
Jared Martin On

Ok, I was just searching for info on how to do this and your question prompted me to figure it out myself. I'm not sure if you're still working on this, but here's how I got it to work:

from django.core import mail
# First we get the initial password reset form.  
# This is not strictly necessary, but I included it for completeness
response = self.c.get(reverse('password_reset'))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.template_name, 'authentication/password_reset_form.html')

# Then we post the response with our "email address"
response = self.c.post(reverse('password_reset'),{'email':'[email protected]'})
self.assertEqual(response.status_code, 302)
# At this point the system will "send" us an email. We can "check" it thusly:
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, 'Password reset on example.com')

# Now, here's the kicker: we get the token and userid from the response
token = response.context[0]['token']
uid = response.context[0]['uid']
# Now we can use the token to get the password change form
response = self.c.get(reverse('password_reset_confirm', kwargs={'token':token,'uidb64':uid}))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.template_name, 'authentication/password_reset_confirm.html')

# Now we post to the same url with our new password:
response = self.c.post(reverse('password_reset_confirm', 
    kwargs={'token':token,'uidb36':uid}), {'new_password1':'pass','new_password2':'pass'})
self.assertEqual(response.status_code, 302)

And that's it! Not so hard after all.

0
iblanco On

This works for Django 4.1:

    def test_can_reset_password(self) -> None:
        user = UserMother.random()

        uid = urlsafe_base64_encode(force_bytes(user.pk))
        token = default_token_generator.make_token(user)

        url = self._get_url(uid, token)
        redirect_response = self.client.get(url)
        redirected_url = self._get_url(uid, "set-password")
        self.assertRedirects(
            redirect_response, redirected_url, fetch_redirect_response=True
        )

        form = {"new_password1": "NEW_PASSWORD", "new_password2": "NEW_PASSWORD"}
        response = self.client.post(redirected_url, form)
        self.assertRedirects(response, self.URL_COMPLETE, fetch_redirect_response=False)

        updated_user = User.objects.get(id=user.id)
        self.assertTrue(updated_user.check_password(form["new_password1"]))

The relevant imports are:

from django.contrib.auth.tokens import default_token_generator
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
0
dan-klasson On

This is how I did it for a functional test:

def test_password_reset_from_key(self):
    from django.contrib.auth.tokens import default_token_generator
    from django.utils.http import base36_to_int, int_to_base36
    user = User.objects.all()[:1].get()
    token = default_token_generator.make_token(user)

    self.get("/accounts/password/reset/key/%s-%s/" % (int_to_base36(user.id), token))
    self.selenium.find_element_by_name("password1").send_keys("password")
    self.selenium.find_element_by_name("password2").send_keys("password")
    self.selenium.find_element_by_name("action").submit()

    alert = self.selenium.find_element_by_css_selector(".alert-success")
    self.assertIn('Password successfully changed.', alert.text)