Skip to content

Commit 6368c62

Browse files
NogaNHSrobg-nhs
andauthored
[NDR-50] refactoring get document reference endpoint (#627)
* [NDR-50] refactoring get document reference endpoint and additional services --------- Co-authored-by: robg-nhs <[email protected]>
1 parent c9a27b2 commit 6368c62

33 files changed

+1686
-1212
lines changed

.github/workflows/base-lambdas-reusable-deploy-all.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,20 +409,19 @@ jobs:
409409
AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }}
410410

411411
deploy_get_document_reference_lambda:
412-
name: Deploy get nrl document reference lambda
412+
name: Deploy get fhir document reference lambda
413413
uses: ./.github/workflows/base-lambdas-reusable-deploy.yml
414414
with:
415415
environment: ${{ inputs.environment}}
416416
python_version: ${{ inputs.python_version }}
417417
build_branch: ${{ inputs.build_branch}}
418418
sandbox: ${{ inputs.sandbox }}
419-
lambda_handler_name: nrl_get_document_reference_handler
419+
lambda_handler_name: get_fhir_document_reference_handler
420420
lambda_aws_name: GetDocumentReference
421421
lambda_layer_names: 'core_lambda_layer'
422422
secrets:
423423
AWS_ASSUME_ROLE: ${{ secrets.AWS_ASSUME_ROLE }}
424424

425-
426425
deploy_edge_presign_lambda:
427426
name: Deploy edge presign cloudfront lambda
428427
uses: ./.github/workflows/base-lambdas-edge-deploy.yml

lambdas/enums/file_size.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from enum import IntEnum
2+
3+
4+
class FileSize(IntEnum):
5+
MAX_FILE_SIZE = 8 * 10**6

lambdas/enums/lambda_error.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,30 @@ def create_error_body(self, params: Optional[dict] = None) -> str:
2828
Errors for SearchPatientException
2929
"""
3030

31-
SearchPatientMissing = {"err_code": "SP_4001", "message": "Missing user details"}
31+
SearchPatientMissing = {
32+
"err_code": "SP_4001",
33+
"message": "Missing user details",
34+
"fhir_coding": FhirIssueCoding.EXCEPTION,
35+
}
3236
SearchPatientNoPDS = {
3337
"err_code": "SP_4002",
3438
"message": "Patient does not exist for given NHS number",
39+
"fhir_coding": FhirIssueCoding.NOT_FOUND,
3540
}
3641
SearchPatientNoAuth = {
3742
"err_code": "SP_4003",
3843
"message": "You do not have access to this patient's record",
44+
"fhir_coding": FhirIssueCoding.FORBIDDEN,
3945
}
4046
SearchPatientNoId = {
4147
"err_code": "SP_4004",
4248
"message": "An error occurred while searching for patient",
49+
"fhir_coding": FhirIssueCoding.EXCEPTION,
4350
}
4451
SearchPatientNoParse = {
4552
"err_code": "SP_4005",
4653
"message": "Failed to parse PDS data",
54+
"fhir_coding": FhirIssueCoding.EXCEPTION,
4755
}
4856

4957
"""
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
from enums.lambda_error import LambdaError
2+
from oauthlib.oauth2 import WebApplicationClient
3+
from services.base.ssm_service import SSMService
4+
from services.dynamic_configuration_service import DynamicConfigurationService
5+
from services.get_fhir_document_reference_service import GetFhirDocumentReferenceService
6+
from services.oidc_service import OidcService
7+
from services.search_patient_details_service import SearchPatientDetailsService
8+
from utils.audit_logging_setup import LoggingService
9+
from utils.decorators.ensure_env_var import ensure_environment_variables
10+
from utils.decorators.handle_lambda_exceptions import handle_lambda_exceptions
11+
from utils.decorators.set_audit_arg import set_request_context_for_logging
12+
from utils.exceptions import AuthorisationException, OidcApiException
13+
from utils.lambda_exceptions import (
14+
GetFhirDocumentReferenceException,
15+
SearchPatientException,
16+
)
17+
from utils.lambda_response import ApiGatewayResponse
18+
19+
logger = LoggingService(__name__)
20+
21+
22+
@handle_lambda_exceptions
23+
@set_request_context_for_logging
24+
@ensure_environment_variables(
25+
names=[
26+
"APPCONFIG_APPLICATION",
27+
"APPCONFIG_CONFIGURATION",
28+
"APPCONFIG_ENVIRONMENT",
29+
"LLOYD_GEORGE_DYNAMODB_NAME",
30+
"PRESIGNED_ASSUME_ROLE",
31+
"CLOUDFRONT_URL",
32+
]
33+
)
34+
def lambda_handler(event, context):
35+
try:
36+
bearer_token = extract_bearer_token(event)
37+
selected_role_id = event.get("headers", {}).get("cis2-urid", None)
38+
document_id, snomed_code = extract_document_parameters(event)
39+
40+
get_document_service = GetFhirDocumentReferenceService()
41+
document_reference = get_document_service.handle_get_document_reference_request(
42+
snomed_code, document_id
43+
)
44+
45+
if selected_role_id:
46+
verify_user_authorisation(
47+
bearer_token, selected_role_id, document_reference.nhs_number
48+
)
49+
50+
document_reference_response = (
51+
get_document_service.create_document_reference_fhir_response(
52+
document_reference
53+
)
54+
)
55+
56+
logger.info(
57+
f"Successfully retrieved document reference for document_id: {document_id}, snomed_code: {snomed_code}"
58+
)
59+
60+
return ApiGatewayResponse(
61+
status_code=200, body=document_reference_response, methods="GET"
62+
).create_api_gateway_response()
63+
64+
except GetFhirDocumentReferenceException as exception:
65+
return ApiGatewayResponse(
66+
status_code=exception.status_code,
67+
body=exception.error.create_error_response().create_error_fhir_response(
68+
exception.error.value.get("fhir_coding")
69+
),
70+
methods="GET",
71+
).create_api_gateway_response()
72+
73+
74+
def extract_bearer_token(event):
75+
"""Extract and validate bearer token from event"""
76+
bearer_token = event.get("headers", {}).get("Authorization", None)
77+
if not bearer_token or not bearer_token.startswith("Bearer "):
78+
logger.warning("No bearer token found in request")
79+
raise GetFhirDocumentReferenceException(
80+
401, LambdaError.DocumentReferenceUnauthorised
81+
)
82+
return bearer_token
83+
84+
85+
def extract_document_parameters(event):
86+
"""Extract document ID and SNOMED code from path parameters"""
87+
path_params = event.get("pathParameters", {}).get("id", None)
88+
document_id, snomed_code = get_id_and_snomed_from_path_parameters(path_params)
89+
90+
if not document_id or not snomed_code:
91+
logger.error("Missing document id or snomed code in request path parameters.")
92+
raise GetFhirDocumentReferenceException(
93+
400, LambdaError.DocumentReferenceInvalidRequest
94+
)
95+
96+
return document_id, snomed_code
97+
98+
99+
def verify_user_authorisation(bearer_token, selected_role_id, nhs_number):
100+
"""Verify user authorisation for accessing patient data"""
101+
logger.info("Detected a cis2 user access request, checking for access permission")
102+
103+
try:
104+
configuration_service = DynamicConfigurationService()
105+
configuration_service.set_auth_ssm_prefix()
106+
107+
oidc_service = OidcService()
108+
oidc_service.set_up_oidc_parameters(SSMService, WebApplicationClient)
109+
userinfo = oidc_service.fetch_userinfo(bearer_token)
110+
111+
org_ods_code = oidc_service.fetch_user_org_code(userinfo, selected_role_id)
112+
smartcard_role_code, _ = oidc_service.fetch_user_role_code(
113+
userinfo, selected_role_id, "R"
114+
)
115+
except (OidcApiException, AuthorisationException) as e:
116+
logger.error(f"Authorization error: {str(e)}")
117+
raise GetFhirDocumentReferenceException(
118+
403, LambdaError.DocumentReferenceUnauthorised
119+
)
120+
121+
try:
122+
search_patient_service = SearchPatientDetailsService(
123+
smartcard_role_code, org_ods_code
124+
)
125+
search_patient_service.handle_search_patient_request(nhs_number, False)
126+
except SearchPatientException as e:
127+
raise GetFhirDocumentReferenceException(e.status_code, e.error)
128+
129+
130+
def get_id_and_snomed_from_path_parameters(path_parameters):
131+
"""Extract document ID and SNOMED code from path parameters"""
132+
if path_parameters:
133+
params = path_parameters.split("~")
134+
if len(params) == 2:
135+
return params[1], params[0]
136+
return None, None

lambdas/handlers/manage_nrl_pointer_handler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from datetime import datetime
33

44
from enums.nrl_sqs_upload import NrlActionTypes
5-
from models.fhir.R4.nrl_fhir_document_reference import DocumentReferenceInfo
5+
from models.fhir.R4.fhir_document_reference import DocumentReferenceInfo
66
from models.sqs.nrl_sqs_message import NrlSqsMessage
77
from services.base.nhs_oauth_service import NhsOauthService
88
from services.base.ssm_service import SSMService
@@ -49,7 +49,7 @@ def lambda_handler(event, context):
4949
document = DocumentReferenceInfo(
5050
**nrl_verified_message,
5151
custodian=nrl_api_service.end_user_ods_code,
52-
).create_fhir_document_reference_object()
52+
).create_nrl_fhir_document_reference_object()
5353
logger.info(
5454
f"Trying to create pointer request: Body: {document.model_dump_json(exclude_none=True)}, "
5555
f"RequestURL: {nrl_api_service.endpoint}, "

lambdas/handlers/nrl_get_document_reference_handler.py

Lines changed: 0 additions & 72 deletions
This file was deleted.

lambdas/handlers/search_patient_details_handler.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ def lambda_handler(event, context):
4141
search_service = SearchPatientDetailsService(
4242
user_role=user_role, user_ods_code=user_ods_code
4343
)
44-
response = search_service.handle_search_patient_request(nhs_number)
4544

46-
return ApiGatewayResponse(200, response, "GET").create_api_gateway_response()
45+
# Get patient details from service
46+
patient_details = search_service.handle_search_patient_request(
47+
nhs_number,
48+
)
49+
formatted_response = patient_details.model_dump_json(
50+
by_alias=True,
51+
exclude={
52+
"death_notification_status",
53+
"general_practice_ods",
54+
},
55+
)
56+
57+
return ApiGatewayResponse(
58+
200, formatted_response, "GET"
59+
).create_api_gateway_response()

0 commit comments

Comments
 (0)