From beec6292aa15ffc48bc4cf0da549309ee102e4b3 Mon Sep 17 00:00:00 2001 From: Niklas Date: Tue, 3 Sep 2024 15:32:12 +0200 Subject: [PATCH 1/3] added /delete to clients api --- fedn/network/api/v1/client_routes.py | 44 +++++++++++++++++++ .../storage/statestore/stores/client_store.py | 12 ++++- .../storage/statestore/stores/store.py | 3 +- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/fedn/network/api/v1/client_routes.py b/fedn/network/api/v1/client_routes.py index 8fa13febe..d8cc632fd 100644 --- a/fedn/network/api/v1/client_routes.py +++ b/fedn/network/api/v1/client_routes.py @@ -368,3 +368,47 @@ def get_client(id: str): return jsonify({"message": f"Entity with id: {id} not found"}), 404 except Exception: return jsonify({"message": "An unexpected error occurred"}), 500 + +# delete client +@bp.route("/", methods=["DELETE"]) +@jwt_auth_required(role="admin") +def delete_client(id: str): + """Delete client + Deletes a client based on the provided id. + --- + tags: + - Clients + parameters: + - name: id + in: path + required: true + type: string + description: The id of the client + responses: + 200: + description: The client was deleted + 404: + description: The client was not found + schema: + type: object + properties: + message: + type: string + 500: + description: An error occurred + schema: + type: object + properties: + message: + type: string + """ + try: + result: bool = client_store.delete(id) + + msg = "Client deleted" if result else "Client not deleted" + + return jsonify({"message": msg}), 200 + except EntityNotFound: + return jsonify({"message": f"Entity with id: {id} not found"}), 404 + except Exception: + return jsonify({"message": "An unexpected error occurred"}), 500 diff --git a/fedn/network/storage/statestore/stores/client_store.py b/fedn/network/storage/statestore/stores/client_store.py index 6d7d5865e..51f77023c 100644 --- a/fedn/network/storage/statestore/stores/client_store.py +++ b/fedn/network/storage/statestore/stores/client_store.py @@ -2,10 +2,13 @@ from typing import Any, Dict, List, Tuple import pymongo +from bson import ObjectId from pymongo.database import Database from fedn.network.storage.statestore.stores.store import Store +from .shared import EntityNotFound + class Client: def __init__(self, id: str, name: str, combiner: str, combiner_preferred: str, ip: str, status: str, updated_at: str, last_seen: datetime): @@ -53,7 +56,14 @@ def add(self, item: Client)-> Tuple[bool, Any]: raise NotImplementedError("Add not implemented for ClientStore") def delete(self, id: str) -> bool: - raise NotImplementedError("Delete not implemented for ClientStore") + kwargs = { "_id": ObjectId(id) } if ObjectId.is_valid(id) else { "client_id": id } + + document = self.database[self.collection].find_one(kwargs) + + if document is None: + raise EntityNotFound(f"Entity with (id | client_id) {id} not found") + + return super().delete(document["_id"]) def list(self, limit: int, skip: int, sort_key: str, sort_order=pymongo.DESCENDING, use_typing: bool = False, **kwargs) -> Dict[int, List[Client]]: """List entities diff --git a/fedn/network/storage/statestore/stores/store.py b/fedn/network/storage/statestore/stores/store.py index f1175c9f7..eb9d8b1bb 100644 --- a/fedn/network/storage/statestore/stores/store.py +++ b/fedn/network/storage/statestore/stores/store.py @@ -51,7 +51,8 @@ def add(self, item: T) -> Tuple[bool, Any]: return False, str(e) def delete(self, id: str) -> bool: - pass + result = self.database[self.collection].delete_one({"_id": ObjectId(id)}) + return result.deleted_count == 1 def list(self, limit: int, skip: int, sort_key: str, sort_order=pymongo.DESCENDING, use_typing: bool = False, **kwargs) -> Dict[int, List[T]]: """List entities From f4c22f5011d7754b6e4a24b6d0e72f6d4c27f0c9 Mon Sep 17 00:00:00 2001 From: Niklas Date: Tue, 3 Sep 2024 15:32:30 +0200 Subject: [PATCH 2/3] added /delete to combiners api --- fedn/network/api/v1/combiner_routes.py | 43 +++++++++++++++++++ .../statestore/stores/combiner_store.py | 12 +++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/fedn/network/api/v1/combiner_routes.py b/fedn/network/api/v1/combiner_routes.py index 9210a7e30..2ca547716 100644 --- a/fedn/network/api/v1/combiner_routes.py +++ b/fedn/network/api/v1/combiner_routes.py @@ -339,3 +339,46 @@ def get_combiner(id: str): return jsonify({"message": f"Entity with id: {id} not found"}), 404 except Exception: return jsonify({"message": "An unexpected error occurred"}), 500 + +@bp.route("/", methods=["DELETE"]) +@jwt_auth_required(role="admin") +def delete_combiner(id: str): + """Delete combiner + Deletes a combiner based on the provided id. + --- + tags: + - Combiners + parameters: + - name: id + in: path + required: true + type: string + description: The id of the combiner + responses: + 200: + description: The combiner was deleted + 404: + description: The combiner was not found + schema: + type: object + properties: + error: + type: string + 500: + description: An error occurred + schema: + type: object + properties: + message: + type: string + """ + try: + result: bool = combiner_store.delete(id) + msg = "Combiner deleted" if result else "Combiner not deleted" + + return jsonify({"message": msg}), 200 + except EntityNotFound: + return jsonify({"message": f"Entity with id: {id} not found"}), 404 + except Exception as e: + # return jsonify({"message": "An unexpected error occurred"}), 500 + return jsonify({"message": e}), 500 diff --git a/fedn/network/storage/statestore/stores/combiner_store.py b/fedn/network/storage/statestore/stores/combiner_store.py index 28f54aa6c..5fceea1b7 100644 --- a/fedn/network/storage/statestore/stores/combiner_store.py +++ b/fedn/network/storage/statestore/stores/combiner_store.py @@ -86,7 +86,17 @@ def add(self, item: Combiner)-> Tuple[bool, Any]: raise NotImplementedError("Add not implemented for CombinerStore") def delete(self, id: str) -> bool: - raise NotImplementedError("Delete not implemented for CombinerStore") + if(ObjectId.is_valid(id)): + kwargs = { "_id": ObjectId(id)} + else: + return False + + document = self.database[self.collection].find_one(kwargs) + + if document is None: + raise EntityNotFound(f"Entity with (id) {id} not found") + + return super().delete(document["_id"]) def list(self, limit: int, skip: int, sort_key: str, sort_order=pymongo.DESCENDING, use_typing: bool = False, **kwargs) -> Dict[int, List[Combiner]]: """List entities From a5b0b5ce1a70b2cd32b1615cb29e9209b28bfedd Mon Sep 17 00:00:00 2001 From: Niklas Date: Wed, 4 Sep 2024 08:52:46 +0200 Subject: [PATCH 3/3] fix information exposure --- fedn/network/api/v1/combiner_routes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fedn/network/api/v1/combiner_routes.py b/fedn/network/api/v1/combiner_routes.py index 2ca547716..b17898a5c 100644 --- a/fedn/network/api/v1/combiner_routes.py +++ b/fedn/network/api/v1/combiner_routes.py @@ -379,6 +379,5 @@ def delete_combiner(id: str): return jsonify({"message": msg}), 200 except EntityNotFound: return jsonify({"message": f"Entity with id: {id} not found"}), 404 - except Exception as e: - # return jsonify({"message": "An unexpected error occurred"}), 500 - return jsonify({"message": e}), 500 + except Exception: + return jsonify({"message": "An unexpected error occurred"}), 500