Esempio n. 1
0
def _validate_password(password, username=None, email=None):
    """Validate the format of the user's password.

    Passwords cannot be the same as the username of the account,
    so we create a temp_user using the username and email to test the password against.
    This user is never saved.

    Arguments:
        password (unicode): The proposed password.
        username (unicode): The username associated with the user's account.
        email (unicode): The email associated with the user's account.

    Returns:
        None

    Raises:
        errors.AccountPasswordInvalid

    """
    try:
        _validate_type(password, basestring, accounts.PASSWORD_BAD_TYPE_MSG)
        temp_user = User(username=username, email=email) if username else None
        validate_password(password, user=temp_user)
    except errors.AccountDataBadType as invalid_password_err:
        raise errors.AccountPasswordInvalid(text_type(invalid_password_err))
    except ValidationError as validation_err:
        raise errors.AccountPasswordInvalid(' '.join(validation_err.messages))
Esempio n. 2
0
 def clean_password(self):
     """Enforce password policies (if applicable)"""
     password = self.cleaned_data["password"]
     if self.enforce_password_policy:
         validate_password(password,
                           username=self.cleaned_data.get('username'))
     return password
Esempio n. 3
0
def _validate_password(password, username=None):
    """Validate the format of the user's password.

    Passwords cannot be the same as the username of the account,
    so we take `username` as an argument.

    Arguments:
        password (unicode): The proposed password.
        username (unicode): The username associated with the user's account.

    Returns:
        None

    Raises:
        errors.AccountPasswordInvalid

    """
    try:
        _validate_type(password, basestring, accounts.PASSWORD_BAD_TYPE_MSG)

        validate_password(password, username=username)
    except errors.AccountDataBadType as invalid_password_err:
        raise errors.AccountPasswordInvalid(text_type(invalid_password_err))
    except ValidationError as validation_err:
        raise errors.AccountPasswordInvalid(validation_err.message)
Esempio n. 4
0
def _validate_password(password, username=None, email=None):
    """Validate the format of the user's password.

    Passwords cannot be the same as the username of the account,
    so we create a temp_user using the username and email to test the password against.
    This user is never saved.

    Arguments:
        password (unicode): The proposed password.
        username (unicode): The username associated with the user's account.
        email (unicode): The email associated with the user's account.

    Returns:
        None

    Raises:
        errors.AccountPasswordInvalid

    """
    try:
        _validate_type(password, six.string_types,
                       accounts.PASSWORD_BAD_TYPE_MSG)
        temp_user = User(username=username, email=email) if username else None
        validate_password(password, user=temp_user)
    except errors.AccountDataBadType as invalid_password_err:
        raise errors.AccountPasswordInvalid(text_type(invalid_password_err))
    except ValidationError as validation_err:
        raise errors.AccountPasswordInvalid(' '.join(validation_err.messages))
Esempio n. 5
0
def _validate_password(password, username=None):
    """Validate the format of the user's password.

    Passwords cannot be the same as the username of the account,
    so we take `username` as an argument.

    Arguments:
        password (unicode): The proposed password.
        username (unicode): The username associated with the user's account.

    Returns:
        None

    Raises:
        errors.AccountPasswordInvalid

    """
    try:
        _validate_type(password, basestring, accounts.PASSWORD_BAD_TYPE_MSG)

        validate_password(password, username=username)
    except errors.AccountDataBadType as invalid_password_err:
        raise errors.AccountPasswordInvalid(text_type(invalid_password_err))
    except ValidationError as validation_err:
        raise errors.AccountPasswordInvalid(validation_err.message)
 def test_validation_errors(self, password, msg):
     """ Tests validate_password error messages """
     if msg is None:
         validate_password(password)
     else:
         with self.assertRaises(ValidationError) as cm:
             validate_password(password)
         self.assertIn(msg, cm.exception.message)
 def test_validation_errors(self, password, msg):
     """ Tests validate_password error messages """
     if msg is None:
         validate_password(password)
     else:
         with self.assertRaises(ValidationError) as cm:
             validate_password(password)
         self.assertIn(msg, cm.exception.message)
    def post(self, request):

        old_password = request.POST.get('old_password', '')
        new_password1 = request.POST.get('new_password1', '')
        new_password2 = request.POST.get('new_password2', '')
        user = request.user
        password = new_password1

        parameters_dict = {
            "old_password": old_password,
            "new_password1": new_password1,
            "new_password2": new_password2
        }
        for (key, value) in parameters_dict.items():
            if not value:
                return JsonResponse({
                    "success":
                    False,
                    "code":
                    400,
                    "msg":
                    _('Request parameter {key} missing!'.format(key=key)),
                })

        if user.check_password(old_password):
            if new_password1 != new_password2:
                return JsonResponse({
                    'success':
                    False,
                    'code':
                    201,
                    'msg':
                    _('New passwords do not match. Please try again.')
                })
            try:
                validate_password(password, user=user)
            except ValidationError as err:
                return JsonResponse({
                    'success': False,
                    'code': 202,
                    'msg': ' '.join(err.messages)
                })
            user.set_password(password)
            user.save()
            return JsonResponse({
                'success': True,
                'code': 200,
                'msg': _('Password modified.')
            })
        else:
            return JsonResponse({
                'success': False,
                'code': 203,
                'msg': _('Wrong password')
            })
Esempio n. 9
0
def _check_user_compliance(user, password):
    """
    Returns a boolean indicating whether or not the user is compliant with password policy rules.
    """
    try:
        validate_password(password, user=user)
        return True
    except Exception:  # pylint: disable=broad-except
        # If anything goes wrong, we should assume the password is not compliant but we don't necessarily
        # need to prevent login.
        return False
Esempio n. 10
0
def _check_user_compliance(user, password):
    """
    Returns a boolean indicating whether or not the user is compliant with password policy rules.
    """
    try:
        validate_password(password, user=user, password_reset=False)
        return True
    except Exception:  # pylint: disable=broad-except
        # If anything goes wrong, we should assume the password is not compliant but we don't necessarily
        # need to prevent login.
        return False
Esempio n. 11
0
 def clean_password(self):
     """Enforce password policies (if applicable)"""
     password = self.cleaned_data["password"]
     if not self.do_third_party_auth:
         # Creating a temporary user object to test password against username
         # This user should NOT be saved
         username = self.cleaned_data.get('username')
         email = self.cleaned_data.get('email')
         temp_user = User(username=username, email=email) if username else None
         validate_password(password, temp_user)
     return password
Esempio n. 12
0
 def clean_password(self):
     """Enforce password policies (if applicable)"""
     password = self.cleaned_data["password"]
     if not self.do_third_party_auth:
         # Creating a temporary user object to test password against username
         # This user should NOT be saved
         username = self.cleaned_data.get('username')
         email = self.cleaned_data.get('email')
         temp_user = User(username=username, email=email) if username else None
         validate_password(password, temp_user)
     return password
Esempio n. 13
0
 def _validate_password(self, password, request):
     try:
         validate_password(password, user=self.user)
     except ValidationError as err:
         context = {
             'validlink': True,
             'form': None,
             'title': _('Password reset unsuccessful'),
             'err_msg': ' '.join(err.messages),
         }
         context.update(self.platform_name)
         return TemplateResponse(
             request, 'registration/password_reset_confirm.html', context)
Esempio n. 14
0
    def test_ignore_reset_checks(self, mock_reuse):
        """
        Test that we don't annoy user about compliance failures that only affect password resets
        """
        user = UserFactory()
        password = '******'
        mock_reuse.return_value = False

        # Sanity check that normal validation would trip us up
        with self.assertRaises(SecurityPolicyError):
            validate_password(password, user=user)

        # Confirm that we don't trip on it
        self.assertTrue(_check_user_compliance(user, password))
Esempio n. 15
0
    def test_ignore_reset_checks(self, mock_reuse):
        """
        Test that we don't annoy user about compliance failures that only affect password resets
        """
        user = UserFactory()
        password = '******'
        mock_reuse.return_value = False

        # Sanity check that normal validation would trip us up
        with self.assertRaises(SecurityPolicyError):
            validate_password(password, user=user)

        # Confirm that we don't trip on it
        self.assertTrue(_check_user_compliance(user, password))
    def validation_errors_checker(self, password, msg, user=None):
        """
        This helper function is used to check the proper error messages are
        being displayed based on the password and validator.

        Parameters:
            password (unicode): the password to validate on
            user (django.contrib.auth.models.User): user object to use in validation.
                This is an optional parameter unless the validator requires a
                user object.
            msg (str): The expected ValidationError message
        """
        if msg is None:
            validate_password(password, user)
        else:
            with self.assertRaises(ValidationError) as cm:
                validate_password(password, user)
            self.assertIn(msg, ' '.join(cm.exception.messages))
    def validation_errors_checker(self, password, msg, user=None):
        """
        This helper function is used to check the proper error messages are
        being displayed based on the password and validator.

        Parameters:
            password (unicode): the password to validate on
            user (django.contrib.auth.models.User): user object to use in validation.
                This is an optional parameter unless the validator requires a
                user object.
            msg (str): The expected ValidationError message
        """
        if msg is None:
            validate_password(password, user)
        else:
            with self.assertRaises(ValidationError) as cm:
                validate_password(password, user)
            self.assertIn(msg, ' '.join(cm.exception.messages))
Esempio n. 18
0
def password_reset_logistration(request, **kwargs):
    """Reset learner password using passed token and new credentials"""

    reset_status = False
    uidb36 = kwargs.get('uidb36')
    token = kwargs.get('token')

    has_required_values, uid_int = _check_token_has_required_values(
        uidb36, token)
    if not has_required_values:
        AUDIT_LOG.exception("Invalid password reset confirm token")
        return JsonResponse({'reset_status': reset_status})

    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:
        user = User.objects.get(id=uid_int)
        if not default_token_generator.check_token(user, token):
            AUDIT_LOG.exception("Token validation failed")
            return JsonResponse({'reset_status': reset_status})

        validate_password(password, user=user)
        form = SetPasswordForm(user, request.POST)
        if form.is_valid():
            form.save()
            reset_status = True

    except ValidationError as err:
        AUDIT_LOG.exception("Password validation failed")
        error_status = {
            'reset_status': reset_status,
            'err_msg': ' '.join(err.messages)
        }
        return JsonResponse(error_status)
    except Exception:  # pylint: disable=broad-except
        AUDIT_LOG.exception("Setting new password failed")

    return JsonResponse({'reset_status': reset_status})
    def test_unicode_password(self):
        """ Tests that validate_password enforces unicode """
        byte_str = b'Ёдно'
        unicode_str = u'Ёдно'

        # Sanity checks and demonstration of why this test is useful
        self.assertEqual(len(byte_str), 4)
        self.assertEqual(len(unicode_str), 1)
        self.assertEqual(password_min_length(), 2)

        # Test length check
        with self.assertRaises(ValidationError):
            validate_password(byte_str)
        validate_password(byte_str + byte_str)

        # Test badly encoded password
        with self.assertRaises(ValidationError) as cm:
            validate_password(b'\xff\xff')
        self.assertEquals('Invalid password.', cm.exception.message)
    def test_unicode_password(self):
        """ Tests that validate_password enforces unicode """
        byte_str = b'Ёдно'
        unicode_str = u'Ёдно'

        # Sanity checks and demonstration of why this test is useful
        self.assertEqual(len(byte_str), 4)
        self.assertEqual(len(unicode_str), 1)
        self.assertEqual(password_min_length(), 2)

        # Test length check
        with self.assertRaises(ValidationError):
            validate_password(byte_str)
        validate_password(byte_str + byte_str)

        # Test badly encoded password
        with self.assertRaises(ValidationError) as cm:
            validate_password(b'\xff\xff')
        self.assertEquals('Invalid password.', cm.exception.message)
Esempio n. 21
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
Esempio n. 22
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
Esempio n. 23
0
 def clean_password(self):
     """Enforce password policies (if applicable)"""
     password = self.cleaned_data["password"]
     if self.enforce_password_policy:
         validate_password(password, username=self.cleaned_data.get('username'))
     return password
Esempio n. 24
0
def password_reset_logistration(request, **kwargs):
    """Reset learner password using passed token and new credentials"""

    reset_status = False
    uidb36 = kwargs.get('uidb36')
    token = kwargs.get('token')

    has_required_values, uid_int = _check_token_has_required_values(
        uidb36, token)
    if not has_required_values:
        AUDIT_LOG.exception("Invalid password reset confirm token")
        return JsonResponse({'reset_status': reset_status})

    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:
        user = User.objects.get(id=uid_int)
        if not default_token_generator.check_token(user, token):
            AUDIT_LOG.exception("Token validation failed")
            return JsonResponse({'reset_status': reset_status})

        validate_password(password, user=user)
        form = SetPasswordForm(user, request.POST)
        if form.is_valid():
            form.save()
            reset_status = True

            if 'is_account_recovery' in request.GET:
                try:
                    old_primary_email = user.email
                    user.email = user.account_recovery.secondary_email
                    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": old_primary_email,
                            "new": user.email,
                            "user_id": user.id,
                        })
                    user.save()
                    send_password_reset_success_email(user, request)
                except ObjectDoesNotExist:
                    log.error(
                        'Account recovery process initiated without AccountRecovery instance for user {username}'
                        .format(username=user.username))
    except ValidationError as err:
        AUDIT_LOG.exception("Password validation failed")
        error_status = {
            'reset_status': reset_status,
            'err_msg': ' '.join(err.messages)
        }
        return JsonResponse(error_status)
    except Exception:  # pylint: disable=broad-except
        AUDIT_LOG.exception("Setting new password failed")

    return JsonResponse({'reset_status': reset_status})
Esempio n. 25
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
Esempio n. 26
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