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 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 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(( '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), 'success') forms.push_errors_to_status(form.errors) return auth_login(forgot_password_form=form)
def auth_register_post(): if not settings.ALLOW_REGISTRATION: status.push_status_message(language.REGISTRATION_UNAVAILABLE) return redirect('/') form = RegistrationForm(request.form, prefix='register') # Process form if form.validate(): try: user = framework.auth.register_unconfirmed( form.username.data, form.password.data, form.fullname.data) framework.auth.signals.user_registered.send(user) except (ValidationValueError, DuplicateEmailError): status.push_status_message( language.ALREADY_REGISTERED.format(email=form.username.data)) return auth_login() if user: if settings.CONFIRM_REGISTRATIONS_BY_EMAIL: send_confirm_email(user, email=user.username) message = language.REGISTRATION_SUCCESS.format(email=user.username) status.push_status_message(message, 'success') return auth_login() else: return redirect('/login/first/') else: forms.push_errors_to_status(form.errors) return auth_login()
def reset_password(auth, **kwargs): if auth.logged_in: logout() 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(): 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 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 auth_register_post(): if not settings.ALLOW_REGISTRATION: status.push_status_message(language.REGISTRATION_UNAVAILABLE) return redirect('/') form = RegistrationForm(request.form, prefix='register') # Process form if form.validate(): try: user = framework.auth.register_unconfirmed(form.username.data, form.password.data, form.fullname.data) framework.auth.signals.user_registered.send(user) except (ValidationValueError, DuplicateEmailError): status.push_status_message( language.ALREADY_REGISTERED.format(email=form.username.data)) return auth_login() if user: if settings.CONFIRM_REGISTRATIONS_BY_EMAIL: send_confirm_email(user, email=user.username) message = language.REGISTRATION_SUCCESS.format( email=user.username) status.push_status_message(message, 'success') return auth_login() else: return redirect('/login/first/') else: forms.push_errors_to_status(form.errors) return auth_login()
def forgot_password(): """Return forgot password page upon GET request. If POST, attempt to send user password reset or return respective error. """ if request.method == 'GET': return {} 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)) forms.push_errors_to_status(form.errors) return auth_login(forgot_password_form=form)
def forgot_password(): """Return forgot password page upon GET request. If POST, attempt to send user password reset or return respective error. """ if request.method == 'GET': return {} 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 claim_user_form(auth, **kwargs): """View for rendering the set password page for a claimed user. Must have ``token`` as a querystring argument. Renders the set password form, validates it, and sets the user's password. """ uid, pid = kwargs['uid'], kwargs['pid'] token = request.form.get('token') or request.args.get('token') # If user is logged in, redirect to 're-enter password' page if auth.logged_in: return redirect( web_url_for('claim_user_registered', uid=uid, pid=pid, token=token)) user = User.load(uid) # The unregistered user # user ID is invalid. Unregistered user is not in database if not user: raise HTTPError(http.BAD_REQUEST) # If claim token not valid, redirect to registration page if not verify_claim_token(user, token, pid): return redirect(web_url_for('auth_login')) unclaimed_record = user.unclaimed_records[pid] user.fullname = unclaimed_record['name'] user.update_guessed_names() # The email can be the original referrer email if no claimer email has been specified. claimer_email = unclaimed_record.get( 'claimer_email') or unclaimed_record.get('email') form = SetEmailAndPasswordForm(request.form, token=token) if request.method == 'POST': if form.validate(): username, password = claimer_email, form.password.data user.register(username=username, password=password) # Clear unclaimed records user.unclaimed_records = {} user.verification_key = security.random_string(20) user.save() # Authenticate user and redirect to project page node = Node.load(pid) status.push_status_message( language.CLAIMED_CONTRIBUTOR.format(node=node), kind='success', trust=True) # Redirect to CAS and authenticate the user with a verification key. return redirect( cas.get_login_url(web_url_for('user_profile', _absolute=True), auto=True, username=user.username, verification_key=user.verification_key)) else: forms.push_errors_to_status(form.errors) return { 'firstname': user.given_name, 'email': claimer_email if claimer_email else '', 'fullname': user.fullname, 'form': forms.utils.jsonify(form) if is_json_request() else form, }
def claim_user_registered(auth, node, **kwargs): """View that prompts user to enter their password in order to claim contributorship on a project. A user must be logged in. """ current_user = auth.user sign_out_url = web_url_for('auth_login', logout=True, next=request.url) if not current_user: return redirect(sign_out_url) # Logged in user should not be a contributor the project if node.is_contributor(current_user): logout_url = web_url_for('auth_logout', redirect_url=request.url) data = { 'message_short': 'Already a contributor', 'message_long': ('The logged-in user is already a contributor to this ' 'project. Would you like to <a href="{}">log out</a>?').format(logout_url) } raise HTTPError(http.BAD_REQUEST, data=data) uid, pid, token = kwargs['uid'], kwargs['pid'], kwargs['token'] unreg_user = User.load(uid) if not verify_claim_token(unreg_user, token, pid=node._primary_key): raise HTTPError(http.BAD_REQUEST) # Store the unreg_user data on the session in case the user registers # a new account session.data['unreg_user'] = { 'uid': uid, 'pid': pid, 'token': token } form = PasswordForm(request.form) if request.method == 'POST': if form.validate(): if current_user.check_password(form.password.data): node.replace_contributor(old=unreg_user, new=current_user) node.save() status.push_status_message( 'You are now a contributor to this project.', kind='success', trust=False ) return redirect(node.url) else: status.push_status_message(language.LOGIN_FAILED, kind='warning', trust=False) else: forms.push_errors_to_status(form.errors) if is_json_request(): form_ret = forms.utils.jsonify(form) user_ret = profile_utils.serialize_user(current_user, full=False) else: form_ret = form user_ret = current_user return { 'form': form_ret, 'user': user_ret, 'signOutUrl': sign_out_url }
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 claim_user_form(auth, **kwargs): """ View for rendering the set password page for a claimed user. Must have ``token`` as a querystring argument. Renders the set password form, validates it, and sets the user's password. HTTP Method: GET, POST """ uid, pid = kwargs['uid'], kwargs['pid'] token = request.form.get('token') or request.args.get('token') user = User.load(uid) # If unregistered user is not in database, or url bears an invalid token raise HTTP 400 error if not user or not verify_claim_token(user, token, pid): error_data = { 'message_short': 'Invalid url.', 'message_long': 'Claim user does not exists, the token in the URL is invalid or has expired.' } raise HTTPError(http.BAD_REQUEST, data=error_data) # If user is logged in, redirect to 're-enter password' page if auth.logged_in: return redirect(web_url_for('claim_user_registered', uid=uid, pid=pid, token=token)) unclaimed_record = user.unclaimed_records[pid] user.fullname = unclaimed_record['name'] user.update_guessed_names() # The email can be the original referrer email if no claimer email has been specified. claimer_email = unclaimed_record.get('claimer_email') or unclaimed_record.get('email') form = SetEmailAndPasswordForm(request.form, token=token) if request.method == 'POST': if not form.validate(): forms.push_errors_to_status(form.errors) elif settings.RECAPTCHA_SITE_KEY and not validate_recaptcha(request.form.get('g-recaptcha-response'), remote_ip=request.remote_addr): status.push_status_message('Invalid captcha supplied.', kind='error') else: username, password = claimer_email, form.password.data user.register(username=username, password=password) # Clear unclaimed records user.unclaimed_records = {} user.verification_key = generate_verification_key() user.save() # Authenticate user and redirect to project page status.push_status_message(language.CLAIMED_CONTRIBUTOR, kind='success', trust=True) # Redirect to CAS and authenticate the user with a verification key. return redirect(cas.get_login_url( web_url_for('view_project', pid=pid, _absolute=True), username=user.username, verification_key=user.verification_key )) return { 'firstname': user.given_name, 'email': claimer_email if claimer_email else '', 'fullname': user.fullname, 'form': forms.utils.jsonify(form) if is_json_request() else form, }
def claim_user_form(auth, **kwargs): """ View for rendering the set password page for a claimed user. Must have ``token`` as a querystring argument. Renders the set password form, validates it, and sets the user's password. HTTP Method: GET, POST """ uid, pid = kwargs['uid'], kwargs['pid'] token = request.form.get('token') or request.args.get('token') user = User.load(uid) # If unregistered user is not in database, or url bears an invalid token raise HTTP 400 error if not user or not verify_claim_token(user, token, pid): error_data = { 'message_short': 'Invalid url.', 'message_long': 'Claim user does not exists, the token in the URL is invalid or has expired.' } raise HTTPError(http.BAD_REQUEST, data=error_data) # If user is logged in, redirect to 're-enter password' page if auth.logged_in: return redirect(web_url_for('claim_user_registered', uid=uid, pid=pid, token=token)) unclaimed_record = user.unclaimed_records[pid] user.fullname = unclaimed_record['name'] user.update_guessed_names() # The email can be the original referrer email if no claimer email has been specified. claimer_email = unclaimed_record.get('claimer_email') or unclaimed_record.get('email') form = SetEmailAndPasswordForm(request.form, token=token) if request.method == 'POST': if form.validate(): username, password = claimer_email, form.password.data user.register(username=username, password=password) # Clear unclaimed records user.unclaimed_records = {} user.verification_key = generate_verification_key() user.save() # Authenticate user and redirect to project page status.push_status_message(language.CLAIMED_CONTRIBUTOR, kind='success', trust=True) # Redirect to CAS and authenticate the user with a verification key. return redirect(cas.get_login_url( web_url_for('view_project', pid=pid, _absolute=True), username=user.username, verification_key=user.verification_key )) else: forms.push_errors_to_status(form.errors) return { 'firstname': user.given_name, 'email': claimer_email if claimer_email else '', 'fullname': user.fullname, 'form': forms.utils.jsonify(form) if is_json_request() else form, }
def auth_login(auth, registration_form=None, forgot_password_form=None, **kwargs): """If GET request, show login page. If POST, attempt to log user in if login form passsed; else send forgot password email. """ if auth.logged_in: if not request.args.get('logout'): return redirect('/dashboard/') logout() direct_call = registration_form or forgot_password_form if request.method == 'POST' and not direct_call: form = SignInForm(request.form) if form.validate(): twofactor_code = None if 'twofactor' in website.settings.ADDONS_REQUESTED: twofactor_code = form.two_factor.data try: response = login( form.username.data, form.password.data, twofactor_code ) return response except exceptions.LoginDisabledError: status.push_status_message(language.DISABLED, 'error') except exceptions.LoginNotAllowedError: status.push_status_message(language.UNCONFIRMED, 'warning') # Don't go anywhere return {'next_url': ''} except exceptions.PasswordIncorrectError: status.push_status_message(language.LOGIN_FAILED) except exceptions.TwoFactorValidationError: status.push_status_message(language.TWO_FACTOR_FAILED) forms.push_errors_to_status(form.errors) if kwargs.get('first', False): status.push_status_message('You may now log in') # Get next URL from GET / POST data next_url = request.args.get( 'next', request.form.get( 'next_url', '' ) ) status_message = request.args.get('status', '') if status_message == 'expired': status.push_status_message('The private link you used is expired.') code = http.OK if next_url: status.push_status_message(language.MUST_LOGIN) # Don't raise error if user is being logged out if not request.args.get('logout'): code = http.UNAUTHORIZED return {'next_url': next_url}, code
def claim_user_registered(auth, node, **kwargs): """ View that prompts user to enter their password in order to claim being a contributor on a project. A user must be logged in. """ current_user = auth.user sign_out_url = web_url_for("auth_register", logout=True, next=request.url) if not current_user: return redirect(sign_out_url) # Logged in user should not be a contributor the project if node.is_contributor(current_user): logout_url = web_url_for("auth_logout", redirect_url=request.url) data = { "message_short": "Already a contributor", "message_long": ( "The logged-in user is already a contributor to this " 'project. Would you like to <a href="{}">log out</a>?' ).format(logout_url), } raise HTTPError(http.BAD_REQUEST, data=data) uid, pid, token = kwargs["uid"], kwargs["pid"], kwargs["token"] unreg_user = User.load(uid) if not verify_claim_token(unreg_user, token, pid=node._primary_key): error_data = { "message_short": "Invalid url.", "message_long": "The token in the URL is invalid or has expired.", } raise HTTPError(http.BAD_REQUEST, data=error_data) # Store the unreg_user data on the session in case the user registers # a new account session.data["unreg_user"] = {"uid": uid, "pid": pid, "token": token} form = PasswordForm(request.form) if request.method == "POST": if form.validate(): if current_user.check_password(form.password.data): node.replace_contributor(old=unreg_user, new=current_user) node.save() status.push_status_message("You are now a contributor to this project.", kind="success", trust=False) return redirect(node.url) else: status.push_status_message(language.LOGIN_FAILED, kind="warning", trust=False) else: forms.push_errors_to_status(form.errors) if is_json_request(): form_ret = forms.utils.jsonify(form) user_ret = profile_utils.serialize_user(current_user, full=False) else: form_ret = form user_ret = current_user return {"form": form_ret, "user": user_ret, "signOutUrl": sign_out_url}
def claim_user_form(auth, **kwargs): """View for rendering the set password page for a claimed user. Must have ``token`` as a querystring argument. Renders the set password form, validates it, and sets the user's password. """ uid, pid = kwargs['uid'], kwargs['pid'] token = request.form.get('token') or request.args.get('token') # If user is logged in, redirect to 're-enter password' page if auth.logged_in: return redirect(web_url_for('claim_user_registered', uid=uid, pid=pid, token=token)) user = User.load(uid) # The unregistered user # user ID is invalid. Unregistered user is not in database if not user: raise HTTPError(http.BAD_REQUEST) # If claim token not valid, redirect to registration page if not verify_claim_token(user, token, pid): return redirect(web_url_for('auth_login')) unclaimed_record = user.unclaimed_records[pid] user.fullname = unclaimed_record['name'] user.update_guessed_names() # The email can be the original referrer email if no claimer email has been specified. claimer_email = unclaimed_record.get('claimer_email') or unclaimed_record.get('email') form = SetEmailAndPasswordForm(request.form, token=token) if request.method == 'POST': if form.validate(): username, password = claimer_email, form.password.data user.register(username=username, password=password) # Clear unclaimed records user.unclaimed_records = {} user.verification_key = security.random_string(20) user.save() # Authenticate user and redirect to project page node = Node.load(pid) status.push_status_message(language.CLAIMED_CONTRIBUTOR.format(node=node), kind='success', trust=True) # Redirect to CAS and authenticate the user with a verification key. return redirect(cas.get_login_url( web_url_for('user_profile', _absolute=True), auto=True, username=user.username, verification_key=user.verification_key )) else: forms.push_errors_to_status(form.errors) return { 'firstname': user.given_name, 'email': claimer_email if claimer_email else '', 'fullname': user.fullname, 'form': forms.utils.jsonify(form) if is_json_request() else form, }
def reset_password_post(uid=None, token=None): """ View for user to submit reset password form. HTTP Method: POST :param uid: the user id :param token: the token in verification key :return: :raises: HTTPError(http.BAD_REQUEST) if verification key for the user is invalid, has expired or was used """ form = ResetPasswordForm(request.form) # Check if request bears a valid pair of `uid` and `token` user_obj = OSFUser.load(uid) if not (user_obj and user_obj.verify_password_token(token=token)): error_data = { 'message_short': 'Invalid Request.', 'message_long': 'The requested URL is invalid, has expired, or was already used', } raise HTTPError(http.BAD_REQUEST, data=error_data) if not form.validate(): # Don't go anywhere forms.push_errors_to_status(form.errors) else: # clear verification key (v2) user_obj.verification_key_v2 = {} # new verification key (v1) for CAS user_obj.verification_key = generate_verification_key( verification_type=None) 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 automatically with 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)) return { 'uid': user_obj._id, 'token': user_obj.verification_key_v2['token'], }
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 auth_login(auth, registration_form=None, forgot_password_form=None, **kwargs): """If GET request, show login page. If POST, attempt to log user in if login form passsed; else send forgot password email. """ if auth.logged_in: if not request.args.get('logout'): return redirect('/dashboard/') logout() direct_call = registration_form or forgot_password_form if request.method == 'POST' and not direct_call: form = SignInForm(request.form) if form.validate(): twofactor_code = None if 'twofactor' in website.settings.ADDONS_REQUESTED: twofactor_code = form.two_factor.data try: response = login(form.username.data, form.password.data, twofactor_code) return response except exceptions.LoginDisabledError: status.push_status_message(language.DISABLED, 'error') except exceptions.LoginNotAllowedError: status.push_status_message(language.UNCONFIRMED, 'warning') # Don't go anywhere return {'next_url': ''} except exceptions.PasswordIncorrectError: status.push_status_message(language.LOGIN_FAILED) except exceptions.TwoFactorValidationError: status.push_status_message(language.TWO_FACTOR_FAILED) forms.push_errors_to_status(form.errors) if kwargs.get('first', False): status.push_status_message('You may now log in') # Get next URL from GET / POST data next_url = request.args.get('next', request.form.get('next_url', '')) status_message = request.args.get('status', '') if status_message == 'expired': status.push_status_message('The private link you used is expired.') code = http.OK if next_url: status.push_status_message(language.MUST_LOGIN) # Don't raise error if user is being logged out if not request.args.get('logout'): code = http.UNAUTHORIZED return {'next_url': next_url}, code
def merge_user_post(auth, **kwargs): '''View for merging an account. Takes either JSON or form data. Request data should include a "merged_username" and "merged_password" properties for the account to be merged in. ''' master = auth.user if request.json: merged_username = request.json.get('merged_username') merged_password = request.json.get('merged_password') else: form = MergeAccountForm(request.form) if not form.validate(): forms.push_errors_to_status(form.errors) return merge_user_get(**kwargs) master_password = form.user_password.data if not master.check_password(master_password): status.push_status_message( 'Could not authenticate. Please check your username and password.', trust=False) return merge_user_get(**kwargs) merged_username = form.merged_username.data merged_password = form.merged_password.data try: merged_user = User.find_one(Q('username', 'eq', merged_username)) except NoResultsFound: status.push_status_message( 'Could not find that user. Please check the username and password.', trust=False) return merge_user_get(**kwargs) if master and merged_user: if merged_user.check_password(merged_password): master.merge_user(merged_user) master.save() if request.form: status.push_status_message( 'Successfully merged {0} with this account'.format( merged_username), kind='success', trust=False) return redirect('/settings/') return {'status': 'success'} else: status.push_status_message( 'Could not find that user. Please check the username and password.', trust=False) return merge_user_get(**kwargs) else: raise HTTPError(http.BAD_REQUEST)
def claim_user_form(auth, **kwargs): """View for rendering the set password page for a claimed user. Must have ``token`` as a querystring argument. Renders the set password form, validates it, and sets the user's password. """ uid, pid = kwargs['uid'], kwargs['pid'] token = request.form.get('token') or request.args.get('token') # If user is logged in, redirect to 're-enter password' page if auth.logged_in: return redirect( web_url_for('claim_user_registered', uid=uid, pid=pid, token=token)) user = User.load(uid) # The unregistered user # user ID is invalid. Unregistered user is not in database if not user: raise HTTPError(http.BAD_REQUEST) # If claim token not valid, redirect to registration page if not verify_claim_token(user, token, pid): return redirect('/account/') unclaimed_record = user.unclaimed_records[pid] user.fullname = unclaimed_record['name'] user.update_guessed_names() email = unclaimed_record['email'] form = SetEmailAndPasswordForm(request.form, token=token) if request.method == 'POST': if form.validate(): username, password = form.username.data, form.password.data user.register(username=username, password=password) # Clear unclaimed records user.unclaimed_records = {} user.save() # Authenticate user and redirect to project page response = redirect('/settings/') node = Node.load(pid) status.push_status_message( language.CLAIMED_CONTRIBUTOR.format(node=node), 'success') return authenticate(user, response) else: forms.push_errors_to_status(form.errors) return { 'firstname': user.given_name, 'email': email if email else '', 'fullname': user.fullname, 'form': forms.utils.jsonify(form) if is_json_request() else form, }
def reset_password_post(uid=None, token=None): """ View for user to submit reset password form. HTTP Method: POST :param uid: the user id :param token: the token in verification key :return: :raises: HTTPError(http.BAD_REQUEST) if verification key for the user is invalid, has expired or was used """ form = ResetPasswordForm(request.form) # Check if request bears a valid pair of `uid` and `token` user_obj = User.load(uid) if not (user_obj and user_obj.verify_password_token(token=token)): error_data = { 'message_short': 'Invalid Request.', 'message_long': 'The requested URL is invalid, has expired, or was already used', } raise HTTPError(http.BAD_REQUEST, data=error_data) if not form.validate(): # Don't go anywhere forms.push_errors_to_status(form.errors) else: # clear verification key (v2) user_obj.verification_key_v2 = {} # new verification key (v1) for CAS user_obj.verification_key = generate_verification_key(verification_type=None) 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 automatically with 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 )) return { 'uid': user_obj._id, 'token': user_obj.verification_key_v2['token'], }
def claim_user_form(auth, **kwargs): """View for rendering the set password page for a claimed user. Must have ``token`` as a querystring argument. Renders the set password form, validates it, and sets the user's password. """ uid, pid = kwargs['uid'], kwargs['pid'] token = request.form.get('token') or request.args.get('token') # If user is logged in, redirect to 're-enter password' page if auth.logged_in: return redirect(web_url_for('claim_user_registered', uid=uid, pid=pid, token=token)) user = User.load(uid) # The unregistered user # user ID is invalid. Unregistered user is not in database if not user: raise HTTPError(http.BAD_REQUEST) # If claim token not valid, redirect to registration page if not verify_claim_token(user, token, pid): return redirect('/account/') unclaimed_record = user.unclaimed_records[pid] user.fullname = unclaimed_record['name'] user.update_guessed_names() email = unclaimed_record['email'] form = SetEmailAndPasswordForm(request.form, token=token) if request.method == 'POST': if form.validate(): username, password = email, form.password.data user.register(username=username, password=password) # Clear unclaimed records user.unclaimed_records = {} user.save() # Authenticate user and redirect to project page response = redirect('/settings/') node = Node.load(pid) status.push_status_message(language.CLAIMED_CONTRIBUTOR.format(node=node), 'success') return authenticate(user, response) else: forms.push_errors_to_status(form.errors) return { 'firstname': user.given_name, 'email': email if email else '', 'fullname': user.fullname, 'form': forms.utils.jsonify(form) if is_json_request() else form, }
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 merge_user_post(**kwargs): '''View for merging an account. Takes either JSON or form data. Request data should include a "merged_username" and "merged_password" properties for the account to be merged in. ''' master = get_current_user() if request.json: merged_username = request.json.get("merged_username") merged_password = request.json.get("merged_password") else: form = MergeAccountForm(request.form) if not form.validate(): forms.push_errors_to_status(form.errors) return merge_user_get(**kwargs) master_password = form.user_password.data if not master.check_password(master_password): status.push_status_message( "Could not authenticate. Please check your username and password." ) return merge_user_get(**kwargs) merged_username = form.merged_username.data merged_password = form.merged_password.data try: merged_user = User.find_one(Q("username", "eq", merged_username)) except NoResultsFound: status.push_status_message( "Could not find that user. Please check the username and password." ) return merge_user_get(**kwargs) if master and merged_user: if merged_user.check_password(merged_password): master.merge_user(merged_user) master.save() if request.form: status.push_status_message( "Successfully merged {0} with this account".format( merged_username)) return redirect("/settings/") return {"status": "success"} else: status.push_status_message( "Could not find that user. Please check the username and password." ) return merge_user_get(**kwargs) else: raise HTTPError(http.BAD_REQUEST)
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 merge_user_post(auth, **kwargs): '''View for merging an account. Takes either JSON or form data. Request data should include a "merged_username" and "merged_password" properties for the account to be merged in. ''' master = auth.user if request.json: merged_username = request.json.get("merged_username") merged_password = request.json.get("merged_password") else: form = MergeAccountForm(request.form) if not form.validate(): forms.push_errors_to_status(form.errors) return merge_user_get(**kwargs) master_password = form.user_password.data if not master.check_password(master_password): status.push_status_message("Could not authenticate. Please check your username and password.", trust=False) return merge_user_get(**kwargs) merged_username = form.merged_username.data merged_password = form.merged_password.data try: merged_user = User.find_one(Q("username", "eq", merged_username)) except NoResultsFound: status.push_status_message("Could not find that user. Please check the username and password.", trust=False) return merge_user_get(**kwargs) if master and merged_user: if merged_user.check_password(merged_password): master.merge_user(merged_user) master.save() if request.form: status.push_status_message("Successfully merged {0} with this account".format(merged_username), kind='success', trust=False) return redirect("/settings/") return {"status": "success"} else: status.push_status_message("Could not find that user. Please check the username and password.", trust=False) return merge_user_get(**kwargs) else: raise HTTPError(http.BAD_REQUEST)
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 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: #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 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 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 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 claim_user_form(auth, **kwargs): """ View for rendering the set password page for a claimed user. Must have ``token`` as a querystring argument. Renders the set password form, validates it, and sets the user's password. HTTP Method: GET, POST """ uid, pid = kwargs["uid"], kwargs["pid"] token = request.form.get("token") or request.args.get("token") user = User.load(uid) # If unregistered user is not in database, or url bears an invalid token raise HTTP 400 error if not user or not verify_claim_token(user, token, pid): error_data = { "message_short": "Invalid url.", "message_long": "Claim user does not exists, the token in the URL is invalid or has expired.", } raise HTTPError(http.BAD_REQUEST, data=error_data) # If user is logged in, redirect to 're-enter password' page if auth.logged_in: return redirect(web_url_for("claim_user_registered", uid=uid, pid=pid, token=token)) unclaimed_record = user.unclaimed_records[pid] user.fullname = unclaimed_record["name"] user.update_guessed_names() # The email can be the original referrer email if no claimer email has been specified. claimer_email = unclaimed_record.get("claimer_email") or unclaimed_record.get("email") # If there is a registered user with this email, redirect to 're-enter password' page found_by_email = User.find_by_email(claimer_email) user_from_email = found_by_email[0] if found_by_email else None if user_from_email and user_from_email.is_registered: return redirect(web_url_for("claim_user_registered", uid=uid, pid=pid, token=token)) form = SetEmailAndPasswordForm(request.form, token=token) if request.method == "POST": if not form.validate(): forms.push_errors_to_status(form.errors) elif settings.RECAPTCHA_SITE_KEY and not validate_recaptcha( request.form.get("g-recaptcha-response"), remote_ip=request.remote_addr ): status.push_status_message("Invalid captcha supplied.", kind="error") else: username, password = claimer_email, form.password.data if not username: raise HTTPError( http.BAD_REQUEST, data=dict( message_long="No email associated with this account. Please claim this " "account on the project to which you were invited." ), ) user.register(username=username, password=password) # Clear unclaimed records user.unclaimed_records = {} user.verification_key = generate_verification_key() user.save() # Authenticate user and redirect to project page status.push_status_message(language.CLAIMED_CONTRIBUTOR, kind="success", trust=True) # Redirect to CAS and authenticate the user with a verification key. return redirect( cas.get_login_url( web_url_for("view_project", pid=pid, _absolute=True), username=user.username, verification_key=user.verification_key, ) ) return { "firstname": user.given_name, "email": claimer_email if claimer_email else "", "fullname": user.fullname, "form": forms.utils.jsonify(form) if is_json_request() else form, }
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 _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 claim_user_registered(auth, node, **kwargs): """ View that prompts user to enter their password in order to claim being a contributor on a project. A user must be logged in. """ current_user = auth.user sign_out_url = cas.get_logout_url(service_url=cas.get_login_url( service_url=request.url)) if not current_user: return redirect(sign_out_url) # Logged in user should not be a contributor the project if hasattr(node, 'is_contributor') and node.is_contributor(current_user): data = { 'message_short': 'Already a contributor', 'message_long': ('The logged-in user is already a contributor to this ' 'project. Would you like to <a href="{}">log out</a>?' ).format(sign_out_url) } raise HTTPError(http_status.HTTP_400_BAD_REQUEST, data=data) # Logged in user is already a member of the OSF Group if hasattr(node, 'is_member') and node.is_member(current_user): data = { 'message_short': 'Already a member', 'message_long': ('The logged-in user is already a member of this OSF Group. ' 'Would you like to <a href="{}">log out</a>?' ).format(sign_out_url) } raise HTTPError(http_status.HTTP_400_BAD_REQUEST, data=data) uid, pid, token = kwargs['uid'], kwargs['pid'], kwargs['token'] unreg_user = OSFUser.load(uid) if not verify_claim_token(unreg_user, token, pid=node._primary_key): error_data = { 'message_short': 'Invalid url.', 'message_long': 'The token in the URL is invalid or has expired.' } raise HTTPError(http_status.HTTP_400_BAD_REQUEST, data=error_data) # Store the unreg_user data on the session in case the user registers # a new account session.data['unreg_user'] = {'uid': uid, 'pid': pid, 'token': token} session.save() # If a user is already validated though external auth, it is OK to claim should_claim = check_external_auth(auth.user) form = PasswordForm(request.form) if request.method == 'POST': if form.validate(): if current_user.check_password(form.password.data): should_claim = True else: status.push_status_message(language.LOGIN_FAILED, kind='warning', trust=False) else: forms.push_errors_to_status(form.errors) if should_claim: node.replace_contributor(old=unreg_user, new=current_user) node.save() if isinstance(node, OSFGroup): status.push_status_message( 'You are now a member of this OSFGroup.', kind='success', trust=False) else: status.push_status_message( 'You are now a contributor to this project.', kind='success', trust=False) return redirect(node.url) if is_json_request(): form_ret = forms.utils.jsonify(form) user_ret = profile_utils.serialize_user(current_user, full=False) else: form_ret = form user_ret = current_user return {'form': form_ret, 'user': user_ret, 'signOutUrl': sign_out_url}
def claim_user_form(auth, **kwargs): """ View for rendering the set password page for a claimed user. Must have ``token`` as a querystring argument. Renders the set password form, validates it, and sets the user's password. HTTP Method: GET, POST """ uid, pid = kwargs['uid'], kwargs['pid'] token = request.form.get('token') or request.args.get('token') user = OSFUser.load(uid) # If unregistered user is not in database, or url bears an invalid token raise HTTP 400 error if not user or not verify_claim_token(user, token, pid): error_data = { 'message_short': 'Invalid url.', 'message_long': 'Claim user does not exists, the token in the URL is invalid or has expired.' } raise HTTPError(http_status.HTTP_400_BAD_REQUEST, data=error_data) # If user is logged in, redirect to 're-enter password' page if auth.logged_in: return redirect( web_url_for('claim_user_registered', uid=uid, pid=pid, token=token)) unclaimed_record = user.unclaimed_records[pid] user.fullname = unclaimed_record['name'] user.update_guessed_names() # The email can be the original referrer email if no claimer email has been specified. claimer_email = unclaimed_record.get( 'claimer_email') or unclaimed_record.get('email') # If there is a registered user with this email, redirect to 're-enter password' page try: user_from_email = OSFUser.objects.get( emails__address=claimer_email.lower().strip( )) if claimer_email else None except OSFUser.DoesNotExist: user_from_email = None if user_from_email and user_from_email.is_registered: return redirect( web_url_for('claim_user_registered', uid=uid, pid=pid, token=token)) form = SetEmailAndPasswordForm(request.form, token=token) if request.method == 'POST': if not form.validate(): forms.push_errors_to_status(form.errors) elif settings.RECAPTCHA_SITE_KEY and not validate_recaptcha( request.form.get('g-recaptcha-response'), remote_ip=request.remote_addr): status.push_status_message('Invalid captcha supplied.', kind='error') else: username, password = claimer_email, form.password.data if not username: raise HTTPError( http_status.HTTP_400_BAD_REQUEST, data=dict( message_long= 'No email associated with this account. Please claim this ' 'account on the project to which you were invited.')) user.register( username=username, password=password, accepted_terms_of_service=form.accepted_terms_of_service.data) # Clear unclaimed records user.unclaimed_records = {} user.verification_key = generate_verification_key() user.save() # Authenticate user and redirect to project page status.push_status_message(language.CLAIMED_CONTRIBUTOR, kind='success', trust=True) # Redirect to CAS and authenticate the user with a verification key. provider = PreprintProvider.load(pid) redirect_url = None if provider: redirect_url = web_url_for('auth_login', next=provider.landing_url, _absolute=True) else: redirect_url = web_url_for('resolve_guid', guid=pid, _absolute=True) return redirect( cas.get_login_url(redirect_url, username=user.username, verification_key=user.verification_key)) return { 'firstname': user.given_name, 'email': claimer_email if claimer_email else '', 'fullname': user.fullname, 'form': forms.utils.jsonify(form) if is_json_request() else form, 'osf_contact_email': settings.OSF_CONTACT_EMAIL, }
def claim_user_form(auth, **kwargs): """ View for rendering the set password page for a claimed user. Must have ``token`` as a querystring argument. Renders the set password form, validates it, and sets the user's password. HTTP Method: GET, POST """ uid, pid = kwargs['uid'], kwargs['pid'] token = request.form.get('token') or request.args.get('token') user = OSFUser.load(uid) # If unregistered user is not in database, or url bears an invalid token raise HTTP 400 error if not user or not verify_claim_token(user, token, pid): error_data = { 'message_short': 'Invalid url.', 'message_long': 'Claim user does not exists, the token in the URL is invalid or has expired.' } raise HTTPError(http.BAD_REQUEST, data=error_data) # If user is logged in, redirect to 're-enter password' page if auth.logged_in: return redirect(web_url_for('claim_user_registered', uid=uid, pid=pid, token=token)) unclaimed_record = user.unclaimed_records[pid] user.fullname = unclaimed_record['name'] user.update_guessed_names() # The email can be the original referrer email if no claimer email has been specified. claimer_email = unclaimed_record.get('claimer_email') or unclaimed_record.get('email') # If there is a registered user with this email, redirect to 're-enter password' page try: user_from_email = OSFUser.objects.get(emails__address=claimer_email.lower().strip()) if claimer_email else None except OSFUser.DoesNotExist: user_from_email = None if user_from_email and user_from_email.is_registered: return redirect(web_url_for('claim_user_registered', uid=uid, pid=pid, token=token)) form = SetEmailAndPasswordForm(request.form, token=token) if request.method == 'POST': if not form.validate(): forms.push_errors_to_status(form.errors) elif settings.RECAPTCHA_SITE_KEY and not validate_recaptcha(request.form.get('g-recaptcha-response'), remote_ip=request.remote_addr): status.push_status_message('Invalid captcha supplied.', kind='error') else: username, password = claimer_email, form.password.data if not username: raise HTTPError(http.BAD_REQUEST, data=dict( message_long='No email associated with this account. Please claim this ' 'account on the project to which you were invited.' )) user.register(username=username, password=password, accepted_terms_of_service=form.accepted_terms_of_service.data) # Clear unclaimed records user.unclaimed_records = {} user.verification_key = generate_verification_key() user.save() # Authenticate user and redirect to project page status.push_status_message(language.CLAIMED_CONTRIBUTOR, kind='success', trust=True) # Redirect to CAS and authenticate the user with a verification key. provider = PreprintProvider.load(pid) redirect_url = None if provider: redirect_url = web_url_for('auth_login', next=provider.landing_url, _absolute=True) else: redirect_url = web_url_for('resolve_guid', guid=pid, _absolute=True) return redirect(cas.get_login_url( redirect_url, username=user.username, verification_key=user.verification_key )) return { 'firstname': user.given_name, 'email': claimer_email if claimer_email else '', 'fullname': user.fullname, 'form': forms.utils.jsonify(form) if is_json_request() else form, 'osf_contact_email': settings.OSF_CONTACT_EMAIL, }
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 }