def _validate_password(password, username=None, email=None, reset_password_page=False): """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. reset_password_page (bool): The flag that determines the validation page. Returns: None Raises: errors.AccountPasswordInvalid """ try: _validate_type(password, str, 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(str(invalid_password_err)) except ValidationError as validation_err: raise errors.AccountPasswordInvalid(' '.join(validation_err.messages)) if ((settings.ENABLE_AUTHN_RESET_PASSWORD_HIBP_POLICY and reset_password_page) or (settings.ENABLE_AUTHN_REGISTER_HIBP_POLICY and not reset_password_page)): pwned_response = check_pwned_password(password) if pwned_response.get('vulnerability', 'no') == 'yes': if (reset_password_page or pwned_response.get('frequency', 0) >= settings.HIBP_REGISTRATION_PASSWORD_FREQUENCY_THRESHOLD): raise errors.AccountPasswordInvalid( accounts.AUTHN_PASSWORD_COMPROMISED_MSG)
def check_pwned_password_and_send_track_event(user_id, password, internal_user=False, is_new_user=False): """ Check the Pwned Databases and send its event to Segment. """ try: pwned_properties = check_pwned_password(password) if pwned_properties: pwned_properties['internal_user'] = internal_user pwned_properties['new_user'] = is_new_user segment.track(user_id, 'edx.bi.user.pwned.password.status', pwned_properties) except Exception: # pylint: disable=W0703 log.exception( 'Unable to get response from pwned password api for user_id: "%s"', user_id, ) return None # lint-amnesty, pylint: disable=raise-missing-from
def _validate_password(password, username=None, email=None, reset_password_page=False): """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. reset_password_page (bool): The flag that determines the validation page. Returns: None Raises: errors.AccountPasswordInvalid """ try: _validate_type(password, str, 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(str(invalid_password_err)) except ValidationError as validation_err: raise errors.AccountPasswordInvalid(' '.join(validation_err.messages)) # TODO: VAN-666 - Restrict this feature to reset password page for now until it is # enabled on account sign in and register. if settings.ENABLE_AUTHN_RESET_PASSWORD_HIBP_POLICY and reset_password_page: pwned_response = check_pwned_password(password) if pwned_response.get('vulnerability', 'no') == 'yes': raise errors.AccountPasswordInvalid( accounts.AUTHN_PASSWORD_COMPROMISED_MSG)
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) if settings.ENABLE_AUTHN_REGISTER_HIBP_POLICY: # Checks the Pwned Databases for password vulnerability. pwned_response = check_pwned_password(password) if (pwned_response.get('vulnerability', 'no') == 'yes' and pwned_response.get('frequency', 0) >= settings.HIBP_REGISTRATION_PASSWORD_FREQUENCY_THRESHOLD ): raise ValidationError( accounts.AUTHN_PASSWORD_COMPROMISED_MSG) return password
def post(self, 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 = self._check_token_has_required_values( uidb36, token) if not has_required_values: AUDIT_LOG.exception("Invalid password reset confirm token") return Response({'reset_status': reset_status}) request.data._mutable = True # lint-amnesty, pylint: disable=protected-access request.data['new_password1'] = normalize_password( request.data['new_password1']) request.data['new_password2'] = normalize_password( request.data['new_password2']) password = request.data['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 Response({'reset_status': reset_status}) validate_password(password, user=user) if settings.ENABLE_AUTHN_RESET_PASSWORD_HIBP_POLICY: # Checks the Pwned Databases for password vulnerability. pwned_response = check_pwned_password(password) if pwned_response.get('vulnerability', 'no') == 'yes': error_status = { 'reset_status': reset_status, 'err_msg': accounts.AUTHN_PASSWORD_COMPROMISED_MSG } return Response(error_status) form = SetPasswordForm(user, request.data) 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() except ObjectDoesNotExist: err = 'Account recovery process initiated without AccountRecovery instance for user {username}' log.error(err.format(username=user.username)) # Handles clearing the failed login counter upon password reset. if LoginFailures.is_feature_enabled(): LoginFailures.clear_lockout_counter(user) send_password_reset_success_email(user, request) update_session_auth_hash(request, user) except ValidationError as err: AUDIT_LOG.exception("Password validation failed") error_status = { 'reset_status': reset_status, 'err_msg': ' '.join(err.messages) } return Response(error_status) except Exception: # pylint: disable=broad-except AUDIT_LOG.exception("Setting new password failed") return Response({'reset_status': reset_status})