Moves user activation away from just clicking on reset password

To following the link in the password reset email
This commit is contained in:
Jason Bau
2013-06-05 23:14:18 -07:00
parent 88060c0590
commit 4a98e2eda7
4 changed files with 93 additions and 14 deletions

View File

@@ -0,0 +1,72 @@
from django import forms
from django.utils.translation import ugettext, ugettext_lazy as _
from django.template import loader
from django.contrib.auth.models import User
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, get_hasher
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.models import get_current_site
from django.utils.http import int_to_base36
# This is a literal copy from Django 1.4.5's django.contrib.auth.forms.PasswordResetForm
# I think copy-and-paste here is somewhat better than subclassing and
# just changing the definition of clean_email, because it's less
# likely to be broken by incompatibility with a new django version.
# (If this form is good enough now, a snapshot of it ought to last a while)
class PasswordResetFormNoActive(forms.Form):
error_messages = {
'unknown': _("That e-mail address doesn't have an associated "
"user account. Are you sure you've registered?"),
'unusable': _("The user account associated with this e-mail "
"address cannot reset the password."),
}
email = forms.EmailField(label=_("E-mail"), max_length=75)
def clean_email(self):
"""
Validates that an active user exists with the given email address.
"""
email = self.cleaned_data["email"]
#The line below contains the only change, removing is_active=True
self.users_cache = User.objects.filter(email__iexact=email)
if not len(self.users_cache):
raise forms.ValidationError(self.error_messages['unknown'])
if any((user.password == UNUSABLE_PASSWORD)
for user in self.users_cache):
raise forms.ValidationError(self.error_messages['unusable'])
return email
def save(self, domain_override=None,
subject_template_name='registration/password_reset_subject.txt',
email_template_name='registration/password_reset_email.html',
use_https=False, token_generator=default_token_generator,
from_email=None, request=None):
"""
Generates a one-use only link for resetting password and sends to the
user.
"""
from django.core.mail import send_mail
for user in self.users_cache:
if not domain_override:
current_site = get_current_site(request)
site_name = current_site.name
domain = current_site.domain
else:
site_name = domain = domain_override
c = {
'email': user.email,
'domain': domain,
'site_name': site_name,
'uid': int_to_base36(user.id),
'user': user,
'token': token_generator.make_token(user),
'protocol': use_https and 'https' or 'http',
}
subject = loader.render_to_string(subject_template_name, c)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
email = loader.render_to_string(email_template_name, c)
send_mail(subject, email, from_email, [user.email])

View File

@@ -11,9 +11,9 @@ import time
from django.conf import settings
from django.contrib.auth import logout, authenticate, login
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.contrib.auth.views import password_reset_confirm
from django.core.cache import cache
from django.core.context_processors import csrf
from django.core.mail import send_mail
@@ -24,6 +24,7 @@ from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbid
from django.shortcuts import redirect
from django_future.csrf import ensure_csrf_cookie
from django.utils.http import cookie_date
from django.utils.http import base36_to_int
from mitxmako.shortcuts import render_to_response, render_to_string
from bs4 import BeautifulSoup
@@ -34,6 +35,8 @@ from student.models import (Registration, UserProfile, TestCenterUser, TestCente
CourseEnrollment, unique_id_for_user,
get_testcenter_registration, CourseEnrollmentAllowed)
from student.forms import PasswordResetFormNoActive
from certificates.models import CertificateStatuses, certificate_status_for_student
from xmodule.course_module import CourseDescriptor
@@ -962,17 +965,7 @@ def password_reset(request):
if request.method != "POST":
raise Http404
# By default, Django doesn't allow Users with is_active = False to reset their passwords,
# but this bites people who signed up a long time ago, never activated, and forgot their
# password. So for their sake, we'll auto-activate a user for whom password_reset is called.
try:
user = User.objects.get(email=request.POST['email'])
user.is_active = True
user.save()
except:
log.exception("Tried to auto-activate user to enable password reset, but failed.")
form = PasswordResetForm(request.POST)
form = PasswordResetFormNoActive(request.POST)
if form.is_valid():
form.save(use_https=request.is_secure(),
from_email=settings.DEFAULT_FROM_EMAIL,
@@ -984,6 +977,20 @@ def password_reset(request):
return HttpResponse(json.dumps({'success': False,
'error': 'Invalid e-mail'}))
def password_reset_confirm_wrapper(request, uidb36=None, token=None):
''' A wrapper around django.contrib.auth.views.password_reset_confirm.
Needed because we want to set the user as active at this step.
'''
#cribbed from django.contrib.auth.views.password_reset_confirm
try:
uid_int = base36_to_int(uidb36)
user = User.objects.get(id=uid_int)
user.is_active = True
user.save()
except (ValueError, User.DoesNotExist):
pass
return password_reset_confirm(request, uidb36=uidb36, token=token)
def reactivation_email_for_user(user):
try:

View File

@@ -3,7 +3,7 @@
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
https://{{domain}}{% url 'django.contrib.auth.views.password_reset_confirm' uidb36=uid token=token %}
https://{{domain}}{% url 'student.views.password_reset_confirm_wrapper' uidb36=uid token=token %}
{% endblock %}
If you didn't request this change, you can disregard this email - we have not yet reset your password.

View File

@@ -51,7 +51,7 @@ urlpatterns = ('', # nopep8
url(r'^password_change_done/$', django.contrib.auth.views.password_change_done,
name='auth_password_change_done'),
url(r'^password_reset_confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
django.contrib.auth.views.password_reset_confirm,
'student.views.password_reset_confirm_wrapper',
name='auth_password_reset_confirm'),
url(r'^password_reset_complete/$', django.contrib.auth.views.password_reset_complete,
name='auth_password_reset_complete'),