Example #1
0
def activate_account(activation_key):
    """Activate a user's account.

    Args:
        activation_key (unicode): The activation key the user received via email.

    Returns:
        None

    Raises:
        errors.UserNotAuthorized
        errors.UserAPIInternalError: the operation failed due to an unexpected error.

    """
    # TODO: Confirm this `activate_account` is only used for tests. If so, this should not be used for tests, and we
    # should instead use the `activate_account` used for /activate.
    set_custom_metric('user_api_activate_account', 'True')
    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG)
    try:
        registration = Registration.objects.get(activation_key=activation_key)
    except Registration.DoesNotExist:
        raise errors.UserNotAuthorized
    else:
        # This implicitly saves the registration
        registration.activate()
Example #2
0
    def dispatch(self, *args, **kwargs):
        self.uidb36 = kwargs.get('uidb36')
        self.token = kwargs.get('token')
        self.uidb64 = _uidb36_to_uidb64(self.uidb36)

        # User can not get this link unless account recovery feature is enabled.
        if 'is_account_recovery' in self.request.GET and not is_secondary_email_feature_enabled(
        ):
            raise Http404

        response = self._set_user(self.request)
        if response:
            return response
        if UserRetirementRequest.has_user_requested_retirement(self.user):
            return self._handle_retired_user(self.request)
        if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
            return self._handle_system_unavailability(self.request)

        if self.request.method == 'POST':
            return self.post(self.request, *args, **kwargs)
        else:
            self._set_token_in_session(self.request, self.token)
            token = self.reset_url_token
            response = super(PasswordResetConfirmWrapper,
                             self).dispatch(self.request,
                                            uidb64=self.uidb64,
                                            token=token,
                                            extra_context=self.platform_name)
            response_was_successful = response.context_data.get('validlink')
            if response_was_successful and not self.user.is_active:
                self.user.is_active = True
                self.user.save()
            return response
Example #3
0
def activate_account_studio(request, key):
    """
    When link in activation e-mail is clicked and the link belongs to studio.
    """
    try:
        registration = Registration.objects.get(activation_key=key)
    except (Registration.DoesNotExist, Registration.MultipleObjectsReturned):
        return render_to_response(
            "registration/activation_invalid.html",
            {'csrf': csrf(request)['csrf_token']}
        )
    else:
        user_logged_in = request.user.is_authenticated
        already_active = True
        if not registration.user.is_active:
            if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
                return render_to_response('registration/activation_invalid.html',
                                          {'csrf': csrf(request)['csrf_token']})
            registration.activate()
            already_active = False

        return render_to_response(
            "registration/activation_complete.html",
            {
                'user_logged_in': user_logged_in,
                'already_active': already_active
            }
        )
Example #4
0
 def test_activate_account_prevent_auth_user_writes(self):
     activation_key = create_account(self.USERNAME, self.PASSWORD,
                                     self.EMAIL)
     with pytest.raises(UserAPIInternalError,
                        message=SYSTEM_MAINTENANCE_MSG):
         with waffle().override(PREVENT_AUTH_USER_WRITES, True):
             activate_account(activation_key)
Example #5
0
def activate_account_studio(request, key):
    """
    When link in activation e-mail is clicked and the link belongs to studio.
    """
    try:
        registration = Registration.objects.get(activation_key=key)
    except (Registration.DoesNotExist, Registration.MultipleObjectsReturned):
        return render_to_response(
            "registration/activation_invalid.html",
            {'csrf': csrf(request)['csrf_token']}
        )
    else:
        user_logged_in = request.user.is_authenticated
        already_active = True
        if not registration.user.is_active:
            if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
                return render_to_response('registration/activation_invalid.html',
                                          {'csrf': csrf(request)['csrf_token']})
            registration.activate()
            already_active = False

        return render_to_response(
            "registration/activation_complete.html",
            {
                'user_logged_in': user_logged_in,
                'already_active': already_active
            }
        )
Example #6
0
def update_last_login(sender, user, **kwargs):  # pylint: disable=unused-argument
    """
    Replacement for Django's ``user_logged_in`` signal handler that knows not
    to attempt updating the ``last_login`` field when we're trying to avoid
    writes to the ``auth_user`` table while running a migration.
    """
    if not waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        user.last_login = timezone.now()
        user.save(update_fields=['last_login'])
Example #7
0
def update_last_login(sender, user, **kwargs):  # pylint: disable=unused-argument
    """
    Replacement for Django's ``user_logged_in`` signal handler that knows not
    to attempt updating the ``last_login`` field when we're trying to avoid
    writes to the ``auth_user`` table while running a migration.
    """
    if not waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        user.last_login = timezone.now()
        user.save(update_fields=['last_login'])
 def test_account_activation_prevent_auth_user_writes(self):
     login_page_url = "{login_url}?next={redirect_url}".format(
         login_url=reverse('signin_user'),
         redirect_url=reverse('dashboard'),
     )
     with waffle().override(PREVENT_AUTH_USER_WRITES, True):
         response = self.client.get(reverse('activate', args=[self.registration.activation_key]), follow=True)
         self.assertRedirects(response, login_page_url)
         self.assertContains(response, SYSTEM_MAINTENANCE_MSG)
         assert not self.user.is_active
 def test_account_activation_prevent_auth_user_writes(self):
     login_page_url = "{login_url}?next={redirect_url}".format(
         login_url=reverse('signin_user'),
         redirect_url=reverse('dashboard'),
     )
     with waffle().override(PREVENT_AUTH_USER_WRITES, True):
         response = self.client.get(reverse('activate', args=[self.registration.activation_key]), follow=True)
         self.assertRedirects(response, login_page_url)
         self.assertContains(response, SYSTEM_MAINTENANCE_MSG)
         assert not self.user.is_active
 def test_password_reset_prevent_auth_user_writes(self):
     with waffle().override(PREVENT_AUTH_USER_WRITES, True):
         url = reverse(
             "password_reset_confirm",
             kwargs={"uidb36": self.uidb36, "token": self.token}
         )
         for request in [self.request_factory.get(url), self.request_factory.post(url)]:
             response = password_reset_confirm_wrapper(request, self.uidb36, self.token)
             assert response.context_data['err_msg'] == SYSTEM_MAINTENANCE_MSG
             self.user.refresh_from_db()
             assert not self.user.is_active
Example #11
0
 def test_password_reset_prevent_auth_user_writes(self):
     with waffle().override(PREVENT_AUTH_USER_WRITES, True):
         url = reverse(
             "password_reset_confirm",
             kwargs={"uidb36": self.uidb36, "token": self.token}
         )
         for request in [self.request_factory.get(url), self.request_factory.post(url)]:
             response = password_reset_confirm_wrapper(request, self.uidb36, self.token)
             assert response.context_data['err_msg'] == SYSTEM_MAINTENANCE_MSG
             self.user.refresh_from_db()
             assert not self.user.is_active
Example #12
0
def create_account(request, post_override=None):
    """
    Deprecated. Use RegistrationView instead.
    JSON call to create new edX account.
    Used by form in signup_modal.html, which is included into header.html
    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)):
        return HttpResponseForbidden(_("Account creation not allowed."))

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        return HttpResponseForbidden(SYSTEM_MAINTENANCE_MSG)

    warnings.warn("Please use RegistrationView instead.", DeprecationWarning)

    try:
        user = create_account_with_params(request, post_override
                                          or request.POST)
    except AccountValidationError as exc:
        return JsonResponse(
            {
                'success': False,
                'value': text_type(exc),
                'field': exc.field
            },
            status=400)
    except ValidationError as exc:
        field, error_list = next(iteritems(exc.message_dict))
        return JsonResponse(
            {
                "success": False,
                "field": field,
                "value": ' '.join(error_list),
            },
            status=400)

    redirect_url = None  # The AJAX method calling should know the default destination upon success

    # Resume the third-party-auth pipeline if necessary.
    if third_party_auth.is_enabled() and pipeline.running(request):
        running_pipeline = pipeline.get(request)
        redirect_url = pipeline.get_complete_url(running_pipeline['backend'])

    response = JsonResponse({
        'success': True,
        'redirect_url': redirect_url,
    })
    set_logged_in_cookies(request, response, user)
    return response
Example #13
0
def create_account(request, post_override=None):
    """
    Deprecated. Use RegistrationView instead.
    JSON call to create new edX account.
    Used by form in signup_modal.html, which is included into header.html
    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
    ):
        return HttpResponseForbidden(_("Account creation not allowed."))

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        return HttpResponseForbidden(SYSTEM_MAINTENANCE_MSG)

    warnings.warn("Please use RegistrationView instead.", DeprecationWarning)

    try:
        user = create_account_with_params(request, post_override or request.POST)
    except AccountValidationError as exc:
        return JsonResponse({'success': False, 'value': text_type(exc), 'field': exc.field}, status=400)
    except ValidationError as exc:
        field, error_list = next(iteritems(exc.message_dict))
        return JsonResponse(
            {
                "success": False,
                "field": field,
                "value": ' '.join(error_list),
            },
            status=400
        )

    redirect_url = None  # The AJAX method calling should know the default destination upon success

    # Resume the third-party-auth pipeline if necessary.
    if third_party_auth.is_enabled() and pipeline.running(request):
        running_pipeline = pipeline.get(request)
        redirect_url = pipeline.get_complete_url(running_pipeline['backend'])

    response = JsonResponse({
        'success': True,
        'redirect_url': redirect_url,
    })
    set_logged_in_cookies(request, response, user)
    return response
Example #14
0
def activate_account(activation_key):
    """Activate a user's account.

    Args:
        activation_key (unicode): The activation key the user received via email.

    Returns:
        None

    Raises:
        errors.UserNotAuthorized
        errors.UserAPIInternalError: the operation failed due to an unexpected error.

    """
    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG)
    try:
        registration = Registration.objects.get(activation_key=activation_key)
    except Registration.DoesNotExist:
        raise errors.UserNotAuthorized
    else:
        # This implicitly saves the registration
        registration.activate()
Example #15
0
def activate_account(activation_key):
    """Activate a user's account.

    Args:
        activation_key (unicode): The activation key the user received via email.

    Returns:
        None

    Raises:
        errors.UserNotAuthorized
        errors.UserAPIInternalError: the operation failed due to an unexpected error.

    """
    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG)
    try:
        registration = Registration.objects.get(activation_key=activation_key)
    except Registration.DoesNotExist:
        raise errors.UserNotAuthorized
    else:
        # This implicitly saves the registration
        registration.activate()
Example #16
0
 def test_login_success_prevent_auth_user_writes(self):
     with waffle().override(PREVENT_AUTH_USER_WRITES, True):
         old_last_login = self.user.last_login
         self.test_login_success()
         self.user.refresh_from_db()
         assert old_last_login == self.user.last_login
Example #17
0
def activate_account(request, key):
    """
    When link in activation e-mail is clicked
    """
    # If request is in Studio call the appropriate view
    if theming_helpers.get_project_root_name().lower() == u'cms':
        return activate_account_studio(request, key)

    try:
        registration = Registration.objects.get(activation_key=key)
    except (Registration.DoesNotExist, Registration.MultipleObjectsReturned):
        messages.error(
            request,
            HTML(_(
                '{html_start}Your account could not be activated{html_end}'
                'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.'
            )).format(
                support_url=configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK),
                html_start=HTML('<p class="message-title">'),
                html_end=HTML('</p>'),
            ),
            extra_tags='account-activation aa-icon'
        )
    else:
        if registration.user.is_active:
            messages.info(
                request,
                HTML(_('{html_start}This account has already been activated.{html_end}')).format(
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                ),
                extra_tags='account-activation aa-icon',
            )
        elif waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
            messages.error(
                request,
                HTML(u'{html_start}{message}{html_end}').format(
                    message=Text(SYSTEM_MAINTENANCE_MSG),
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                ),
                extra_tags='account-activation aa-icon',
            )
        else:
            registration.activate()
            # Success message for logged in users.
            message = _('{html_start}Success{html_end} You have activated your account.')

            if not request.user.is_authenticated:
                # Success message for logged out users
                message = _(
                    '{html_start}Success! You have activated your account.{html_end}'
                    'You will now receive email updates and alerts from us related to'
                    ' the courses you are enrolled in. Sign In to continue.'
                )

            # Add message for later use.
            messages.success(
                request,
                HTML(message).format(
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                ),
                extra_tags='account-activation aa-icon',
            )

    return redirect('dashboard')
Example #18
0
def confirm_email_change(request, key):  # pylint: disable=unused-argument
    """
    User requested a new e-mail. This is called when the activation
    link is clicked. We confirm with the old e-mail, and update
    """
    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        return render_to_response('email_change_failed.html',
                                  {'err_msg': SYSTEM_MAINTENANCE_MSG})

    with transaction.atomic():
        try:
            pec = PendingEmailChange.objects.get(activation_key=key)
        except PendingEmailChange.DoesNotExist:
            response = render_to_response("invalid_email_key.html", {})
            transaction.set_rollback(True)
            return response

        user = pec.user
        address_context = {'old_email': user.email, 'new_email': pec.new_email}

        if len(User.objects.filter(email=pec.new_email)) != 0:
            response = render_to_response("email_exists.html", {})
            transaction.set_rollback(True)
            return response

        subject = render_to_string('emails/email_change_subject.txt',
                                   address_context)
        subject = ''.join(subject.splitlines())
        message = render_to_string('emails/confirm_email_change.txt',
                                   address_context)
        u_prof = UserProfile.objects.get(user=user)
        meta = u_prof.get_meta()
        if 'old_emails' not in meta:
            meta['old_emails'] = []
        meta['old_emails'].append(
            [user.email, datetime.datetime.now(UTC).isoformat()])
        u_prof.set_meta(meta)
        u_prof.save()
        # Send it to the old email...
        try:
            user.email_user(
                subject, message,
                configuration_helpers.get_value('email_from_address',
                                                settings.DEFAULT_FROM_EMAIL))
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to old address',
                        exc_info=True)
            response = render_to_response("email_change_failed.html",
                                          {'email': user.email})
            transaction.set_rollback(True)
            return response

        user.email = pec.new_email
        user.save()
        pec.delete()
        # And send it to the new email...
        try:
            user.email_user(
                subject, message,
                configuration_helpers.get_value('email_from_address',
                                                settings.DEFAULT_FROM_EMAIL))
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to new address',
                        exc_info=True)
            response = render_to_response("email_change_failed.html",
                                          {'email': pec.new_email})
            transaction.set_rollback(True)
            return response

        response = render_to_response("email_change_successful.html",
                                      address_context)
        return response
Example #19
0
 def test_activate_account_prevent_auth_user_writes(self):
     activation_key = create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
     with pytest.raises(UserAPIInternalError, message=SYSTEM_MAINTENANCE_MSG):
         with waffle().override(PREVENT_AUTH_USER_WRITES, True):
             activate_account(activation_key)
Example #20
0
def create_account(username, password, email):
    """Create a new user account.

    This will implicitly create an empty profile for the user.

    WARNING: This function does NOT yet implement all the features
    in `student/views.py`.  Until it does, please use this method
    ONLY for tests of the account API, not in production code.
    In particular, these are currently missing:

    * 3rd party auth
    * External auth (shibboleth)
    * Complex password policies (ENFORCE_PASSWORD_POLICY)

    In addition, we assume that some functionality is handled
    at higher layers:

    * Analytics events
    * Activation email
    * Terms of service / honor code checking
    * Recording demographic info (use profile API)
    * Auto-enrollment in courses (if invited via instructor dash)

    Args:
        username (unicode): The username for the new account.
        password (unicode): The user's password.
        email (unicode): The email address associated with the account.

    Returns:
        unicode: an activation key for the account.

    Raises:
        errors.AccountUserAlreadyExists
        errors.AccountUsernameInvalid
        errors.AccountEmailInvalid
        errors.AccountPasswordInvalid
        errors.UserAPIInternalError: the operation failed due to an unexpected error.

    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
    ):
        return HttpResponseForbidden(_("Account creation not allowed."))

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG)

    # Validate the username, password, and email
    # This will raise an exception if any of these are not in a valid format.
    _validate_username(username)
    _validate_password(password, username)
    _validate_email(email)

    # Create the user account, setting them to "inactive" until they activate their account.
    user = User(username=username, email=email, is_active=False)
    user.set_password(password)

    try:
        user.save()
    except IntegrityError:
        raise errors.AccountUserAlreadyExists

    # Create a registration to track the activation process
    # This implicitly saves the registration.
    registration = Registration()
    registration.register(user)

    # Create an empty user profile with default values
    UserProfile(user=user).save()

    # Return the activation key, which the caller should send to the user
    return registration.activation_key
Example #21
0
def confirm_email_change(request, key):  # pylint: disable=unused-argument
    """
    User requested a new e-mail. This is called when the activation
    link is clicked. We confirm with the old e-mail, and update
    """
    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        return render_to_response('email_change_failed.html', {'err_msg': SYSTEM_MAINTENANCE_MSG})

    with transaction.atomic():
        try:
            pec = PendingEmailChange.objects.get(activation_key=key)
        except PendingEmailChange.DoesNotExist:
            response = render_to_response("invalid_email_key.html", {})
            transaction.set_rollback(True)
            return response

        user = pec.user
        address_context = {
            'old_email': user.email,
            'new_email': pec.new_email
        }

        if len(User.objects.filter(email=pec.new_email)) != 0:
            response = render_to_response("email_exists.html", {})
            transaction.set_rollback(True)
            return response

        use_https = request.is_secure()
        if settings.FEATURES['ENABLE_MKTG_SITE']:
            contact_link = marketing_link('CONTACT')
        else:
            contact_link = '{protocol}://{site}{link}'.format(
                protocol='https' if use_https else 'http',
                site=configuration_helpers.get_value('SITE_NAME', settings.SITE_NAME),
                link=reverse('contact'),
            )

        site = Site.objects.get_current()
        message_context = get_base_template_context(site)
        message_context.update({
            'old_email': user.email,
            'new_email': pec.new_email,
            'contact_link': contact_link,
            'from_address': configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL),
        })

        msg = EmailChangeConfirmation().personalize(
            recipient=Recipient(user.username, user.email),
            language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
            user_context=message_context,
        )

        u_prof = UserProfile.objects.get(user=user)
        meta = u_prof.get_meta()
        if 'old_emails' not in meta:
            meta['old_emails'] = []
        meta['old_emails'].append([user.email, datetime.datetime.now(UTC).isoformat()])
        u_prof.set_meta(meta)
        u_prof.save()
        # Send it to the old email...
        try:
            ace.send(msg)
        except Exception:    # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to old address', exc_info=True)
            response = render_to_response("email_change_failed.html", {'email': user.email})
            transaction.set_rollback(True)
            return response

        user.email = pec.new_email
        user.save()
        pec.delete()
        # And send it to the new email...
        msg.recipient = Recipient(user.username, pec.new_email)
        try:
            ace.send(msg)
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to new address', exc_info=True)
            response = render_to_response("email_change_failed.html", {'email': pec.new_email})
            transaction.set_rollback(True)
            return response

        response = render_to_response("email_change_successful.html", address_context)
        return response
Example #22
0
 def test_prevent_auth_user_writes(self, email_user):  # pylint: disable=unused-argument
     with waffle().override(PREVENT_AUTH_USER_WRITES, True):
         self.check_confirm_email_change('email_change_failed.html', {
             'err_msg': SYSTEM_MAINTENANCE_MSG
         })
         self.assertRolledBack()
Example #23
0
def confirm_email_change(request, key):  # pylint: disable=unused-argument
    """
    User requested a new e-mail. This is called when the activation
    link is clicked. We confirm with the old e-mail, and update
    """
    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        return render_to_response('email_change_failed.html',
                                  {'err_msg': SYSTEM_MAINTENANCE_MSG})

    with transaction.atomic():
        try:
            pec = PendingEmailChange.objects.get(activation_key=key)
        except PendingEmailChange.DoesNotExist:
            response = render_to_response("invalid_email_key.html", {})
            transaction.set_rollback(True)
            return response

        user = pec.user
        address_context = {'old_email': user.email, 'new_email': pec.new_email}

        if len(User.objects.filter(email=pec.new_email)) != 0:
            response = render_to_response("email_exists.html", {})
            transaction.set_rollback(True)
            return response

        use_https = request.is_secure()
        if settings.FEATURES['ENABLE_MKTG_SITE']:
            contact_link = marketing_link('CONTACT')
        else:
            contact_link = '{protocol}://{site}{link}'.format(
                protocol='https' if use_https else 'http',
                site=configuration_helpers.get_value('SITE_NAME',
                                                     settings.SITE_NAME),
                link=reverse('contact'),
            )

        site = Site.objects.get_current()
        message_context = get_base_template_context(site)
        message_context.update({
            'old_email':
            user.email,
            'new_email':
            pec.new_email,
            'contact_link':
            contact_link,
            'from_address':
            configuration_helpers.get_value('email_from_address',
                                            settings.DEFAULT_FROM_EMAIL),
        })

        msg = EmailChangeConfirmation().personalize(
            recipient=Recipient(user.username, user.email),
            language=preferences_api.get_user_preference(user, LANGUAGE_KEY),
            user_context=message_context,
        )

        u_prof = UserProfile.objects.get(user=user)
        meta = u_prof.get_meta()
        if 'old_emails' not in meta:
            meta['old_emails'] = []
        meta['old_emails'].append(
            [user.email, datetime.datetime.now(UTC).isoformat()])
        u_prof.set_meta(meta)
        u_prof.save()
        # Send it to the old email...
        try:
            ace.send(msg)
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to old address',
                        exc_info=True)
            response = render_to_response("email_change_failed.html",
                                          {'email': user.email})
            transaction.set_rollback(True)
            return response

        user.email = pec.new_email
        user.save()
        pec.delete()
        # And send it to the new email...
        msg.recipient = Recipient(user.username, pec.new_email)
        try:
            ace.send(msg)
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to new address',
                        exc_info=True)
            response = render_to_response("email_change_failed.html",
                                          {'email': pec.new_email})
            transaction.set_rollback(True)
            return response

        response = render_to_response("email_change_successful.html",
                                      address_context)
        return response
Example #24
0
 def test_create_account_prevent_auth_user_writes(self):
     with waffle().override(PREVENT_AUTH_USER_WRITES, True):
         response = self.client.get(self.url)
         assert response.status_code == 403
Example #25
0
def create_account(username, password, email):
    """Create a new user account.

    This will implicitly create an empty profile for the user.

    WARNING: This function does NOT yet implement all the features
    in `student/views.py`.  Until it does, please use this method
    ONLY for tests of the account API, not in production code.
    In particular, these are currently missing:

    * 3rd party auth
    * External auth (shibboleth)
    * Complex password policies (ENFORCE_PASSWORD_POLICY)

    In addition, we assume that some functionality is handled
    at higher layers:

    * Analytics events
    * Activation email
    * Terms of service / honor code checking
    * Recording demographic info (use profile API)
    * Auto-enrollment in courses (if invited via instructor dash)

    Args:
        username (unicode): The username for the new account.
        password (unicode): The user's password.
        email (unicode): The email address associated with the account.

    Returns:
        unicode: an activation key for the account.

    Raises:
        errors.AccountUserAlreadyExists
        errors.AccountUsernameInvalid
        errors.AccountEmailInvalid
        errors.AccountPasswordInvalid
        errors.UserAPIInternalError: the operation failed due to an unexpected error.

    """
    # Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
    if not configuration_helpers.get_value(
            'ALLOW_PUBLIC_ACCOUNT_CREATION',
            settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)):
        return HttpResponseForbidden(_("Account creation not allowed."))

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG)

    # Validate the username, password, and email
    # This will raise an exception if any of these are not in a valid format.
    _validate_username(username)
    _validate_password(password, username)
    _validate_email(email)

    # Create the user account, setting them to "inactive" until they activate their account.
    user = User(username=username, email=email, is_active=False)
    user.set_password(password)

    try:
        user.save()
    except IntegrityError:
        raise errors.AccountUserAlreadyExists

    # Create a registration to track the activation process
    # This implicitly saves the registration.
    registration = Registration()
    registration.register(user)

    # Create an empty user profile with default values
    UserProfile(user=user).save()

    # Return the activation key, which the caller should send to the user
    return registration.activation_key
Example #26
0
def confirm_email_change(request, key):  # pylint: disable=unused-argument
    """
    User requested a new e-mail. This is called when the activation
    link is clicked. We confirm with the old e-mail, and update
    """
    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        return render_to_response('email_change_failed.html', {'err_msg': SYSTEM_MAINTENANCE_MSG})

    with transaction.atomic():
        try:
            pec = PendingEmailChange.objects.get(activation_key=key)
        except PendingEmailChange.DoesNotExist:
            response = render_to_response("invalid_email_key.html", {})
            transaction.set_rollback(True)
            return response

        user = pec.user
        address_context = {
            'old_email': user.email,
            'new_email': pec.new_email
        }

        if len(User.objects.filter(email=pec.new_email)) != 0:
            response = render_to_response("email_exists.html", {})
            transaction.set_rollback(True)
            return response

        subject = render_to_string('emails/email_change_subject.txt', address_context)
        subject = ''.join(subject.splitlines())
        message = render_to_string('emails/confirm_email_change.txt', address_context)
        u_prof = UserProfile.objects.get(user=user)
        meta = u_prof.get_meta()
        if 'old_emails' not in meta:
            meta['old_emails'] = []
        meta['old_emails'].append([user.email, datetime.datetime.now(UTC).isoformat()])
        u_prof.set_meta(meta)
        u_prof.save()
        # Send it to the old email...
        try:
            user.email_user(
                subject,
                message,
                configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
            )
        except Exception:    # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to old address', exc_info=True)
            response = render_to_response("email_change_failed.html", {'email': user.email})
            transaction.set_rollback(True)
            return response

        user.email = pec.new_email
        user.save()
        pec.delete()
        # And send it to the new email...
        try:
            user.email_user(
                subject,
                message,
                configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
            )
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to new address', exc_info=True)
            response = render_to_response("email_change_failed.html", {'email': pec.new_email})
            transaction.set_rollback(True)
            return response

        response = render_to_response("email_change_successful.html", address_context)
        return response
Example #27
0
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.
    We also optionally do some additional password policy checks.
    """
    # convert old-style base36-encoded user id to base64
    uidb64 = uidb36_to_uidb64(uidb36)
    platform_name = {
        "platform_name":
        configuration_helpers.get_value('platform_name',
                                        settings.PLATFORM_NAME)
    }
    try:
        uid_int = base36_to_int(uidb36)
        user = User.objects.get(id=uid_int)
    except (ValueError, User.DoesNotExist):
        # if there's any error getting a user, just let django's
        # password_reset_confirm function handle it.
        return password_reset_confirm(request,
                                      uidb64=uidb64,
                                      token=token,
                                      extra_context=platform_name)

    if UserRetirementRequest.has_user_requested_retirement(user):
        # Refuse to reset the password of any user that has requested retirement.
        context = {
            'validlink': True,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': _('Error in resetting your password.'),
        }
        context.update(platform_name)
        return TemplateResponse(request,
                                'registration/password_reset_confirm.html',
                                context)

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        context = {
            'validlink': False,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': SYSTEM_MAINTENANCE_MSG,
        }
        context.update(platform_name)
        return TemplateResponse(request,
                                'registration/password_reset_confirm.html',
                                context)

    if request.method == 'POST':
        password = request.POST['new_password1']

        try:
            validate_password(password, user=user)
        except ValidationError as err:
            # We have a password reset attempt which violates some security
            # policy, or any other validation. Use the existing Django template to communicate that
            # back to the user.
            context = {
                'validlink': True,
                'form': None,
                'title': _('Password reset unsuccessful'),
                'err_msg': err.message,
            }
            context.update(platform_name)
            return TemplateResponse(
                request, 'registration/password_reset_confirm.html', context)

        # remember what the old password hash is before we call down
        old_password_hash = user.password

        response = password_reset_confirm(request,
                                          uidb64=uidb64,
                                          token=token,
                                          extra_context=platform_name)

        # If password reset was unsuccessful a template response is returned (status_code 200).
        # Check if form is invalid then show an error to the user.
        # Note if password reset was successful we get response redirect (status_code 302).
        if response.status_code == 200:
            form_valid = response.context_data['form'].is_valid(
            ) if response.context_data['form'] else False
            if not form_valid:
                log.warning(
                    u'Unable to reset password for user [%s] because form is not valid. '
                    u'A possible cause is that the user had an invalid reset token',
                    user.username,
                )
                response.context_data['err_msg'] = _(
                    'Error in resetting your password. Please try again.')
                return response

        # get the updated user
        updated_user = User.objects.get(id=uid_int)

        # did the password hash change, if so record it in the PasswordHistory
        if updated_user.password != old_password_hash:
            entry = PasswordHistory()
            entry.create(updated_user)

    else:
        response = password_reset_confirm(request,
                                          uidb64=uidb64,
                                          token=token,
                                          extra_context=platform_name)

        response_was_successful = response.context_data.get('validlink')
        if response_was_successful and not user.is_active:
            user.is_active = True
            user.save()

    return response
 def test_create_account_prevent_auth_user_writes(self):
     with waffle().override(PREVENT_AUTH_USER_WRITES, True):
         response = self.client.get(self.url)
         assert response.status_code == 403
Example #29
0
def activate_account(request, key):
    """
    When link in activation e-mail is clicked
    """
    # If request is in Studio call the appropriate view
    if theming_helpers.get_project_root_name().lower() == u'cms':
        monitoring_utils.set_custom_metric('student_activate_account', 'cms')
        return activate_account_studio(request, key)

    # TODO: Use metric to determine if there are any `activate_account` calls for cms in Production.
    # If not, the templates wouldn't be needed for cms, but we still need a way to activate for cms tests.
    monitoring_utils.set_custom_metric('student_activate_account', 'lms')
    try:
        registration = Registration.objects.get(activation_key=key)
    except (Registration.DoesNotExist, Registration.MultipleObjectsReturned):
        messages.error(
            request,
            HTML(
                _('{html_start}Your account could not be activated{html_end}'
                  'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.'
                  )).format(
                      support_url=configuration_helpers.get_value(
                          'ACTIVATION_EMAIL_SUPPORT_LINK',
                          settings.ACTIVATION_EMAIL_SUPPORT_LINK)
                      or settings.SUPPORT_SITE_LINK,
                      html_start=HTML('<p class="message-title">'),
                      html_end=HTML('</p>'),
                  ),
            extra_tags='account-activation aa-icon')
    else:
        if registration.user.is_active:
            messages.info(
                request,
                HTML(
                    _('{html_start}This account has already been activated.{html_end}'
                      )).format(
                          html_start=HTML('<p class="message-title">'),
                          html_end=HTML('</p>'),
                      ),
                extra_tags='account-activation aa-icon',
            )
        elif waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
            messages.error(
                request,
                HTML(u'{html_start}{message}{html_end}').format(
                    message=Text(SYSTEM_MAINTENANCE_MSG),
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                ),
                extra_tags='account-activation aa-icon',
            )
        else:
            registration.activate()
            # Success message for logged in users.
            message = _(
                '{html_start}Success{html_end} You have activated your account.'
            )

            if not request.user.is_authenticated:
                # Success message for logged out users
                message = _(
                    '{html_start}Success! You have activated your account.{html_end}'
                    'You will now receive email updates and alerts from us related to'
                    ' the courses you are enrolled in. Sign In to continue.')

            # Add message for later use.
            messages.success(
                request,
                HTML(message).format(
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                ),
                extra_tags='account-activation aa-icon',
            )

    return redirect('dashboard')
Example #30
0
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.
    We also optionally do some additional password policy checks.
    """
    # convert old-style base36-encoded user id to base64
    uidb64 = uidb36_to_uidb64(uidb36)
    platform_name = {
        "platform_name": configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
    }

    # User can not get this link unless account recovery feature is enabled.
    if 'is_account_recovery' in request.GET and not is_secondary_email_feature_enabled():
        raise Http404

    try:
        uid_int = base36_to_int(uidb36)
        user = User.objects.get(id=uid_int)
    except (ValueError, User.DoesNotExist):
        # if there's any error getting a user, just let django's
        # password_reset_confirm function handle it.
        return password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

    if UserRetirementRequest.has_user_requested_retirement(user):
        # Refuse to reset the password of any user that has requested retirement.
        context = {
            'validlink': True,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': _('Error in resetting your password.'),
        }
        context.update(platform_name)
        return TemplateResponse(
            request, 'registration/password_reset_confirm.html', context
        )

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        context = {
            'validlink': False,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': SYSTEM_MAINTENANCE_MSG,
        }
        context.update(platform_name)
        return TemplateResponse(
            request, 'registration/password_reset_confirm.html', context
        )

    if request.method == 'POST':
        # We have to make a copy of request.POST because it is a QueryDict object which is immutable until copied.
        # We have to use request.POST because the password_reset_confirm method takes in the request and a user's
        # password is set to the request.POST['new_password1'] field. We have to also normalize the new_password2
        # field so it passes the equivalence check that new_password1 == new_password2
        # In order to switch out of having to do this copy, we would want to move the normalize_password code into
        # a custom User model's set_password method to ensure it is always happening upon calling set_password.
        request.POST = request.POST.copy()
        request.POST['new_password1'] = normalize_password(request.POST['new_password1'])
        request.POST['new_password2'] = normalize_password(request.POST['new_password2'])

        password = request.POST['new_password1']

        try:
            validate_password(password, user=user)
        except ValidationError as err:
            # We have a password reset attempt which violates some security
            # policy, or any other validation. Use the existing Django template to communicate that
            # back to the user.
            context = {
                'validlink': True,
                'form': None,
                'title': _('Password reset unsuccessful'),
                'err_msg': ' '.join(err.messages),
            }
            context.update(platform_name)
            return TemplateResponse(
                request, 'registration/password_reset_confirm.html', context
            )

        # remember what the old password hash is before we call down
        old_password_hash = user.password

        if 'is_account_recovery' in request.GET:
            response = password_reset_confirm(
                request,
                uidb64=uidb64,
                token=token,
                extra_context=platform_name,
                template_name='registration/password_reset_confirm.html',
                post_reset_redirect='signin_user',
            )
        else:
            response = password_reset_confirm(
                request, uidb64=uidb64, token=token, extra_context=platform_name
            )

        # If password reset was unsuccessful a template response is returned (status_code 200).
        # Check if form is invalid then show an error to the user.
        # Note if password reset was successful we get response redirect (status_code 302).
        if response.status_code == 200:
            form_valid = response.context_data['form'].is_valid() if response.context_data['form'] else False
            if not form_valid:
                log.warning(
                    u'Unable to reset password for user [%s] because form is not valid. '
                    u'A possible cause is that the user had an invalid reset token',
                    user.username,
                )
                response.context_data['err_msg'] = _('Error in resetting your password. Please try again.')
                return response

        # get the updated user
        updated_user = User.objects.get(id=uid_int)
        if 'is_account_recovery' in request.GET:
            try:
                updated_user.email = updated_user.account_recovery.secondary_email
                updated_user.account_recovery.delete()
                # emit an event that the user changed their secondary email to the primary email
                tracker.emit(
                    SETTING_CHANGE_INITIATED,
                    {
                        "setting": "email",
                        "old": user.email,
                        "new": updated_user.email,
                        "user_id": updated_user.id,
                    }
                )
            except ObjectDoesNotExist:
                log.error(
                    'Account recovery process initiated without AccountRecovery instance for user {username}'.format(
                        username=updated_user.username
                    )
                )

        updated_user.save()

        if response.status_code == 302 and 'is_account_recovery' in request.GET:
            messages.success(
                request,
                HTML(_(
                    '{html_start}Password Creation Complete{html_end}'
                    'Your password has been created. {bold_start}{email}{bold_end} is now your primary login email.'
                )).format(
                    support_url=configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK),
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                    bold_start=HTML('<b>'),
                    bold_end=HTML('</b>'),
                    email=updated_user.email,
                ),
                extra_tags='account-recovery aa-icon submission-success'
            )
    else:
        response = password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

        response_was_successful = response.context_data.get('validlink')
        if response_was_successful and not user.is_active:
            user.is_active = True
            user.save()

    return response
Example #31
0
 def test_login_success_prevent_auth_user_writes(self):
     with waffle().override(PREVENT_AUTH_USER_WRITES, True):
         old_last_login = self.user.last_login
         self.test_login_success()
         self.user.refresh_from_db()
         assert old_last_login == self.user.last_login
Example #32
0
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.
    We also optionally do some additional password policy checks.
    """
    # convert old-style base36-encoded user id to base64
    uidb64 = uidb36_to_uidb64(uidb36)
    platform_name = {
        "platform_name": configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
    }

    # User can not get this link unless account recovery feature is enabled.
    if 'is_account_recovery' in request.GET and not is_secondary_email_feature_enabled():
        raise Http404

    try:
        uid_int = base36_to_int(uidb36)
        user = User.objects.get(id=uid_int)
    except (ValueError, User.DoesNotExist):
        # if there's any error getting a user, just let django's
        # password_reset_confirm function handle it.
        return password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

    if UserRetirementRequest.has_user_requested_retirement(user):
        # Refuse to reset the password of any user that has requested retirement.
        context = {
            'validlink': True,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': _('Error in resetting your password.'),
        }
        context.update(platform_name)
        return TemplateResponse(
            request, 'registration/password_reset_confirm.html', context
        )

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        context = {
            'validlink': False,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': SYSTEM_MAINTENANCE_MSG,
        }
        context.update(platform_name)
        return TemplateResponse(
            request, 'registration/password_reset_confirm.html', context
        )

    if request.method == 'POST':
        # We have to make a copy of request.POST because it is a QueryDict object which is immutable until copied.
        # We have to use request.POST because the password_reset_confirm method takes in the request and a user's
        # password is set to the request.POST['new_password1'] field. We have to also normalize the new_password2
        # field so it passes the equivalence check that new_password1 == new_password2
        # In order to switch out of having to do this copy, we would want to move the normalize_password code into
        # a custom User model's set_password method to ensure it is always happening upon calling set_password.
        request.POST = request.POST.copy()
        request.POST['new_password1'] = normalize_password(request.POST['new_password1'])
        request.POST['new_password2'] = normalize_password(request.POST['new_password2'])

        password = request.POST['new_password1']

        try:
            validate_password(password, user=user)
        except ValidationError as err:
            # We have a password reset attempt which violates some security
            # policy, or any other validation. Use the existing Django template to communicate that
            # back to the user.
            context = {
                'validlink': True,
                'form': None,
                'title': _('Password reset unsuccessful'),
                'err_msg': ' '.join(err.messages),
            }
            context.update(platform_name)
            return TemplateResponse(
                request, 'registration/password_reset_confirm.html', context
            )

        # remember what the old password hash is before we call down
        old_password_hash = user.password

        if 'is_account_recovery' in request.GET:
            response = password_reset_confirm(
                request,
                uidb64=uidb64,
                token=token,
                extra_context=platform_name,
                template_name='registration/password_reset_confirm.html',
                post_reset_redirect='signin_user',
            )
        else:
            response = password_reset_confirm(
                request, uidb64=uidb64, token=token, extra_context=platform_name
            )

        # If password reset was unsuccessful a template response is returned (status_code 200).
        # Check if form is invalid then show an error to the user.
        # Note if password reset was successful we get response redirect (status_code 302).
        if response.status_code == 200:
            form_valid = response.context_data['form'].is_valid() if response.context_data['form'] else False
            if not form_valid:
                log.warning(
                    u'Unable to reset password for user [%s] because form is not valid. '
                    u'A possible cause is that the user had an invalid reset token',
                    user.username,
                )
                response.context_data['err_msg'] = _('Error in resetting your password. Please try again.')
                return response

        # get the updated user
        updated_user = User.objects.get(id=uid_int)
        if 'is_account_recovery' in request.GET:
            try:
                updated_user.email = updated_user.account_recovery.secondary_email
                updated_user.account_recovery.delete()
                # emit an event that the user changed their secondary email to the primary email
                tracker.emit(
                    SETTING_CHANGE_INITIATED,
                    {
                        "setting": "email",
                        "old": user.email,
                        "new": updated_user.email,
                        "user_id": updated_user.id,
                    }
                )
            except ObjectDoesNotExist:
                log.error(
                    'Account recovery process initiated without AccountRecovery instance for user {username}'.format(
                        username=updated_user.username
                    )
                )

        updated_user.save()

        if response.status_code == 302 and 'is_account_recovery' in request.GET:
            messages.success(
                request,
                HTML(_(
                    '{html_start}Password Creation Complete{html_end}'
                    'Your password has been created. {bold_start}{email}{bold_end} is now your primary login email.'
                )).format(
                    support_url=configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK),
                    html_start=HTML('<p class="message-title">'),
                    html_end=HTML('</p>'),
                    bold_start=HTML('<b>'),
                    bold_end=HTML('</b>'),
                    email=updated_user.email,
                ),
                extra_tags='account-recovery aa-icon submission-success'
            )
    else:
        response = password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

        response_was_successful = response.context_data.get('validlink')
        if response_was_successful and not user.is_active:
            user.is_active = True
            user.save()

    return response
Example #33
0
 def test_prevent_auth_user_writes(self, email_user):  # pylint: disable=unused-argument
     with waffle().override(PREVENT_AUTH_USER_WRITES, True):
         self.check_confirm_email_change('email_change_failed.html', {
             'err_msg': SYSTEM_MAINTENANCE_MSG
         })
         self.assertRolledBack()
Example #34
0
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.
    We also optionally do some additional password policy checks.
    """
    # convert old-style base36-encoded user id to base64
    uidb64 = uidb36_to_uidb64(uidb36)
    platform_name = {
        "platform_name": configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)
    }
    try:
        uid_int = base36_to_int(uidb36)
        user = User.objects.get(id=uid_int)
    except (ValueError, User.DoesNotExist):
        # if there's any error getting a user, just let django's
        # password_reset_confirm function handle it.
        return password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

    if UserRetirementRequest.has_user_requested_retirement(user):
        # Refuse to reset the password of any user that has requested retirement.
        context = {
            'validlink': True,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': _('Error in resetting your password.'),
        }
        context.update(platform_name)
        return TemplateResponse(
            request, 'registration/password_reset_confirm.html', context
        )

    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        context = {
            'validlink': False,
            'form': None,
            'title': _('Password reset unsuccessful'),
            'err_msg': SYSTEM_MAINTENANCE_MSG,
        }
        context.update(platform_name)
        return TemplateResponse(
            request, 'registration/password_reset_confirm.html', context
        )

    if request.method == 'POST':
        password = request.POST['new_password1']

        try:
            validate_password(password, user=user)
        except ValidationError as err:
            # We have a password reset attempt which violates some security
            # policy, or any other validation. Use the existing Django template to communicate that
            # back to the user.
            context = {
                'validlink': True,
                'form': None,
                'title': _('Password reset unsuccessful'),
                'err_msg': err.message,
            }
            context.update(platform_name)
            return TemplateResponse(
                request, 'registration/password_reset_confirm.html', context
            )

        # remember what the old password hash is before we call down
        old_password_hash = user.password

        response = password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

        # If password reset was unsuccessful a template response is returned (status_code 200).
        # Check if form is invalid then show an error to the user.
        # Note if password reset was successful we get response redirect (status_code 302).
        if response.status_code == 200:
            form_valid = response.context_data['form'].is_valid() if response.context_data['form'] else False
            if not form_valid:
                log.warning(
                    u'Unable to reset password for user [%s] because form is not valid. '
                    u'A possible cause is that the user had an invalid reset token',
                    user.username,
                )
                response.context_data['err_msg'] = _('Error in resetting your password. Please try again.')
                return response

        # get the updated user
        updated_user = User.objects.get(id=uid_int)

        # did the password hash change, if so record it in the PasswordHistory
        if updated_user.password != old_password_hash:
            entry = PasswordHistory()
            entry.create(updated_user)

    else:
        response = password_reset_confirm(
            request, uidb64=uidb64, token=token, extra_context=platform_name
        )

        response_was_successful = response.context_data.get('validlink')
        if response_was_successful and not user.is_active:
            user.is_active = True
            user.save()

    return response