Moves user activation away from just clicking on reset password
To following the link in the password reset email
This commit is contained in:
72
common/djangoapps/student/forms.py
Normal file
72
common/djangoapps/student/forms.py
Normal 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])
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user