def resend_confirmation_post(): """ View for user to submit resend confirmation form. HTTP Method: POST """ form = ResendConfirmationForm(request.form) if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) status_message = ('If there is an OSF account associated with this unconfirmed email {0}, ' 'a confirmation email has been resent to it. If you do not receive an email and believe ' 'you should have, please contact OSF Support.').format(clean_email) kind = 'success' if user: try: send_confirm_email(user, clean_email) except KeyError: # already confirmed, redirect to dashboard status_message = 'This email {0} has already been confirmed.'.format(clean_email) kind = 'warning' status.push_status_message(status_message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {'form': form}
def forgot_password(): form = ForgotPasswordForm(request.form, prefix='forgot_password') if form.validate(): email = form.email.data user_obj = get_user(username=email) if user_obj: user_obj.verification_key = security.random_string(20) user_obj.save() reset_link = "http://{0}{1}".format( request.host, web_url_for( 'reset_password', verification_key=user_obj.verification_key ) ) mails.send_mail( to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link ) status.push_status_message('Reset email sent to {0}'.format(email)) else: status.push_status_message('Email {email} not found'.format(email=email)) forms.push_errors_to_status(form.errors) return auth_login(forgot_password_form=form)
def forgot_password_post(): """Attempt to send user password reset or return respective error. """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if form.validate(): email = form.email.data user_obj = get_user(email=email) if user_obj: user_obj.verification_key = security.random_string(20) user_obj.save() reset_link = "http://{0}{1}".format( request.host, web_url_for( 'reset_password', verification_key=user_obj.verification_key ) ) mails.send_mail( to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link ) status.push_status_message( ('An email with instructions on how to reset the password ' 'for the account associated with {0} has been sent. If you ' 'do not receive an email and believe you should have please ' 'contact OSF Support.').format(email), 'success') forms.push_errors_to_status(form.errors) return auth_login(forgot_password_form=form)
def form_valid(self, form): email = form.cleaned_data.get('emails') user = get_user(email) if user is None or user._id != self.kwargs.get('guid'): return HttpResponse( '{} with id "{}" and email "{}" not found.'.format( self.context_object_name.title(), self.kwargs.get('guid'), email ), status=409 ) reset_abs_url = furl(DOMAIN) user.verification_key_v2 = generate_verification_key(verification_type='password') user.save() reset_abs_url.path.add(('resetpassword/{}/{}'.format(user._id, user.verification_key_v2['token']))) send_mail( subject='Reset OSF Password', message='Follow this link to reset your password: {}'.format( reset_abs_url.url ), from_email=OSF_SUPPORT_EMAIL, recipient_list=[email] ) update_admin_log( user_id=self.request.user.id, object_id=user.pk, object_repr='User', message='Emailed user {} a reset link.'.format(user.pk), action_flag=USER_EMAILED ) return super(ResetPasswordView, self).form_valid(form)
def invite_contributor_post(node, **kwargs): """API view for inviting an unregistered user. Expects JSON arguments with 'fullname' (required) and email (not required). """ fullname = request.json.get('fullname').strip() email = request.json.get('email') if email: email = email.lower().strip() if not fullname: return {'status': 400, 'message': 'Must provide fullname'}, 400 # Check if email is in the database user = get_user(email=email) if user: if user.is_registered: msg = 'User is already in database. Please go back and try your search again.' return {'status': 400, 'message': msg}, 400 elif node.is_contributor(user): msg = 'User with this email address is already a contributor to this project.' return {'status': 400, 'message': msg}, 400 else: serialized = utils.add_contributor_json(user) # use correct display name serialized['fullname'] = fullname serialized['email'] = email else: # Create a placeholder serialized = utils.serialize_unregistered(fullname, email) return {'status': 'success', 'contributor': serialized}
def claim_user_post(node, **kwargs): """View for claiming a user from the X-editable form on a project page. """ reqdata = request.json # Unreg user user = User.load(reqdata['pk']) unclaimed_data = user.get_unclaimed_record(node._primary_key) # Submitted through X-editable if 'value' in reqdata: # Submitted email address email = reqdata['value'].lower().strip() claimer = get_user(email=email) if claimer and claimer.is_registered: send_claim_registered_email(claimer=claimer, unreg_user=user, node=node) else: send_claim_email(email, user, node, notify=True) # TODO(sloria): Too many assumptions about the request data. Just use elif 'claimerId' in reqdata: # User is logged in and confirmed identity claimer_id = reqdata['claimerId'] claimer = User.load(claimer_id) send_claim_registered_email(claimer=claimer, unreg_user=user, node=node) email = claimer.username else: raise HTTPError(http.BAD_REQUEST) return { 'status': 'success', 'email': email, 'fullname': unclaimed_data['name'] }
def reset_password(auth, **kwargs): if auth.logged_in: return auth_logout(redirect_url=request.url) verification_key = kwargs['verification_key'] form = ResetPasswordForm(request.form) user_obj = get_user(verification_key=verification_key) if not user_obj: error_data = {'message_short': 'Invalid url.', 'message_long': 'The verification key in the URL is invalid or ' 'has expired.'} raise HTTPError(400, data=error_data) if request.method == 'POST' and form.validate(): # new random verification key, allows CAS to authenticate the user w/o password one time only. user_obj.verification_key = security.random_string(20) user_obj.set_password(form.password.data) user_obj.save() status.push_status_message('Password reset', 'success') # Redirect to CAS and authenticate the user with a verification key. return redirect(cas.get_login_url( web_url_for('user_account', _absolute=True), auto=True, username=user_obj.username, verification_key=user_obj.verification_key )) forms.push_errors_to_status(form.errors) return { 'verification_key': verification_key, }
def form_valid(self, form): email = form.cleaned_data.get('emails') user = get_user(email) if user is None or user.pk != self.kwargs.get('guid'): raise AttributeError reset_abs_url = furl(DOMAIN) user.verification_key = random_string(20) user.save() reset_abs_url.path.add(('resetpassword/{}'.format(user.verification_key))) send_mail( subject='Reset OSF Password', message='Follow this link to reset your password: {}'.format( reset_abs_url.url ), from_email=SUPPORT_EMAIL, recipient_list=[email] ) update_admin_log( user_id=self.request.user.id, object_id=user.pk, object_repr='User', message='Emailed user {} a reset link.'.format(user.pk), action_flag=USER_EMAILED ) return super(ResetPasswordView, self).form_valid(form)
def __call__(self, form, field): user = auth.get_user(email=field.data) if user: if self.allow_unregistered and not user.is_registered: return True msg = self.message or language.ALREADY_REGISTERED.format(email=field.data) raise ValidationError(msg) return True
def forgot_password_post(): """ View for user to submit forgot password form. HTTP Method: POST :return {} """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if not form.validate(): # Don't go anywhere forms.push_errors_to_status(form.errors) else: email = form.email.data status_message = ('If there is an OSF account associated with {0}, an email with instructions on how to ' 'reset the OSF password has been sent to {0}. If you do not receive an email and believe ' 'you should have, please contact OSF Support. ').format(email) kind = 'success' # check if the user exists user_obj = get_user(email=email) if user_obj: # rate limit forgot_password_post if not throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE): status_message = 'You have recently requested to change your password. Please wait a few minutes ' \ 'before trying again.' kind = 'error' else: # TODO [OSF-6673]: Use the feature in [OSF-6998] for user to resend claim email. # if the user account is not claimed yet if (user_obj.is_invited and user_obj.unclaimed_records and not user_obj.date_last_login and not user_obj.is_claimed and not user_obj.is_registered): status_message = 'You cannot reset password on this account. Please contact OSF Support.' kind = 'error' else: # new random verification key (v2) user_obj.verification_key_v2 = generate_verification_key(verification_type='password') user_obj.email_last_sent = datetime.datetime.utcnow() user_obj.save() reset_link = furl.urljoin( settings.DOMAIN, web_url_for( 'reset_password_get', uid=user_obj._id, token=user_obj.verification_key_v2['token'] ) ) mails.send_mail( to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link ) status.push_status_message(status_message, kind=kind, trust=False) return {}
def test_get_user_with_wrong_password_returns_false(self): user = UserFactory.build() user.set_password('killerqueen') assert_false( auth.get_user( username=user.username, password='******' ) )
def forgot_password_post(auth, **kwargs): """ View for user to submit forgot password form. HTTP Method: POST """ # If user is already logged in, redirect to dashboard page. if auth.logged_in: return redirect(web_url_for('dashboard')) form = ForgotPasswordForm(request.form, prefix='forgot_password') if form.validate(): email = form.email.data status_message = ('If there is an OSF account associated with {0}, an email with instructions on how to ' 'reset the OSF password has been sent to {0}. If you do not receive an email and believe ' 'you should have, please contact OSF Support. ').format(email) # check if the user exists user_obj = get_user(email=email) if user_obj: # check forgot_password rate limit if throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE): # new random verification key, allows OSF to check whether the reset_password request is valid, # this verification key is used twice, one for GET reset_password and one for POST reset_password # and it will be destroyed when POST reset_password succeeds user_obj.verification_key = generate_verification_key() user_obj.email_last_sent = datetime.datetime.utcnow() user_obj.save() reset_link = furl.urljoin( settings.DOMAIN, web_url_for( 'reset_password_get', verification_key=user_obj.verification_key ) ) mails.send_mail( to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link ) status.push_status_message(status_message, kind='success', trust=False) else: status.push_status_message('You have recently requested to change your password. Please wait a ' 'few minutes before trying again.', kind='error', trust=False) else: status.push_status_message(status_message, kind='success', trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {}
def deserialize_contributors(node, user_dicts, auth): """View helper that returns a list of User objects from a list of serialized users (dicts). The users in the list may be registered or unregistered users. e.g. ``[{'id': 'abc123', 'registered': True, 'fullname': ..}, {'id': None, 'registered': False, 'fullname'...}, {'id': '123ab', 'registered': False, 'fullname': ...}] If a dict represents an unregistered user without an ID, creates a new unregistered User record. :param Node node: The node to add contributors to :param list(dict) user_dicts: List of serialized users in the format above. :param Auth auth: """ # Add the registered contributors contribs = [] for contrib_dict in user_dicts: fullname = contrib_dict['fullname'] visible = contrib_dict['visible'] email = contrib_dict.get('email') if contrib_dict['id']: contributor = User.load(contrib_dict['id']) else: try: contributor = User.create_unregistered( fullname=fullname, email=email) contributor.save() except ValidationValueError: contributor = get_user(email=email) # Add unclaimed record if necessary if (not contributor.is_registered and node._primary_key not in contributor.unclaimed_records): contributor.add_unclaimed_record(node=node, referrer=auth.user, given_name=fullname, email=email) contributor.save() unreg_contributor_added.send(node, contributor=contributor, auth=auth) contribs.append({ 'user': contributor, 'visible': visible, 'permissions': expand_permissions(contrib_dict.get('permission')) }) return contribs
def make_user(user_dict): if 'id' in user_dict: user = models.User.load(user_dict['id']) else: name, email = user_dict['nr_name'], user_dict['nr_email'] try: user = models.User.create_unregistered(fullname=name, email=email) user.save() except ValidationValueError: user = auth.get_user(email=email) if user is None: logger.error('Could not load user {0}'.format(user_dict)) return user
def reset_password_post(auth, verification_key=None, **kwargs): """ View for user to submit reset password form. HTTP Method: POST :raises: HTTPError(http.BAD_REQUEST) if verification_key is invalid """ # If user is already logged in, log user out if auth.logged_in: return auth_logout(redirect_url=request.url) form = ResetPasswordForm(request.form) # Check if request bears a valid verification_key user_obj = get_user(verification_key=verification_key) if not user_obj: error_data = { 'message_short': 'Invalid url.', 'message_long': 'The verification key in the URL is invalid or has expired.' } raise HTTPError(400, data=error_data) if form.validate(): # new random verification key, allows CAS to authenticate the user w/o password, one-time only. # this overwrite also invalidates the verification key generated by forgot_password_post user_obj.verification_key = generate_verification_key() try: user_obj.set_password(form.password.data) user_obj.save() except exceptions.ChangePasswordError as error: for message in error.messages: status.push_status_message(message, kind='warning', trust=False) else: status.push_status_message('Password reset', kind='success', trust=False) # redirect to CAS and authenticate the user with the one-time verification key. return redirect(cas.get_login_url( web_url_for('user_account', _absolute=True), username=user_obj.username, verification_key=user_obj.verification_key )) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return { 'verification_key': verification_key }, 400
def forgot_password_post(): """ View for user to submit forgot password form. HTTP Method: POST :return {} """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if not form.validate(): # Don't go anywhere forms.push_errors_to_status(form.errors) else: email = form.email.data status_message = ( 'If there is an OSF account associated with {0}, an email with instructions on how to ' 'reset the OSF password has been sent to {0}. If you do not receive an email and believe ' 'you should have, please contact OSF Support. ').format(email) kind = 'success' # check if the user exists user_obj = get_user(email=email) if user_obj: # rate limit forgot_password_post if not throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE): status_message = 'You have recently requested to change your password. Please wait a few minutes ' \ 'before trying again.' kind = 'error' # TODO [OSF-6673]: Use the feature in [OSF-6998] for user to resend claim email. elif user_obj.is_active: # new random verification key (v2) user_obj.verification_key_v2 = generate_verification_key( verification_type='password') user_obj.email_last_sent = timezone.now() user_obj.save() reset_link = furl.urljoin( settings.DOMAIN, web_url_for('reset_password_get', uid=user_obj._id, token=user_obj.verification_key_v2['token'])) mails.send_mail(to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link) status.push_status_message(status_message, kind=kind, trust=False) return {}
def form_valid(self, form): email = form.cleaned_data.get('emails') user = get_user(email) if user is None: raise TypeError reset_abs_url = furl(DOMAIN) user.verification_key = random_string(20) user.save() reset_abs_url.path.add( ('resetpassword/{}'.format(user.verification_key))) send_mail(subject='Reset OSF Password', message='Follow this link to reset your password: {}'.format( reset_abs_url.url), from_email=SUPPORT_EMAIL, recipient_list=[email]) return super(ResetPasswordView, self).form_valid(form)
def serialize_unregistered(fullname, email): """Serializes an unregistered user.""" user = auth.get_user(email=email) if user is None: serialized = { 'fullname': fullname, 'id': None, 'registered': False, 'active': False, 'gravatar': gravatar(email, use_ssl=True, size=settings.PROFILE_IMAGE_MEDIUM), 'email': email, } else: serialized = add_contributor_json(user) serialized['fullname'] = fullname serialized['email'] = email return serialized
def resend_confirmation_post(auth): """ View for user to submit resend confirmation form. HTTP Method: POST """ # If user is already logged in, log user out if auth.logged_in: return auth_logout(redirect_url=request.url) form = ResendConfirmationForm(request.form) if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) status_message = ( 'If there is an OSF account associated with this unconfirmed email address {0}, ' 'a confirmation email has been resent to it. If you do not receive an email and believe ' 'you should have, please contact OSF Support.').format(clean_email) kind = 'success' if user: if throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): try: send_confirm_email(user, clean_email, renew=True) except KeyError: # already confirmed, redirect to dashboard status_message = 'This email {0} has already been confirmed.'.format( clean_email) kind = 'warning' user.email_last_sent = timezone.now() user.save() else: status_message = ( 'You have recently requested to resend your confirmation email. ' 'Please wait a few minutes before trying again.') kind = 'error' status.push_status_message(status_message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {'form': form}
def reset_password(auth, **kwargs): """ Show reset password page. """ if auth.logged_in: return auth_logout(redirect_url=request.url) verification_key = kwargs['verification_key'] # Check if request bears a valid verification_key user_obj = get_user(verification_key=verification_key) if not user_obj: error_data = { 'message_short': 'Invalid url.', 'message_long': 'The verification key in the URL is invalid or has expired.' } raise HTTPError(400, data=error_data) return { 'verification_key': verification_key }
def form_valid(self, form): email = form.cleaned_data.get('emails') user = get_user(email) if user is None: raise TypeError reset_abs_url = furl(DOMAIN) user.verification_key = random_string(20) user.save() reset_abs_url.path.add(('resetpassword/{}'.format(user.verification_key))) send_mail( subject='Reset OSF Password', message='Follow this link to reset your password: {}'.format( reset_abs_url.url ), from_email=SUPPORT_EMAIL, recipient_list=[email] ) return super(ResetPasswordView, self).form_valid(form)
def forgot_password_post(): """Attempt to send user password reset or return respective error. """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if form.validate(): email = form.email.data status_message = ('If there is an OSF account associated with {0}, an email with instructions on how to reset ' 'the OSF password has been sent to {0}. If you do not receive an email and believe you ' 'should have, please contact OSF Support. ').format(email) user_obj = get_user(email=email) if user_obj: #TODO: Remove this rate limiting and replace it with something that doesn't write to the User model now = datetime.datetime.utcnow() last_attempt = user_obj.forgot_password_last_post or now - datetime.timedelta(seconds=FORGOT_PASSWORD_MINIMUM_TIME) user_obj.forgot_password_last_post = now time_since_last_attempt = now - last_attempt if time_since_last_attempt.seconds >= FORGOT_PASSWORD_MINIMUM_TIME: user_obj.verification_key = security.random_string(20) user_obj.save() reset_link = "http://{0}{1}".format( request.host, web_url_for( 'reset_password', verification_key=user_obj.verification_key ) ) mails.send_mail( to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link ) status.push_status_message(status_message, 'success') else: user_obj.save() status.push_status_message('You have recently requested to change your password. Please wait a little ' 'while before trying again.', 'error') else: status.push_status_message(status_message, 'success') forms.push_errors_to_status(form.errors) return auth_login(forgot_password_form=form)
def forgot_password_post(): """Attempt to send user password reset or return respective error. """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if form.validate(): email = form.email.data status_message = ( 'If there is an OSF account associated with {0}, an email with instructions on how to reset ' 'the OSF password has been sent to {0}. If you do not receive an email and believe you ' 'should have, please contact OSF Support. ').format(email) user_obj = get_user(email=email) if user_obj: if throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE): user_obj.verification_key = security.random_string(20) user_obj.email_last_sent = datetime.datetime.utcnow() user_obj.save() reset_link = furl.urljoin( settings.DOMAIN, web_url_for('reset_password', verification_key=user_obj.verification_key)) mails.send_mail(to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link) status.push_status_message(status_message, kind='success', trust=False) else: status.push_status_message( 'You have recently requested to change your password. Please wait a little ' 'while before trying again.', kind='error', trust=False) else: status.push_status_message(status_message, kind='success', trust=False) forms.push_errors_to_status(form.errors) return auth_login(forgot_password_form=form)
def invite_contributor_post(node, **kwargs): """API view for inviting an unregistered user. Performs validation, but does not actually invite the user. Expects JSON arguments with 'fullname' (required) and email (not required). """ fullname = request.json.get('fullname').strip() email = request.json.get('email') # Validate and sanitize inputs as needed. Email will raise error if invalid. fullname = sanitize.strip_html(fullname) if email: email = email.lower().strip() try: validate_email(email) except ValidationError as e: return {'status': 400, 'message': e.message}, 400 if not fullname: return { 'status': 400, 'message': 'Full name field cannot be empty' }, 400 # Check if email is in the database user = get_user(email=email) if user: if user.is_registered: msg = 'User is already in database. Please go back and try your search again.' return {'status': 400, 'message': msg}, 400 elif node.is_contributor(user): msg = 'User with this email address is already a contributor to this project.' return {'status': 400, 'message': msg}, 400 else: serialized = profile_utils.add_contributor_json(user) # use correct display name serialized['fullname'] = fullname serialized['email'] = email else: # Create a placeholder serialized = profile_utils.serialize_unregistered(fullname, email) return {'status': 'success', 'contributor': serialized}
def forgot_password_post(): """Attempt to send user password reset or return respective error. """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if form.validate(): email = form.email.data status_message = ( 'If there is an OSF account associated with {0}, an email with instructions on how to reset ' 'the OSF password has been sent to {0}. If you do not receive an email and believe you ' 'should have, please contact OSF Support. ').format(email) user_obj = get_user(email=email) if user_obj: #TODO: Remove this rate limiting and replace it with something that doesn't write to the User model now = datetime.datetime.utcnow() last_attempt = user_obj.forgot_password_last_post or now - datetime.timedelta( seconds=FORGOT_PASSWORD_MINIMUM_TIME) user_obj.forgot_password_last_post = now time_since_last_attempt = now - last_attempt if time_since_last_attempt.seconds >= FORGOT_PASSWORD_MINIMUM_TIME: user_obj.verification_key = security.random_string(20) user_obj.save() reset_link = "http://{0}{1}".format( request.host, web_url_for('reset_password', verification_key=user_obj.verification_key)) mails.send_mail(to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link) status.push_status_message(status_message, 'success') else: user_obj.save() status.push_status_message( 'You have recently requested to change your password. Please wait a little ' 'while before trying again.', 'error') else: status.push_status_message(status_message, 'success') forms.push_errors_to_status(form.errors) return auth_login(forgot_password_form=form)
def resend_confirmation_post(auth): """ View for user to submit resend confirmation form. HTTP Method: POST """ # If user is already logged in, log user out if auth.logged_in: return auth_logout(redirect_url=request.url) form = ResendConfirmationForm(request.form) if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) status_message = ('If there is an OSF account associated with this unconfirmed email {0}, ' 'a confirmation email has been resent to it. If you do not receive an email and believe ' 'you should have, please contact OSF Support.').format(clean_email) kind = 'success' if user: if throttle_period_expired(user.email_last_sent, settings.SEND_EMAIL_THROTTLE): try: send_confirm_email(user, clean_email, renew=True) except KeyError: # already confirmed, redirect to dashboard status_message = 'This email {0} has already been confirmed.'.format(clean_email) kind = 'warning' user.email_last_sent = datetime.datetime.utcnow() user.save() else: status_message = ('You have recently requested to resend your confirmation email. ' 'Please wait a few minutes before trying again.') kind = 'error' status.push_status_message(status_message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {'form': form}
def forgot_password_post(): """Attempt to send user password reset or return respective error. """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if form.validate(): email = form.email.data status_message = ('If there is an OSF account associated with {0}, an email with instructions on how to reset ' 'the OSF password has been sent to {0}. If you do not receive an email and believe you ' 'should have, please contact OSF Support. ').format(email) user_obj = get_user(email=email) if user_obj: if throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE): user_obj.verification_key = security.random_string(20) user_obj.email_last_sent = datetime.datetime.utcnow() user_obj.save() reset_link = furl.urljoin( settings.DOMAIN, web_url_for( 'reset_password', verification_key=user_obj.verification_key ) ) mails.send_mail( to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link ) status.push_status_message(status_message, kind='success', trust=False) else: status.push_status_message('You have recently requested to change your password. Please wait a little ' 'while before trying again.', kind='error', trust=False) else: status.push_status_message(status_message, kind='success', trust=False) forms.push_errors_to_status(form.errors) return auth_login(forgot_password_form=form)
def reset_password(**kwargs): verification_key = kwargs['verification_key'] form = ResetPasswordForm(request.form) user_obj = get_user(verification_key=verification_key) if not user_obj: error_data = {'message_short': 'Invalid url.', 'message_long': 'The verification key in the URL is invalid or ' 'has expired.'} raise exceptions.HTTPError(400, data=error_data) if request.method == 'POST' and form.validate(): user_obj.verification_key = None user_obj.set_password(form.password.data) user_obj.save() status.push_status_message('Password reset') return redirect('/account/') forms.push_errors_to_status(form.errors) return { 'verification_key': verification_key, }
def post(self, request, *args, **kwargs): email = self.request.POST['emails'] user = get_user(email) url = furl(DOMAIN) user.verification_key_v2 = generate_verification_key( verification_type='password') user.save() url.path.add( f'resetpassword/{user._id}/{user.verification_key_v2["token"]}') send_mail( subject='Reset OSF Password', message=f'Follow this link to reset your password: {url.url}', from_email=OSF_SUPPORT_EMAIL, recipient_list=[email]) update_admin_log(user_id=self.request.user.id, object_id=user.pk, object_repr='User', message=f'Emailed user {user.pk} a reset link.', action_flag=USER_EMAILED) return redirect(self.get_success_url())
def resend_confirmation(): """View for resending an email confirmation email. """ form = ResendConfirmationForm(request.form) if request.method == 'POST': if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) if not user: return {'form': form} try: send_confirm_email(user, clean_email) except KeyError: # already confirmed, redirect to dashboard status_message = 'Email has already been confirmed.' kind = 'warning' else: status_message = 'Resent email to {0}'.format(clean_email) kind = 'success' status.push_status_message(status_message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {'form': form}
def resend_confirmation(): """View for resending an email confirmation email. """ form = ResendConfirmationForm(request.form) if request.method == 'POST': if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) if not user: return {'form': form} try: send_confirm_email(user, clean_email) except KeyError: # already confirmed, redirect to dashboard status_message = 'Email has already been confirmed.' type_ = 'warning' else: status_message = 'Resent email to <em>{0}</em>'.format(clean_email) type_ = 'success' status.push_status_message(status_message, type_) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {'form': form}
def forgot_password(): form = ForgotPasswordForm(request.form, prefix='forgot_password') if form.validate(): email = form.email.data user_obj = get_user(username=email) if user_obj: user_obj.verification_key = security.random_string(20) user_obj.save() reset_link = "http://{0}{1}".format( request.host, web_url_for('reset_password', verification_key=user_obj.verification_key)) mails.send_mail(to_addr=email, mail=mails.FORGOT_PASSWORD, reset_link=reset_link) status.push_status_message('Reset email sent to {0}'.format(email)) else: status.push_status_message( 'Email {email} not found'.format(email=email)) forms.push_errors_to_status(form.errors) return auth_login(forgot_password_form=form)
def invite_contributor_post(node, **kwargs): """API view for inviting an unregistered user. Performs validation, but does not actually invite the user. Expects JSON arguments with 'fullname' (required) and email (not required). """ fullname = request.json.get('fullname').strip() email = request.json.get('email') # Validate and sanitize inputs as needed. Email will raise error if invalid. fullname = sanitize.strip_html(fullname) if email: email = email.lower().strip() try: validate_email(email) except ValidationError as e: return {'status': 400, 'message': e.message}, 400 if not fullname: return {'status': 400, 'message': 'Full name field cannot be empty'}, 400 # Check if email is in the database user = get_user(email=email) if user: if user.is_registered: msg = 'User is already in database. Please go back and try your search again.' return {'status': 400, 'message': msg}, 400 elif node.is_contributor(user): msg = 'User with this email address is already a contributor to this project.' return {'status': 400, 'message': msg}, 400 else: serialized = profile_utils.add_contributor_json(user) # use correct display name serialized['fullname'] = fullname serialized['email'] = email else: # Create a placeholder serialized = profile_utils.serialize_unregistered(fullname, email) return {'status': 'success', 'contributor': serialized}
def reset_password_get(auth, verification_key=None, **kwargs): """ View for user to land on the reset password page. HTTp Method: GET :raises: HTTPError(http.BAD_REQUEST) if verification_key is invalid """ # If user is already logged in, log user out if auth.logged_in: return auth_logout(redirect_url=request.url) # Check if request bears a valid verification_key user_obj = get_user(verification_key=verification_key) if not user_obj: error_data = { 'message_short': 'Invalid url.', 'message_long': 'The verification key in the URL is invalid or has expired.' } raise HTTPError(400, data=error_data) return { 'verification_key': verification_key, }
def test_get_user_by_external_info(self): service_url = 'http://localhost:5000/dashboard/' user, validated_credentials, cas_resp = generate_external_user_with_resp(service_url) user.save() assert_equal(auth.get_user(external_id_provider=validated_credentials['provider'], external_id=validated_credentials['id']), user)
def test_get_user_by_username(self): user = UserFactory() assert_equal(auth.get_user(username=user.username), user)
def deserialize_contributors(node, user_dicts, auth, validate=False): """View helper that returns a list of User objects from a list of serialized users (dicts). The users in the list may be registered or unregistered users. e.g. ``[{'id': 'abc123', 'registered': True, 'fullname': ..}, {'id': None, 'registered': False, 'fullname'...}, {'id': '123ab', 'registered': False, 'fullname': ...}] If a dict represents an unregistered user without an ID, creates a new unregistered User record. :param Node node: The node to add contributors to :param list(dict) user_dicts: List of serialized users in the format above. :param Auth auth: :param bool validate: Whether to validate and sanitize fields (if necessary) """ # Add the registered contributors contribs = [] for contrib_dict in user_dicts: fullname = contrib_dict['fullname'] visible = contrib_dict['visible'] email = contrib_dict.get('email') if validate is True: # Validate and sanitize inputs as needed. Email will raise error if invalid. # TODO Edge case bug: validation and saving are performed in same loop, so all in list # up to the invalid entry will be saved. (communicate to the user what needs to be retried) fullname = sanitize.strip_html(fullname) if not fullname: raise ValidationValueError('Full name field cannot be empty') if email is not None: validate_email( email) # Will raise a ValidationError if email invalid if contrib_dict['id']: contributor = User.load(contrib_dict['id']) else: try: contributor = User.create_unregistered(fullname=fullname, email=email) contributor.save() except ValidationValueError: ## FIXME: This suppresses an exception if ID not found & new validation fails; get_user will return None contributor = get_user(email=email) # Add unclaimed record if necessary if (not contributor.is_registered and node._primary_key not in contributor.unclaimed_records): contributor.add_unclaimed_record(node=node, referrer=auth.user, given_name=fullname, email=email) contributor.save() unreg_contributor_added.send(node, contributor=contributor, auth=auth) contribs.append({ 'user': contributor, 'visible': visible, 'permissions': expand_permissions(contrib_dict.get('permission')) }) return contribs
def __call__(self, form, field): if not auth.get_user(email=field.data): msg = self.message or language.EMAIL_NOT_FOUND.format( email=field.data) raise ValidationError(msg)
def test_get_user_by_email(self): user = UserFactory() assert_equal(auth.get_user(email=user.username), user)
def deserialize_contributors(node, user_dicts, auth, validate=False): """View helper that returns a list of User objects from a list of serialized users (dicts). The users in the list may be registered or unregistered users. e.g. ``[{'id': 'abc123', 'registered': True, 'fullname': ..}, {'id': None, 'registered': False, 'fullname'...}, {'id': '123ab', 'registered': False, 'fullname': ...}] If a dict represents an unregistered user without an ID, creates a new unregistered User record. :param Node node: The node to add contributors to :param list(dict) user_dicts: List of serialized users in the format above. :param Auth auth: :param bool validate: Whether to validate and sanitize fields (if necessary) """ # Add the registered contributors contribs = [] for contrib_dict in user_dicts: fullname = contrib_dict['fullname'] visible = contrib_dict['visible'] email = contrib_dict.get('email') if validate is True: # Validate and sanitize inputs as needed. Email will raise error if invalid. # TODO Edge case bug: validation and saving are performed in same loop, so all in list # up to the invalid entry will be saved. (communicate to the user what needs to be retried) fullname = sanitize.strip_html(fullname) if not fullname: raise ValidationValueError('Full name field cannot be empty') if email: validate_email(email) # Will raise a ValidationError if email invalid if contrib_dict['id']: contributor = User.load(contrib_dict['id']) else: try: contributor = User.create_unregistered( fullname=fullname, email=email) contributor.save() except ValidationValueError: ## FIXME: This suppresses an exception if ID not found & new validation fails; get_user will return None contributor = get_user(email=email) # Add unclaimed record if necessary if (not contributor.is_registered and node._primary_key not in contributor.unclaimed_records): contributor.add_unclaimed_record(node=node, referrer=auth.user, given_name=fullname, email=email) contributor.save() unreg_contributor_added.send(node, contributor=contributor, auth=auth) contribs.append({ 'user': contributor, 'visible': visible, 'permissions': expand_permissions(contrib_dict.get('permission')) }) return contribs
def __call__(self, form, field): if not auth.get_user(email=field.data): msg = self.message or language.EMAIL_NOT_FOUND.format(email=field.data) raise ValidationError(msg)
def _forgot_password_post(mail_template, reset_route, institutional=False): """ View for user to submit forgot password form (standard or institutional). Validates submitted form and sends reset-password link via email if valid. If user has submitted another password reset request recently, declines to create a new one and asks the user to not submit again for awhile. Standard and institutional forgot-password requests behave similarly but use slightly different language and interfaces. When an institution is deactivated, the user should be given the opportunity to reclaim their account. CAS co-ops the forgot-password functionality to send a "set a new password" email link to the institutional user. The language of the email has been adjusted from the standard context, the response html the status message from the reset action is displayed as regular text, and the password form is not shown. HTTP Method: POST :return {} """ form = ForgotPasswordForm(request.form, prefix='forgot_password') if not form.validate(): # Don't go anywhere forms.push_errors_to_status(form.errors) else: email = form.email.data status_message = ('If there is an OSF account associated with {0}, an email with instructions on how to ' 'reset the OSF password has been sent to {0}. If you do not receive an email and believe ' 'you should have, please contact OSF Support. ').format(email) kind = 'success' # check if the user exists user_obj = get_user(email=email) if user_obj: # rate limit forgot_password_post if not throttle_period_expired(user_obj.email_last_sent, settings.SEND_EMAIL_THROTTLE): status_message = 'You have recently requested to change your password. Please wait a few minutes ' \ 'before trying again.' kind = 'error' # TODO [OSF-6673]: Use the feature in [OSF-6998] for user to resend claim email. elif user_obj.is_active: # new random verification key (v2) user_obj.verification_key_v2 = generate_verification_key(verification_type='password') user_obj.email_last_sent = timezone.now() user_obj.save() reset_link = furl.urljoin( settings.DOMAIN, web_url_for( reset_route, uid=user_obj._id, token=user_obj.verification_key_v2['token'] ) ) mails.send_mail( to_addr=email, mail=mail_template, reset_link=reset_link, can_change_preferences=False, ) # institutional forgot password page displays the message as main text, not as an alert if institutional: # pass isError instead of kind to template to decouple python error flag from template's # css class return {'message': status_message, 'isError': (kind == 'error'), 'institutional': institutional} status.push_status_message(status_message, kind=kind, trust=False) return {}
def external_login_email_post(): """ View to handle email submission for first-time oauth-login user. HTTP Method: POST """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] external_id = session.data['auth_user_external_id'] fullname = session.data['auth_user_fullname'] if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) external_identity = { external_id_provider: { external_id: None, }, } try: ensure_external_identity_uniqueness(external_id_provider, external_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if user: # 1. update user oauth, with pending status external_identity[external_id_provider][external_id] = 'LINK' if external_id_provider in user.external_identity: user.external_identity[external_id_provider].update(external_identity[external_id_provider]) else: user.external_identity.update(external_identity) # 2. add unconfirmed email and send confirmation email user.add_unconfirmed_email(clean_email, external_identity=external_identity) user.save() send_confirm_email(user, clean_email, external_id_provider=external_id_provider, external_id=external_id) # 3. notify user message = language.EXTERNAL_LOGIN_EMAIL_LINK_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 4. remove session and osf cookie remove_session(session) else: # 1. create unconfirmed user with pending status external_identity[external_id_provider][external_id] = 'CREATE' user = User.create_unconfirmed( username=clean_email, password=str(uuid.uuid4()), fullname=fullname, external_identity=external_identity, campaign=None ) # TODO: [#OSF-6934] update social fields, verified social fields cannot be modified user.save() # 3. send confirmation email send_confirm_email(user, user.username, external_id_provider=external_id_provider, external_id=external_id) # 4. notify user message = language.EXTERNAL_LOGIN_EMAIL_CREATE_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 5. remove session remove_session(session) status.push_status_message(message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return { 'form': form, 'external_id_provider': external_id_provider }
def external_login_email_post(): """ View to handle email submission for first-time oauth-login user. HTTP Method: POST """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] external_id = session.data['auth_user_external_id'] fullname = session.data['auth_user_fullname'] service_url = session.data['service_url'] destination = 'dashboard' for campaign in campaigns.get_campaigns(): if campaign != 'institution': # Handle different url encoding schemes between `furl` and `urlparse/urllib`. # OSF use `furl` to parse service url during service validation with CAS. However, `web_url_for()` uses # `urlparse/urllib` to generate service url. `furl` handles `urlparser/urllib` generated urls while ` but # not vice versa. campaign_url = furl.furl(campaigns.campaign_url_for(campaign)).url if campaigns.is_proxy_login(campaign): # proxy campaigns: OSF Preprints and branded ones if check_service_url_with_proxy_campaign(service_url, campaign_url): destination = campaign # continue to check branded preprints even service url matches osf preprints if campaign != 'osf-preprints': break elif service_url.startswith(campaign_url): # osf campaigns: OSF Prereg and ERPC destination = campaign break if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) external_identity = { external_id_provider: { external_id: None, }, } try: ensure_external_identity_uniqueness(external_id_provider, external_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if user: # 1. update user oauth, with pending status external_identity[external_id_provider][external_id] = 'LINK' if external_id_provider in user.external_identity: user.external_identity[external_id_provider].update(external_identity[external_id_provider]) else: user.external_identity.update(external_identity) # 2. add unconfirmed email and send confirmation email user.add_unconfirmed_email(clean_email, external_identity=external_identity) user.save() send_confirm_email( user, clean_email, external_id_provider=external_id_provider, external_id=external_id, destination=destination ) # 3. notify user message = language.EXTERNAL_LOGIN_EMAIL_LINK_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 4. remove session and osf cookie remove_session(session) else: # 1. create unconfirmed user with pending status external_identity[external_id_provider][external_id] = 'CREATE' user = User.create_unconfirmed( username=clean_email, password=str(uuid.uuid4()), fullname=fullname, external_identity=external_identity, campaign=None ) # TODO: [#OSF-6934] update social fields, verified social fields cannot be modified user.save() # 3. send confirmation email send_confirm_email( user, user.username, external_id_provider=external_id_provider, external_id=external_id, destination=destination ) # 4. notify user message = language.EXTERNAL_LOGIN_EMAIL_CREATE_SUCCESS.format( external_id_provider=external_id_provider, email=user.username ) kind = 'success' # 5. remove session remove_session(session) status.push_status_message(message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return { 'form': form, 'external_id_provider': external_id_provider }
def external_login_email_post(): """ View to handle email submission for first-time oauth-login user. HTTP Method: POST """ form = ResendConfirmationForm(request.form) session = get_session() if not session.is_external_first_login: raise HTTPError(http.UNAUTHORIZED) external_id_provider = session.data['auth_user_external_id_provider'] external_id = session.data['auth_user_external_id'] fullname = session.data['auth_user_fullname'] service_url = session.data['service_url'] # TODO: @cslzchen use user tags instead of destination destination = 'dashboard' for campaign in campaigns.get_campaigns(): if campaign != 'institution': # Handle different url encoding schemes between `furl` and `urlparse/urllib`. # OSF use `furl` to parse service url during service validation with CAS. However, `web_url_for()` uses # `urlparse/urllib` to generate service url. `furl` handles `urlparser/urllib` generated urls while ` but # not vice versa. campaign_url = furl.furl(campaigns.campaign_url_for(campaign)).url external_campaign_url = furl.furl( campaigns.external_campaign_url_for(campaign)).url if campaigns.is_proxy_login(campaign): # proxy campaigns: OSF Preprints and branded ones if check_service_url_with_proxy_campaign( str(service_url), campaign_url, external_campaign_url): destination = campaign # continue to check branded preprints even service url matches osf preprints if campaign != 'osf-preprints': break elif service_url.startswith(campaign_url): # osf campaigns: OSF Prereg and ERPC destination = campaign break if form.validate(): clean_email = form.email.data user = get_user(email=clean_email) external_identity = { external_id_provider: { external_id: None, }, } try: ensure_external_identity_uniqueness(external_id_provider, external_id, user) except ValidationError as e: raise HTTPError(http.FORBIDDEN, e.message) if user: # 1. update user oauth, with pending status external_identity[external_id_provider][external_id] = 'LINK' if external_id_provider in user.external_identity: user.external_identity[external_id_provider].update( external_identity[external_id_provider]) else: user.external_identity.update(external_identity) # 2. add unconfirmed email and send confirmation email user.add_unconfirmed_email(clean_email, external_identity=external_identity) user.save() send_confirm_email(user, clean_email, external_id_provider=external_id_provider, external_id=external_id, destination=destination) # 3. notify user message = language.EXTERNAL_LOGIN_EMAIL_LINK_SUCCESS.format( external_id_provider=external_id_provider, email=user.username) kind = 'success' # 4. remove session and osf cookie remove_session(session) else: # 1. create unconfirmed user with pending status external_identity[external_id_provider][external_id] = 'CREATE' user = OSFUser.create_unconfirmed( username=clean_email, password=None, fullname=fullname, external_identity=external_identity, campaign=None) # TODO: [#OSF-6934] update social fields, verified social fields cannot be modified user.save() # 3. send confirmation email send_confirm_email(user, user.username, external_id_provider=external_id_provider, external_id=external_id, destination=destination) # 4. notify user message = language.EXTERNAL_LOGIN_EMAIL_CREATE_SUCCESS.format( external_id_provider=external_id_provider, email=user.username) kind = 'success' # 5. remove session remove_session(session) status.push_status_message(message, kind=kind, trust=False) else: forms.push_errors_to_status(form.errors) # Don't go anywhere return {'form': form, 'external_id_provider': external_id_provider}
def test_get_user_with_wrong_password_returns_false(self): user = UserFactory.build() user.set_password('killerqueen') assert_false(auth.get_user(email=user.username, password='******'))