def node_register_template_page(auth, node, metaschema_id, **kwargs): if node.is_registration and bool(node.registered_schema): try: meta_schema = MetaSchema.find_one( Q('_id', 'eq', metaschema_id) ) except NoResultsFound: # backwards compatability for old urls, lookup by name try: meta_schema = MetaSchema.find( Q('name', 'eq', _id_to_name(metaschema_id)) ).sort('-schema_version')[0] except IndexError: raise HTTPError(http.NOT_FOUND, data={ 'message_short': 'Invalid schema name', 'message_long': 'No registration schema with that name could be found.' }) if meta_schema not in node.registered_schema: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': 'Invalid schema', 'message_long': 'This registration has no registration supplment with that name.' }) ret = _view_project(node, auth, primary=True) ret['node']['registered_schema'] = serialize_meta_schema(meta_schema) return ret else: status.push_status_message( 'You have been redirected to the project\'s registrations page. From here you can initiate a new Draft Registration to complete the registration process', trust=False ) return redirect(node.web_url_for('node_registrations', view=kwargs.get('template')))
def project_generate_private_link_post(auth, node, **kwargs): """ creata a new private link object and add it to the node and its selected children""" node_ids = request.json.get('node_ids', []) name = request.json.get('name', '') anonymous = request.json.get('anonymous', False) if node._id not in node_ids: node_ids.insert(0, node._id) nodes = [Node.load(node_id) for node_id in node_ids] has_public_node = any(node.is_public for node in nodes) new_link = new_private_link( name=name, user=auth.user, nodes=nodes, anonymous=anonymous ) if anonymous and has_public_node: status.push_status_message( 'Anonymized view-only links <b>DO NOT</b> ' 'anonymize contributors of public project or component.' ) return new_link
def confirm_email_get(**kwargs): """View for email confirmation links. Authenticates and redirects to user settings page if confirmation is successful, otherwise shows an "Expired Link" error. methods: GET """ user = User.load(kwargs['uid']) token = kwargs['token'] if user: if user.confirm_email(token): # Confirm and register the user user.date_last_login = datetime.datetime.utcnow() user.save() # Go to settings page status.push_status_message(language.WELCOME_MESSAGE, 'success') response = redirect('/settings/') return framework.auth.authenticate(user, response=response) # Return data for the error template return { 'code': http.BAD_REQUEST, 'message_short': 'Link Expired', 'message_long': language.LINK_EXPIRED }, http.BAD_REQUEST
def embargo_termination_handler(action, registration, registered_from): status.push_status_message({ 'approve': 'Your approval to make this embargo public has been accepted.', 'reject': 'Your disapproval has been accepted and this embargo will not be made public.', }[action], kind='success', trust=False) # Allow decorated view function to return response return None
def node_registration_embargo_disapprove(auth, node, token, **kwargs): """Handles disapproval of registration embargoes :param auth: User wanting to disapprove the embargo :return: Redirect to registration or :raises: HTTPError if invalid token or user is not admin """ if not node.pending_embargo: raise HTTPError( http.BAD_REQUEST, data={"message_short": "Invalid Token", "message_long": "This registration is not pending an embargo."}, ) # Note(hryabcki): node.registered_from not accessible after disapproval if node.embargo.for_existing_registration: redirect_url = node.web_url_for("view_project") else: redirect_url = node.registered_from.web_url_for("view_project") try: node.embargo.disapprove_embargo(auth.user, token) node.embargo.save() except InvalidEmbargoDisapprovalToken as e: raise HTTPError(http.BAD_REQUEST, data={"message_short": e.message_short, "message_long": e.message_long}) except PermissionsError as e: raise HTTPError(http.FORBIDDEN, data={"message_short": "Unauthorized access", "message_long": e.message}) status.push_status_message("Your disapproval has been accepted and the embargo has been cancelled.", "success") return redirect(redirect_url)
def node_registration_embargo_approve(auth, node, token, **kwargs): """Handles approval of registration embargoes :param auth: User wanting to approve the embargo :param kwargs: :return: Redirect to registration or :raises: HTTPError if invalid token or user is not admin """ if not node.pending_embargo: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': 'Invalid Token', 'message_long': 'This registration is not pending an embargo.' }) try: node.embargo.approve_embargo(auth.user, token) node.embargo.save() except InvalidEmbargoApprovalToken as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': e.message_short, 'message_long': e.message_long }) except PermissionsError as e: raise HTTPError(http.FORBIDDEN, data={ 'message_short': 'Unauthorized access', 'message_long': e.message }) status.push_status_message('Your approval has been accepted.', kind='success', trust=False) return redirect(node.web_url_for('view_project'))
def node_register_template_page(auth, node, metaschema_id, **kwargs): if node.is_registration and bool(node.registered_schema): try: meta_schema = RegistrationSchema.objects.get(_id=metaschema_id) except RegistrationSchema.DoesNotExist: # backwards compatability for old urls, lookup by name meta_schema = RegistrationSchema.objects.filter(name=_id_to_name(metaschema_id)).order_by('-schema_version').first() if not meta_schema: raise HTTPError(http.NOT_FOUND, data={ 'message_short': 'Invalid schema name', 'message_long': 'No registration schema with that name could be found.' }) if not node.registered_schema.filter(id=meta_schema.id).exists(): raise HTTPError(http.BAD_REQUEST, data={ 'message_short': 'Invalid schema', 'message_long': 'This registration has no registration supplment with that name.' }) ret = _view_project(node, auth, primary=True) my_meta = serialize_meta_schema(meta_schema) if has_anonymous_link(node, auth): for indx, schema_page in enumerate(my_meta['schema']['pages']): for idx, schema_question in enumerate(schema_page['questions']): if schema_question['title'] in settings.ANONYMIZED_TITLES: del my_meta['schema']['pages'][indx]['questions'][idx] ret['node']['registered_schema'] = serialize_meta_schema(meta_schema) return ret else: status.push_status_message( 'You have been redirected to the project\'s registrations page. From here you can initiate a new Draft Registration to complete the registration process', trust=False, id='redirected_to_registrations', ) return redirect(node.web_url_for('node_registrations', view=kwargs.get('template'), _guid=True))
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 project_new_node(auth, node, **kwargs): form = NewNodeForm(request.form) user = auth.user if form.validate(): try: new_component = new_node( title=strip_html(form.title.data), user=user, category=form.category.data, parent=node, ) except ValidationValueError as e: raise HTTPError( http.BAD_REQUEST, data=dict(message_long=e.message) ) message = ( 'Your component was created successfully. You can keep working on the component page below, ' 'or return to the <u><a href="{url}">project page</a></u>.' ).format(url=node.url) status.push_status_message(message, kind='info', trust=True) return { 'status': 'success', }, 201, None, new_component.url else: # TODO: This function doesn't seem to exist anymore? status.push_errors_to_status(form.errors) raise HTTPError(http.BAD_REQUEST, redirect_url=node.url)
def node_registration_retraction_disapprove(auth, node, token, **kwargs): """Handles approval of registration retractions :param auth: User wanting to approve retraction :param kwargs: :return: Redirect to registration or :raises: HTTPError if invalid token or user is not admin """ if not node.pending_retraction: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': 'Invalid Token', 'message_long': 'This registration is not pending a retraction.' }) try: node.retraction.disapprove_retraction(auth.user, token) node.retraction.save() except InvalidRetractionDisapprovalToken as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': e.message_short, 'message_long': e.message_long }) except PermissionsError as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': 'Unauthorized access', 'message_long': e.message }) status.push_status_message('Your disapproval has been accepted and the retraction has been cancelled.', kind='success', trust=False) return redirect(node.web_url_for('view_project'))
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 retraction_handler(action, registration, registered_from): status.push_status_message({ 'approve': 'Your withdrawal approval has been accepted.', 'reject': 'Your disapproval has been accepted and the withdrawal has been cancelled.' }[action], kind='success', trust=False) # Allow decorated view function to return response return None
def component_remove(auth, node, **kwargs): """Remove component, and recursively remove its children. If node has a parent, add log and redirect to parent; else redirect to user dashboard. """ try: node.remove_node(auth) except NodeStateError as e: raise HTTPError( http.BAD_REQUEST, data={ 'message_short': 'Error', 'message_long': 'Could not delete component: ' + e.message }, ) node.save() message = '{} deleted'.format( node.project_or_component.capitalize() ) status.push_status_message(message) parent = node.parent_node if parent and parent.can_view(auth): redirect_url = node.node__parent[0].url else: redirect_url = '/dashboard/' return { 'url': redirect_url, }
def project_removecontributor(auth, node, **kwargs): contributor = User.load(request.json['id']) if contributor is None: raise HTTPError(http.BAD_REQUEST) # Forbidden unless user is removing herself if not node.has_permission(auth.user, 'admin'): if auth.user != contributor: raise HTTPError(http.FORBIDDEN) outcome = node.remove_contributor( contributor=contributor, auth=auth, ) if outcome: if auth.user == contributor: status.push_status_message('Removed self from project', 'info') return {'redirectUrl': web_url_for('dashboard')} status.push_status_message('Contributor removed', 'info') return {} raise HTTPError( http.BAD_REQUEST, data={ 'message_long': ( '{0} must have at least one contributor with admin ' 'rights'.format( node.project_or_component.capitalize() ) ) } )
def submit_draft_for_review(auth, node, draft, *args, **kwargs): """Submit for approvals and/or notifications :return: serialized registration :rtype: dict :raises: HTTPError if embargo end date is invalid """ data = request.get_json() meta = {} registration_choice = data.get('registrationChoice', 'immediate') validate_registration_choice(registration_choice) if registration_choice == 'embargo': # Initiate embargo end_date_string = data['embargoEndDate'] validate_embargo_end_date(end_date_string, node) meta['embargo_end_date'] = end_date_string meta['registration_choice'] = registration_choice draft.submit_for_review( initiated_by=auth.user, meta=meta, save=True ) push_status_message(language.AFTER_SUBMIT_FOR_REVIEW, kind='info', trust=False) return { 'status': 'initiated', 'urls': { 'registrations': node.web_url_for('node_registrations') } }, http.ACCEPTED
def auth_email_logout(token, user): """ When a user is adding an email or merging an account, add the email to the user and log them out. """ redirect_url = cas.get_logout_url(service_url=cas.get_login_url(service_url=web_url_for('index', _absolute=True))) try: unconfirmed_email = user.get_unconfirmed_email_for_token(token) except InvalidTokenError: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': 'Bad token', 'message_long': 'The provided token is invalid.' }) except ExpiredTokenError: status.push_status_message('The private link you used is expired.') raise HTTPError(http.BAD_REQUEST, data={ 'message_short': 'Expired link', 'message_long': 'The private link you used is expired.' }) try: user_merge = User.find_one(Q('emails', 'eq', unconfirmed_email)) except NoResultsFound: user_merge = False if user_merge: remove_sessions_for_user(user_merge) user.email_verifications[token]['confirmed'] = True user.save() remove_sessions_for_user(user) resp = redirect(redirect_url) resp.delete_cookie(settings.COOKIE_NAME, domain=settings.OSF_COOKIE_DOMAIN) return resp
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 user_account_password(auth, **kwargs): user = auth.user old_password = request.form.get('old_password', None) new_password = request.form.get('new_password', None) confirm_password = request.form.get('confirm_password', None) # It has been more than 1 hour since last invalid attempt to change password. Reset the counter for invalid attempts. if throttle_period_expired(user.change_password_last_attempt, settings.TIME_RESET_CHANGE_PASSWORD_ATTEMPTS): user.reset_old_password_invalid_attempts() # There have been more than 3 failed attempts and throttle hasn't expired. if user.old_password_invalid_attempts >= settings.INCORRECT_PASSWORD_ATTEMPTS_ALLOWED and not throttle_period_expired(user.change_password_last_attempt, settings.CHANGE_PASSWORD_THROTTLE): push_status_message( message='Too many failed attempts. Please wait a while before attempting to change your password.', kind='warning', trust=False ) return redirect(web_url_for('user_account')) try: user.change_password(old_password, new_password, confirm_password) except ChangePasswordError as error: for m in error.messages: push_status_message(m, kind='warning', trust=False) else: # We have to logout the user first so all CAS sessions are invalid user.save() osf_logout() return redirect(cas.get_logout_url(cas.get_login_url( web_url_for('user_account', _absolute=True) + '?password_reset=True', username=user.username, verification_key=user.verification_key, ))) user.save() return redirect(web_url_for('user_account'))
def component_remove(**kwargs): """Remove component, and recursively remove its children. If node has a parent, add log and redirect to parent; else redirect to user dashboard. """ node_to_use = kwargs['node'] or kwargs['project'] auth = kwargs['auth'] try: node_to_use.remove_node(auth) except NodeStateError as e: raise HTTPError( http.BAD_REQUEST, data={ 'message_long': 'Could not delete component: ' + e.message }, ) node_to_use.save() message = '{} deleted'.format( node_to_use.project_or_component.capitalize() ) status.push_status_message(message) if node_to_use.node__parent: redirect_url = node_to_use.node__parent[0].url else: redirect_url = '/dashboard/' return { 'url': redirect_url, }
def node_registration_retraction_approve(auth, node, token, **kwargs): """Handles disapproval of registration retractions :param auth: User wanting to disapprove retraction :return: Redirect to registration or :raises: HTTPError if invalid token or user is not admin """ if not node.pending_retraction: raise HTTPError( http.BAD_REQUEST, data={"message_short": "Invalid Token", "message_long": "This registration is not pending a retraction."}, ) try: node.retraction.approve_retraction(auth.user, token) node.retraction.save() if node.is_retracted: node.update_search() except InvalidRetractionApprovalToken as e: raise HTTPError(http.BAD_REQUEST, data={"message_short": e.message_short, "message_long": e.message_long}) except PermissionsError as e: raise HTTPError(http.BAD_REQUEST, data={"message_short": "Unauthorized access", "message_long": e.message}) status.push_status_message("Your approval has been accepted.", "success") return redirect(node.web_url_for("view_project"))
def test_push_status_message_unexpected_error(self, mock_sesh): status_message = 'This is a message' exception_message = 'this is some very unexpected problem' mock_get = mock.Mock(side_effect=RuntimeError(exception_message)) mock_data = mock.Mock() mock_data.attach_mock(mock_get, 'get') mock_sesh.attach_mock(mock_data, 'data') try: push_status_message(status_message, kind='error') assert_true( False, 'push_status_message() should have generated a RuntimeError exception.' ) except ValidationError as e: assert_true( False, 'push_status_message() should have re-raised the RuntimeError not gotten ValidationError.' ) except RuntimeError as e: assert_equal(getattr(e, 'message', None), exception_message, 'push_status_message() should have re-raised the ' 'original RuntimeError with the original message.') except BaseException: assert_true( False, 'Unexpected Exception from push_status_message when called ' 'from the v2 API with type "error"')
def project_generate_private_link_post(auth, node, **kwargs): """ creata a new private link object and add it to the node and its selected children""" node_ids = request.json.get('node_ids', []) name = request.json.get('name', '') anonymous = request.json.get('anonymous', False) if node._id not in node_ids: node_ids.insert(0, node._id) nodes = [Node.load(node_id) for node_id in node_ids] has_public_node = any(node.is_public for node in nodes) try: new_link = new_private_link( name=name, user=auth.user, nodes=nodes, anonymous=anonymous ) except ValidationValueError as e: raise HTTPError( http.BAD_REQUEST, data=dict(message_long=e.message) ) if anonymous and has_public_node: status.push_status_message( 'Anonymized view-only links <b>DO NOT</b> ' 'anonymize contributors of public projects or components.', trust=True ) return new_link
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 on_add(self): push_status_message('Please <u><a href="#TfaVerify">activate your' ' device</a></u> before continuing.', 'info') super(TwoFactorUserSettings, self).on_add() self.totp_secret = _generate_seed() self.totp_drift = 0 self.is_confirmed = False
def confirm_email_get(token, auth=None, **kwargs): """View for email confirmation links. Authenticates and redirects to user settings page if confirmation is successful, otherwise shows an "Expired Link" error. methods: GET """ user = User.load(kwargs['uid']) is_merge = 'confirm_merge' in request.args is_initial_confirmation = not user.date_confirmed if user is None: raise HTTPError(http.NOT_FOUND) if auth and auth.user and (auth.user._id == user._id or auth.user._id == user.merged_by._id): if not is_merge: # determine if the user registered through a campaign campaign = campaigns.campaign_for_user(user) if campaign: return redirect( campaigns.campaign_url_for(campaign) ) status.push_status_message(language.WELCOME_MESSAGE, 'default', jumbotron=True) # Go to dashboard return redirect(web_url_for('dashboard')) status.push_status_message(language.MERGE_COMPLETE, 'success') return redirect(web_url_for('user_account')) try: user.confirm_email(token, merge=is_merge) except exceptions.EmailConfirmTokenError as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': e.message_short, 'message_long': e.message_long }) if is_initial_confirmation: user.date_last_login = datetime.datetime.utcnow() user.save() # Send out our welcome message mails.send_mail( to_addr=user.username, mail=mails.WELCOME, mimetype='html', user=user ) # Redirect to CAS and authenticate the user with a verification key. user.verification_key = security.random_string(20) user.save() return redirect(cas.get_login_url( request.url, auto=True, username=user.username, verification_key=user.verification_key ))
def auth_register(auth): """ View for OSF register. Land on the register page, redirect or go to `auth_logout` depending on `data` returned by `login_and_register_handler`. `/register` only takes a valid campaign, a valid next, the logout flag or no query parameter `login_and_register_handler()` handles the following cases: if campaign and logged in, go to campaign landing page (or valid next_url if presents) if campaign and logged out, go to campaign register page (with next_url if presents) if next_url and logged in, go to next url if next_url and logged out, go to cas login page with current request url as service parameter if next_url and logout flag, log user out first and then go to the next_url if none, go to `/dashboard` which is decorated by `@must_be_logged_in` :param auth: the auth context :return: land, redirect or `auth_logout` :raise: http.BAD_REQUEST """ context = {} # a target campaign in `auth.campaigns` campaign = request.args.get('campaign') # the service url for CAS login or redirect url for OSF next_url = request.args.get('next') # used only for `claim_user_registered` logout = request.args.get('logout') # logout must have next_url if logout and not next_url: raise HTTPError(http.BAD_REQUEST) data = login_and_register_handler(auth, login=False, campaign=campaign, next_url=next_url, logout=logout) # land on register page if data['status_code'] == http.OK: if data['must_login_warning']: status.push_status_message(language.MUST_LOGIN, trust=False) destination = cas.get_login_url(data['next_url']) # "Already have and account?" link context['non_institution_login_url'] = destination # "Sign In" button in navigation bar, overwrite the default value set in routes.py context['login_url'] = destination # "Login through your institution" link context['institution_login_url'] = cas.get_login_url(data['next_url'], campaign='institution') context['preprint_campaigns'] = {k._id + '-preprints': { 'id': k._id, 'name': k.name, 'logo_path': k.get_asset_url('square_color_no_transparent') } for k in PreprintProvider.objects.all() if k._id != 'osf'} context['campaign'] = data['campaign'] return context, http.OK # redirect to url elif data['status_code'] == http.FOUND: return redirect(data['next_url']) # go to other views elif data['status_code'] == 'auth_logout': return auth_logout(redirect_url=data['next_url']) raise HTTPError(http.BAD_REQUEST)
def test_push_status_message_no_response(self): status_message = 'This is a message' statuses = ['info', 'warning', 'warn', 'success', 'danger', 'default'] for status in statuses: try: push_status_message(status_message, kind=status) except: assert_true(False, 'Exception from push_status_message via API v2 with type "{}".'.format(status))
def test_push_status_message_no_response(self): status_message = "This is a message" statuses = ["info", "warning", "warn", "success", "danger", "default"] for status in statuses: try: push_status_message(status_message, kind=status) except: assert_true(False, 'Exception from push_status_message via API v2 with type "{}".'.format(status))
def node_fork_page(auth, node, **kwargs): try: fork = node.fork_node(auth) except PermissionsError: raise HTTPError(http.FORBIDDEN, redirect_url=node.url) message = "{} has been successfully forked.".format(node.project_or_component.capitalize()) status.push_status_message(message, kind="success", trust=False) return fork.url
def registration_approval_handler(action, registration, registered_from): # TODO: Unnecessary and duplicated dictionary. status.push_status_message({ 'approve': 'Your registration approval has been accepted.', 'reject': 'Your disapproval has been accepted and the registration has been cancelled.', }[action], kind='success', trust=False) # Allow decorated view function to return response return None
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 project_remove_contributor(auth, **kwargs): """Remove a contributor from a list of nodes. :param Auth auth: Consolidated authorization :raises: HTTPError(400) if contributors to be removed are not in list or if no admin users would remain after changes were applied """ contributor_id = request.get_json()['contributorID'] node_ids = request.get_json()['nodeIDs'] contributor = User.load(contributor_id) if contributor is None: raise HTTPError(http.BAD_REQUEST, data={'message_long': 'Contributor not found.'}) redirect_url = {} parent_id = node_ids[0] for node_id in node_ids: # Update permissions and order node = Node.load(node_id) # Forbidden unless user is removing herself if not node.has_permission(auth.user, 'admin'): if auth.user != contributor: raise HTTPError(http.FORBIDDEN) if len(node.visible_contributor_ids) == 1 \ and node.visible_contributor_ids[0] == contributor._id: raise HTTPError(http.FORBIDDEN, data={ 'message_long': 'Must have at least one bibliographic contributor' }) nodes_removed = node.remove_contributor(contributor, auth=auth) # remove_contributor returns false if there is not one admin or visible contributor left after the move. if not nodes_removed: raise HTTPError(http.BAD_REQUEST, data={ 'message_long': 'Could not remove contributor.'}) # On parent node, if user has removed herself from project, alert; redirect to # node summary if node is public, else to user's dashboard page if not node.is_contributor(auth.user) and node_id == parent_id: status.push_status_message( 'You have removed yourself as a contributor from this project', kind='success', trust=False ) if node.is_public: redirect_url = {'redirectUrl': node.url} else: redirect_url = {'redirectUrl': web_url_for('dashboard')} return redirect_url
def claim_user_form(**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 get_current_user(): 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 = 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 user_account(auth, **kwargs): user = auth.user user_addons = addon_utils.get_addons_by_config_type('user', user) if 'password_reset' in request.args: push_status_message('Password updated successfully.', kind='success', trust=False) return { 'user_id': user._id, 'addons': user_addons, 'addons_js': collect_user_config_js([addon for addon in settings.ADDONS_AVAILABLE if 'user' in addon.configs]), 'addons_css': [], 'requested_deactivation': user.requested_deactivation, 'external_identity': user.external_identity, 'storage_flag_is_active': storage_i18n_flag_active(), }
def user_account_password(auth, **kwargs): user = auth.user old_password = request.form.get('old_password', None) new_password = request.form.get('new_password', None) confirm_password = request.form.get('confirm_password', None) try: user.change_password(old_password, new_password, confirm_password) user.save() except ChangePasswordError as error: push_status_message('<br />'.join(error.messages) + '.', kind='warning') else: push_status_message('Password updated successfully.', kind='info') return redirect(web_url_for('user_account'))
def check_can_access(node, user, key=None, api_node=None): """View helper that returns whether a given user can access a node. If ``user`` is None, returns False. :rtype: boolean :raises: HTTPError (403) if user cannot access the node """ if user is None: return False if not node.can_view(Auth(user=user)) and api_node != node: if key in node.private_link_keys_deleted: status.push_status_message( "The view-only links you used are expired.") raise HTTPError(http.FORBIDDEN) return True
def node_register_page(auth, node, **kwargs): """Display the registration metadata for a registration. :return: serialized Node """ if node.is_registration: return serialize_node(node, auth) else: status.push_status_message( 'You have been redirected to the project\'s registrations page. From here you can initiate a new Draft Registration to complete the registration process', trust=False, id='redirected_to_registrations', ) return redirect(node.web_url_for('node_registrations', view='draft', _guid=True))
def project_new_node(auth, node, **kwargs): form = NewNodeForm(request.form) user = auth.user if form.validate(): try: new_component = new_node( title=strip_html(form.title.data), user=user, category=form.category.data, parent=node, ) except ValidationError as e: raise HTTPError( http.BAD_REQUEST, data=dict(message_long=e.message) ) redirect_url = node.url message = ( 'Your component was created successfully. You can keep working on the project page below, ' 'or go to the new <u><a href={component_url}>component</a></u>.' ).format(component_url=new_component.url) if form.inherit_contributors.data and node.has_permission(user, WRITE): for contributor in node.contributors: perm = CREATOR_PERMISSIONS if contributor._id == user._id else node.get_permissions(contributor) if contributor._id == user._id and not contributor.is_registered: new_component.add_unregistered_contributor( fullname=contributor.fullname, email=contributor.email, permissions=perm, auth=auth, existing_user=contributor ) else: new_component.add_contributor(contributor, permissions=perm, auth=auth) new_component.save() redirect_url = new_component.url + 'contributors/' message = ( 'Your component was created successfully. You can edit the contributor permissions below, ' 'work on your <u><a href={component_url}>component</a></u> or return to the <u> ' '<a href="{project_url}">project page</a></u>.' ).format(component_url=new_component.url, project_url=node.url) status.push_status_message(message, kind='info', trust=True) return { 'status': 'success', }, 201, None, redirect_url else: # TODO: This function doesn't seem to exist anymore? status.push_errors_to_status(form.errors) raise HTTPError(http.BAD_REQUEST, redirect_url=node.url)
def replace_unclaimed_user_with_registered(user): """Listens for the user_registered signal. If unreg_user is stored in the session, then the current user is trying to claim themselves as a contributor. Replaces the old, unregistered contributor with the newly registered account. """ unreg_user_info = session.data.get('unreg_user') if unreg_user_info: unreg_user = User.load(unreg_user_info['uid']) pid = unreg_user_info['pid'] node = Node.load(pid) node.replace_contributor(old=unreg_user, new=user) node.save() status.push_status_message( 'Successfully claimed contributor.', kind='success', trust=False)
def check_can_access(node, user, key=None, api_node=None): """View helper that returns whether a given user can access a node. If ``user`` is None, returns False. :rtype: boolean :raises: HTTPError (403) if user cannot access the node """ if user is None: return False if not node.can_view(Auth(user=user)) and api_node != node: if key in node.private_link_keys_deleted: status.push_status_message('The view-only links you used are expired.', trust=False) raise HTTPError(http.FORBIDDEN, data={'message_long': ('User has restricted access to this page. ' 'If this should not have occurred and the issue persists, please report it to ' '<a href="mailto:[email protected]">[email protected]</a>.')}) return True
def node_register_template_page(auth, node, metaschema_id, **kwargs): if node.is_registration and bool(node.registered_schema): try: meta_schema = RegistrationSchema.objects.get(_id=metaschema_id) except RegistrationSchema.DoesNotExist: # backwards compatability for old urls, lookup by name meta_schema = RegistrationSchema.objects.filter(name=_id_to_name( metaschema_id)).order_by('-schema_version').first() if not meta_schema: raise HTTPError( http.NOT_FOUND, data={ 'message_short': 'Invalid schema name', 'message_long': 'No registration schema with that name could be found.' }) if not node.registered_schema.filter(id=meta_schema.id).exists(): raise HTTPError( http.BAD_REQUEST, data={ 'message_short': 'Invalid schema', 'message_long': 'This registration has no registration supplment with that name.' }) ret = _view_project(node, auth, primary=True) my_meta = serialize_meta_schema(meta_schema) if has_anonymous_link(node, auth): for indx, schema_page in enumerate(my_meta['schema']['pages']): for idx, schema_question in enumerate( schema_page['questions']): if schema_question['title'] in settings.ANONYMIZED_TITLES: del my_meta['schema']['pages'][indx]['questions'][idx] ret['node']['registered_schema'] = serialize_meta_schema(meta_schema) return ret else: status.push_status_message( 'You have been redirected to the project\'s registrations page. From here you can initiate a new Draft Registration to complete the registration process', trust=False, id='redirected_to_registrations', ) return redirect( node.web_url_for('node_registrations', view=kwargs.get('template'), _guid=True))
def register_draft_registration(auth, node, draft, *args, **kwargs): """Initiate a registration from a draft registration :return: success message; url to registrations page :rtype: dict """ json_data = request.get_json() if 'data' not in json_data: raise HTTPError(http.BAD_REQUEST, data=dict(message_long='Payload must include "data".')) data = json_data['data'] if 'attributes' not in data: raise HTTPError(http.BAD_REQUEST, data=dict(message_long='Payload must include "data/attributes".')) attributes = data['attributes'] registration_choice = attributes['registration_choice'] validate_registration_choice(registration_choice) # Don't allow resubmission unless submission was rejected if draft.approval and draft.approval.state != Sanction.REJECTED: raise HTTPError(http.CONFLICT, data=dict(message_long='Cannot resubmit previously submitted draft.')) register = draft.register(auth) draft.save() if registration_choice == 'embargo': # Initiate embargo embargo_end_date = parse_date(attributes['lift_embargo'], ignoretz=True).replace(tzinfo=pytz.utc) try: register.embargo_registration(auth.user, embargo_end_date) except ValidationError as err: raise HTTPError(http.BAD_REQUEST, data=dict(message_long=err.message)) else: try: register.require_approval(auth.user) except NodeStateError as err: raise HTTPError(http.BAD_REQUEST, data=dict(message_long=str(err))) register.save() push_status_message(language.AFTER_REGISTER_ARCHIVING, kind='info', trust=False, id='registration_archiving') return { 'status': 'initiated', 'urls': { 'registrations': node.web_url_for('node_registrations', _guid=True) } }, http.ACCEPTED
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 submit_draft_for_review(auth, node, draft, *args, **kwargs): """Submit for approvals and/or notifications :return: serialized registration :rtype: dict :raises: HTTPError if embargo end date is invalid """ data = request.get_json() meta = {} registration_choice = data.get('registrationChoice', 'immediate') validate_registration_choice(registration_choice) if registration_choice == 'embargo': # Initiate embargo end_date_string = data['embargoEndDate'] validate_embargo_end_date(end_date_string, node) meta['embargo_end_date'] = end_date_string meta['registration_choice'] = registration_choice # Don't allow resubmission unless submission was rejected if draft.approval and draft.approval.state != Sanction.REJECTED: raise HTTPError(http.CONFLICT, data=dict(message_long='Cannot resubmit previously submitted draft.')) draft.submit_for_review( initiated_by=auth.user, meta=meta, save=True ) if prereg_utils.get_prereg_schema() == draft.registration_schema: node.add_log( action=NodeLog.PREREG_REGISTRATION_INITIATED, params={'node': node._primary_key}, auth=auth, save=False ) node.save() push_status_message(language.AFTER_SUBMIT_FOR_REVIEW, kind='info', trust=False) return { 'status': 'initiated', 'urls': { 'registrations': node.web_url_for('node_registrations') } }, http.ACCEPTED
def register_draft_registration(auth, node, draft, *args, **kwargs): """Initiate a registration from a draft registration :return: success message; url to registrations page :rtype: dict """ data = request.get_json() registration_choice = data.get('registrationChoice', 'immediate') validate_registration_choice(registration_choice) # Don't allow resubmission unless submission was rejected if draft.approval and draft.approval.state != Sanction.REJECTED: raise HTTPError( http.CONFLICT, data=dict( message_long='Cannot resubmit previously submitted draft.')) register = draft.register(auth) draft.save() if registration_choice == 'embargo': # Initiate embargo embargo_end_date = parse_date(data['embargoEndDate'], ignoretz=True).replace(tzinfo=pytz.utc) try: register.embargo_registration(auth.user, embargo_end_date) except ValidationError as err: raise HTTPError(http.BAD_REQUEST, data=dict(message_long=err.message)) else: try: register.require_approval(auth.user) except NodeStateError as err: raise HTTPError(http.BAD_REQUEST, data=dict(message_long=err.message)) register.save() push_status_message(language.AFTER_REGISTER_ARCHIVING, kind='info', trust=False) return { 'status': 'initiated', 'urls': { 'registrations': node.web_url_for('node_registrations') } }, http.ACCEPTED
def figshare_oauth_callback(auth, **kwargs): user = auth.user nid = kwargs.get('nid') or kwargs.get('pid') node = models.Node.load(nid) if nid else None # Fail if node provided and user not contributor if node and not node.is_contributor(user): raise HTTPError(http.FORBIDDEN) if user is None: raise HTTPError(http.NOT_FOUND) if kwargs.get('nid') and not node: raise HTTPError(http.NOT_FOUND) figshare_user = user.get_addon('figshare') verifier = request.args.get('oauth_verifier') access_token, access_token_secret = oauth_get_token( figshare_user.oauth_request_token, figshare_user.oauth_request_token_secret, verifier) # Handle request cancellations from FigShare's API if not access_token or not access_token_secret: push_status_message('figshare authorization request cancelled.') if node: return redirect(node.web_url_for('node_setting')) return redirect(web_url_for('user_addons')) figshare_user.oauth_request_token = None figshare_user.oauth_request_token_secret = None figshare_user.oauth_access_token = access_token figshare_user.oauth_access_token_secret = access_token_secret figshare_user.save() if node: figshare_node = node.get_addon('figshare') figshare_node.user_settings = figshare_user figshare_node.save() if node: return redirect(os.path.join(node.url, 'settings')) return redirect(web_url_for('user_addons'))
def node_register_template_page(auth, node, metaschema_id, **kwargs): if node.is_registration and bool(node.registered_schema): try: meta_schema = MetaSchema.find_one(Q('_id', 'eq', metaschema_id)) except NoResultsFound: # backwards compatability for old urls, lookup by name try: meta_schema = MetaSchema.find( Q('name', 'eq', _id_to_name(metaschema_id))).sort('-schema_version')[0] except IndexError: raise HTTPError( http.NOT_FOUND, data={ 'message_short': 'Invalid schema name', 'message_long': 'No registration schema with that name could be found.' }) if meta_schema not in node.registered_schema: raise HTTPError( http.BAD_REQUEST, data={ 'message_short': 'Invalid schema', 'message_long': 'This registration has no registration supplment with that name.' }) ret = _view_project(node, auth, primary=True) my_meta = serialize_meta_schema(meta_schema) if has_anonymous_link(node, auth): for indx, schema_page in enumerate(my_meta['schema']['pages']): for idx, schema_question in enumerate( schema_page['questions']): if schema_question['title'] in settings.ANONYMIZED_TITLES: del my_meta['schema']['pages'][indx]['questions'][idx] ret['node']['registered_schema'] = serialize_meta_schema(meta_schema) return ret else: status.push_status_message( 'You have been redirected to the project\'s registrations page. From here you can initiate a new Draft Registration to complete the registration process', trust=False) return redirect( node.web_url_for('node_registrations', view=kwargs.get('template')))
def node_register_template_page_post(auth, node, **kwargs): data = request.json if settings.DISK_SAVING_MODE: raise HTTPError(http.METHOD_NOT_ALLOWED, redirect_url=node.url) # Sanitize payload data clean_data = process_payload(data) template = kwargs['template'] # TODO: Using json.dumps because node_to_use.registered_meta's values are # expected to be strings (not dicts). Eventually migrate all these to be # dicts, as this is unnecessary schema = MetaSchema.find(Q('name', 'eq', template)).sort('-schema_version')[0] # Create the registration register = node.register_node( schema, auth, template, json.dumps(clean_data), ) try: if data.get('registrationChoice', 'immediate') == 'embargo': # Initiate embargo embargo_end_date = parse_date(data['embargoEndDate'], ignoretz=True) register.embargo_registration(auth.user, embargo_end_date) else: register.require_approval(auth.user) register.save() except ValidationValueError as err: raise HTTPError(http.BAD_REQUEST, data=dict(message_long=err.message)) push_status_message(language.AFTER_REGISTER_ARCHIVING, kind='info', trust=False) return { 'status': 'initiated', 'urls': { 'registrations': node.web_url_for('node_registrations') } }, http.CREATED
def post(self, request, *args, **kwargs): node = self.get_object() node.is_public = False node.keenio_read_key = '' # After set permissions callback for addon in node.get_addons(): message = addon.after_set_privacy(node, 'private') if message: status.push_status_message(message, kind='info', trust=False) if node.get_identifier_value('doi'): node.request_identifier_update(category='doi') node.save() return redirect(self.get_success_url())
def two_factor(**kwargs): """View for handling two factor code authentication methods: GET, POST """ if request.method != 'POST': return {} two_factor_code = request.form['twoFactorCode'] try: # verify two factor for current user response = verify_two_factor( session.data['two_factor_auth']['auth_user_id'], two_factor_code) return response except exceptions.TwoFactorValidationError: status.push_status_message(language.TWO_FACTOR_FAILED) # Get next URL from GET / POST data next_url = request.args.get('next', request.form.get('next_url', '')) return {'next_url': next_url}, http.UNAUTHORIZED
def confirm_email_get(**kwargs): """View for email confirmation links. Authenticates and redirects to user settings page if confirmation is successful, otherwise shows an "Expired Link" error. methods: GET """ user = User.load(kwargs['uid']) is_initial_confirmation = not user.date_confirmed token = kwargs['token'] if user is None: raise HTTPError(http.NOT_FOUND) try: user.confirm_email(token) except exceptions.EmailConfirmTokenError as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': e.message_short, 'message_long': e.message_long }) except exceptions.DuplicateEmailError as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': 'Email Confirmation Failed', 'message_long': 'This email address has already been confirmed by ' 'another user.' }) if is_initial_confirmation: user.date_last_login = datetime.datetime.utcnow() user.save() # Go to settings page status.push_status_message(language.WELCOME_MESSAGE, 'success') response = redirect('/settings/') else: status.push_status_message(language.CONFIRMED_EMAIL, 'success') response = redirect(web_url_for('user_account')) return framework.auth.authenticate(user, response=response)
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 check_can_access(node, user, key=None, api_node=None, include_groups=True): """View helper that returns whether a given user can access a node. If ``user`` is None, returns False. :rtype: boolean :raises: HTTPError (403) if user cannot access the node """ if user is None: return False if request.args.get('action', '') == 'download': if check_can_download_preprint_file(user, node): return True if (not node.can_view(Auth(user=user)) and api_node != node) or (not include_groups and not node.is_contributor(user)): if node.is_deleted: raise HTTPError(http.GONE, data={'message_long': 'The node for this file has been deleted.'}) if getattr(node, 'private_link_keys_deleted', False) and key in node.private_link_keys_deleted: status.push_status_message('The view-only links you used are expired.', trust=False) if getattr(node, 'access_requests_enabled', False): access_request = node.requests.filter(creator=user).exclude(machine_state='accepted') data = { 'node': { 'id': node._id, 'url': node.url }, 'user': { 'access_request_state': access_request.get().machine_state if access_request else None } } raise TemplateHTTPError( http.FORBIDDEN, template='request_access.mako', data=data ) raise HTTPError( http.FORBIDDEN, data={'message_long': ('User has restricted access to this page. If this should not ' 'have occurred and the issue persists, ' + language.SUPPORT_LINK)} ) return True
def project_removecontributor(auth, node, **kwargs): contributor = User.load(request.json['id']) if contributor is None: raise HTTPError(http.BAD_REQUEST) # Forbidden unless user is removing herself if not node.has_permission(auth.user, 'admin'): if auth.user != contributor: raise HTTPError(http.FORBIDDEN) if len(node.visible_contributor_ids) == 1 \ and node.visible_contributor_ids[0] == contributor._id: raise HTTPError(http.FORBIDDEN, data={ 'message_long': 'Must have at least one bibliographic contributor' }) outcome = node.remove_contributor( contributor=contributor, auth=auth, ) if outcome: if auth.user == contributor: status.push_status_message('Removed self from project', kind='success', trust=False) return {'redirectUrl': web_url_for('dashboard')} status.push_status_message('Contributor removed', kind='success', trust=False) return {} raise HTTPError(http.BAD_REQUEST, data={ 'message_long': ('{0} must have at least one contributor with admin ' 'rights'.format( node.project_or_component.capitalize())) })
def test_push_status_message_unexpected_error(self, mock_sesh): status_message = 'This is a message' exception_message = 'this is some very unexpected problem' mock_get = mock.Mock(side_effect=RuntimeError(exception_message)) mock_data = mock.Mock() mock_data.attach_mock(mock_get, 'get') mock_sesh.attach_mock(mock_data, 'data') try: push_status_message(status_message, kind='error') assert_true(False, 'push_status_message() should have generated a RuntimeError exception.') except ValidationError as e: assert_true(False, 'push_status_message() should have re-raised the RuntimeError not gotten ValidationError.') except RuntimeError as e: assert_equal(getattr(e, 'message', None), exception_message, 'push_status_message() should have re-raised the ' 'original RuntimeError with the original message.') except: assert_true(False, 'Unexpected Exception from push_status_message when called ' 'from the v2 API with type "error"')
def node_registration_embargo_disapprove(auth, node, token, **kwargs): """Handles disapproval of registration embargoes :param auth: User wanting to disapprove the embargo :return: Redirect to registration or :raises: HTTPError if invalid token or user is not admin """ if not node.pending_embargo: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': 'Invalid Token', 'message_long': 'This registration is not pending an embargo.' }) # Note(hryabcki): node.registered_from not accessible after disapproval if node.embargo.for_existing_registration: redirect_url = node.web_url_for('view_project') else: redirect_url = node.registered_from.web_url_for('view_project') try: node.embargo.disapprove_embargo(auth.user, token) node.embargo.save() except InvalidEmbargoDisapprovalToken as e: raise HTTPError(http.BAD_REQUEST, data={ 'message_short': e.message_short, 'message_long': e.message_long }) except PermissionsError as e: raise HTTPError(http.FORBIDDEN, data={ 'message_short': 'Unauthorized access', 'message_long': e.message }) status.push_status_message( 'Your disapproval has been accepted and the embargo has been cancelled.', kind='success', trust=False) return redirect(redirect_url)
def project_new_node(auth, node, **kwargs): form = NewNodeForm(request.form) user = auth.user if form.validate(): try: new_component = new_node( title=strip_html(form.title.data), user=user, category=form.category.data, parent=node, ) except ValidationValueError as e: raise HTTPError(http.BAD_REQUEST, data=dict(message_long=e.message)) redirect_url = new_component.url message = ( 'Your component was created successfully. You can keep working on the component page below, ' 'or return to the <u><a href="{url}">project page</a></u>.' ).format(url=node.url) if form.inherit_contributors.data and node.has_permission(user, ADMIN): for contributor in node.contributors: new_component.add_contributor( contributor, permissions=node.get_permissions(contributor), auth=auth) new_component.save() redirect_url = redirect_url + 'contributors/' message = ( 'Your component was created successfully. You can edit the contributor permissions below, ' 'work on your <u><a href=' + new_component.url + '>component</a></u> or return to the <u><a href="{url}">project page</a></u>.' ).format(url=node.url) status.push_status_message(message, kind='info', trust=True) return { 'status': 'success', }, 201, None, redirect_url else: # TODO: This function doesn't seem to exist anymore? status.push_errors_to_status(form.errors) raise HTTPError(http.BAD_REQUEST, redirect_url=node.url)
def check_can_access(node, user, key=None, api_node=None): """View helper that returns whether a given user can access a node. If ``user`` is None, returns False. :rtype: boolean :raises: HTTPError (403) if user cannot access the node """ if user is None: return False if not node.can_view(Auth(user=user)) and api_node != node: if key in node.private_link_keys_deleted: status.push_status_message( 'The view-only links you used are expired.', trust=False) if node.access_requests_enabled: access_request = node.requests.filter(creator=user).exclude( machine_state='accepted') data = { 'node': { 'id': node._id, 'url': node.url }, 'user': { 'access_request_state': access_request.get().machine_state if access_request else None } } raise TemplateHTTPError(http.FORBIDDEN, template='request_access.mako', data=data) raise HTTPError( http.FORBIDDEN, data={ 'message_long': ('User has restricted access to this page. If this should not ' 'have occurred and the issue persists, ' + language.SUPPORT_LINK) }) return True
def user_account_password(auth, **kwargs): user = auth.user old_password = request.form.get('old_password', None) new_password = request.form.get('new_password', None) confirm_password = request.form.get('confirm_password', None) # It has been more than 1 hour since last invalid attempt to change password. Reset the counter for invalid attempts. if throttle_period_expired(user.change_password_last_attempt, settings.TIME_RESET_CHANGE_PASSWORD_ATTEMPTS): user.reset_old_password_invalid_attempts() # There have been more than 3 failed attempts and throttle hasn't expired. if user.old_password_invalid_attempts >= settings.INCORRECT_PASSWORD_ATTEMPTS_ALLOWED and not throttle_period_expired( user.change_password_last_attempt, settings.CHANGE_PASSWORD_THROTTLE): push_status_message( message= 'Too many failed attempts. Please wait a while before attempting to change your password.', kind='warning', trust=False) return redirect(web_url_for('user_account')) try: user.change_password(old_password, new_password, confirm_password) except ChangePasswordError as error: for m in error.messages: push_status_message(m, kind='warning', trust=False) else: # We have to logout the user first so all CAS sessions are invalid user.save() osf_logout() return redirect( cas.get_logout_url( cas.get_login_url( web_url_for('user_account', _absolute=True) + '?password_reset=True', username=user.username, verification_key=user.verification_key, ))) user.save() return redirect(web_url_for('user_account'))