commit c9f97bd7397a39ebcb9f8831b28d5ab3b4e57038
Author: Jonas Haag <jonas@lophus.org>
Date: Tue Jun 28 15:55:54 2011 +0200
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 3dcbd84..5854367 100644
a
|
b
|
from django.contrib.sites.models import get_current_site
|
5 | 5 | from django.template import Context, loader |
6 | 6 | from django import forms |
7 | 7 | from django.utils.translation import ugettext_lazy as _ |
8 | | from django.utils.http import int_to_base36 |
| 8 | from django.utils.http import urlsafe_base64_encode |
9 | 9 | |
10 | 10 | class UserCreationForm(forms.ModelForm): |
11 | 11 | """ |
… |
… |
class PasswordResetForm(forms.Form):
|
138 | 138 | 'email': user.email, |
139 | 139 | 'domain': domain, |
140 | 140 | 'site_name': site_name, |
141 | | 'uid': int_to_base36(user.id), |
| 141 | 'uid': urlsafe_base64_encode(str(user.id)), |
142 | 142 | 'user': user, |
143 | 143 | 'token': token_generator.make_token(user), |
144 | 144 | '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 b03489c..40fdab0 100644
a
|
b
|
class PasswordResetTest(AuthViewsTestCase):
|
100 | 100 | |
101 | 101 | def test_confirm_invalid_user(self): |
102 | 102 | # Ensure that we get a 200 response for a non-existant user, not a 404 |
103 | | response = self.client.get('/reset/123456-1-1/') |
| 103 | response = self.client.get('/reset/123456/1-1/') |
104 | 104 | self.assertEqual(response.status_code, 200) |
105 | 105 | self.assertTrue("The password reset link was invalid" in response.content) |
106 | 106 | |
107 | 107 | def test_confirm_overflow_user(self): |
108 | 108 | # Ensure that we get a 200 response for a base36 user id that overflows int |
109 | | response = self.client.get('/reset/zzzzzzzzzzzzz-1-1/') |
| 109 | response = self.client.get('/reset/zzzzzzzzzzzzz/1-1/') |
110 | 110 | self.assertEqual(response.status_code, 200) |
111 | 111 | self.assertTrue("The password reset link was invalid" in response.content) |
112 | 112 | |
diff --git a/django/contrib/auth/urls.py b/django/contrib/auth/urls.py
index 42b4e8f..ed8db75 100644
a
|
b
|
urlpatterns = patterns('',
|
11 | 11 | (r'^password_change/done/$', 'django.contrib.auth.views.password_change_done'), |
12 | 12 | (r'^password_reset/$', 'django.contrib.auth.views.password_reset'), |
13 | 13 | (r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'), |
14 | | (r'^reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', 'django.contrib.auth.views.password_reset_confirm'), |
| 14 | (r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', 'django.contrib.auth.views.password_reset_confirm'), |
15 | 15 | (r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete'), |
16 | 16 | ) |
17 | 17 | |
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index eba83a2..7ecfd5c 100644
a
|
b
|
from django.core.urlresolvers import reverse
|
5 | 5 | from django.http import HttpResponseRedirect, QueryDict |
6 | 6 | from django.shortcuts import render_to_response |
7 | 7 | from django.template import RequestContext |
8 | | from django.utils.http import base36_to_int |
| 8 | from django.utils.http import urlsafe_base64_decode |
9 | 9 | from django.utils.translation import ugettext as _ |
10 | 10 | from django.views.decorators.cache import never_cache |
11 | 11 | from django.views.decorators.csrf import csrf_protect |
… |
… |
def password_reset_done(request,
|
173 | 173 | |
174 | 174 | # Doesn't need csrf_protect since no-one can guess the URL |
175 | 175 | @never_cache |
176 | | def password_reset_confirm(request, uidb36=None, token=None, |
| 176 | def password_reset_confirm(request, uidb64=None, token=None, |
177 | 177 | template_name='registration/password_reset_confirm.html', |
178 | 178 | token_generator=default_token_generator, |
179 | 179 | set_password_form=SetPasswordForm, |
… |
… |
def password_reset_confirm(request, uidb36=None, token=None,
|
183 | 183 | View that checks the hash in a password reset link and presents a |
184 | 184 | form for entering a new password. |
185 | 185 | """ |
186 | | assert uidb36 is not None and token is not None # checked by URLconf |
| 186 | assert uidb64 is not None and token is not None # checked by URLconf |
187 | 187 | if post_reset_redirect is None: |
188 | 188 | post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete') |
189 | 189 | try: |
190 | | uid_int = base36_to_int(uidb36) |
191 | | user = User.objects.get(id=uid_int) |
192 | | except (ValueError, User.DoesNotExist): |
| 190 | uid = urlsafe_base64_decode(str(uidb64)) |
| 191 | user = User.objects.get(id=uid) |
| 192 | except (TypeError, ValueError, User.DoesNotExist): |
193 | 193 | user = None |
194 | 194 | |
195 | 195 | 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 c93a338..a839fa8 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.encoding import smart_str, force_unicode |
… |
… |
def int_to_base36(i):
|
168 | 170 | factor -= 1 |
169 | 171 | return ''.join(base36) |
170 | 172 | |
| 173 | def urlsafe_base64_encode(s): |
| 174 | return base64.urlsafe_b64encode(s).rstrip('\n=') |
| 175 | |
| 176 | def urlsafe_base64_decode(s): |
| 177 | assert isinstance(s, str) |
| 178 | try: |
| 179 | return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, '=')) |
| 180 | except (LookupError, BinasciiError), e: |
| 181 | raise ValueError(e) |
| 182 | |
171 | 183 | def parse_etags(etag_str): |
172 | 184 | """ |
173 | 185 | Parses a string with one or several etags passed in If-None-Match and |