diff --git a/lambdas/enums/lambda_error.py b/lambdas/enums/lambda_error.py index 192fd4d303..5d6d73c95d 100644 --- a/lambdas/enums/lambda_error.py +++ b/lambdas/enums/lambda_error.py @@ -517,6 +517,10 @@ def create_error_body(self, params: Optional[dict] = None, **kwargs) -> str: "err_code": "DRV_4005", "message": "The NHS number provided is invalid", } + DocumentReviewUnsupportedFileType = { + "err_code": "DRV_4006", + "message": "The file type provided is not supported", + } """ Errors for get ods report lambda @@ -690,10 +694,7 @@ def create_error_body(self, params: Optional[dict] = None, **kwargs) -> str: "message": "Invalid request", } - DocumentReviewUploadForbidden = { - "err_code": "UDR_4031", - "message": "Forbidden" - } + DocumentReviewUploadForbidden = {"err_code": "UDR_4031", "message": "Forbidden"} DocumentReviewPresignedFailure = { "err_code": "UDR_5003", diff --git a/lambdas/handlers/post_document_review_handler.py b/lambdas/handlers/post_document_review_handler.py index 238e8c34e7..eef9add024 100644 --- a/lambdas/handlers/post_document_review_handler.py +++ b/lambdas/handlers/post_document_review_handler.py @@ -10,7 +10,7 @@ from utils.decorators.ensure_env_var import ensure_environment_variables from utils.decorators.handle_lambda_exceptions import handle_lambda_exceptions from utils.decorators.set_audit_arg import set_request_context_for_logging -from utils.exceptions import InvalidNhsNumberException +from utils.exceptions import InvalidNhsNumberException, InvalidFileTypeException from utils.lambda_exceptions import DocumentReviewLambdaException from utils.lambda_response import ApiGatewayResponse @@ -60,3 +60,8 @@ def validate_event_body(body): raise DocumentReviewLambdaException( 400, LambdaError.DocumentReviewUploadInvalidRequest ) + except InvalidFileTypeException as e: + logger.error(e) + raise DocumentReviewLambdaException( + 400, LambdaError.DocumentReviewUnsupportedFileType + ) diff --git a/lambdas/models/document_review.py b/lambdas/models/document_review.py index 41a366c0e4..4d4e0fef99 100644 --- a/lambdas/models/document_review.py +++ b/lambdas/models/document_review.py @@ -1,12 +1,15 @@ import uuid from datetime import datetime, timezone +from typing import Self from enums.document_review_status import DocumentReviewStatus from enums.metadata_field_names import DocumentReferenceMetadataFields +from enums.upload_forbidden_file_extensions import is_file_type_allowed from enums.snomed_codes import SnomedCodes -from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator, ValidationError from pydantic.alias_generators import to_camel, to_pascal -from utils.exceptions import InvalidNhsNumberException +from utils.exceptions import InvalidNhsNumberException, ConfigNotFoundException, InvalidFileTypeException +from utils import upload_file_configs from utils.utilities import validate_nhs_number @@ -155,3 +158,16 @@ def check_snomed_code(cls, value) -> SnomedCodes | None: def verify_nhs_number(cls, value) -> str | None: if validate_nhs_number(value): return value + + @model_validator(mode="after") + def validate_file_extension(self) -> Self: + try: + accepted_file_types = upload_file_configs.get_config_by_snomed_code(self.snomed_code.code).accepted_file_types + + for file in self.documents: + if not is_file_type_allowed(file, accepted_file_types): + raise InvalidFileTypeException("Invalid file extension.") + return self + except ConfigNotFoundException: + raise InvalidFileTypeException("Unable to find file configuration.") + diff --git a/lambdas/services/post_document_review_service.py b/lambdas/services/post_document_review_service.py index f37a3b2305..bdc92dae3f 100644 --- a/lambdas/services/post_document_review_service.py +++ b/lambdas/services/post_document_review_service.py @@ -84,6 +84,7 @@ def process_event(self, event: DocumentReviewUploadEvent) -> dict: except ClientError: raise DocumentReviewLambdaException(500, LambdaError.DocumentReviewDB) + def create_response( self, document_review_reference: DocumentUploadReviewReference ) -> dict: diff --git a/lambdas/tests/unit/handlers/test_post_document_review_handler.py b/lambdas/tests/unit/handlers/test_post_document_review_handler.py index 7b51c77c2f..715bbae49e 100644 --- a/lambdas/tests/unit/handlers/test_post_document_review_handler.py +++ b/lambdas/tests/unit/handlers/test_post_document_review_handler.py @@ -38,6 +38,12 @@ "documents": [], } +INVALID_EVENT_INVALID_FILE_EXTENSION = { + "nhsNumber": TEST_NHS_NUMBER, + "snomedCode": SnomedCodes.LLOYD_GEORGE.value.code, + "documents": ["testFile.job"], +} + TEST_PRESIGNED_URL_1 = "https://s3.amazonaws.com/presigned1?signature=abc123" @@ -215,13 +221,21 @@ def test_validate_event_body_valid_event_returns_document_review_upload_event_mo def test_validate_event_body_throws_error_unsupported_snomed_code(invalid_event): - invalid_event["body"] = INVALID_EVENT_UNSUPPORTED_SNOMED_CODE + invalid_event["body"] = json.dumps(INVALID_EVENT_UNSUPPORTED_SNOMED_CODE) with pytest.raises(DocumentReviewLambdaException) as e: validate_event_body(invalid_event["body"]) assert e.value.status_code == 400 assert e.value.err_code == "UDR_4003" +def test_validate_event_body_throws_error_unsupported_file_type(invalid_event): + invalid_event["body"] = json.dumps(INVALID_EVENT_INVALID_FILE_EXTENSION) + with pytest.raises(DocumentReviewLambdaException) as e: + validate_event_body(invalid_event["body"]) + assert e.value.status_code == 400 + assert e.value.err_code == "DRV_4006" + + def test_lambda_handler_calls_service_with_validated_event( mock_service, context, mock_upload_document_iteration_3_enabled, valid_event ): diff --git a/lambdas/tests/unit/services/test_post_document_review_service.py b/lambdas/tests/unit/services/test_post_document_review_service.py index e9b9728852..8576b4a764 100644 --- a/lambdas/tests/unit/services/test_post_document_review_service.py +++ b/lambdas/tests/unit/services/test_post_document_review_service.py @@ -293,4 +293,4 @@ def test_create_response(mock_service): nhs_number=TEST_NHS_NUMBER, ) ) - assert actual == expected + assert actual == expected \ No newline at end of file diff --git a/lambdas/tests/unit/services/test_post_fhir_document_reference_service.py b/lambdas/tests/unit/services/test_post_fhir_document_reference_service.py index c6f763b8bf..b389bd433a 100644 --- a/lambdas/tests/unit/services/test_post_fhir_document_reference_service.py +++ b/lambdas/tests/unit/services/test_post_fhir_document_reference_service.py @@ -549,7 +549,7 @@ def test_extract_author_from_fhir( ], ) def test_extract_author_from_fhir_raises_error( - mock_post_fhir_doc_ref_service, mocker, fhir_author + mock_fhir_doc_ref_base_service, mock_post_fhir_doc_ref_service, mocker, fhir_author ): """Test _extract_author_from_fhir method with malformed json returns Validation errors.""" fhir_doc = mocker.MagicMock(spec=FhirDocumentReference) diff --git a/lambdas/utils/exceptions.py b/lambdas/utils/exceptions.py index 07a4a14d33..21b1a99449 100644 --- a/lambdas/utils/exceptions.py +++ b/lambdas/utils/exceptions.py @@ -218,3 +218,7 @@ class ReviewProcessCreateRecordException(Exception): class CorruptedFileException(Exception): pass + + +class InvalidFileTypeException(Exception): + pass