From 60626d43164c89a9e20bb87a271d1ffa99b637a2 Mon Sep 17 00:00:00 2001 From: aydarng Date: Fri, 30 Aug 2024 23:47:53 +0200 Subject: [PATCH] Resourse DB+ Upload check validations+ (#42) * Resourse DB+ Upload check validations+ * Update src/regps/app/fastapi_app.py --------- Co-authored-by: Lance --- src/regps/app/api/controllers.py | 2 +- src/regps/app/api/utils/reports_db.py | 33 ++++++++++ src/regps/app/fastapi_app.py | 89 ++++++++++++++++++++++++--- tests/unit/test_reports_db.py | 65 +++++++++++++++++++ 4 files changed, 179 insertions(+), 10 deletions(-) create mode 100644 src/regps/app/api/utils/reports_db.py create mode 100644 tests/unit/test_reports_db.py diff --git a/src/regps/app/api/controllers.py b/src/regps/app/api/controllers.py index 1b9cb90..82d5ed5 100644 --- a/src/regps/app/api/controllers.py +++ b/src/regps/app/api/controllers.py @@ -23,7 +23,7 @@ def check_login(self, aid: str): def login(self, said: str, vlei: str): verifier_response = self.verifier_adapter.verify_vlei_request(said, vlei) - if verifier_response.status_code != 200: + if verifier_response.status_code != 202: raise VerifierServiceException( verifier_response.json(), verifier_response.status_code ) diff --git a/src/regps/app/api/utils/reports_db.py b/src/regps/app/api/utils/reports_db.py new file mode 100644 index 0000000..1b562f2 --- /dev/null +++ b/src/regps/app/api/utils/reports_db.py @@ -0,0 +1,33 @@ +from collections import defaultdict + + +class ReportsDB: + def __init__(self): + self.aid_reports = defaultdict(list) + self.lei_reports = defaultdict(list) + self.aid_to_lei_mapping = dict() + self.lei_digests = defaultdict(set) + + def register_aid(self, aid, lei): + self.aid_to_lei_mapping[aid] = lei + + def add_report(self, aid, dig, report): + lei = self.aid_to_lei_mapping[aid] or "-" + self.aid_reports[aid].append(report) + self.lei_reports[lei].append(report) + self.lei_digests[lei].add(dig) + + def drop_status(self, aid): + self.aid_reports[aid] = [] + return True + + def get_reports_for_aid(self, aid): + return self.aid_reports[aid] + + def get_reports_for_lei(self, aid): + lei = self.aid_to_lei_mapping[aid] or "-" + return self.lei_reports[lei] + + def authorized_to_check_status(self, aid, dig): + lei = self.aid_to_lei_mapping[aid] or "-" + return dig in self.lei_digests[lei] \ No newline at end of file diff --git a/src/regps/app/fastapi_app.py b/src/regps/app/fastapi_app.py index 620dfe4..1387405 100644 --- a/src/regps/app/fastapi_app.py +++ b/src/regps/app/fastapi_app.py @@ -1,7 +1,4 @@ import os -from collections import defaultdict - - from regps.app.api.signed_headers_verifier import logger, VerifySignedHeaders from fastapi import ( FastAPI, @@ -23,6 +20,7 @@ VerifierServiceException, ) from regps.app.api.controllers import APIController +from regps.app.api.utils.reports_db import ReportsDB from regps.app.api.utils.swagger_examples import ( check_login_examples, upload_examples, @@ -37,8 +35,7 @@ api_controller = APIController() verify_signed_headers = VerifySignedHeaders(api_controller) -reports = defaultdict(list) - +reports_db = ReportsDB() @app.get("/ping") async def ping(): @@ -56,7 +53,10 @@ async def login(response: Response, data: LoginRequest): try: logger.info(f"Login: sending login cred {str(data)[:50]}...") resp = api_controller.login(data.said, data.vlei) - return JSONResponse(status_code=200, content=resp) + lei = resp.get("lei") + aid = resp.get("aid") + reports_db.register_aid(aid, lei) + return JSONResponse(status_code=202, content=resp) except VerifierServiceException as e: logger.error(f"Login: Exception: {e}") response.status_code = e.status_code @@ -176,7 +176,7 @@ async def upload_route( logger.info( f"Upload: completed upload for {aid} {dig} with code {resp.status_code}" ) - reports[aid].append(resp.json()) + reports_db.add_report(aid, dig, resp.json()) return JSONResponse(status_code=200, content=resp.json()) except HTTPException as e: logger.error(f"Upload: Exception: {e}") @@ -249,12 +249,18 @@ async def check_upload_route( """ try: verify_signed_headers.process_request(request, aid) + if not reports_db.authorized_to_check_status(aid, dig): + raise HTTPException(status_code=401, detail=f"AID {aid} is not authorized to check status for digest {dig}") resp = api_controller.check_upload(aid, dig) return JSONResponse(status_code=200, content=resp) except VerifierServiceException as e: logger.error(f"CheckUpload: Exception: {e}") response.status_code = e.status_code return JSONResponse(content=e.detail, status_code=e.status_code) + except HTTPException as e: + logger.error(f"CheckUpload: Exception: {e}") + response.status_code = e.status_code + return JSONResponse(content=e.detail, status_code=e.status_code) except Exception as e: logger.error(f"CheckUpload: Exception: {e}") raise HTTPException(status_code=500, detail=str(e)) @@ -312,7 +318,70 @@ async def status_route( """ try: verify_signed_headers.process_request(request, aid) - resp = reports.get(aid, []) + resp = reports_db.get_reports_for_aid(aid) + return JSONResponse(status_code=202, content=resp) + except HTTPException as e: + logger.error(f"Status: Exception: {e}") + response.status_code = e.status_code + return JSONResponse(content=e.detail, status_code=e.status_code) + except Exception as e: + logger.error(f"Status: Exception: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@app.get("/report/status/lei/{aid}") +async def status_for_lei_route( + request: Request, + response: Response, + aid: str = Path( + ..., + description="AID", + openapi_examples={ + "default": { + "summary": "Default AID. Must have logged into the verifier with a role credential specifying the LEI.", + "value": check_upload_examples["request"]["aid"], + } + }, + ), + signature: str = Header( + openapi_examples={ + "default": { + "summary": "Default signature for signed headers.", + "value": upload_examples["request"]["headers"]["signature"], + } + } + ), + signature_input: str = Header( + openapi_examples={ + "default": { + "summary": "Default signature_input for signed headers.", + "value": upload_examples["request"]["headers"]["signature_input"], + } + } + ), + signify_resource: str = Header( + openapi_examples={ + "default": { + "summary": "Default signify_resource for signed headers.", + "value": upload_examples["request"]["headers"]["signify_resource"], + } + } + ), + signify_timestamp: str = Header( + openapi_examples={ + "default": { + "summary": "Default signify_timestamp for signed headers.", + "value": upload_examples["request"]["headers"]["signify_timestamp"], + } + } + ), +): + """ + Check upload status by aid. + """ + try: + verify_signed_headers.process_request(request, aid) + resp = reports_db.get_reports_for_lei(aid) return JSONResponse(status_code=202, content=resp) except VerifierServiceException as e: logger.error(f"Status: Exception: {e}") @@ -326,6 +395,7 @@ async def status_route( # TODO: Remove this endpoint when we will have DB. IT's only for tests @app.post("/status/{aid}/drop") def clear_status_route( + request: Request, aid: str = Path( ..., description="AID", @@ -340,7 +410,8 @@ def clear_status_route( """ Drop upload status for specified AID. For the test purposes """ - reports[aid] = [] + verify_signed_headers.process_request(request, aid) + reports_db.drop_status(aid) resp = {"status": "success", "aid": aid} return JSONResponse(status_code=202, content=resp) diff --git a/tests/unit/test_reports_db.py b/tests/unit/test_reports_db.py new file mode 100644 index 0000000..238f41d --- /dev/null +++ b/tests/unit/test_reports_db.py @@ -0,0 +1,65 @@ +from regps.app.api.utils.reports_db import ReportsDB + + +def test_check_report_status_authorization(): + aid = "jnhh8f7h79nufb97hbw3fieBHJBgg7uhn" + lei = "j9h7ufehhcWBUTDVWYH98h9bfyaebgGBFfsa3wFf" + report_1 = "report 1" + dig_1 = "sha256_moiuhLFBf9afnHJDfaffg4ehgh" + report_2 = "report 2" + dig_2 = "sha256_fer4grniuojnfaNHBBcaaUh89h" + reports_db = ReportsDB() + reports_db.register_aid(aid, lei) + reports_db.add_report(aid, dig_1, report_1) + reports_db.add_report(aid, dig_2, report_2) + assert reports_db.authorized_to_check_status(aid, dig_1) + assert reports_db.authorized_to_check_status(aid, dig_2) + assert len(reports_db.get_reports_for_aid(aid)) == 2 + assert len(reports_db.get_reports_for_lei(aid)) == 2 + + +def test_check_report_status_authorization_2_aids_from_the_same_lei(): + aid_1 = "jnhh8f7h79nufb97hbw3fieBHJBgg7uhn" + aid_2 = "UNBOUb8dadh98hnansudHD0jndbuh8hnd" + lei = "j9h7ufehhcWBUTDVWYH98h9bfyaebgGBFfsa3wFf" + report_1 = "report 1" + dig_1 = "sha256_moiuhLFBf9afnHJDfaffg4ehgh" + report_2 = "report 2" + dig_2 = "sha256_fer4grniuojnfaNHBBcaaUh89h" + reports_db = ReportsDB() + reports_db.register_aid(aid_1, lei) + reports_db.register_aid(aid_2, lei) + reports_db.add_report(aid_1, dig_1, report_1) + reports_db.add_report(aid_2, dig_2, report_2) + assert reports_db.authorized_to_check_status(aid_1, dig_1) + assert reports_db.authorized_to_check_status(aid_1, dig_2) + assert reports_db.authorized_to_check_status(aid_2, dig_1) + assert reports_db.authorized_to_check_status(aid_2, dig_2) + assert len(reports_db.get_reports_for_aid(aid_1)) == 1 + assert len(reports_db.get_reports_for_aid(aid_2)) == 1 + assert len(reports_db.get_reports_for_lei(aid_1)) == 2 + assert len(reports_db.get_reports_for_lei(aid_2)) == 2 + + +def test_check_report_status_authorization_2_aids_from_different_lei(): + aid_1 = "jnhh8f7h79nufb97hbw3fieBHJBgg7uhn" + aid_2 = "UNBOUb8dadh98hnansudHD0jndbuh8hnd" + lei_1 = "j9h7ufehhcWBUTDVWYH98h9bfyaebgGBFfsa3wFf" + lei_2 = "mOI8hbsah80hihSHFIHh8h3r8hf8h08hfaiffha0" + report_1 = "report 1" + dig_1 = "sha256_moiuhLFBf9afnHJDfaffg4ehgh" + report_2 = "report 2" + dig_2 = "sha256_fer4grniuojnfaNHBBcaaUh89h" + reports_db = ReportsDB() + reports_db.register_aid(aid_1, lei_1) + reports_db.register_aid(aid_2, lei_2) + reports_db.add_report(aid_1, dig_1, report_1) + reports_db.add_report(aid_2, dig_2, report_2) + assert reports_db.authorized_to_check_status(aid_1, dig_1) + assert not reports_db.authorized_to_check_status(aid_1, dig_2) + assert not reports_db.authorized_to_check_status(aid_2, dig_1) + assert reports_db.authorized_to_check_status(aid_2, dig_2) + assert len(reports_db.get_reports_for_aid(aid_1)) == 1 + assert len(reports_db.get_reports_for_aid(aid_2)) == 1 + assert len(reports_db.get_reports_for_lei(aid_1)) == 1 + assert len(reports_db.get_reports_for_lei(aid_2)) == 1 \ No newline at end of file