예제 #1
0
def delete_collection(identifier: str):
    """
    Delete a collection.

    Can be deleted only by an owner or user with DATA_MANAGEMENT permissions.

    Args:
        identifier (str): The collection uuid.
    """
    perm_status = utils.req_check_permissions(["DATA_EDIT"])
    if perm_status != 200:
        flask.abort(status=perm_status)

    entry = utils.req_get_entry("collections", identifier)
    if not entry:
        flask.abort(status=404)

    # permission check
    if (not utils.req_has_permission("DATA_MANAGEMENT")
            and flask.g.current_user["_id"] not in entry["editors"]):
        flask.abort(status=403)

    result = utils.req_commit_to_db("collections", "delete",
                                    {"_id": entry["_id"]})
    if not result.log or not result.data:
        flask.abort(status=500)
    return flask.Response(status=200)
예제 #2
0
def gen_new_api_key(identifier: str = None):
    """
    Generate a new API key for the provided or current user.

    Args:
        identifier (str): The user identifier.

    Returns:
        flask.Response: The new API key
    """
    if identifier != flask.g.current_user["_id"]:
        perm_status = utils.req_check_permissions(["USER_MANAGEMENT"])
        if perm_status != 200:
            flask.abort(status=perm_status)

    user_data = utils.req_get_entry("users", identifier)
    if not user_data:
        flask.abort(status=404)

    apikey = utils.gen_api_key()
    new_hash = utils.gen_api_key_hash(apikey.key, apikey.salt)
    new_values = {"api_key": new_hash, "api_salt": apikey.salt}
    user_data.update(new_values)
    result = flask.g.db["users"].update_one({"_id": identifier},
                                            {"$set": new_values})
    if not result.acknowledged:
        flask.current_app.logger.error("Updating API key for user %s failed",
                                       identifier)
        flask.Response(status=500)
    else:
        utils.make_log("user", "edit", "New API key", user_data)

    return utils.response_json({"key": apikey.key})
예제 #3
0
def get_collection(identifier):
    """
    Retrieve the collection with uuid <identifier>.

    Args:
        identifier (str): uuid for the wanted collection

    Returns:
        flask.Request: json structure for the collection

    """
    entry = utils.req_get_entry("collections", identifier)
    if not entry:
        flask.abort(status=404)

    # only show editors if owner/admin
    if not flask.g.current_user or (
            not utils.req_has_permission("DATA_MANAGEMENT")
            and flask.g.current_user["_id"] not in entry["editors"]):
        flask.current_app.logger.debug(
            "Not allowed to access editors field %s", flask.g.current_user)
        del entry["editors"]
    else:
        entry["editors"] = utils.user_uuid_data(entry["editors"], flask.g.db)

    # return {_id, _title} for datasets
    entry["datasets"] = [
        flask.g.db.datasets.find_one({"_id": dataset}, {"title": 1})
        for dataset in entry["datasets"]
    ]

    return utils.response_json({"collection": entry})
예제 #4
0
def get_user_data(identifier: str):
    """
    Get information about a user.

    Args:
        identifier (str): The user identifier.

    Returns:
        flask.Response: Information about the user as json.
    """
    perm_status = utils.req_check_permissions(["USER_MANAGEMENT"])
    if perm_status != 200:
        flask.abort(status=perm_status)

    user_info = utils.req_get_entry("users", identifier)
    if not user_info:
        flask.abort(status=404)

    # The hash and salt should never leave the system
    del user_info["api_key"]
    del user_info["api_salt"]

    user_info["permissions"] = utils.prepare_permissions(
        user_info["permissions"])

    return utils.response_json({"user": user_info})
예제 #5
0
def delete_dataset(identifier: str):
    """
    Delete a dataset.

    Can be deleted only by editors or user with DATA_MANAGEMENT permissions.

    Args:
        identifier (str): The dataset uuid.
    """
    perm_status = utils.req_check_permissions(["DATA_EDIT"])
    if perm_status != 200:
        flask.abort(status=perm_status)

    ds = utils.req_get_entry("datasets", identifier)
    if not ds:
        flask.abort(status=404)

    # permission check
    order = flask.g.db["orders"].find_one({"datasets": ds["_id"]})
    if not order:
        flask.current_app.logger.error("Dataset without parent order: %s", ds["_id"])
        flask.current_app.logger.error(ds)
    # permission check
    if (
        not utils.req_has_permission("DATA_MANAGEMENT")
        and flask.g.current_user["_id"] not in order["editors"]
    ):
        flask.abort(status=403)

    result = utils.req_commit_to_db("datasets", "delete", {"_id": ds["_id"]})
    if not result.log or not result.data:
        flask.abort(status=500)

    collections = list(flask.g.db["collections"].find({"datasets": ds["_id"]}))
    flask.g.db["collections"].update_many({}, {"$pull": {"datasets": ds["_id"]}})
    for collection in collections:
        collection["datasets"] = [collection["datasets"].remove(ds["_id"])]
        utils.req_make_log_new(
            data_type="collection",
            action="edit",
            comment="Dataset deleted",
            data=collection,
        )

    flask.g.db["orders"].update_many({}, {"$pull": {"datasets": ds["_id"]}})
    order["datasets"].remove(ds["_id"])
    utils.req_make_log_new(
        data_type="order",
        action="edit",
        comment="Dataset deleted",
        data=order,
    )

    return flask.Response(status=200)
예제 #6
0
def add_dataset(identifier: str):  # pylint: disable=too-many-branches
    """
    Add a dataset to the given order.

    Args:
        identifier (str): The order to add the dataset to.
    """
    order = utils.req_get_entry("orders", identifier)
    if not order:
        flask.abort(status=404)

    if (not utils.req_has_permission("DATA_MANAGEMENT")
            and flask.g.current_user["_id"] not in order["editors"]):
        flask.abort(status=403)

    new_dataset = structure.dataset()

    jsondata = flask.request.json
    if not jsondata or "dataset" not in jsondata or not isinstance(
            jsondata["dataset"], dict):
        flask.abort(status=400)
    indata = jsondata["dataset"]

    validation = utils.basic_check_indata(indata, new_dataset, ["_id"])
    if not validation.result:
        flask.abort(status=validation.status)

    indata = utils.prepare_for_db(indata)

    new_dataset.update(indata)

    ds_result = utils.req_commit_to_db("datasets", "add", new_dataset)
    if not ds_result.log or not ds_result.data:
        flask.abort(status=500)

    order_result = flask.g.db["orders"].update_one(
        {"_id": order["_id"]}, {"$push": {
            "datasets": new_dataset["_id"]
        }})
    if not order_result.acknowledged:
        flask.current_app.logger.error("Failed to add dataset %s to order %s",
                                       new_dataset["_id"], order["_id"])
        flask.abort(status=500)
    order["datasets"].append(new_dataset["_id"])
    utils.req_make_log_new(
        data_type="order",
        action="edit",
        comment="Dataset added",
        data=order,
    )

    return utils.response_json({"_id": ds_result.ins_id})
예제 #7
0
def update_order(identifier: str):  # pylint: disable=too-many-branches
    """
    Update an existing order.

    Args:
        identifier (str): Order uuid.

    Returns:
        flask.Response: Status code of the request.
    """
    order = utils.req_get_entry("orders", identifier)
    if not order:
        flask.abort(status=404)

    # permission check
    if (not utils.req_has_permission("DATA_MANAGEMENT")
            and flask.g.current_user["_id"] not in order["editors"]):
        flask.abort(status=403)

    jsondata = flask.request.json
    if not jsondata or "order" not in jsondata or not isinstance(
            jsondata["order"], dict):
        flask.abort(status=400)
    indata = jsondata["order"]

    validation = utils.basic_check_indata(indata, order, ["_id", "datasets"])
    if not validation.result:
        flask.abort(status=validation.status)

    # DATA_EDIT may not delete itself from editors
    if (not utils.req_has_permission("DATA_MANAGEMENT")
            and indata.get("editors")
            and str(flask.g.current_user["_id"]) not in indata["editors"]):
        flask.abort(status=400)

    # convert all incoming uuids to uuid.UUID
    indata = utils.prepare_for_db(indata)

    is_different = False
    for field in indata:
        if indata[field] != order[field]:
            is_different = True
            break

    order.update(indata)

    if indata and is_different:
        result = utils.req_commit_to_db("orders", "edit", order)
        if not result.log or not result.data:
            flask.abort(status=500)

    return flask.Response(status=200)
예제 #8
0
def update_dataset(identifier):
    """
    Update a dataset with new values.

    Args:
        identifier (str): uuid for the wanted dataset

    Returns:
        flask.Response: success: 200, failure: 400
    """
    perm_status = utils.req_check_permissions(["DATA_EDIT"])
    if perm_status != 200:
        flask.abort(status=perm_status)

    dataset = utils.req_get_entry("datasets", identifier)
    if not dataset:
        flask.abort(status=404)
    # permissions
    order = flask.g.db["orders"].find_one({"datasets": dataset["_id"]})
    if (
        not utils.req_has_permission("DATA_MANAGEMENT")
        and flask.g.current_user["_id"] not in order["editors"]
    ):
        flask.abort(status=403)

    jsondata = flask.request.json
    if not jsondata or "dataset" not in jsondata or not isinstance(jsondata["dataset"], dict):
        flask.abort(status=400)
    indata = jsondata["dataset"]

    validation = utils.basic_check_indata(indata, dataset, prohibited=("_id"))
    if not validation.result:
        flask.abort(status=validation.status)

    indata = utils.prepare_for_db(indata)

    is_different = False
    for field in indata:
        if indata[field] != dataset[field]:
            is_different = True
            break

    dataset.update(indata)

    if indata and is_different:
        result = utils.req_commit_to_db("datasets", "edit", dataset)
        if not result.log or not result.data:
            flask.abort(status=500)

    return flask.Response(status=200)
예제 #9
0
def update_user_info(identifier: str):
    """
    Update the information about a user.

    Requires USER_MANAGEMENT.

    Args:
        identifier (str): The uuid of the user to modify.

    Returns:
        flask.Response: Response code.
    """
    perm_status = utils.req_check_permissions(["USER_MANAGEMENT"])
    if perm_status != 200:
        flask.abort(status=perm_status)

    user_data = utils.req_get_entry("users", identifier)
    if not user_data:
        flask.abort(status=404)

    jsondata = flask.request.json
    if not jsondata.get("user") or not isinstance(jsondata["user"], dict):
        flask.abort(status=400)
    indata = jsondata["user"]

    validation = utils.basic_check_indata(
        indata, user_data, ("_id", "api_key", "api_salt", "auth_ids"))
    if not validation.result:
        flask.abort(status=validation.status)

    if "email" in indata:
        old_user = flask.g.db["users"].find_one({"email": indata["email"]})
        if old_user and old_user.get("_id") != user_data["_id"]:
            flask.current_app.logger.debug("User already exists")
            flask.abort(status=409)

    # Avoid "updating" and making log if there are no changes
    is_different = False
    for field in indata:
        if indata[field] != user_data[field]:
            is_different = True
            break
    user_data.update(indata)

    if is_different:
        result = utils.req_commit_to_db("users", "edit", user_data)
        if not result.log or not result.data:
            flask.abort(status=500)

    return flask.Response(status=200)
예제 #10
0
def delete_order(identifier: str):
    """
    Delete the order with the given identifier.

    Returns:
        flask.Response: Status code
    """
    entry = utils.req_get_entry("orders", identifier)
    if not entry:
        flask.abort(status=404)

    # permission check
    if (not utils.req_has_permission("DATA_MANAGEMENT")
            and flask.g.current_user["_id"] not in entry["editors"]):
        flask.abort(status=403)

    for dataset_uuid in entry["datasets"]:
        result = utils.req_commit_to_db("datasets", "delete",
                                        {"_id": dataset_uuid})
        if not result.log or not result.data:
            flask.abort(status=500)
    # delete dataset references in all collections
    collections = list(flask.g.db["collections"].find(
        {"datasets": {
            "$in": entry["datasets"]
        }}))
    flask.g.db["collections"].update_many(
        {}, {"$pull": {
            "datasets": {
                "$in": entry["datasets"]
            }
        }})
    for collection in collections:
        collection["datasets"] = [
            ds for ds in collection["datasets"] if ds not in entry["datasets"]
        ]
        utils.req_make_log_new(
            data_type="collection",
            action="edit",
            comment="Order deleted",
            data=collection,
        )

    result = utils.req_commit_to_db("orders", "delete", {"_id": entry["_id"]})
    if not result.log or not result.data:
        flask.abort(status=500)
    return flask.Response(status=200)
예제 #11
0
def get_dataset_log(identifier: str = None):
    """
    Get change logs for the user entry with uuid ``identifier``.

    Can be accessed by editors with DATA_EDIT and admin (DATA_MANAGEMENT).

    Logs for deleted datasets cannot be accessed.

    Args:
        identifier (str): The uuid of the dataset.

    Returns:
        flask.Response: Logs as json.
    """
    perm_status = utils.req_check_permissions(["DATA_EDIT"])
    if perm_status != 200:
        flask.abort(status=perm_status)

    dataset = utils.req_get_entry("datasets", identifier)
    if not dataset:
        flask.abort(status=404)

    order_data = flask.g.db["orders"].find_one({"datasets": dataset["_id"]})
    if not order_data:
        flask.current_app.logger.error("Dataset without parent order: %s", dataset["_id"])
        flask.abort(500)

    if (
        not utils.req_has_permission("DATA_MANAGEMENT")
        and flask.g.current_user["_id"] not in order_data["editors"]
    ):
        flask.abort(403)

    dataset_logs = list(
        flask.g.db["logs"].find({"data_type": "dataset", "data._id": dataset["_id"]})
    )
    for log in dataset_logs:
        del log["data_type"]

    utils.incremental_logs(dataset_logs)

    return utils.response_json(
        {"entry_id": dataset["_id"], "data_type": "dataset", "logs": dataset_logs}
    )
예제 #12
0
def get_collection_log(identifier: str = None):
    """
    Get change logs for the collection matching ``identifier``.

    Can be accessed by editors (with DATA_EDIT) and admin (DATA_MANAGEMENT).

    Deleted entries cannot be accessed.

    Args:
        identifier (str): The uuid of the collection.

    Returns:
        flask.Response: Logs as json.
    """
    perm_status = utils.req_check_permissions(["DATA_EDIT"])
    if perm_status != 200:
        flask.abort(status=perm_status)

    collection = utils.req_get_entry("collections", identifier)
    if not collection:
        flask.abort(status=404)
    if (not utils.req_has_permission("DATA_MANAGEMENT")
            and flask.g.current_user["_id"] not in collection["editors"]):
        flask.abort(403)

    collection_logs = list(flask.g.db["logs"].find({
        "data_type":
        "collection",
        "data._id":
        collection["_id"]
    }))

    for log in collection_logs:
        del log["data_type"]

    utils.incremental_logs(collection_logs)

    return utils.response_json({
        "entry_id": collection["_id"],
        "data_type": "collection",
        "logs": collection_logs,
    })
예제 #13
0
def get_order(identifier):
    """
    Retrieve the order with the provided uuid.

    ``order['datasets']`` is returned as ``[{_id, title}, ...]``.

    Args:
        identifier (str): Uuid for the wanted order.

    Returns:
        flask.Response: JSON structure for the order.
    """
    entry = utils.req_get_entry("orders", identifier)
    if not entry:
        flask.abort(status=404)
    if not (utils.req_has_permission("DATA_MANAGEMENT")
            or flask.g.current_user["_id"] in entry["editors"]):
        flask.abort(status=403)

    prepare_order_response(entry, flask.g.db)

    return utils.response_json({"order": entry})
예제 #14
0
def get_order_logs(identifier):
    """
    List changes to the dataset.

    Logs will be sorted chronologically.

    The ``data`` in each log will be trimmed to only show the changed fields.

    Args:
        identifier (str): Uuid for the wanted order.

    Returns:
        flask.Response: Json structure for the logs.
    """
    entry = utils.req_get_entry("orders", identifier)
    if not entry:
        flask.abort(status=404)
    if (not utils.req_has_permission("DATA_MANAGEMENT")
            and flask.g.current_user["_id"] not in entry["editors"]):
        flask.abort(status=403)

    order_logs = list(flask.g.db["logs"].find({
        "data_type": "order",
        "data._id": entry["_id"]
    }))
    if not order_logs:
        flask.abort(status=404)

    for log in order_logs:
        del log["data_type"]

    utils.incremental_logs(order_logs)

    return utils.response_json({
        "entry_id": entry["_id"],
        "data_type": "order",
        "logs": order_logs
    })
예제 #15
0
def build_dataset_info(identifier: str):
    """
    Query for a dataset from the database.

    Args:
        identifier (str): The uuid of the dataset.

    Returns:
        dict: The prepared dataset entry.
    """
    dataset = utils.req_get_entry("datasets", identifier)
    if not dataset:
        return None
    order = flask.g.db["orders"].find_one({"datasets": dataset["_id"]})
    if flask.g.current_user:
        curr_user = flask.g.current_user["_id"]
    else:
        curr_user = None

    if utils.req_has_permission("DATA_MANAGEMENT") or curr_user in order["editors"]:
        dataset["order"] = {"_id": order["_id"], "title": order["title"]}
    dataset["related"] = list(
        flask.g.db["datasets"].find({"_id": {"$in": order["datasets"]}}, {"title": 1})
    )
    dataset["related"].remove({"_id": dataset["_id"], "title": dataset["title"]})
    dataset["collections"] = list(
        flask.g.db["collections"].find({"datasets": dataset["_id"]}, {"title": 1})
    )
    for field in ("editors", "generators", "authors"):
        if field == "editors" and (
            not utils.req_has_permission("DATA_MANAGEMENT") and curr_user not in order[field]
        ):
            continue
        dataset[field] = utils.user_uuid_data(order[field], flask.g.db)

    dataset["organisation"] = utils.user_uuid_data(order["organisation"], flask.g.db)
    dataset["organisation"] = dataset["organisation"][0] if dataset["organisation"] else ""
    return dataset
예제 #16
0
def delete_user(identifier: str):
    """
    Delete a user.

    Args:
        identifier (str): The user identifier.

    Returns:
        flask.Response: Response code.
    """
    perm_status = utils.req_check_permissions(["USER_MANAGEMENT"])
    if perm_status != 200:
        flask.abort(status=perm_status)

    user_info = utils.req_get_entry("users", identifier)
    if not user_info:
        flask.abort(status=404)

    result = utils.req_commit_to_db("users", "delete", {"_id": identifier})
    if not result.log or not result.data:
        flask.abort(status=500)

    return flask.Response(status=200)
예제 #17
0
def update_collection(identifier):
    """
    Update a collection.

    Args:
        identifier (str): The collection uuid.

    Returns:
        flask.Response: Status code.
    """
    perm_status = utils.req_check_permissions(["DATA_EDIT"])
    if perm_status != 200:
        flask.abort(status=perm_status)

    collection = utils.req_get_entry("collections", identifier)
    if not collection:
        flask.abort(status=404)

    jsondata = flask.request.json
    if not jsondata or "collection" not in jsondata or not isinstance(
            jsondata["collection"], dict):
        flask.abort(status=400)
    indata = jsondata["collection"]

    # permission check
    if (not utils.req_has_permission("DATA_MANAGEMENT")
            and flask.g.current_user["_id"] not in collection["editors"]):
        flask.current_app.logger.debug(
            "Unauthorized update attempt (collection %s, user %s)",
            collection["_id"],
            flask.g.current_user["_id"],
        )
        flask.abort(status=403)

    # indata validation
    validation = utils.basic_check_indata(indata,
                                          collection,
                                          prohibited=["_id"])
    if not validation.result:
        flask.abort(status=validation.status)

    # DATA_EDIT may not delete itself from editors
    if (not utils.req_has_permission("DATA_MANAGEMENT")
            and indata.get("editors")
            and str(flask.g.current_user["_id"]) not in indata["editors"]):
        flask.abort(status=400)

    indata = utils.prepare_for_db(indata)

    is_different = False
    for field in indata:
        if indata[field] != collection[field]:
            is_different = True
            break

    if indata and is_different:
        collection.update(indata)
        result = utils.req_commit_to_db("collections", "edit", collection)
        if not result.log or not result.data:
            flask.abort(status=500)

    return flask.Response(status=200)