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