commit 602e933874aff1d618e60920ac862d272d3e0eaf
Author: Jonas Haag <jonas@lophus.org>
Date: Wed Sep 28 21:56:51 2011 +0200
Non-integer-pk auth password reset
diff --git a/django/contrib/admin/templates/registration/password_reset_email.html b/django/contrib/admin/templates/registration/password_reset_email.html
index de9dc79..665ea11 100644
|
a
|
b
|
|
| 3 | 3 | |
| 4 | 4 | {% trans "Please go to the following page and choose a new password:" %} |
| 5 | 5 | {% block reset_link %} |
| 6 | | {{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %} |
| | 6 | {{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb64=uid token=token %} |
| 7 | 7 | {% endblock %} |
| 8 | 8 | {% trans "Your username, in case you've forgotten:" %} {{ user.username }} |
| 9 | 9 | |
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index b97c5d7..3864341 100644
|
a
|
b
|
|
| 1 | 1 | from django import forms |
| 2 | 2 | from django.template import loader |
| 3 | | from django.utils.http import int_to_base36 |
| | 3 | from django.utils.http import urlsafe_base64_encode |
| 4 | 4 | from django.utils.translation import ugettext_lazy as _ |
| 5 | 5 | |
| 6 | 6 | from django.contrib.auth.models import User |
| … |
… |
class PasswordResetForm(forms.Form):
|
| 144 | 144 | 'email': user.email, |
| 145 | 145 | 'domain': domain, |
| 146 | 146 | 'site_name': site_name, |
| 147 | | 'uid': int_to_base36(user.id), |
| | 147 | 'uid': urlsafe_base64_encode(str(user.id)), |
| 148 | 148 | 'user': user, |
| 149 | 149 | 'token': token_generator.make_token(user), |
| 150 | 150 | 'protocol': use_https and 'https' or 'http', |
diff --git a/django/contrib/auth/tests/templates/registration/password_reset_email.html b/django/contrib/auth/tests/templates/registration/password_reset_email.html
index 1b9a482..baac2fc 100644
|
a
|
b
|
|
| 1 | | {{ protocol }}://{{ domain }}/reset/{{ uid }}-{{ token }}/ |
| 2 | | No newline at end of file |
| | 1 | {{ protocol }}://{{ domain }}/reset/{{ uid }}/{{ token }}/ |
diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py
index a0be92f..c536d30 100644
|
a
|
b
|
class PasswordResetTest(AuthViewsTestCase):
|
| 129 | 129 | |
| 130 | 130 | def test_confirm_invalid_user(self): |
| 131 | 131 | # Ensure that we get a 200 response for a non-existant user, not a 404 |
| 132 | | response = self.client.get('/reset/123456-1-1/') |
| | 132 | response = self.client.get('/reset/123456/1-1/') |
| 133 | 133 | self.assertEqual(response.status_code, 200) |
| 134 | 134 | self.assertTrue("The password reset link was invalid" in response.content) |
| 135 | 135 | |
| 136 | 136 | def test_confirm_overflow_user(self): |
| 137 | 137 | # Ensure that we get a 200 response for a base36 user id that overflows int |
| 138 | | response = self.client.get('/reset/zzzzzzzzzzzzz-1-1/') |
| | 138 | response = self.client.get('/reset/zzzzzzzzzzzzz/1-1/') |
| 139 | 139 | self.assertEqual(response.status_code, 200) |
| 140 | 140 | self.assertTrue("The password reset link was invalid" in response.content) |
| 141 | 141 | |
diff --git a/django/contrib/auth/urls.py b/django/contrib/auth/urls.py
index c5e87ed..85b8b28 100644
|
a
|
b
|
urlpatterns = patterns('',
|
| 12 | 12 | url(r'^password_change/done/$', 'django.contrib.auth.views.password_change_done', name='password_change_done'), |
| 13 | 13 | url(r'^password_reset/$', 'django.contrib.auth.views.password_reset', name='password_reset'), |
| 14 | 14 | url(r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done', name='password_reset_done'), |
| 15 | | url(r'^reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', |
| | 15 | url(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', |
| 16 | 16 | 'django.contrib.auth.views.password_reset_confirm', |
| 17 | 17 | name='password_reset_confirm'), |
| 18 | 18 | url(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete', name='password_reset_complete'), |
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index c86ef53..86c0d47 100644
|
a
|
b
|
from django.conf import settings
|
| 4 | 4 | from django.core.urlresolvers import reverse |
| 5 | 5 | from django.http import HttpResponseRedirect, QueryDict |
| 6 | 6 | from django.template.response import TemplateResponse |
| 7 | | from django.utils.http import base36_to_int |
| | 7 | from django.utils.http import urlsafe_base64_decode |
| 8 | 8 | from django.utils.translation import ugettext as _ |
| 9 | 9 | from django.views.decorators.debug import sensitive_post_parameters |
| 10 | 10 | from django.views.decorators.cache import never_cache |
| … |
… |
def password_reset_done(request,
|
| 181 | 181 | # Doesn't need csrf_protect since no-one can guess the URL |
| 182 | 182 | @sensitive_post_parameters() |
| 183 | 183 | @never_cache |
| 184 | | def password_reset_confirm(request, uidb36=None, token=None, |
| | 184 | def password_reset_confirm(request, uidb64=None, token=None, |
| 185 | 185 | template_name='registration/password_reset_confirm.html', |
| 186 | 186 | token_generator=default_token_generator, |
| 187 | 187 | set_password_form=SetPasswordForm, |
| … |
… |
def password_reset_confirm(request, uidb36=None, token=None,
|
| 191 | 191 | View that checks the hash in a password reset link and presents a |
| 192 | 192 | form for entering a new password. |
| 193 | 193 | """ |
| 194 | | assert uidb36 is not None and token is not None # checked by URLconf |
| | 194 | assert uidb64 is not None and token is not None # checked by URLconf |
| 195 | 195 | if post_reset_redirect is None: |
| 196 | 196 | post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete') |
| 197 | 197 | try: |
| 198 | | uid_int = base36_to_int(uidb36) |
| 199 | | user = User.objects.get(id=uid_int) |
| 200 | | except (ValueError, User.DoesNotExist): |
| | 198 | uid = urlsafe_base64_decode(str(uidb64)) |
| | 199 | user = User.objects.get(id=uid) |
| | 200 | except (TypeError, ValueError, User.DoesNotExist): |
| 201 | 201 | user = None |
| 202 | 202 | |
| 203 | 203 | if user is not None and token_generator.check_token(user, token): |
diff --git a/django/utils/http.py b/django/utils/http.py
index af44ff4..b1bb7bf 100644
|
a
|
b
|
|
| | 1 | import base64 |
| 1 | 2 | import calendar |
| 2 | 3 | import datetime |
| 3 | 4 | import re |
| 4 | 5 | import sys |
| 5 | 6 | import urllib |
| 6 | 7 | import urlparse |
| | 8 | from binascii import Error as BinasciiError |
| 7 | 9 | from email.utils import formatdate |
| 8 | 10 | |
| 9 | 11 | from django.utils.datastructures import MultiValueDict |
| … |
… |
def int_to_base36(i):
|
| 171 | 173 | factor -= 1 |
| 172 | 174 | return ''.join(base36) |
| 173 | 175 | |
| | 176 | def urlsafe_base64_encode(s): |
| | 177 | return base64.urlsafe_b64encode(s).rstrip('\n=') |
| | 178 | |
| | 179 | def urlsafe_base64_decode(s): |
| | 180 | assert isinstance(s, str) |
| | 181 | try: |
| | 182 | return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, '=')) |
| | 183 | except (LookupError, BinasciiError), e: |
| | 184 | raise ValueError(e) |
| | 185 | |
| 174 | 186 | def parse_etags(etag_str): |
| 175 | 187 | """ |
| 176 | 188 | Parses a string with one or several etags passed in If-None-Match and |