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/django/contrib/admin/templates/registration/password_reset_email.html
+++ b/django/contrib/admin/templates/registration/password_reset_email.html
@@ -3,7 +3,7 @@
 
 {% trans "Please go to the following page and choose a new password:" %}
 {% block reset_link %}
-{{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
+{{ protocol }}://{{ domain }}{% url 'django.contrib.auth.views.password_reset_confirm' uidb64=uid token=token %}
 {% endblock %}
 {% trans "Your username, in case you've forgotten:" %} {{ user.username }}
 
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 3dcbd84..5854367 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -5,7 +5,7 @@ from django.contrib.sites.models import get_current_site
 from django.template import Context, loader
 from django import forms
 from django.utils.translation import ugettext_lazy as _
-from django.utils.http import int_to_base36
+from django.utils.http import urlsafe_base64_encode
 
 class UserCreationForm(forms.ModelForm):
     """
@@ -138,7 +138,7 @@ class PasswordResetForm(forms.Form):
                 'email': user.email,
                 'domain': domain,
                 'site_name': site_name,
-                'uid': int_to_base36(user.id),
+                'uid': urlsafe_base64_encode(str(user.id)),
                 'user': user,
                 'token': token_generator.make_token(user),
                 '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/django/contrib/auth/tests/templates/registration/password_reset_email.html
+++ b/django/contrib/auth/tests/templates/registration/password_reset_email.html
@@ -1 +1 @@
-{{ protocol }}://{{ domain }}/reset/{{ uid }}-{{ token }}/
\ No newline at end of file
+{{ 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/django/contrib/auth/tests/views.py
+++ b/django/contrib/auth/tests/views.py
@@ -100,13 +100,13 @@ class PasswordResetTest(AuthViewsTestCase):
 
     def test_confirm_invalid_user(self):
         # Ensure that we get a 200 response for a non-existant user, not a 404
-        response = self.client.get('/reset/123456-1-1/')
+        response = self.client.get('/reset/123456/1-1/')
         self.assertEqual(response.status_code, 200)
         self.assertTrue("The password reset link was invalid" in response.content)
 
     def test_confirm_overflow_user(self):
         # Ensure that we get a 200 response for a base36 user id that overflows int
-        response = self.client.get('/reset/zzzzzzzzzzzzz-1-1/')
+        response = self.client.get('/reset/zzzzzzzzzzzzz/1-1/')
         self.assertEqual(response.status_code, 200)
         self.assertTrue("The password reset link was invalid" in response.content)
 
diff --git a/django/contrib/auth/urls.py b/django/contrib/auth/urls.py
index 42b4e8f..ed8db75 100644
--- a/django/contrib/auth/urls.py
+++ b/django/contrib/auth/urls.py
@@ -11,7 +11,7 @@ urlpatterns = patterns('',
     (r'^password_change/done/$', 'django.contrib.auth.views.password_change_done'),
     (r'^password_reset/$', 'django.contrib.auth.views.password_reset'),
     (r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'),
-    (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'),
+    (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'),
     (r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete'),
 )
 
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index eba83a2..7ecfd5c 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -5,7 +5,7 @@ from django.core.urlresolvers import reverse
 from django.http import HttpResponseRedirect, QueryDict
 from django.shortcuts import render_to_response
 from django.template import RequestContext
-from django.utils.http import base36_to_int
+from django.utils.http import urlsafe_base64_decode
 from django.utils.translation import ugettext as _
 from django.views.decorators.cache import never_cache
 from django.views.decorators.csrf import csrf_protect
@@ -173,7 +173,7 @@ def password_reset_done(request,
 
 # Doesn't need csrf_protect since no-one can guess the URL
 @never_cache
-def password_reset_confirm(request, uidb36=None, token=None,
+def password_reset_confirm(request, uidb64=None, token=None,
                            template_name='registration/password_reset_confirm.html',
                            token_generator=default_token_generator,
                            set_password_form=SetPasswordForm,
@@ -183,13 +183,13 @@ def password_reset_confirm(request, uidb36=None, token=None,
     View that checks the hash in a password reset link and presents a
     form for entering a new password.
     """
-    assert uidb36 is not None and token is not None # checked by URLconf
+    assert uidb64 is not None and token is not None # checked by URLconf
     if post_reset_redirect is None:
         post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete')
     try:
-        uid_int = base36_to_int(uidb36)
-        user = User.objects.get(id=uid_int)
-    except (ValueError, User.DoesNotExist):
+        uid = urlsafe_base64_decode(str(uidb64))
+        user = User.objects.get(id=uid)
+    except (TypeError, ValueError, User.DoesNotExist):
         user = None
 
     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/django/utils/http.py
+++ b/django/utils/http.py
@@ -1,9 +1,11 @@
+import base64
 import calendar
 import datetime
 import re
 import sys
 import urllib
 import urlparse
+from binascii import Error as BinasciiError
 from email.Utils import formatdate
 
 from django.utils.encoding import smart_str, force_unicode
@@ -168,6 +170,16 @@ def int_to_base36(i):
         factor -= 1
     return ''.join(base36)
 
+def urlsafe_base64_encode(s):
+    return base64.urlsafe_b64encode(s).rstrip('\n=')
+
+def urlsafe_base64_decode(s):
+    assert isinstance(s, str)
+    try:
+        return base64.urlsafe_b64decode(s.ljust(len(s) + len(s) % 4, '='))
+    except (LookupError, BinasciiError), e:
+        raise ValueError(e)
+
 def parse_etags(etag_str):
     """
     Parses a string with one or several etags passed in If-None-Match and
