Ejemplo n.º 1
0
    def test_http_error_raised(self):
        """When an HTTPError is raised in the view function, it is passed as
        input to the renderer.

        ``WebRenderer`` should return a 2-tuple, where the first element is the
        rendered error template. Each HTTPError exposes a ``to_data()`` method,
        which yields the appropriate error message text.
        """

        self.app.app.preprocess_request()

        err = HTTPError(http.NOT_FOUND)

        resp = self.r(err)

        self.assertIn(err.to_data()["message_short"], resp[0])
        self.assertEqual(http.NOT_FOUND, resp[1])
Ejemplo n.º 2
0
def verify_user_match(auth, **kwargs):
    uid = kwargs.get('uid')
    if uid and uid != auth.user._id:
        raise HTTPError(http.FORBIDDEN)
Ejemplo n.º 3
0
def get_auth(auth, **kwargs):
    cas_resp = None
    if not auth.user:
        # Central Authentication Server OAuth Bearer Token
        authorization = request.headers.get('Authorization')
        if authorization and authorization.startswith('Bearer '):
            client = cas.get_client()
            try:
                access_token = cas.parse_auth_header(authorization)
                cas_resp = client.profile(access_token)
            except cas.CasError as err:
                sentry.log_exception()
                # NOTE: We assume that the request is an AJAX request
                return json_renderer(err)
            if cas_resp.authenticated:
                auth.user = User.load(cas_resp.user)

    try:
        data = jwt.decode(
            jwe.decrypt(request.args.get('payload', '').encode('utf-8'), WATERBUTLER_JWE_KEY),
            settings.WATERBUTLER_JWT_SECRET,
            options={'require_exp': True},
            algorithm=settings.WATERBUTLER_JWT_ALGORITHM
        )['data']
    except (jwt.InvalidTokenError, KeyError):
        raise HTTPError(httplib.FORBIDDEN)

    if not auth.user:
        auth.user = User.from_cookie(data.get('cookie', ''))

    try:
        action = data['action']
        node_id = data['nid']
        provider_name = data['provider']
    except KeyError:
        raise HTTPError(httplib.BAD_REQUEST)

    node = Node.load(node_id)
    if not node:
        raise HTTPError(httplib.NOT_FOUND)

    check_access(node, auth, action, cas_resp)

    provider_settings = node.get_addon(provider_name)
    if not provider_settings:
        raise HTTPError(httplib.BAD_REQUEST)

    try:
        credentials = provider_settings.serialize_waterbutler_credentials()
        waterbutler_settings = provider_settings.serialize_waterbutler_settings()
    except exceptions.AddonError:
        log_exception()
        raise HTTPError(httplib.BAD_REQUEST)

    return {'payload': jwe.encrypt(jwt.encode({
        'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=settings.WATERBUTLER_JWT_EXPIRATION),
        'data': {
            'auth': make_auth(auth.user),  # A waterbutler auth dict not an Auth object
            'credentials': credentials,
            'settings': waterbutler_settings,
            'callback_url': node.api_url_for(
                ('create_waterbutler_log' if not node.is_registration else 'registration_callbacks'),
                _absolute=True,
            ),
        }
    }, settings.WATERBUTLER_JWT_SECRET, algorithm=settings.WATERBUTLER_JWT_ALGORITHM), WATERBUTLER_JWE_KEY)}
Ejemplo n.º 4
0
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:
                # Add related claimed tags to user
                _add_related_claimed_tag_to_user(pid, user)
                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,
    }
Ejemplo n.º 5
0
def send_claim_email(email,
                     unclaimed_user,
                     node,
                     notify=True,
                     throttle=24 * 3600,
                     email_template='default'):
    """
    Unregistered user claiming a user account as an contributor to a project. Send an email for claiming the account.
    Either sends to the given email or the referrer's email, depending on the email address provided.

    :param str email: The address given in the claim user form
    :param User unclaimed_user: The User record to claim.
    :param Node node: The node where the user claimed their account.
    :param bool notify: If True and an email is sent to the referrer, an email
        will also be sent to the invited user about their pending verification.
    :param int throttle: Time period (in seconds) after the referrer is
        emailed during which the referrer will not be emailed again.
    :param str email_template: the email template to use
    :return
    :raise http_status.HTTP_400_BAD_REQUEST

    """

    claimer_email = email.lower().strip()
    unclaimed_record = unclaimed_user.get_unclaimed_record(node._primary_key)
    referrer = OSFUser.load(unclaimed_record['referrer_id'])
    claim_url = unclaimed_user.get_claim_url(node._primary_key, external=True)

    # Option 1:
    #   When adding the contributor, the referrer provides both name and email.
    #   The given email is the same provided by user, just send to that email.
    logo = None
    if unclaimed_record.get('email') == claimer_email:
        # check email template for branded preprints
        if email_template == 'preprint':
            if node.provider.is_default:
                mail_tpl = mails.INVITE_OSF_PREPRINT
                logo = settings.OSF_PREPRINTS_LOGO
            else:
                mail_tpl = mails.INVITE_PREPRINT(node.provider)
                logo = node.provider._id
        else:
            mail_tpl = mails.INVITE_DEFAULT

        to_addr = claimer_email
        unclaimed_record['claimer_email'] = claimer_email
        unclaimed_user.save()
    # Option 2:
    # TODO: [new improvement ticket] this option is disabled from preprint but still available on the project page
    #   When adding the contributor, the referred only provides the name.
    #   The account is later claimed by some one who provides the email.
    #   Send email to the referrer and ask her/him to forward the email to the user.
    else:
        # check throttle
        timestamp = unclaimed_record.get('last_sent')
        if not throttle_period_expired(timestamp, throttle):
            raise HTTPError(
                http_status.HTTP_400_BAD_REQUEST,
                data=dict(
                    message_long=
                    'User account can only be claimed with an existing user once every 24 hours'
                ))
        # roll the valid token for each email, thus user cannot change email and approve a different email address
        verification_key = generate_verification_key(verification_type='claim')
        unclaimed_record['last_sent'] = get_timestamp()
        unclaimed_record['token'] = verification_key['token']
        unclaimed_record['expires'] = verification_key['expires']
        unclaimed_record['claimer_email'] = claimer_email
        unclaimed_user.save()

        claim_url = unclaimed_user.get_claim_url(node._primary_key,
                                                 external=True)
        # send an email to the invited user without `claim_url`
        if notify:
            pending_mail = mails.PENDING_VERIFICATION
            mails.send_mail(
                claimer_email,
                pending_mail,
                user=unclaimed_user,
                referrer=referrer,
                fullname=unclaimed_record['name'],
                node=node,
                can_change_preferences=False,
                osf_contact_email=settings.OSF_CONTACT_EMAIL,
            )
        mail_tpl = mails.FORWARD_INVITE
        to_addr = referrer.username

    # Send an email to the claimer (Option 1) or to the referrer (Option 2) with `claim_url`
    mails.send_mail(
        to_addr,
        mail_tpl,
        user=unclaimed_user,
        referrer=referrer,
        node=node,
        claim_url=claim_url,
        email=claimer_email,
        fullname=unclaimed_record['name'],
        branded_service=node.provider,
        can_change_preferences=False,
        logo=logo if logo else settings.OSF_LOGO,
        osf_contact_email=settings.OSF_CONTACT_EMAIL,
    )

    return to_addr
Ejemplo n.º 6
0
def publish_dataverse(dataverse):
    try:
        dataverse.publish()
    except OperationFailedError:
        raise HTTPError(http.BAD_REQUEST)
Ejemplo n.º 7
0
    def get_folders(self, folder_id=None, **kwargs):
        """Get list of folders underneath the folder with id ``folder_id``.  If
        ``folder_id`` is ``None``, return a single entry representing the root folder.
        In OneDrive, the root folder has a unique id, so fetch that and return it.

        This method returns a list of dicts with metadata about each folder under ``folder_id``.
        These dicts have the following properties::

            {
                'addon': 'onedrive',          # short name of the addon
                'id': folder_id,              # id of the folder.  root may need special casing
                'path': '/',                  # human-readable path of the folder
                'kind': 'folder',             # always 'folder'
                'name': '/ (Full OneDrive)',  # human readable name of the folder. root may need special casing
                'urls': {                     # urls to fetch information about the folder
                    'folders': api_v2_url(    # url to get subfolders of this folder.
                        'nodes/{}/addons/onedrive/folders/'.format(self.owner._id),
                         params={'id': folder_id}
                    ),
                }
            }

        Some providers include additional information::

        * figshare includes ``permissions``, ``hasChildren``

        * googledrive includes ``urls.fetch``

        :param str folder_id: the id of the folder to fetch subfolders of. Defaults to ``None``
        :rtype: list
        :return: a list of dicts with metadata about the subfolder of ``folder_id``.
        """

        if folder_id is None:
            return [{
                'id': DEFAULT_ROOT_ID,
                'path': '/',
                'addon': 'onedrive',
                'kind': 'folder',
                'name': '/ (Full OneDrive)',
                'urls': {
                    'folders': api_v2_url('nodes/{}/addons/onedrive/folders/'.format(self.owner._id),
                                          params={'id': DEFAULT_ROOT_ID}),
                }
            }]

        try:
            access_token = self.fetch_access_token()
        except exceptions.InvalidAuthError:
            raise HTTPError(403)

        client = OneDriveClient(access_token)
        items = client.folders(self.drive_id, folder_id)
        return [
            {
                'addon': 'onedrive',
                'kind': 'folder',
                'id': item['id'],
                'name': item['name'],
                'path': item['name'],
                'urls': {
                    'folders': api_v2_url('nodes/{}/addons/onedrive/folders/'.format(self.owner._id),
                                          params={'id': item['id']}),
                }
            }
            for item in items
        ]
Ejemplo n.º 8
0
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}
Ejemplo n.º 9
0
def addon_view_or_download_file(auth, path, provider, **kwargs):
    extras = request.args.to_dict()
    extras.pop('_', None)  # Clean up our url params a bit
    action = extras.get('action', 'view')
    node = kwargs.get('node') or kwargs['project']

    node_addon = node.get_addon(provider)

    provider_safe = markupsafe.escape(provider)
    path_safe = markupsafe.escape(path)
    project_safe = markupsafe.escape(node.project_or_component)

    if not path:
        raise HTTPError(httplib.BAD_REQUEST)

    if not isinstance(node_addon, StorageAddonBase):
        raise HTTPError(
            httplib.BAD_REQUEST,
            data={
                'message_short':
                'Bad Request',
                'message_long':
                'The {} add-on containing {} is no longer connected to {}.'.
                format(provider_safe, path_safe, project_safe)
            })

    if not node_addon.has_auth:
        raise HTTPError(
            httplib.UNAUTHORIZED,
            data={
                'message_short':
                'Unauthorized',
                'message_long':
                'The {} add-on containing {} is no longer authorized.'.format(
                    provider_safe, path_safe)
            })

    if not node_addon.complete:
        raise HTTPError(
            httplib.BAD_REQUEST,
            data={
                'message_short':
                'Bad Request',
                'message_long':
                'The {} add-on containing {} is no longer configured.'.format(
                    provider_safe, path_safe)
            })

    file_node = FileNode.resolve_class(provider, FileNode.FILE).get_or_create(
        node, path)

    # Note: Cookie is provided for authentication to waterbutler
    # it is overriden to force authentication as the current user
    # the auth header is also pass to support basic auth
    version = file_node.touch(
        request.headers.get('Authorization'),
        **dict(extras, cookie=request.cookies.get(settings.COOKIE_NAME)))

    if version is None:
        return addon_deleted_file(file_node=file_node, path=path, **kwargs)

    # TODO clean up these urls and unify what is used as a version identifier
    if request.method == 'HEAD':
        return make_response(('', 200, {
            'Location':
            file_node.generate_waterbutler_url(
                **dict(extras, direct=None, version=version.identifier))
        }))

    if action == 'download':
        return redirect(
            file_node.generate_waterbutler_url(
                **dict(extras, direct=None, version=version.identifier)))

    if action == 'get_guid':
        draft_id = extras.get('draft')
        draft = DraftRegistration.load(draft_id)
        if draft is None or draft.is_approved:
            raise HTTPError(httplib.BAD_REQUEST,
                            data={
                                'message_short':
                                'Bad Request',
                                'message_long':
                                'File not associated with required object.'
                            })
        guid = file_node.get_guid(create=True)
        guid.referent.save()
        return dict(guid=guid._id)

    if len(request.path.strip('/').split('/')) > 1:
        guid = file_node.get_guid(create=True)
        return redirect(
            furl.furl('/{}/'.format(guid._id)).set(args=extras).url)
    return addon_view_file(auth, node, file_node, version)
Ejemplo n.º 10
0
def addon_view_or_download_file(auth, path, provider, **kwargs):
    extras = request.args.to_dict()
    extras.pop('_', None)  # Clean up our url params a bit
    action = extras.get('action', 'view')
    guid = kwargs.get('guid')
    guid_target = getattr(Guid.load(guid), 'referent', None)
    target = guid_target or kwargs.get('node') or kwargs['project']

    provider_safe = markupsafe.escape(provider)
    path_safe = markupsafe.escape(path)

    if not path:
        raise HTTPError(http_status.HTTP_400_BAD_REQUEST)

    if hasattr(target, 'get_addon'):

        node_addon = target.get_addon(provider)

        if not isinstance(node_addon, BaseStorageAddon):
            object_text = markupsafe.escape(
                getattr(target, 'project_or_component', 'this object'))
            raise HTTPError(
                http_status.HTTP_400_BAD_REQUEST,
                data={
                    'message_short':
                    'Bad Request',
                    'message_long':
                    'The {} add-on containing {} is no longer connected to {}.'
                    .format(provider_safe, path_safe, object_text)
                })

        if not node_addon.has_auth:
            raise HTTPError(
                http_status.HTTP_401_UNAUTHORIZED,
                data={
                    'message_short':
                    'Unauthorized',
                    'message_long':
                    'The {} add-on containing {} is no longer authorized.'.
                    format(provider_safe, path_safe)
                })

        if not node_addon.complete:
            raise HTTPError(
                http_status.HTTP_400_BAD_REQUEST,
                data={
                    'message_short':
                    'Bad Request',
                    'message_long':
                    'The {} add-on containing {} is no longer configured.'.
                    format(provider_safe, path_safe)
                })

    savepoint_id = transaction.savepoint()
    file_node = BaseFileNode.resolve_class(provider,
                                           BaseFileNode.FILE).get_or_create(
                                               target, path)

    # Note: Cookie is provided for authentication to waterbutler
    # it is overriden to force authentication as the current user
    # the auth header is also pass to support basic auth
    version = file_node.touch(
        request.headers.get('Authorization'),
        **dict(extras, cookie=request.cookies.get(settings.COOKIE_NAME)))
    if version is None:
        # File is either deleted or unable to be found in the provider location
        # Rollback the insertion of the file_node
        transaction.savepoint_rollback(savepoint_id)
        if not file_node.pk:
            file_node = BaseFileNode.load(path)

            if not file_node:
                raise HTTPError(http_status.HTTP_404_NOT_FOUND,
                                data={
                                    'message_short':
                                    'File Not Found',
                                    'message_long':
                                    'The requested file could not be found.'
                                })

            if file_node.kind == 'folder':
                raise HTTPError(
                    http_status.HTTP_400_BAD_REQUEST,
                    data={
                        'message_short':
                        'Bad Request',
                        'message_long':
                        'You cannot request a folder from this endpoint.'
                    })

            # Allow osfstorage to redirect if the deep url can be used to find a valid file_node
            if file_node.provider == 'osfstorage' and not file_node.is_deleted:
                return redirect(
                    file_node.target.web_url_for('addon_view_or_download_file',
                                                 path=file_node._id,
                                                 provider=file_node.provider))
        return addon_deleted_file(target=target,
                                  file_node=file_node,
                                  path=path,
                                  **kwargs)
    else:
        transaction.savepoint_commit(savepoint_id)

    # TODO clean up these urls and unify what is used as a version identifier
    if request.method == 'HEAD':
        return make_response(('', http_status.HTTP_302_FOUND, {
            'Location':
            file_node.generate_waterbutler_url(
                **dict(extras,
                       direct=None,
                       version=version.identifier,
                       _internal=extras.get('mode') == 'render'))
        }))

    if action == 'download':
        format = extras.get('format')
        _, extension = os.path.splitext(file_node.name)
        # avoid rendering files with the same format type.
        if format and '.{}'.format(format.lower()) != extension.lower():
            return redirect('{}/export?format={}&url={}'.format(
                get_mfr_url(target, provider), format,
                quote(
                    file_node.generate_waterbutler_url(
                        **dict(extras,
                               direct=None,
                               version=version.identifier,
                               _internal=extras.get('mode') == 'render')))))
        return redirect(
            file_node.generate_waterbutler_url(
                **dict(extras,
                       direct=None,
                       version=version.identifier,
                       _internal=extras.get('mode') == 'render')))

    if action == 'get_guid':
        draft_id = extras.get('draft')
        draft = DraftRegistration.load(draft_id)
        if draft is None or draft.is_approved:
            raise HTTPError(http_status.HTTP_400_BAD_REQUEST,
                            data={
                                'message_short':
                                'Bad Request',
                                'message_long':
                                'File not associated with required object.'
                            })
        guid = file_node.get_guid(create=True)
        guid.referent.save()
        return dict(guid=guid._id)

    if len(request.path.strip('/').split('/')) > 1:
        guid = file_node.get_guid(create=True)
        return redirect(
            furl.furl('/{}/'.format(guid._id)).set(args=extras).url)
    if isinstance(target, Preprint):
        # Redirecting preprint file guids to the preprint detail page
        return redirect('/{}/'.format(target._id))

    return addon_view_file(auth, target, file_node, version)
Ejemplo n.º 11
0
def create_waterbutler_log(payload, **kwargs):
    with transaction.atomic():
        try:
            auth = payload['auth']
            # Don't log download actions
            if payload['action'] in DOWNLOAD_ACTIONS:
                guid = Guid.load(payload['metadata'].get('nid'))
                if guid:
                    node = guid.referent
                return {'status': 'success'}

            user = OSFUser.load(auth['id'])
            if user is None:
                raise HTTPError(http_status.HTTP_400_BAD_REQUEST)

            action = LOG_ACTION_MAP[payload['action']]
        except KeyError:
            raise HTTPError(http_status.HTTP_400_BAD_REQUEST)

        auth = Auth(user=user)
        node = kwargs.get('node') or kwargs.get('project') or Preprint.load(
            kwargs.get('nid')) or Preprint.load(kwargs.get('pid'))

        if action in (NodeLog.FILE_MOVED, NodeLog.FILE_COPIED):

            for bundle in ('source', 'destination'):
                for key in ('provider', 'materialized', 'name', 'nid'):
                    if key not in payload[bundle]:
                        raise HTTPError(http_status.HTTP_400_BAD_REQUEST)

            dest = payload['destination']
            src = payload['source']

            if src is not None and dest is not None:
                dest_path = dest['materialized']
                src_path = src['materialized']
                if dest_path.endswith('/') and src_path.endswith('/'):
                    dest_path = os.path.dirname(dest_path)
                    src_path = os.path.dirname(src_path)
                if (os.path.split(dest_path)[0] == os.path.split(src_path)[0]
                        and dest['provider'] == src['provider']
                        and dest['nid'] == src['nid']
                        and dest['name'] != src['name']):
                    action = LOG_ACTION_MAP['rename']

            destination_node = node  # For clarity
            source_node = AbstractNode.load(src['nid']) or Preprint.load(
                src['nid'])

            # We return provider fullname so we need to load node settings, if applicable
            source = None
            if hasattr(source_node, 'get_addon'):
                source = source_node.get_addon(payload['source']['provider'])
            destination = None
            if hasattr(node, 'get_addon'):
                destination = node.get_addon(
                    payload['destination']['provider'])

            payload['source'].update({
                'materialized':
                payload['source']['materialized'].lstrip('/'),
                'addon':
                source.config.full_name if source else 'osfstorage',
                'url':
                source_node.web_url_for(
                    'addon_view_or_download_file',
                    path=payload['source']['path'].lstrip('/'),
                    provider=payload['source']['provider']),
                'node': {
                    '_id': source_node._id,
                    'url': source_node.url,
                    'title': source_node.title,
                }
            })

            payload['destination'].update({
                'materialized':
                payload['destination']['materialized'].lstrip('/'),
                'addon':
                destination.config.full_name if destination else 'osfstorage',
                'url':
                destination_node.web_url_for(
                    'addon_view_or_download_file',
                    path=payload['destination']['path'].lstrip('/'),
                    provider=payload['destination']['provider']),
                'node': {
                    '_id': destination_node._id,
                    'url': destination_node.url,
                    'title': destination_node.title,
                }
            })

            if not payload.get('errors'):
                destination_node.add_log(action=action,
                                         auth=auth,
                                         params=payload)

            if payload.get('email') is True or payload.get('errors'):
                mails.send_mail(
                    user.username,
                    mails.FILE_OPERATION_FAILED
                    if payload.get('errors') else mails.FILE_OPERATION_SUCCESS,
                    action=payload['action'],
                    source_node=source_node,
                    destination_node=destination_node,
                    source_path=payload['source']['materialized'],
                    source_addon=payload['source']['addon'],
                    destination_addon=payload['destination']['addon'],
                    osf_support_email=settings.OSF_SUPPORT_EMAIL)

            if payload.get('errors'):
                # Action failed but our function succeeded
                # Bail out to avoid file_signals
                return {'status': 'success'}

        else:
            node.create_waterbutler_log(auth, action, payload)

    with transaction.atomic():
        file_signals.file_updated.send(target=node,
                                       user=user,
                                       event_type=action,
                                       payload=payload)

    return {'status': 'success'}
Ejemplo n.º 12
0
def get_auth(auth, **kwargs):
    cas_resp = None
    if not auth.user:
        # Central Authentication Server OAuth Bearer Token
        authorization = request.headers.get('Authorization')
        if authorization and authorization.startswith('Bearer '):
            client = cas.get_client()
            try:
                access_token = cas.parse_auth_header(authorization)
                cas_resp = client.profile(access_token)
            except cas.CasError as err:
                sentry.log_exception()
                # NOTE: We assume that the request is an AJAX request
                return json_renderer(err)
            if cas_resp.authenticated:
                auth.user = OSFUser.load(cas_resp.user)

    try:
        data = jwt.decode(jwe.decrypt(
            request.args.get('payload', '').encode('utf-8'),
            WATERBUTLER_JWE_KEY),
                          settings.WATERBUTLER_JWT_SECRET,
                          options={'require_exp': True},
                          algorithm=settings.WATERBUTLER_JWT_ALGORITHM)['data']
    except (jwt.InvalidTokenError, KeyError) as err:
        sentry.log_message(str(err))
        raise HTTPError(http_status.HTTP_403_FORBIDDEN)

    if not auth.user:
        auth.user = OSFUser.from_cookie(data.get('cookie', ''))

    try:
        action = data['action']
        node_id = data['nid']
        provider_name = data['provider']
    except KeyError:
        raise HTTPError(http_status.HTTP_400_BAD_REQUEST)

    node = AbstractNode.load(node_id) or Preprint.load(node_id)
    if node and node.is_deleted:
        raise HTTPError(http_status.HTTP_410_GONE)
    elif not node:
        raise HTTPError(http_status.HTTP_404_NOT_FOUND)

    check_access(node, auth, action, cas_resp)
    provider_settings = None
    if hasattr(node, 'get_addon'):
        provider_settings = node.get_addon(provider_name)
        if not provider_settings:
            raise HTTPError(http_status.HTTP_400_BAD_REQUEST)

    path = data.get('path')
    credentials = None
    waterbutler_settings = None
    fileversion = None
    if provider_name == 'osfstorage':
        if path:
            file_id = path.strip('/')
            # check to see if this is a file or a folder
            filenode = OsfStorageFileNode.load(path.strip('/'))
            if filenode and filenode.is_file:
                # default to most recent version if none is provided in the response
                version = int(data['version']) if data.get(
                    'version') else filenode.versions.count()
                try:
                    fileversion = FileVersion.objects.filter(
                        basefilenode___id=file_id,
                        identifier=version).select_related('region').get()
                except FileVersion.DoesNotExist:
                    raise HTTPError(http_status.HTTP_400_BAD_REQUEST)
                if auth.user:
                    # mark fileversion as seen
                    FileVersionUserMetadata.objects.get_or_create(
                        user=auth.user, file_version=fileversion)
                if not node.is_contributor_or_group_member(auth.user):
                    from_mfr = download_is_from_mfr(request, payload=data)
                    # version index is 0 based
                    version_index = version - 1
                    if action == 'render':
                        update_analytics(node, filenode, version_index, 'view')
                    elif action == 'download' and not from_mfr:
                        update_analytics(node, filenode, version_index,
                                         'download')
                    if waffle.switch_is_active(features.ELASTICSEARCH_METRICS):
                        if isinstance(node, Preprint):
                            metric_class = get_metric_class_for_action(
                                action, from_mfr=from_mfr)
                            if metric_class:
                                try:
                                    metric_class.record_for_preprint(
                                        preprint=node,
                                        user=auth.user,
                                        version=fileversion.identifier
                                        if fileversion else None,
                                        path=path)
                                except es_exceptions.ConnectionError:
                                    log_exception()
        if fileversion and provider_settings:
            region = fileversion.region
            credentials = region.waterbutler_credentials
            waterbutler_settings = fileversion.serialize_waterbutler_settings(
                node_id=provider_settings.owner._id,
                root_id=provider_settings.root_node._id,
            )
    # If they haven't been set by version region, use the NodeSettings or Preprint directly
    if not (credentials and waterbutler_settings):
        credentials = node.serialize_waterbutler_credentials(provider_name)
        waterbutler_settings = node.serialize_waterbutler_settings(
            provider_name)

    return {
        'payload':
        jwe.encrypt(
            jwt.encode(
                {
                    'exp':
                    timezone.now() + datetime.timedelta(
                        seconds=settings.WATERBUTLER_JWT_EXPIRATION),
                    'data': {
                        'auth':
                        make_auth(
                            auth.user
                        ),  # A waterbutler auth dict not an Auth object
                        'credentials':
                        credentials,
                        'settings':
                        waterbutler_settings,
                        'callback_url':
                        node.api_url_for(
                            ('create_waterbutler_log'
                             if not getattr(node, 'is_registration', False)
                             else 'registration_callbacks'),
                            _absolute=True,
                            _internal=True)
                    }
                },
                settings.WATERBUTLER_JWT_SECRET,
                algorithm=settings.WATERBUTLER_JWT_ALGORITHM),
            WATERBUTLER_JWE_KEY)
    }
Ejemplo n.º 13
0
def check_access(node, auth, action, cas_resp):
    """Verify that user can perform requested action on resource. Raise appropriate
    error code if action cannot proceed.
    """
    permission = permission_map.get(action, None)
    if permission is None:
        raise HTTPError(http_status.HTTP_400_BAD_REQUEST)

    if cas_resp:
        if permission == permissions.READ:
            if node.can_view_files(auth=None):
                return True
            required_scope = node.file_read_scope
        else:
            required_scope = node.file_write_scope

        if not cas_resp.authenticated \
           or required_scope not in oauth_scopes.normalize_scopes(cas_resp.attributes['accessTokenScope']):
            raise HTTPError(http_status.HTTP_403_FORBIDDEN)

    if permission == permissions.READ:
        if node.can_view_files(auth):
            return True
        # The user may have admin privileges on a parent node, in which
        # case they should have read permissions
        if getattr(node, 'is_registration',
                   False) and node.registered_from.can_view(auth):
            return True
    if permission == permissions.WRITE and node.can_edit(auth):
        return True

    # Users attempting to register projects with components might not have
    # `write` permissions for all components. This will result in a 403 for
    # all `upload` actions as well as `copyfrom` actions if the component
    # in question is not public. To get around this, we have to recursively
    # check the node's parent node to determine if they have `write`
    # permissions up the stack.
    # TODO(hrybacki): is there a way to tell if this is for a registration?
    # All nodes being registered that receive the `upload` action will have
    # `node.is_registration` == True. However, we have no way of telling if
    # `copyfrom` actions are originating from a node being registered.
    # TODO This is raise UNAUTHORIZED for registrations that have not been archived yet
    if isinstance(node, AbstractNode):
        if action == 'copyfrom' or (action == 'upload'
                                    and node.is_registration):
            parent = node.parent_node
            while parent:
                if parent.can_edit(auth):
                    return True
                parent = parent.parent_node

        # Users with the prereg admin permission should be allowed to download files
        # from prereg challenge draft registrations.
        try:
            prereg_schema = RegistrationSchema.objects.get(
                name='Prereg Challenge', schema_version=2)
            allowed_nodes = [node] + node.parents
            prereg_draft_registration = DraftRegistration.objects.filter(
                branched_from__in=allowed_nodes,
                registration_schema=prereg_schema)
            if action == 'download' and \
                        auth.user is not None and \
                        prereg_draft_registration.count() > 0 and \
                        auth.user.has_perm('osf.administer_prereg'):
                return True
        except RegistrationSchema.DoesNotExist:
            pass

    raise HTTPError(http_status.HTTP_403_FORBIDDEN if auth.
                    user else http_status.HTTP_401_UNAUTHORIZED)
Ejemplo n.º 14
0
def addon_view_or_download_file(auth, path, provider, **kwargs):
    extras = request.args.to_dict()
    extras.pop('_', None)  # Clean up our url params a bit
    action = extras.get('action', 'view')
    node = kwargs.get('node') or kwargs['project']

    node_addon = node.get_addon(provider)

    if not path:
        raise HTTPError(httplib.BAD_REQUEST)

    if not isinstance(node_addon, StorageAddonBase):
        raise HTTPError(
            httplib.BAD_REQUEST, {
                'message_short':
                'Bad Request',
                'message_long':
                'The add-on containing this file is no longer connected to the {}.'
                .format(node.project_or_component)
            })

    if not node_addon.has_auth:
        raise HTTPError(
            httplib.UNAUTHORIZED, {
                'message_short':
                'Unauthorized',
                'message_long':
                'The add-on containing this file is no longer authorized.'
            })

    if not node_addon.complete:
        raise HTTPError(
            httplib.BAD_REQUEST, {
                'message_short':
                'Bad Request',
                'message_long':
                'The add-on containing this file is no longer configured.'
            })

    file_node = FileNode.resolve_class(provider, FileNode.FILE).get_or_create(
        node, path)

    # Note: Cookie is provided for authentication to waterbutler
    # it is overriden to force authentication as the current user
    # the auth header is also pass to support basic auth
    version = file_node.touch(
        request.headers.get('Authorization'),
        **dict(extras, cookie=request.cookies.get(settings.COOKIE_NAME)))

    if version is None:
        if file_node.get_guid():
            # If this file has been successfully view before but no longer exists
            # Show a nice error message
            return addon_deleted_file(file_node=file_node, **kwargs)

        raise HTTPError(
            httplib.NOT_FOUND, {
                'message_short': 'Not Found',
                'message_long': 'This file does not exist'
            })

    # TODO clean up these urls and unify what is used as a version identifier
    if request.method == 'HEAD':
        return make_response(('', 200, {
            'Location':
            file_node.generate_waterbutler_url(
                **dict(extras, direct=None, version=version.identifier))
        }))

    if action == 'download':
        return redirect(
            file_node.generate_waterbutler_url(
                **dict(extras, direct=None, version=version.identifier)))

    if len(request.path.strip('/').split('/')) > 1:
        guid = file_node.get_guid(create=True)
        return redirect(
            furl.furl('/{}/'.format(guid._id)).set(args=extras).url)

    return addon_view_file(auth, node, file_node, version)
Ejemplo n.º 15
0
 def wrapped(*args, **kwargs):
     remote = request.remote_addr
     if remote not in addrs:
         raise HTTPError(httplib.FORBIDDEN)
     return func(*args, **kwargs)
Ejemplo n.º 16
0
def external_login_confirm_email_get(auth, uid, token):
    """
    View for email confirmation links when user first login through external identity provider.
    HTTP Method: GET

    When users click the confirm link, they are expected not to be logged in. If not, they will be logged out first and
    redirected back to this view. After OSF verifies the link and performs all actions, they will be automatically
    logged in through CAS and redirected back to this view again being authenticated.

    :param auth: the auth context
    :param uid: the user's primary key
    :param token: the verification token
    """

    user = User.load(uid)
    if not user:
        raise HTTPError(http.BAD_REQUEST)

    destination = request.args.get('destination')
    if not destination:
        raise HTTPError(http.BAD_REQUEST)

    # if user is already logged in
    if auth and auth.user:
        # if it is a wrong user
        if auth.user._id != user._id:
            return auth_logout(redirect_url=request.url)
        # if it is the expected user
        new = request.args.get('new', None)
        if destination in campaigns.get_campaigns():
            return redirect(campaigns.campaign_url_for(destination))
        if new:
            status.push_status_message(language.WELCOME_MESSAGE,
                                       kind='default',
                                       jumbotron=True,
                                       trust=True)
        return redirect(web_url_for('dashboard'))

    # token is invalid
    if token not in user.email_verifications:
        raise HTTPError(http.BAD_REQUEST)
    verification = user.email_verifications[token]
    email = verification['email']
    provider = verification['external_identity'].keys()[0]
    provider_id = verification['external_identity'][provider].keys()[0]
    # wrong provider
    if provider not in user.external_identity:
        raise HTTPError(http.BAD_REQUEST)
    external_status = user.external_identity[provider][provider_id]

    try:
        ensure_external_identity_uniqueness(provider, provider_id, user)
    except ValidationError as e:
        raise HTTPError(http.FORBIDDEN, e.message)

    if not user.is_registered:
        user.set_password(uuid.uuid4(), notify=False)
        user.register(email)

    if email.lower() not in user.emails:
        user.emails.append(email.lower())

    user.date_last_logged_in = datetime.datetime.utcnow()
    user.external_identity[provider][provider_id] = 'VERIFIED'
    user.social[provider.lower()] = provider_id
    del user.email_verifications[token]
    user.verification_key = generate_verification_key()
    user.save()

    service_url = request.url

    if external_status == 'CREATE':
        mails.send_mail(to_addr=user.username,
                        mail=mails.WELCOME,
                        mimetype='html',
                        user=user)
        service_url += '&{}'.format(urllib.urlencode({'new': 'true'}))
    elif external_status == 'LINK':
        mails.send_mail(
            user=user,
            to_addr=user.username,
            mail=mails.EXTERNAL_LOGIN_LINK_SUCCESS,
            external_id_provider=provider,
        )

    # redirect to CAS and authenticate the user with the verification key
    return redirect(
        cas.get_login_url(service_url,
                          username=user.username,
                          verification_key=user.verification_key))
Ejemplo n.º 17
0
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.
    HTTP Method: GET
    """

    user = User.load(kwargs['uid'])
    is_merge = 'confirm_merge' in request.args
    is_initial_confirmation = not user.date_confirmed
    log_out = request.args.get('logout', None)

    if user is None:
        raise HTTPError(http.NOT_FOUND)

    # if the user is merging or adding an email (they already are an osf user)
    if log_out:
        return auth_email_logout(token, user)

    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))

            # go to home page with push notification
            if len(auth.user.emails) == 1 and len(
                    auth.user.email_verifications) == 0:
                status.push_status_message(language.WELCOME_MESSAGE,
                                           kind='default',
                                           jumbotron=True,
                                           trust=True)
            if token in auth.user.email_verifications:
                status.push_status_message(
                    language.CONFIRM_ALTERNATE_EMAIL_ERROR,
                    kind='danger',
                    trust=True)
            return redirect(web_url_for('index'))

        status.push_status_message(language.MERGE_COMPLETE,
                                   kind='success',
                                   trust=False)
        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)

    # new random verification key, allows CAS to authenticate the user w/o password one-time only.
    user.verification_key = generate_verification_key()
    user.save()
    # redirect to CAS and authenticate the user with a verification key.
    return redirect(
        cas.get_login_url(request.url,
                          username=user.username,
                          verification_key=user.verification_key))
Ejemplo n.º 18
0
def get_record_or_404(path, node_addon):
    record = model.OsfStorageFileRecord.find_by_path(path, node_addon)
    if record is not None:
        return record
    raise HTTPError(httplib.NOT_FOUND)
Ejemplo n.º 19
0
def github_set_config(auth, **kwargs):
    node_settings = kwargs.get('node_addon', None)
    node = kwargs.get('node', None)
    user_settings = kwargs.get('user_addon', None)

    try:
        if not node:
            node = node_settings.owner
        if not user_settings:
            user_settings = node_settings.user_settings
    except AttributeError:
        raise HTTPError(http.BAD_REQUEST)

    # Parse request
    github_user_name = request.json.get('github_user', '')
    github_repo_name = request.json.get('github_repo', '')

    if not github_user_name or not github_repo_name:
        raise HTTPError(http.BAD_REQUEST)

    # Verify that repo exists and that user can access
    connection = GitHubClient(external_account=node_settings.external_account)
    repo = connection.repo(github_user_name, github_repo_name)
    if repo is None:
        if user_settings:
            message = ('Cannot access repo. Either the repo does not exist '
                       'or your account does not have permission to view it.')
        else:
            message = ('Cannot access repo.')
        return {'message': message}, http.BAD_REQUEST

    changed = (github_user_name != node_settings.user
               or github_repo_name != node_settings.repo)

    # Update hooks
    if changed:

        # Delete existing hook, if any
        node_settings.delete_hook()

        # Update node settings
        node_settings.user = github_user_name
        node_settings.repo = github_repo_name

        # Log repo select
        node.add_log(
            action='github_repo_linked',
            params={
                'project': node.parent_id,
                'node': node._id,
                'github': {
                    'user': github_user_name,
                    'repo': github_repo_name,
                }
            },
            auth=auth,
        )

        # Add new hook
        if node_settings.user and node_settings.repo:
            node_settings.add_hook(save=False)

        node_settings.save()

    return {}
Ejemplo n.º 20
0
def create_waterbutler_log(payload, **kwargs):
    with transaction.atomic():
        try:
            auth = payload['auth']
            action = LOG_ACTION_MAP[payload['action']]
        except KeyError:
            raise HTTPError(httplib.BAD_REQUEST)

        user = User.load(auth['id'])
        if user is None:
            raise HTTPError(httplib.BAD_REQUEST)

        auth = Auth(user=user)
        node = kwargs['node'] or kwargs['project']

        if action in (NodeLog.FILE_MOVED, NodeLog.FILE_COPIED):

            for bundle in ('source', 'destination'):
                for key in ('provider', 'materialized', 'name', 'nid'):
                    if key not in payload[bundle]:
                        raise HTTPError(httplib.BAD_REQUEST)

            dest = payload['destination']
            src = payload['source']

            if src is not None and dest is not None:
                dest_path = dest['materialized']
                src_path = src['materialized']
                if dest_path.endswith('/') and src_path.endswith('/'):
                    dest_path = os.path.dirname(dest_path)
                    src_path = os.path.dirname(src_path)
                if (os.path.split(dest_path)[0] == os.path.split(src_path)[0]
                        and dest['provider'] == src['provider']
                        and dest['nid'] == src['nid']
                        and dest['name'] != src['name']):
                    action = LOG_ACTION_MAP['rename']

            destination_node = node  # For clarity
            source_node = Node.load(payload['source']['nid'])

            source = source_node.get_addon(payload['source']['provider'])
            destination = node.get_addon(payload['destination']['provider'])

            payload['source'].update({
                'materialized':
                payload['source']['materialized'].lstrip('/'),
                'addon':
                source.config.full_name,
                'url':
                source_node.web_url_for(
                    'addon_view_or_download_file',
                    path=payload['source']['path'].lstrip('/'),
                    provider=payload['source']['provider']),
                'node': {
                    '_id': source_node._id,
                    'url': source_node.url,
                    'title': source_node.title,
                }
            })

            payload['destination'].update({
                'materialized':
                payload['destination']['materialized'].lstrip('/'),
                'addon':
                destination.config.full_name,
                'url':
                destination_node.web_url_for(
                    'addon_view_or_download_file',
                    path=payload['destination']['path'].lstrip('/'),
                    provider=payload['destination']['provider']),
                'node': {
                    '_id': destination_node._id,
                    'url': destination_node.url,
                    'title': destination_node.title,
                }
            })

            payload.update({
                'node': destination_node._id,
                'project': destination_node.parent_id,
            })

            if not payload.get('errors'):
                destination_node.add_log(action=action,
                                         auth=auth,
                                         params=payload)

            if payload.get('email') is True or payload.get('errors'):
                mails.send_mail(
                    user.username,
                    mails.FILE_OPERATION_FAILED
                    if payload.get('errors') else mails.FILE_OPERATION_SUCCESS,
                    action=payload['action'],
                    source_node=source_node,
                    destination_node=destination_node,
                    source_path=payload['source']['materialized'],
                    destination_path=payload['source']['materialized'],
                    source_addon=payload['source']['addon'],
                    destination_addon=payload['destination']['addon'],
                )

            if payload.get('errors'):
                # Action failed but our function succeeded
                # Bail out to avoid file_signals
                return {'status': 'success'}

        else:
            try:
                metadata = payload['metadata']
                node_addon = node.get_addon(payload['provider'])
            except KeyError:
                raise HTTPError(httplib.BAD_REQUEST)

            if node_addon is None:
                raise HTTPError(httplib.BAD_REQUEST)

            metadata['path'] = metadata['path'].lstrip('/')

            node_addon.create_waterbutler_log(auth, action, metadata)

    with transaction.atomic():
        file_signals.file_updated.send(node=node,
                                       user=user,
                                       event_type=action,
                                       payload=payload)

    return {'status': 'success'}
Ejemplo n.º 21
0
def validate_page_num(page, pages):
    if page < 0 or (pages and page >= pages):
        raise HTTPError(http.BAD_REQUEST, data=dict(
            message_long='Invalid value for "page".'
        ))
Ejemplo n.º 22
0
def addon_view_or_download_file(auth, path, provider, **kwargs):
    extras = request.args.to_dict()
    extras.pop('_', None)  # Clean up our url params a bit
    action = extras.get('action', 'view')
    node = kwargs.get('node') or kwargs['project']

    node_addon = node.get_addon(provider)

    provider_safe = markupsafe.escape(provider)
    path_safe = markupsafe.escape(path)
    project_safe = markupsafe.escape(node.project_or_component)

    if not path:
        raise HTTPError(httplib.BAD_REQUEST)

    if not isinstance(node_addon, BaseStorageAddon):
        raise HTTPError(
            httplib.BAD_REQUEST,
            data={
                'message_short':
                'Bad Request',
                'message_long':
                'The {} add-on containing {} is no longer connected to {}.'.
                format(provider_safe, path_safe, project_safe)
            })

    if not node_addon.has_auth:
        raise HTTPError(
            httplib.UNAUTHORIZED,
            data={
                'message_short':
                'Unauthorized',
                'message_long':
                'The {} add-on containing {} is no longer authorized.'.format(
                    provider_safe, path_safe)
            })

    if not node_addon.complete:
        raise HTTPError(
            httplib.BAD_REQUEST,
            data={
                'message_short':
                'Bad Request',
                'message_long':
                'The {} add-on containing {} is no longer configured.'.format(
                    provider_safe, path_safe)
            })

    savepoint_id = transaction.savepoint()
    file_node = FileNode.resolve_class(provider, FileNode.FILE).get_or_create(
        node, path)

    # Note: Cookie is provided for authentication to waterbutler
    # it is overriden to force authentication as the current user
    # the auth header is also pass to support basic auth
    version = file_node.touch(
        request.headers.get('Authorization'),
        **dict(extras, cookie=request.cookies.get(settings.COOKIE_NAME)))

    if version is None:
        # File is either deleted or unable to be found in the provider location
        # Rollback the insertion of the file_node
        transaction.savepoint_rollback(savepoint_id)
        if not file_node.pk:
            raise HTTPError(httplib.NOT_FOUND,
                            data={
                                'message_short':
                                'File Not Found',
                                'message_long':
                                'The requested file could not be found.'
                            })
        return addon_deleted_file(file_node=file_node, path=path, **kwargs)
    else:
        transaction.savepoint_commit(savepoint_id)

    # TODO clean up these urls and unify what is used as a version identifier
    if request.method == 'HEAD':
        return make_response(('', 200, {
            'Location':
            file_node.generate_waterbutler_url(
                **dict(extras,
                       direct=None,
                       version=version.identifier,
                       _internal=extras.get('mode') == 'render'))
        }))

    if action == 'download':
        format = extras.get('format')
        _, extension = os.path.splitext(file_node.name)
        # avoid rendering files with the same format type.
        if format and '.{}'.format(format) != extension:
            return redirect('{}/export?format={}&url={}'.format(
                MFR_SERVER_URL, format,
                urllib.quote(
                    file_node.generate_waterbutler_url(
                        **dict(extras,
                               direct=None,
                               version=version.identifier,
                               _internal=extras.get('mode') == 'render')))))
        return redirect(
            file_node.generate_waterbutler_url(
                **dict(extras,
                       direct=None,
                       version=version.identifier,
                       _internal=extras.get('mode') == 'render')))

    if action == 'get_guid':
        draft_id = extras.get('draft')
        draft = DraftRegistration.load(draft_id)
        if draft is None or draft.is_approved:
            raise HTTPError(httplib.BAD_REQUEST,
                            data={
                                'message_short':
                                'Bad Request',
                                'message_long':
                                'File not associated with required object.'
                            })
        guid = file_node.get_guid(create=True)
        guid.referent.save()
        return dict(guid=guid._id)

    if len(request.path.strip('/').split('/')) > 1:
        guid = file_node.get_guid(create=True)
        return redirect(
            furl.furl('/{}/'.format(guid._id)).set(args=extras).url)
    return addon_view_file(auth, node, file_node, version)
Ejemplo n.º 23
0
def send_claim_registered_email(claimer,
                                unclaimed_user,
                                node,
                                throttle=24 * 3600):
    """
    A registered user claiming the unclaimed user account as an contributor to a project.
    Send an email for claiming the account to the referrer and notify the claimer.

    :param claimer: the claimer
    :param unclaimed_user: the user account to claim
    :param node: the project node where the user account is claimed
    :param throttle: the time period in seconds before another claim for the account can be made
    :return:
    :raise: http_status.HTTP_400_BAD_REQUEST
    """

    unclaimed_record = unclaimed_user.get_unclaimed_record(node._primary_key)

    # check throttle
    timestamp = unclaimed_record.get('last_sent')
    if not throttle_period_expired(timestamp, throttle):
        raise HTTPError(
            http_status.HTTP_400_BAD_REQUEST,
            data=dict(
                message_long=
                'User account can only be claimed with an existing user once every 24 hours'
            ))

    # roll the valid token for each email, thus user cannot change email and approve a different email address
    verification_key = generate_verification_key(verification_type='claim')
    unclaimed_record['token'] = verification_key['token']
    unclaimed_record['expires'] = verification_key['expires']
    unclaimed_record['claimer_email'] = claimer.username
    unclaimed_user.save()

    referrer = OSFUser.load(unclaimed_record['referrer_id'])
    claim_url = web_url_for(
        'claim_user_registered',
        uid=unclaimed_user._primary_key,
        pid=node._primary_key,
        token=unclaimed_record['token'],
        _absolute=True,
    )

    # Send mail to referrer, telling them to forward verification link to claimer
    mails.send_mail(
        referrer.username,
        mails.FORWARD_INVITE_REGISTERED,
        user=unclaimed_user,
        referrer=referrer,
        node=node,
        claim_url=claim_url,
        fullname=unclaimed_record['name'],
        can_change_preferences=False,
        osf_contact_email=settings.OSF_CONTACT_EMAIL,
    )
    unclaimed_record['last_sent'] = get_timestamp()
    unclaimed_user.save()

    # Send mail to claimer, telling them to wait for referrer
    mails.send_mail(
        claimer.username,
        mails.PENDING_VERIFICATION_REGISTERED,
        fullname=claimer.fullname,
        referrer=referrer,
        node=node,
        can_change_preferences=False,
        osf_contact_email=settings.OSF_CONTACT_EMAIL,
    )
Ejemplo n.º 24
0
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':
                settings.PREPRINTS_ASSETS + k._id +
                '/square_color_no_transparent.png'
            }
            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)
Ejemplo n.º 25
0
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}
Ejemplo n.º 26
0
def make_url_map(app):
    """Set up all the routes for the OSF app.

    :param app: A Flask/Werkzeug app to bind the rules to.
    """

    # Set default views to 404, using URL-appropriate renderers
    process_rules(app, [
        Rule('/<path:_>', ['get', 'post'], HTTPError(http.NOT_FOUND),
             OsfWebRenderer('', render_mako_string)),
        Rule('/api/v1/<path:_>', ['get', 'post'], HTTPError(http.NOT_FOUND),
             json_renderer),
    ])

    ### GUID ###
    process_rules(app, [
        Rule(
            [
                '/<guid>/',
                '/<guid>/<path:suffix>',
            ],
            ['get', 'post', 'put', 'patch', 'delete'],
            website_views.resolve_guid,
            notemplate,
        ),
        Rule(
            [
                '/api/v1/<guid>/',
                '/api/v1/<guid>/<path:suffix>',
            ],
            ['get', 'post', 'put', 'patch', 'delete'],
            website_views.resolve_guid,
            json_renderer,
        ),
    ])

    # Static files
    process_rules(app, [
        Rule('/favicon.ico', 'get', favicon, json_renderer),
        Rule('/robots.txt', 'get', robots, json_renderer),
    ])

    ### Base ###

    process_rules(app, [
        Rule('/dashboard/', 'get', website_views.dashboard,
             OsfWebRenderer('dashboard.mako')),
        Rule('/reproducibility/', 'get', website_views.reproducibility,
             OsfWebRenderer('', render_mako_string)),
        Rule(
            '/about/',
            'get',
            website_views.redirect_about,
            json_renderer,
        ),
        Rule(
            '/howosfworks/',
            'get',
            website_views.redirect_howosfworks,
            json_renderer,
        ),
        Rule('/faq/', 'get', {}, OsfWebRenderer('public/pages/faq.mako')),
        Rule('/getting-started/', 'get', {},
             OsfWebRenderer('public/pages/getting_started.mako')),
        Rule('/explore/', 'get', {}, OsfWebRenderer('public/explore.mako')),
        Rule(['/messages/', '/help/'], 'get', {},
             OsfWebRenderer('public/comingsoon.mako')),
        Rule(
            '/view/<meeting>/',
            'get',
            conference_views.conference_results,
            OsfWebRenderer('public/pages/meeting.mako'),
        ),
        Rule(
            '/view/<meeting>/plain/',
            'get',
            conference_views.conference_results,
            OsfWebRenderer('public/pages/meeting_plain.mako'),
            endpoint_suffix='__plain',
        ),
        Rule(
            '/api/v1/view/<meeting>/',
            'get',
            conference_views.conference_data,
            json_renderer,
        ),
        Rule(
            '/meetings/',
            'get',
            conference_views.conference_view,
            OsfWebRenderer('public/pages/meeting_landing.mako'),
        ),
        Rule(
            '/presentations/',
            'get',
            conference_views.redirect_to_meetings,
            json_renderer,
        ),
        Rule('/news/', 'get', {}, OsfWebRenderer('public/pages/news.mako')),
    ])

    # Site-wide API routes

    process_rules(app, [
        Rule(
            '/citations/styles/',
            'get',
            citation_views.list_citation_styles,
            json_renderer,
        ),
    ],
                  prefix='/api/v1')

    process_rules(app, [
        Rule(
            [
                '/project/<pid>/<addon>/settings/disable/',
                '/project/<pid>/node/<nid>/<addon>/settings/disable/',
            ],
            'post',
            addon_views.disable_addon,
            json_renderer,
        ),
        Rule(
            '/profile/<uid>/<addon>/settings/',
            'get',
            addon_views.get_addon_user_config,
            json_renderer,
        ),
    ],
                  prefix='/api/v1')

    # OAuth

    process_rules(app, [
        Rule(
            '/oauth/connect/<service_name>/',
            'get',
            oauth_views.oauth_connect,
            json_renderer,
        ),
        Rule(
            '/oauth/callback/<service_name>/',
            'get',
            oauth_views.oauth_callback,
            OsfWebRenderer('util/oauth_complete.mako'),
        ),
    ])

    process_rules(app, [
        Rule(
            [
                '/oauth/accounts/<external_account_id>/',
            ],
            'delete',
            oauth_views.oauth_disconnect,
            json_renderer,
        )
    ],
                  prefix='/api/v1')

    process_rules(app, [
        Rule('/dashboard/get_nodes/', 'get', website_views.get_dashboard_nodes,
             json_renderer),
        Rule([
            '/dashboard/<nid>',
            '/dashboard/',
        ], 'get', website_views.get_dashboard, json_renderer),
    ],
                  prefix='/api/v1')

    ### Metadata ###

    process_rules(app, [
        Rule(
            [
                '/project/<pid>/comments/',
                '/project/<pid>/node/<nid>/comments/',
            ],
            'get',
            project_views.comment.list_comments,
            json_renderer,
        ),
        Rule(
            [
                '/project/<pid>/comments/discussion/',
                '/project/<pid>/node/<nid>/comments/discussion/',
            ],
            'get',
            project_views.comment.comment_discussion,
            json_renderer,
        ),
        Rule(
            [
                '/project/<pid>/comment/',
                '/project/<pid>/node/<nid>/comment/',
            ],
            'post',
            project_views.comment.add_comment,
            json_renderer,
        ),
        Rule(
            [
                '/project/<pid>/comment/<cid>/',
                '/project/<pid>/node/<nid>/comment/<cid>/',
            ],
            'put',
            project_views.comment.edit_comment,
            json_renderer,
        ),
        Rule(
            [
                '/project/<pid>/comment/<cid>/',
                '/project/<pid>/node/<nid>/comment/<cid>/',
            ],
            'delete',
            project_views.comment.delete_comment,
            json_renderer,
        ),
        Rule(
            [
                '/project/<pid>/comment/<cid>/undelete/',
                '/project/<pid>/node/<nid>/comment/<cid>/undelete/',
            ],
            'put',
            project_views.comment.undelete_comment,
            json_renderer,
        ),
        Rule(
            [
                '/project/<pid>/comments/timestamps/',
                '/project/<pid>/node/<nid>/comments/timestamps/',
            ],
            'put',
            project_views.comment.update_comments_timestamp,
            json_renderer,
        ),
        Rule(
            [
                '/project/<pid>/comment/<cid>/report/',
                '/project/<pid>/node/<nid>/comment/<cid>/report/',
            ],
            'post',
            project_views.comment.report_abuse,
            json_renderer,
        ),
        Rule(
            [
                '/project/<pid>/comment/<cid>/unreport/',
                '/project/<pid>/node/<nid>/comment/<cid>/unreport/',
            ],
            'post',
            project_views.comment.unreport_abuse,
            json_renderer,
        ),
        Rule(
            [
                '/project/<pid>/citation/',
                '/project/<pid>/node/<nid>/citation/',
            ],
            'get',
            citation_views.node_citation,
            json_renderer,
        ),
    ],
                  prefix='/api/v1')

    ### Forms ###

    process_rules(app, [
        Rule('/forms/registration/', 'get', website_views.registration_form,
             json_renderer),
        Rule('/forms/signin/', 'get', website_views.signin_form,
             json_renderer),
        Rule('/forms/forgot_password/', 'get',
             website_views.forgot_password_form, json_renderer),
        Rule('/forms/reset_password/', 'get',
             website_views.reset_password_form, json_renderer),
    ],
                  prefix='/api/v1')

    ### Discovery ###

    process_rules(app, [
        Rule('/explore/activity/', 'get', discovery_views.activity,
             OsfWebRenderer('public/pages/active_nodes.mako', trust=False)),
    ])

    ### Auth ###

    # Web

    process_rules(
        app,
        [
            Rule(
                '/confirm/<uid>/<token>/',
                'get',
                auth_views.confirm_email_get,
                # View will either redirect or display error message
                OsfWebRenderer('error.mako', render_mako_string)),
            Rule(
                '/resetpassword/<verification_key>/', ['get', 'post'],
                auth_views.reset_password,
                OsfWebRenderer('public/resetpassword.mako',
                               render_mako_string)),

            # Resend confirmation URL linked to in CAS login page
            Rule('/resend/', ['get', 'post'], auth_views.resend_confirmation,
                 OsfWebRenderer('resend.mako', render_mako_string)),

            # TODO: Remove `auth_register_post`
            Rule('/register/', 'post', auth_views.auth_register_post,
                 OsfWebRenderer('public/login.mako')),
            Rule('/api/v1/register/', 'post', auth_views.register_user,
                 json_renderer),
            Rule(['/login/', '/account/'], 'get', auth_views.auth_login,
                 OsfWebRenderer('public/login.mako')),
            Rule('/login/first/',
                 'get',
                 auth_views.auth_login,
                 OsfWebRenderer('public/login.mako'),
                 endpoint_suffix='__first',
                 view_kwargs={'first': True}),
            Rule('/logout/', 'get', auth_views.auth_logout, notemplate),
            Rule('/forgotpassword/', 'get', auth_views.forgot_password_get,
                 OsfWebRenderer('public/forgot_password.mako')),
            Rule('/forgotpassword/', 'post', auth_views.forgot_password_post,
                 OsfWebRenderer('public/login.mako')),
            Rule(['/midas/', '/summit/', '/accountbeta/', '/decline/'], 'get',
                 auth_views.auth_registerbeta,
                 OsfWebRenderer('', render_mako_string)),
            Rule('/login/connected_tools/', 'get',
                 landing_page_views.connected_tools,
                 OsfWebRenderer('public/login_landing.mako')),
            Rule('/login/enriched_profile/', 'get',
                 landing_page_views.enriched_profile,
                 OsfWebRenderer('public/login_landing.mako')),
        ])

    ### Profile ###

    # Web

    process_rules(
        app,
        [
            Rule('/profile/', 'get', profile_views.profile_view,
                 OsfWebRenderer('profile.mako', trust=False)),
            Rule('/profile/<uid>/', 'get', profile_views.profile_view_id,
                 OsfWebRenderer('profile.mako', trust=False)),
            Rule(["/user/merge/"], 'get', auth_views.merge_user_get,
                 OsfWebRenderer("merge_accounts.mako", trust=False)),
            Rule(["/user/merge/"], 'post', auth_views.merge_user_post,
                 OsfWebRenderer("merge_accounts.mako", trust=False)),
            # Route for claiming and setting email and password.
            # Verification token must be querystring argument
            Rule(['/user/<uid>/<pid>/claim/'], ['get', 'post'],
                 project_views.contributor.claim_user_form,
                 OsfWebRenderer('claim_account.mako', trust=False)),
            Rule(['/user/<uid>/<pid>/claim/verify/<token>/'], ['get', 'post'],
                 project_views.contributor.claim_user_registered,
                 OsfWebRenderer('claim_account_registered.mako', trust=False)),
            Rule(
                '/settings/',
                'get',
                profile_views.user_profile,
                OsfWebRenderer('profile/settings.mako', trust=False),
            ),
            Rule(
                '/settings/account/',
                'get',
                profile_views.user_account,
                OsfWebRenderer('profile/account.mako', trust=False),
            ),
            Rule(
                '/settings/account/password',
                'post',
                profile_views.user_account_password,
                OsfWebRenderer('profile/account.mako', trust=False),
            ),
            Rule(
                '/settings/addons/',
                'get',
                profile_views.user_addons,
                OsfWebRenderer('profile/addons.mako', trust=False),
            ),
            Rule(
                '/settings/notifications/',
                'get',
                profile_views.user_notifications,
                OsfWebRenderer('profile/notifications.mako', trust=False),
            ),

            # TODO: Uncomment once outstanding issues with this feature are addressed
            # Rule(
            #     '/@<twitter_handle>/',
            #     'get',
            #     profile_views.redirect_to_twitter,
            #     OsfWebRenderer('error.mako', render_mako_string, trust=False)
            # ),
        ])

    # API

    process_rules(
        app,
        [
            Rule('/profile/', 'get', profile_views.profile_view,
                 json_renderer),
            Rule('/profile/', 'put', profile_views.update_user, json_renderer),
            Rule('/resend/', 'put', profile_views.resend_confirmation,
                 json_renderer),
            Rule('/profile/<uid>/', 'get', profile_views.profile_view_id,
                 json_renderer),

            # Used by profile.html
            Rule('/profile/<uid>/edit/', 'post', profile_views.edit_profile,
                 json_renderer),
            Rule('/profile/<uid>/public_projects/', 'get',
                 profile_views.get_public_projects, json_renderer),
            Rule('/profile/<uid>/public_components/', 'get',
                 profile_views.get_public_components, json_renderer),
            Rule('/profile/<user_id>/summary/', 'get',
                 profile_views.get_profile_summary, json_renderer),
            Rule('/user/<uid>/<pid>/claim/email/', 'post',
                 project_views.contributor.claim_user_post, json_renderer),
            Rule(
                '/profile/export/',
                'post',
                profile_views.request_export,
                json_renderer,
            ),
            Rule(
                '/profile/deactivate/',
                'post',
                profile_views.request_deactivation,
                json_renderer,
            ),
            Rule(
                [
                    '/profile/gravatar/',
                    '/users/gravatar/',
                    '/profile/gravatar/<size>',
                    '/users/gravatar/<size>',
                ],
                'get',
                profile_views.current_user_gravatar,
                json_renderer,
            ),
            Rule(
                [
                    '/profile/<uid>/gravatar/',
                    '/users/<uid>/gravatar/',
                    '/profile/<uid>/gravatar/<size>',
                    '/users/<uid>/gravatar/<size>',
                ],
                'get',
                profile_views.get_gravatar,
                json_renderer,
            ),

            # Rules for user profile configuration
            Rule('/settings/names/', 'get', profile_views.serialize_names,
                 json_renderer),
            Rule('/settings/names/', 'put', profile_views.unserialize_names,
                 json_renderer),
            Rule('/settings/names/impute/', 'get', profile_views.impute_names,
                 json_renderer),
            Rule(
                [
                    '/settings/social/',
                    '/settings/social/<uid>/',
                ],
                'get',
                profile_views.serialize_social,
                json_renderer,
            ),
            Rule(
                [
                    '/settings/jobs/',
                    '/settings/jobs/<uid>/',
                ],
                'get',
                profile_views.serialize_jobs,
                json_renderer,
            ),
            Rule(
                [
                    '/settings/schools/',
                    '/settings/schools/<uid>/',
                ],
                'get',
                profile_views.serialize_schools,
                json_renderer,
            ),
            Rule([
                '/settings/social/',
                '/settings/social/<uid>/',
            ], 'put', profile_views.unserialize_social, json_renderer),
            Rule([
                '/settings/jobs/',
                '/settings/jobs/<uid>/',
            ], 'put', profile_views.unserialize_jobs, json_renderer),
            Rule([
                '/settings/schools/',
                '/settings/schools/<uid>/',
            ], 'put', profile_views.unserialize_schools, json_renderer),
        ],
        prefix='/api/v1',
    )

    ### Search ###

    # Web

    process_rules(app, [
        Rule('/search/', 'get', {}, OsfWebRenderer('search.mako')),
        Rule('/share/', 'get', {}, OsfWebRenderer('share_search.mako')),
        Rule('/share/registration/', 'get',
             {'register': settings.SHARE_REGISTRATION_URL},
             OsfWebRenderer('share_registration.mako')),
        Rule('/share/help/', 'get', {'help': settings.SHARE_API_DOCS_URL},
             OsfWebRenderer('share_api_docs.mako')),
        Rule('/share_dashboard/', 'get', {},
             OsfWebRenderer('share_dashboard.mako')),
        Rule('/share/atom/', 'get', search_views.search_share_atom,
             xml_renderer),
        Rule('/api/v1/user/search/', 'get', search_views.search_contributor,
             json_renderer),
        Rule(
            '/api/v1/search/node/',
            'post',
            project_views.node.search_node,
            json_renderer,
        ),
    ])

    # API

    process_rules(app, [
        Rule(['/search/', '/search/<type>/'], ['get', 'post'],
             search_views.search_search, json_renderer),
        Rule('/search/projects/', 'get', search_views.search_projects_by_title,
             json_renderer),
        Rule('/share/search/', ['get', 'post'], search_views.search_share,
             json_renderer),
        Rule('/share/stats/', 'get', search_views.search_share_stats,
             json_renderer),
        Rule('/share/providers/', 'get', search_views.search_share_providers,
             json_renderer),
    ],
                  prefix='/api/v1')

    # Project

    # Web

    process_rules(
        app,
        [
            Rule('/', 'get', website_views.index,
                 OsfWebRenderer('index.mako')),
            Rule('/goodbye/', 'get', goodbye, OsfWebRenderer('index.mako')),
            Rule([
                '/project/<pid>/',
                '/project/<pid>/node/<nid>/',
            ], 'get', project_views.node.view_project,
                 OsfWebRenderer('project/project.mako', trust=False)),

            # Create a new subproject/component
            Rule('/project/<pid>/newnode/', 'post',
                 project_views.node.project_new_node, notemplate),

            # # TODO: Add API endpoint for tags
            # Rule('/tags/<tag>/', 'get', project_views.tag.project_tag, OsfWebRenderer('tags.mako')),
            Rule('/api/v1/folder/<nid>', 'post',
                 project_views.node.folder_new_post, json_renderer),
            Rule('/project/new/<pid>/beforeTemplate/', 'get',
                 project_views.node.project_before_template, json_renderer),
            Rule(
                [
                    '/project/<pid>/contributors/',
                    '/project/<pid>/node/<nid>/contributors/',
                ],
                'get',
                project_views.node.node_contributors,
                OsfWebRenderer('project/contributors.mako', trust=False),
            ),
            Rule([
                '/project/<pid>/settings/',
                '/project/<pid>/node/<nid>/settings/',
            ], 'get', project_views.node.node_setting,
                 OsfWebRenderer('project/settings.mako', trust=False)),

            # Permissions
            Rule(
                [
                    '/project/<pid>/permissions/<permissions>/',
                    '/project/<pid>/node/<nid>/permissions/<permissions>/',
                ],
                'post',
                project_views.node.project_set_privacy,
                OsfWebRenderer(
                    'project/project.mako'
                )  # TODO: Should this be notemplate? (post request)
            ),

            ### Logs ###

            # View forks
            Rule([
                '/project/<pid>/forks/',
                '/project/<pid>/node/<nid>/forks/',
            ], 'get', project_views.node.node_forks,
                 OsfWebRenderer('project/forks.mako', trust=False)),

            # Registrations
            Rule([
                '/project/<pid>/register/',
                '/project/<pid>/node/<nid>/register/',
            ], 'get', project_views.register.node_register_page,
                 OsfWebRenderer('project/register.mako', trust=False)),
            Rule([
                '/project/<pid>/register/<template>/',
                '/project/<pid>/node/<nid>/register/<template>/',
            ], 'get', project_views.register.node_register_template_page,
                 OsfWebRenderer('project/register.mako', trust=False)),
            Rule([
                '/project/<pid>/registrations/',
                '/project/<pid>/node/<nid>/registrations/',
            ], 'get', project_views.node.node_registrations,
                 OsfWebRenderer('project/registrations.mako', trust=False)),

            # TODO: Can't create a registration locally, so can't test this one..?
            Rule([
                '/project/<pid>/retraction/',
                '/project/<pid>/node/<nid>/retraction/',
            ], 'get', project_views.register.node_registration_retraction_get,
                 OsfWebRenderer('project/retract_registration.mako',
                                trust=False)),
            Rule(
                '/ids/<category>/<path:value>/',
                'get',
                project_views.register.get_referent_by_identifier,
                notemplate,
            ),

            # Statistics
            Rule(
                [
                    '/project/<pid>/statistics/',
                    '/project/<pid>/node/<nid>/statistics/',
                ],
                'get',
                project_views.node.project_statistics_redirect,
                notemplate,
            ),
            Rule([
                '/project/<pid>/analytics/',
                '/project/<pid>/node/<nid>/analytics/',
            ], 'get', project_views.node.project_statistics,
                 OsfWebRenderer('project/statistics.mako', trust=False)),

            ### Files ###

            # Note: Web endpoint for files view must pass `mode` = `page` to
            # include project view data and JS includes
            # TODO: Start waterbutler to test
            Rule(
                [
                    '/project/<pid>/files/',
                    '/project/<pid>/node/<nid>/files/',
                ],
                'get',
                project_views.file.collect_file_trees,
                OsfWebRenderer('project/files.mako', trust=False),
                view_kwargs={'mode': 'page'},
            ),
            Rule([
                '/project/<pid>/files/<provider>/<path:path>/',
                '/project/<pid>/node/<nid>/files/<provider>/<path:path>/',
            ], 'get', addon_views.addon_view_or_download_file,
                 OsfWebRenderer('project/view_file.mako', trust=False)),
            Rule(
                [
                    # Legacy Addon view file paths
                    '/project/<pid>/<provider>/files/<path:path>/',
                    '/project/<pid>/node/<nid>/<provider>/files/<path:path>/',
                    '/project/<pid>/<provider>/files/<path:path>/download/',
                    '/project/<pid>/node/<nid>/<provider>/files/<path:path>/download/',

                    # Legacy routes for `download_file`
                    '/project/<pid>/osffiles/<fid>/download/',
                    '/project/<pid>/node/<nid>/osffiles/<fid>/download/',

                    # Legacy routes for `view_file`
                    '/project/<pid>/osffiles/<fid>/',
                    '/project/<pid>/node/<nid>/osffiles/<fid>/',

                    # Note: Added these old URLs for backwards compatibility with
                    # hard-coded links.
                    '/project/<pid>/osffiles/download/<fid>/',
                    '/project/<pid>/node/<nid>/osffiles/download/<fid>/',
                    '/project/<pid>/files/<fid>/',
                    '/project/<pid>/node/<nid>/files/<fid>/',
                    '/project/<pid>/files/download/<fid>/',
                    '/project/<pid>/node/<nid>/files/download/<fid>/',

                    # Legacy routes for `download_file_by_version`
                    '/project/<pid>/osffiles/<fid>/version/<vid>/download/',
                    '/project/<pid>/node/<nid>/osffiles/<fid>/version/<vid>/download/',
                    # Note: Added these old URLs for backwards compatibility with
                    # hard-coded links.
                    '/project/<pid>/osffiles/<fid>/version/<vid>/',
                    '/project/<pid>/node/<nid>/osffiles/<fid>/version/<vid>/',
                    '/project/<pid>/osffiles/download/<fid>/version/<vid>/',
                    '/project/<pid>/node/<nid>/osffiles/download/<fid>/version/<vid>/',
                    '/project/<pid>/files/<fid>/version/<vid>/',
                    '/project/<pid>/node/<nid>/files/<fid>/version/<vid>/',
                    '/project/<pid>/files/download/<fid>/version/<vid>/',
                    '/project/<pid>/node/<nid>/files/download/<fid>/version/<vid>/',
                ],
                'get',
                addon_views.addon_view_or_download_file_legacy,
                OsfWebRenderer('project/view_file.mako', trust=False),
            ),
            Rule(
                [
                    # api/v1 Legacy routes for `download_file`
                    '/api/v1/project/<pid>/osffiles/<fid>/',
                    '/api/v1/project/<pid>/node/<nid>/osffiles/<fid>/',
                    '/api/v1/project/<pid>/files/download/<fid>/',
                    '/api/v1/project/<pid>/node/<nid>/files/download/<fid>/',

                    #api/v1 Legacy routes for `download_file_by_version`
                    '/api/v1/project/<pid>/osffiles/<fid>/version/<vid>/',
                    '/api/v1/project/<pid>/node/<nid>/osffiles/<fid>/version/<vid>/',
                    '/api/v1/project/<pid>/files/download/<fid>/version/<vid>/',
                    '/api/v1/project/<pid>/node/<nid>/files/download/<fid>/version/<vid>/',
                ],
                'get',
                addon_views.addon_view_or_download_file_legacy,
                json_renderer),
        ])

    # API

    process_rules(
        app,
        [
            Rule(
                '/email/meeting/',
                'post',
                conference_views.meeting_hook,
                json_renderer,
            ),
            Rule('/mailchimp/hooks/', 'get',
                 profile_views.mailchimp_get_endpoint, json_renderer),
            Rule('/mailchimp/hooks/', 'post',
                 profile_views.sync_data_from_mailchimp, json_renderer),

            # Create project, used by projectCreator.js
            Rule('/project/new/', 'post', project_views.node.project_new_post,
                 json_renderer),
            Rule([
                '/project/<pid>/contributors_abbrev/',
                '/project/<pid>/node/<nid>/contributors_abbrev/',
            ], 'get', project_views.contributor.get_node_contributors_abbrev,
                 json_renderer),
            Rule('/tags/<tag>/', 'get', project_views.tag.project_tag,
                 json_renderer),
            Rule([
                '/project/<pid>/',
                '/project/<pid>/node/<nid>/',
            ], 'get', project_views.node.view_project, json_renderer),
            Rule([
                '/project/<pid>/expand/',
                '/project/<pid>/node/<nid>/expand/',
            ], 'post', project_views.node.expand, json_renderer),
            Rule([
                '/project/<pid>/collapse/',
                '/project/<pid>/node/<nid>/collapse/',
            ], 'post', project_views.node.collapse, json_renderer),
            Rule(
                [
                    '/project/<pid>/pointer/',
                    '/project/<pid>/node/<nid>/pointer/',
                ],
                'get',
                project_views.node.get_pointed,
                json_renderer,
            ),
            Rule(
                [
                    '/project/<pid>/pointer/',
                    '/project/<pid>/node/<nid>/pointer/',
                ],
                'post',
                project_views.node.add_pointers,
                json_renderer,
            ),
            Rule(
                [
                    '/pointer/',
                ],
                'post',
                project_views.node.add_pointer,
                json_renderer,
            ),
            Rule(
                [
                    '/pointers/move/',
                ],
                'post',
                project_views.node.move_pointers,
                json_renderer,
            ),
            Rule(
                [
                    '/project/<pid>/pointer/',
                    '/project/<pid>/node/<nid>pointer/',
                ],
                'delete',
                project_views.node.remove_pointer,
                json_renderer,
            ),
            Rule(
                [
                    '/folder/<pid>/pointer/<pointer_id>',
                ],
                'delete',
                project_views.node.remove_pointer_from_folder,
                json_renderer,
            ),
            Rule(
                [
                    '/folder/<pid>/pointers/',
                ],
                'delete',
                project_views.node.remove_pointers_from_folder,
                json_renderer,
            ),
            Rule(
                [
                    '/folder/<pid>',
                ],
                'delete',
                project_views.node.delete_folder,
                json_renderer,
            ),
            Rule('/folder/', 'put', project_views.node.add_folder,
                 json_renderer),
            Rule([
                '/project/<pid>/get_summary/',
                '/project/<pid>/node/<nid>/get_summary/',
            ], 'get', project_views.node.get_summary, json_renderer),
            Rule([
                '/project/<pid>/get_children/',
                '/project/<pid>/node/<nid>/get_children/',
            ], 'get', project_views.node.get_children, json_renderer),
            Rule(['/project/<pid>/get_folder_pointers/'], 'get',
                 project_views.node.get_folder_pointers, json_renderer),
            Rule([
                '/project/<pid>/get_forks/',
                '/project/<pid>/node/<nid>/get_forks/',
            ], 'get', project_views.node.get_forks, json_renderer),
            Rule([
                '/project/<pid>/get_registrations/',
                '/project/<pid>/node/<nid>/get_registrations/',
            ], 'get', project_views.node.get_registrations, json_renderer),
            Rule('/log/<log_id>/', 'get', project_views.log.get_log,
                 json_renderer),
            Rule([
                '/project/<pid>/log/',
                '/project/<pid>/node/<nid>/log/',
            ], 'get', project_views.log.get_logs, json_renderer),
            Rule([
                '/project/<pid>/get_contributors/',
                '/project/<pid>/node/<nid>/get_contributors/',
            ], 'get', project_views.contributor.get_contributors,
                 json_renderer),
            Rule([
                '/project/<pid>/get_contributors_from_parent/',
                '/project/<pid>/node/<nid>/get_contributors_from_parent/',
            ], 'get', project_views.contributor.get_contributors_from_parent,
                 json_renderer),

            # Reorder contributors
            Rule(
                [
                    '/project/<pid>/contributors/manage/',
                    '/project/<pid>/node/<nid>/contributors/manage/',
                ],
                'POST',
                project_views.contributor.project_manage_contributors,
                json_renderer,
            ),
            Rule([
                '/project/<pid>/get_most_in_common_contributors/',
                '/project/<pid>/node/<nid>/get_most_in_common_contributors/',
            ], 'get',
                 project_views.contributor.get_most_in_common_contributors,
                 json_renderer),
            Rule([
                '/project/<pid>/get_recently_added_contributors/',
                '/project/<pid>/node/<nid>/get_recently_added_contributors/',
            ], 'get',
                 project_views.contributor.get_recently_added_contributors,
                 json_renderer),
            Rule([
                '/project/<pid>/get_editable_children/',
                '/project/<pid>/node/<nid>/get_editable_children/',
            ], 'get', project_views.node.get_editable_children, json_renderer),

            # Private Link
            Rule([
                '/project/<pid>/private_link/',
                '/project/<pid>/node/<nid>/private_link/',
            ], 'post', project_views.node.project_generate_private_link_post,
                 json_renderer),
            Rule([
                '/project/<pid>/private_link/edit/',
                '/project/<pid>/node/<nid>/private_link/edit/',
            ], 'put', project_views.node.project_private_link_edit,
                 json_renderer),
            Rule([
                '/project/<pid>/private_link/',
                '/project/<pid>/node/<nid>/private_link/',
            ], 'delete', project_views.node.remove_private_link,
                 json_renderer),
            Rule([
                '/project/<pid>/private_link/',
                '/project/<pid>/node/<nid>/private_link/',
            ], 'get', project_views.node.private_link_table, json_renderer),

            # Create, using existing project as a template
            Rule([
                '/project/new/<nid>/',
            ], 'post', project_views.node.project_new_from_template,
                 json_renderer),

            # Update
            Rule(
                [
                    '/project/<pid>/',
                    '/project/<pid>/node/<nid>/',
                ],
                'put',
                project_views.node.update_node,
                json_renderer,
            ),

            # Remove
            Rule(
                [
                    '/project/<pid>/',
                    '/project/<pid>/node/<nid>/',
                ],
                'delete',
                project_views.node.component_remove,
                json_renderer,
            ),

            # Reorder components
            Rule('/project/<pid>/reorder_components/', 'post',
                 project_views.node.project_reorder_components, json_renderer),

            # Edit node
            Rule([
                '/project/<pid>/edit/',
                '/project/<pid>/node/<nid>/edit/',
            ], 'post', project_views.node.edit_node, json_renderer),

            # Add / remove tags
            Rule([
                '/project/<pid>/tags/',
                '/project/<pid>/node/<nid>/tags/',
                '/project/<pid>/tags/<tag>/',
                '/project/<pid>/node/<nid>/tags/<tag>/',
            ], 'post', project_views.tag.project_add_tag, json_renderer),
            Rule([
                '/project/<pid>/tags/',
                '/project/<pid>/node/<nid>/tags/',
                '/project/<pid>/tags/<tag>/',
                '/project/<pid>/node/<nid>/tags/<tag>/',
            ], 'delete', project_views.tag.project_remove_tag, json_renderer),

            # Add / remove contributors
            Rule([
                '/project/<pid>/contributors/',
                '/project/<pid>/node/<nid>/contributors/',
            ], 'post', project_views.contributor.project_contributors_post,
                 json_renderer),
            Rule([
                '/project/<pid>/beforeremovecontributors/',
                '/project/<pid>/node/<nid>/beforeremovecontributors/',
            ], 'post',
                 project_views.contributor.project_before_remove_contributor,
                 json_renderer),
            # TODO(sloria): should be a delete request to /contributors/
            Rule([
                '/project/<pid>/removecontributors/',
                '/project/<pid>/node/<nid>/removecontributors/',
            ], 'post', project_views.contributor.project_removecontributor,
                 json_renderer),

            # Forks
            Rule(
                [
                    '/project/<pid>/fork/before/',
                    '/project/<pid>/node/<nid>/fork/before/',
                ],
                'get',
                project_views.node.project_before_fork,
                json_renderer,
            ),
            Rule(
                [
                    '/project/<pid>/fork/',
                    '/project/<pid>/node/<nid>/fork/',
                ],
                'post',
                project_views.node.node_fork_page,
                json_renderer,
            ),
            Rule(
                [
                    '/project/<pid>/pointer/fork/',
                    '/project/<pid>/node/<nid>/pointer/fork/',
                ],
                'post',
                project_views.node.fork_pointer,
                json_renderer,
            ),

            # View forks
            Rule([
                '/project/<pid>/forks/',
                '/project/<pid>/node/<nid>/forks/',
            ], 'get', project_views.node.node_forks, json_renderer),

            # Registrations
            Rule([
                '/project/<pid>/beforeregister/',
                '/project/<pid>/node/<nid>/beforeregister',
            ], 'get', project_views.register.project_before_register,
                 json_renderer),
            Rule([
                '/project/<pid>/register/<template>/',
                '/project/<pid>/node/<nid>/register/<template>/',
            ], 'get', project_views.register.node_register_template_page,
                 json_renderer),
            Rule([
                '/project/<pid>/retraction/',
                '/project/<pid>/node/<nid>/retraction/'
            ], 'post',
                 project_views.register.node_registration_retraction_post,
                 json_renderer),
            Rule([
                '/project/<pid>/register/<template>/',
                '/project/<pid>/node/<nid>/register/<template>/',
            ], 'post', project_views.register.node_register_template_page_post,
                 json_renderer),
            Rule(
                [
                    '/project/<pid>/identifiers/',
                    '/project/<pid>/node/<nid>/identifiers/',
                ],
                'get',
                project_views.register.node_identifiers_get,
                json_renderer,
            ),
            Rule(
                [
                    '/project/<pid>/identifiers/',
                    '/project/<pid>/node/<nid>/identifiers/',
                ],
                'post',
                project_views.register.node_identifiers_post,
                json_renderer,
            ),

            # Statistics
            Rule([
                '/project/<pid>/statistics/',
                '/project/<pid>/node/<nid>/statistics/',
            ], 'get', project_views.node.project_statistics, json_renderer),

            # Permissions
            Rule([
                '/project/<pid>/permissions/<permissions>/',
                '/project/<pid>/node/<nid>/permissions/<permissions>/',
            ], 'post', project_views.node.project_set_privacy, json_renderer),
            Rule([
                '/project/<pid>/permissions/beforepublic/',
                '/project/<pid>/node/<nid>/permissions/beforepublic/',
            ], 'get', project_views.node.project_before_set_public,
                 json_renderer),

            ### Watching ###
            Rule(['/project/<pid>/watch/', '/project/<pid>/node/<nid>/watch/'],
                 'post', project_views.node.watch_post, json_renderer),
            Rule([
                '/project/<pid>/unwatch/', '/project/<pid>/node/<nid>/unwatch/'
            ], 'post', project_views.node.unwatch_post, json_renderer),
            Rule([
                '/project/<pid>/togglewatch/',
                '/project/<pid>/node/<nid>/togglewatch/'
            ], 'post', project_views.node.togglewatch_post, json_renderer),
            Rule(['/watched/logs/'], 'get', website_views.watched_logs_get,
                 json_renderer),

            ### Accounts ###
            Rule(['/user/merge/'], 'post', auth_views.merge_user_post,
                 json_renderer),

            # Combined files
            Rule(
                ['/project/<pid>/files/', '/project/<pid>/node/<nid>/files/'],
                'get',
                project_views.file.collect_file_trees,
                json_renderer,
            ),

            # Endpoint to fetch Rubeus.JS/Hgrid-formatted data
            Rule([
                '/project/<pid>/files/grid/',
                '/project/<pid>/node/<nid>/files/grid/'
            ], 'get', project_views.file.grid_data, json_renderer),

            # Settings
            Rule(
                '/files/auth/',
                'get',
                addon_views.get_auth,
                json_renderer,
            ),
            Rule(
                [
                    '/project/<pid>/waterbutler/logs/',
                    '/project/<pid>/node/<nid>/waterbutler/logs/',
                ],
                'put',
                addon_views.create_waterbutler_log,
                json_renderer,
            ),
            Rule(
                [
                    '/registration/<pid>/callbacks/',
                ],
                'put',
                project_views.register.registration_callbacks,
                json_renderer,
            ),
            Rule(
                '/settings/addons/',
                'post',
                profile_views.user_choose_addons,
                json_renderer,
            ),
            Rule(
                '/settings/notifications/',
                'get',
                profile_views.user_notifications,
                json_renderer,
            ),
            Rule(
                '/settings/notifications/',
                'post',
                profile_views.user_choose_mailing_lists,
                json_renderer,
            ),
            Rule(
                '/subscriptions/',
                'get',
                notification_views.get_subscriptions,
                json_renderer,
            ),
            Rule(
                [
                    '/project/<pid>/subscriptions/',
                    '/project/<pid>/node/<nid>/subscriptions/'
                ],
                'get',
                notification_views.get_node_subscriptions,
                json_renderer,
            ),
            Rule(
                '/subscriptions/',
                'post',
                notification_views.configure_subscription,
                json_renderer,
            ),
            Rule(
                [
                    '/project/<pid>/settings/addons/',
                    '/project/<pid>/node/<nid>/settings/addons/',
                ],
                'post',
                project_views.node.node_choose_addons,
                json_renderer,
            ),
            Rule(
                [
                    '/project/<pid>/settings/comments/',
                    '/project/<pid>/node/<nid>/settings/comments/',
                ],
                'post',
                project_views.node.configure_comments,
                json_renderer,
            ),

            # Invite Users
            Rule([
                '/project/<pid>/invite_contributor/',
                '/project/<pid>/node/<nid>/invite_contributor/'
            ], 'post', project_views.contributor.invite_contributor_post,
                 json_renderer),
        ],
        prefix='/api/v1')

    # Set up static routing for addons
    # NOTE: We use nginx to serve static addon assets in production
    addon_base_path = os.path.abspath('website/addons')
    if settings.DEV_MODE:

        @app.route('/static/addons/<addon>/<path:filename>')
        def addon_static(addon, filename):
            addon_path = os.path.join(addon_base_path, addon, 'static')
            return send_from_directory(addon_path, filename)
Ejemplo n.º 27
0
def check_access(node, auth, action, cas_resp):
    """Verify that user can perform requested action on resource. Raise appropriate
    error code if action cannot proceed.
    """
    permission = permission_map.get(action, None)
    if permission is None:
        raise HTTPError(httplib.BAD_REQUEST)

    if cas_resp:
        if permission == 'read':
            if node.is_public:
                return True
            required_scope = oauth_scopes.CoreScopes.NODE_FILE_READ
        else:
            required_scope = oauth_scopes.CoreScopes.NODE_FILE_WRITE
        if not cas_resp.authenticated \
           or required_scope not in oauth_scopes.normalize_scopes(cas_resp.attributes['accessTokenScope']):
            raise HTTPError(httplib.FORBIDDEN)

    if permission == 'read' and node.can_view(auth):
        return True
    if permission == 'write' and node.can_edit(auth):
        return True

    # Users attempting to register projects with components might not have
    # `write` permissions for all components. This will result in a 403 for
    # all `copyto` actions as well as `copyfrom` actions if the component
    # in question is not public. To get around this, we have to recursively
    # check the node's parent node to determine if they have `write`
    # permissions up the stack.
    # TODO(hrybacki): is there a way to tell if this is for a registration?
    # All nodes being registered that receive the `copyto` action will have
    # `node.is_registration` == True. However, we have no way of telling if
    # `copyfrom` actions are originating from a node being registered.
    # TODO This is raise UNAUTHORIZED for registrations that have not been archived yet
    if action == 'copyfrom' or (action == 'copyto' and node.is_registration):
        parent = node.parent_node
        while parent:
            if parent.can_edit(auth):
                return True
            parent = parent.parent_node

    # Users with the PREREG_ADMIN_TAG should be allowed to download files
    # from prereg challenge draft registrations.
    try:
        prereg_schema = MetaSchema.find_one(
            Q('name', 'eq', 'Prereg Challenge') &
            Q('schema_version', 'eq', 2)
        )
        prereg_draft_registration = DraftRegistration.find(
            Q('branched_from', 'eq', node) &
            Q('registration_schema', 'eq', prereg_schema)
        )

        if action == 'download' and \
                    auth.user is not None and \
                    prereg_draft_registration.count() > 0 and \
                    settings.PREREG_ADMIN_TAG in auth.user.system_tags:
            return True
    except NoResultsFound:
        pass

    raise HTTPError(httplib.FORBIDDEN if auth.user else httplib.UNAUTHORIZED)
Ejemplo n.º 28
0
def register_user(**kwargs):
    """
    Register new user account.
    HTTP Method: POST

    :param-json str email1:
    :param-json str email2:
    :param-json str password:
    :param-json str fullName:
    :param-json str campaign:

    :raises: HTTPError(http.BAD_REQUEST) if validation fails or user already exists
    """

    # Verify that email address match.
    # Note: Both `landing.mako` and `register.mako` already have this check on the form. Users can not submit the form
    # if emails do not match. However, this check should not be removed given we may use the raw api call directly.
    json_data = request.get_json()
    if str(json_data['email1']).lower() != str(json_data['email2']).lower():
        raise HTTPError(
            http.BAD_REQUEST,
            data=dict(message_long='Email addresses must match.')
        )

    # Verify that captcha is valid
    if settings.RECAPTCHA_SITE_KEY and not validate_recaptcha(json_data.get('g-recaptcha-response'), remote_ip=request.remote_addr):
        raise HTTPError(
            http.BAD_REQUEST,
            data=dict(message_long='Invalid Captcha')
        )

    try:
        full_name = request.json['fullName']
        full_name = strip_html(full_name)

        campaign = json_data.get('campaign')
        if campaign and campaign not in campaigns.get_campaigns():
            campaign = None

        accepted_terms_of_service = timezone.now() if json_data.get('acceptedTermsOfService') else None
        user = framework_auth.register_unconfirmed(
            request.json['email1'],
            request.json['password'],
            full_name,
            campaign=campaign,
            accepted_terms_of_service=accepted_terms_of_service
        )
        framework_auth.signals.user_registered.send(user)
    except (ValidationValueError, DuplicateEmailError):
        raise HTTPError(
            http.BAD_REQUEST,
            data=dict(
                message_long=language.ALREADY_REGISTERED.format(
                    email=markupsafe.escape(request.json['email1'])
                )
            )
        )
    except BlacklistedEmailError as e:
        raise HTTPError(
            http.BAD_REQUEST,
            data=dict(message_long=language.BLACKLISTED_EMAIL)
        )
    except ValidationError as e:
        raise HTTPError(
            http.BAD_REQUEST,
            data=dict(message_long=e.message)
        )

    if settings.CONFIRM_REGISTRATIONS_BY_EMAIL:
        send_confirm_email(user, email=user.username)
        message = language.REGISTRATION_SUCCESS.format(email=user.username)
        return {'message': message}
    else:
        return {'message': 'You may now log in.'}
Ejemplo n.º 29
0
def login_and_register_handler(auth,
                               login=True,
                               campaign=None,
                               next_url=None,
                               logout=None):
    """
    Non-view helper to handle `login` and `register` requests.

    :param auth: the auth context
    :param login: `True` if `GET /login`, `False` if `GET /register`
    :param campaign: a target campaign defined in `auth.campaigns`
    :param next_url: the service url for CAS login or redirect url for OSF
    :param logout: used only for `claim_user_registered`
    :return: data object that contains actions for `auth_register` and `auth_login`
    :raises: http.BAD_REQUEST
    """

    # Only allow redirects which are relative root or full domain. Disallows external redirects.
    if next_url and not validate_next_url(next_url):
        raise HTTPError(http.BAD_REQUEST)

    data = {
        'status_code': http.FOUND if login else http.OK,
        'next_url': next_url,
        'campaign': None,
        'must_login_warning': False,
    }

    # login or register with campaign parameter
    if campaign:
        if validate_campaign(campaign):
            # GET `/register` or '/login` with `campaign=institution`
            # unlike other campaigns, institution login serves as an alternative for authentication
            if campaign == 'institution':
                next_url = web_url_for('dashboard', _absolute=True)
                data['status_code'] = http.FOUND
                if auth.logged_in:
                    data['next_url'] = next_url
                else:
                    data['next_url'] = cas.get_login_url(
                        next_url, campaign='institution')
            # for non-institution campaigns
            else:
                destination = next_url if next_url else campaigns.campaign_url_for(
                    campaign)
                if auth.logged_in:
                    # if user is already logged in, go to the campaign landing page
                    data['status_code'] = http.FOUND
                    data['next_url'] = destination
                else:
                    # if user is logged out, go to the osf register page with campaign context
                    if login:
                        # `GET /login?campaign=...`
                        data['next_url'] = web_url_for('auth_register',
                                                       campaign=campaign,
                                                       next=destination)
                    else:
                        # `GET /register?campaign=...`
                        data['campaign'] = campaign
                        if campaigns.is_proxy_login(campaign):
                            data['next_url'] = web_url_for('auth_login',
                                                           next=destination,
                                                           _absolute=True)
                        else:
                            data['next_url'] = destination
        else:
            # invalid campaign
            raise HTTPError(http.BAD_REQUEST)
    # login or register with next parameter
    elif next_url:
        if logout:
            # handle `claim_user_registered`
            data['next_url'] = next_url
            if auth.logged_in:
                # log user out and come back
                data['status_code'] = 'auth_logout'
            else:
                # after logout, land on the register page with "must_login" warning
                data['status_code'] = http.OK
                data['must_login_warning'] = True
        elif auth.logged_in:
            # if user is already logged in, redirect to `next_url`
            data['status_code'] = http.FOUND
            data['next_url'] = next_url
        elif login:
            # `/login?next=next_url`: go to CAS login page with current request url as service url
            data['status_code'] = http.FOUND
            data['next_url'] = cas.get_login_url(request.url)
        else:
            # `/register?next=next_url`: land on OSF register page with request url as next url
            data['status_code'] = http.OK
            data['next_url'] = request.url
    else:
        # `/login/` or `/register/` without any parameter
        if auth.logged_in:
            data['status_code'] = http.FOUND
        data['next_url'] = web_url_for('dashboard', _absolute=True)

    return data
Ejemplo n.º 30
0
def get_target_user(auth, uid=None):
    target = User.load(uid) if uid else auth.user
    if target is None:
        raise HTTPError(http.NOT_FOUND)
    return target