Example #1
0
    def check_authz(self, action):
        if not self.index_document.get("authz"):
            raise ValueError("index record missing `authz`")

        return flask.current_app.arborist.auth_request(
            jwt=get_jwt(),
            service="fence",
            methods=action,
            resources=self.index_document["authz"],
        )
Example #2
0
    def check_authz(self, action):
        if not self.index_document.get("authz"):
            raise ValueError("index record missing `authz`")

        request = {"user": {"token": get_jwt()}, "requests": []}
        for resource in self.index_document["authz"]:
            request["requests"].append({
                "resource": resource,
                "action": {
                    "service": "fence",
                    "method": action
                }
            })

        return flask.current_app.arborist.auth_request(request)
Example #3
0
    def index_document(self):
        """
        Get the record from indexd for this index.

        Return:
            dict:
                response from indexd (the contents of the record), containing ``guid``
                and ``url``
        """
        index_url = self.indexd.rstrip("/") + "/index/blank/"
        params = {"uploader": self.uploader, "file_name": self.file_name}

        # if attempting to set record's authz field, need to pass token
        # through
        if self.authz:
            params["authz"] = self.authz
            token = get_jwt()

            auth = None
            headers = {"Authorization": f"bearer {token}"}
            logger.info("passing users authorization header to create blank record")
        else:
            logger.info("using indexd basic auth to create blank record")
            auth = (config["INDEXD_USERNAME"], config["INDEXD_PASSWORD"])
            headers = {}

        indexd_response = requests.post(
            index_url, json=params, headers=headers, auth=auth
        )
        if indexd_response.status_code not in [200, 201]:
            try:
                data = indexd_response.json()
            except ValueError:
                data = indexd_response.text
            self.logger.error(
                "could not create new record in indexd; got response: {}".format(data)
            )
            raise InternalError(
                "received error from indexd trying to create blank record"
            )
        document = indexd_response.json()
        guid = document["did"]
        self.logger.info(
            "created blank index record with GUID {} for upload".format(guid)
        )
        return document
Example #4
0
    def check_authz(self, action):
        if not self.index_document.get("authz"):
            raise ValueError("index record missing `authz`")

        logger.debug(
            f"authz check can user {action} on {self.index_document['authz']} for fence?"
        )

        try:
            token = get_jwt()
        except Unauthorized:
            #  get_jwt raises an Unauthorized error when user is anonymous (no
            #  availble token), so to allow anonymous users possible access to
            #  public data, we still make the request to Arborist
            token = None

        return flask.current_app.arborist.auth_request(
            jwt=token,
            service="fence",
            methods=action,
            resources=self.index_document["authz"],
        )
Example #5
0
def delete_data_file(file_id):
    """
    Delete all the locations for a data file which was uploaded to bucket storage from
    indexd.
    If the data file has authz matching the user's permissions, delete it.
    If the data file has no authz, then the deleting user should match the uploader
    field on the record in indexd.

    Args:
        file_id (str): GUID of file to delete
    """
    record = IndexedFile(file_id)

    authz = record.index_document.get("authz")
    has_correct_authz = None
    if authz:
        logger.debug(
            "Trying to ask arborist if user can delete in fence for {}".format(authz)
        )
        has_correct_authz = flask.current_app.arborist.auth_request(
            jwt=get_jwt(), service="fence", methods="delete", resources=authz
        )

        # If authz is not empty, use *only* arborist to check if user can delete
        # Don't fall back on uploader -- this prevents users from escalating from edit to
        # delete permissions by changing the uploader field to their own username
        # (b/c users only have edit access through arborist/authz)
        if has_correct_authz:
            logger.info("Deleting record and files for {}".format(file_id))
            message, status_code = record.delete_files(delete_all=True)
            if str(status_code)[0] != "2":
                return flask.jsonify({"message": message}), status_code

            try:
                return record.delete()
            except Exception as e:
                logger.error(e)
                return (
                    flask.jsonify(
                        {"message": "There was an error deleting this index record."}
                    ),
                    500,
                )
        else:
            return (
                flask.jsonify(
                    {
                        "message": "You do not have arborist permissions to delete this file."
                    }
                ),
                403,
            )

    # If authz is empty: use uploader == user to see if user can delete.
    uploader_mismatch_error_message = "You cannot delete this file because the uploader field indicates it does not belong to you."
    uploader = record.index_document.get("uploader")
    if not uploader:
        return (
            flask.jsonify({"message": uploader_mismatch_error_message}),
            403,
        )
    if current_token["context"]["user"]["name"] != uploader:
        return (
            flask.jsonify({"message": uploader_mismatch_error_message}),
            403,
        )
    logger.info("deleting record and files for {}".format(file_id))

    message, status_code = record.delete_files(delete_all=True)
    if str(status_code)[0] != "2":
        return flask.jsonify({"message": message}), status_code

    try:
        return record.delete()
    except Exception as e:
        logger.error(e)
        return (
            flask.jsonify(
                {"message": "There was an error deleting this index record."}
            ),
            500,
        )
Example #6
0
def upload_data_file():
    """
    Return a presigned URL for use with uploading a data file.

    See the documentation on the entire flow here for more info:

        https://github.com/uc-cdis/cdis-wiki/tree/master/dev/gen3/data_upload

    """
    # make new record in indexd, with just the `uploader` field (and a GUID)
    params = flask.request.get_json()
    if not params:
        raise UserError("wrong Content-Type; expected application/json")

    if "file_name" not in params:
        raise UserError("missing required argument `file_name`")

    authorized = False
    authz_err_msg = "Auth error when attempting to get a presigned URL for upload. User must have '{}' access on '{}'."

    authz = params.get("authz")
    uploader = None

    if authz:
        # if requesting an authz field, using new authorization method which doesn't
        # rely on uploader field, so clear it out
        uploader = ""
        authorized = flask.current_app.arborist.auth_request(
            jwt=get_jwt(),
            service="fence",
            methods=["create", "write-storage"],
            resources=authz,
        )
        if not authorized:
            logger.error(authz_err_msg.format("create' and 'write-storage", authz))
    else:
        # no 'authz' was provided, so fall back on 'file_upload' logic
        authorized = flask.current_app.arborist.auth_request(
            jwt=get_jwt(),
            service="fence",
            methods=["file_upload"],
            resources=["/data_file"],
        )
        if not authorized:
            logger.error(authz_err_msg.format("file_upload", "/data_file"))

    if not authorized:
        raise Forbidden(
            "You do not have access to upload data. You either need "
            "general file uploader permissions or create and write-storage permissions "
            "on the authz resources you specified (if you specified any)."
        )

    blank_index = BlankIndex(
        file_name=params["file_name"], authz=params.get("authz"), uploader=uploader
    )
    default_expires_in = flask.current_app.config.get("MAX_PRESIGNED_URL_TTL", 3600)

    expires_in = get_valid_expiration(
        params.get("expires_in"),
        max_limit=default_expires_in,
        default=default_expires_in,
    )

    response = {
        "guid": blank_index.guid,
        "url": blank_index.make_signed_url(params["file_name"], expires_in=expires_in),
    }

    return flask.jsonify(response), 201