Skip to content

Commit b128293

Browse files
NRL-1949 Add Update consumer endpoints with v2 permission policy
1 parent 81ada46 commit b128293

6 files changed

Lines changed: 359 additions & 8 deletions

File tree

api/consumer/readDocumentReference/read_document_reference.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,18 @@ def handler(
4545
diagnostics="The requested DocumentReference could not be found"
4646
)
4747

48-
if result.type not in metadata.pointer_types:
48+
allowed_types = (
49+
metadata.nrl_permissions_policy.types
50+
if metadata.nrl_permissions_policy
51+
else metadata.pointer_types
52+
)
53+
54+
if result.type not in allowed_types:
4955
logger.log(
5056
LogReference.CONREAD002,
5157
ods_code=metadata.ods_code,
5258
type=result.type,
53-
pointer_types=metadata.pointer_types,
59+
pointer_types=allowed_types,
5460
)
5561
return SpineErrorResponse.ACCESS_DENIED(
5662
diagnostics="The requested DocumentReference is not of a type that this organisation is allowed to access"

api/consumer/readDocumentReference/tests/test_read_document_reference_consumer.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
from unittest.mock import patch
23

34
from moto import mock_aws
45

@@ -41,6 +42,47 @@ def test_read_document_reference_happy_path(
4142
assert parsed_body == doc_ref.model_dump(exclude_none=True)
4243

4344

45+
@mock_aws
46+
@mock_repository
47+
@patch("nrlf.core.decorators.get_pointer_permissions_v2")
48+
def test_read_document_reference_happy_path_v2(
49+
get_pointer_permissions_mock, repository: DocumentPointerRepository
50+
):
51+
doc_ref = load_document_reference("Y05868-736253002-Valid")
52+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
53+
repository.create(doc_pointer)
54+
55+
v2_headers = create_headers(
56+
additional_headers={
57+
"nhsd-end-user-organisation-ods": "Y05868",
58+
"nhsd-nrl-app-id": "Y05868-TestApp-12345678",
59+
}
60+
)
61+
v2_headers.pop("nhsd-client-rp-details")
62+
63+
get_pointer_permissions_mock.return_value = {
64+
"access_controls": [],
65+
"types": ["http://snomed.info/sct|736253002"],
66+
}
67+
68+
event = create_test_api_gateway_event(
69+
headers=v2_headers,
70+
path_parameters={"id": doc_pointer.id},
71+
)
72+
73+
result = handler(event, create_mock_context())
74+
body = result.pop("body")
75+
76+
assert result == {
77+
"statusCode": "200",
78+
"headers": default_response_headers(),
79+
"isBase64Encoded": False,
80+
}
81+
82+
parsed_body = json.loads(body)
83+
assert parsed_body == doc_ref.model_dump(exclude_none=True)
84+
85+
4486
@mock_aws
4587
@mock_repository
4688
def test_read_document_reference_not_found(repository: DocumentPointerRepository):
@@ -159,6 +201,65 @@ def test_read_document_reference_unauthorised_for_type(
159201
}
160202

161203

204+
@mock_aws
205+
@mock_repository
206+
@patch("nrlf.core.decorators.get_pointer_permissions_v2")
207+
def test_read_document_reference_unauthorised_for_type_v2(
208+
get_pointer_permissions_mock, repository: DocumentPointerRepository
209+
):
210+
doc_ref = load_document_reference("Y05868-736253002-Valid")
211+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
212+
repository.create(doc_pointer)
213+
214+
headers = create_headers(
215+
additional_headers={
216+
"nhsd-end-user-organisation-ods": "Y05868",
217+
"nhsd-nrl-app-id": "Y05868-TestApp-12345678",
218+
}
219+
)
220+
headers.pop("nhsd-client-rp-details")
221+
222+
get_pointer_permissions_mock.return_value = {
223+
"access_controls": [],
224+
"types": ["http://snomed.info/sct|736373009"],
225+
}
226+
227+
event = create_test_api_gateway_event(
228+
headers=headers,
229+
path_parameters={"id": doc_pointer.id},
230+
)
231+
232+
result = handler(event, create_mock_context())
233+
body = result.pop("body")
234+
235+
assert result == {
236+
"statusCode": "403",
237+
"headers": default_response_headers(),
238+
"isBase64Encoded": False,
239+
}
240+
241+
parsed_body = json.loads(body)
242+
assert parsed_body == {
243+
"resourceType": "OperationOutcome",
244+
"issue": [
245+
{
246+
"severity": "error",
247+
"code": "forbidden",
248+
"details": {
249+
"coding": [
250+
{
251+
"code": "ACCESS DENIED",
252+
"display": "Access has been denied to process this request",
253+
"system": "https://fhir.nhs.uk/CodeSystem/Spine-ErrorOrWarningCode",
254+
}
255+
]
256+
},
257+
"diagnostics": "The requested DocumentReference is not of a type that this organisation is allowed to access",
258+
}
259+
],
260+
}
261+
262+
162263
@mock_aws
163264
@mock_repository
164265
def test_document_reference_invalid_json(repository: DocumentPointerRepository):

api/consumer/searchDocumentReference/search_document_reference.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,17 @@ def handler(
5050
base_url = f"https://{config.ENVIRONMENT}.api.service.nhs.uk/"
5151
self_link = f"{base_url}record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|{params.nhs_number}"
5252

53-
if not validate_type(params.type, metadata.pointer_types):
53+
allowed_types = (
54+
metadata.nrl_permissions_policy.types
55+
if metadata.nrl_permissions_policy
56+
else metadata.pointer_types
57+
)
58+
59+
if not validate_type(params.type, allowed_types):
5460
logger.log(
5561
LogReference.CONSEARCH002,
5662
type=params.type,
57-
pointer_types=metadata.pointer_types,
63+
pointer_types=allowed_types,
5864
)
5965
return SpineErrorResponse.INVALID_CODE_SYSTEM(
6066
diagnostics="Invalid query parameter (The provided type does not match the allowed types for this organisation)",
@@ -80,7 +86,7 @@ def handler(
8086
if custodian_id:
8187
self_link += f"&custodian:identifier=https://fhir.nhs.uk/Id/ods-organization-code|{custodian_id}"
8288

83-
pointer_types = [params.type.root] if params.type else metadata.pointer_types
89+
pointer_types = [params.type.root] if params.type else allowed_types
8490
if params.type:
8591
self_link += f"&type={params.type.root}"
8692

api/consumer/searchDocumentReference/tests/test_search_document_reference_consumer.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,60 @@ def test_search_document_reference_happy_path(
6363
}
6464

6565

66+
@mock_aws
67+
@mock_repository
68+
@patch("nrlf.core.decorators.get_pointer_permissions_v2")
69+
def test_search_document_reference_happy_path_v2(
70+
get_pointer_permissions_mock, repository: DocumentPointerRepository
71+
):
72+
doc_ref = load_document_reference("Y05868-736253002-Valid")
73+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
74+
repository.create(doc_pointer)
75+
76+
v2_headers = create_headers(
77+
additional_headers={
78+
"nhsd-end-user-organisation-ods": "Y05868",
79+
"nhsd-nrl-app-id": "Y05868-TestApp-12345678",
80+
}
81+
)
82+
v2_headers.pop("nhsd-client-rp-details")
83+
84+
get_pointer_permissions_mock.return_value = {
85+
"access_controls": [],
86+
"types": ["http://snomed.info/sct|736253002"],
87+
}
88+
89+
event = create_test_api_gateway_event(
90+
headers=v2_headers,
91+
query_string_parameters={
92+
"subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191",
93+
},
94+
)
95+
96+
result = handler(event, create_mock_context())
97+
body = result.pop("body")
98+
99+
assert result == {
100+
"statusCode": "200",
101+
"headers": default_response_headers(),
102+
"isBase64Encoded": False,
103+
}
104+
105+
parsed_body = json.loads(body)
106+
assert parsed_body == {
107+
"resourceType": "Bundle",
108+
"type": "searchset",
109+
"link": [
110+
{
111+
"relation": "self",
112+
"url": "https://pytest.api.service.nhs.uk/record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|6700028191",
113+
}
114+
],
115+
"total": 1,
116+
"entry": [{"resource": doc_ref.model_dump(exclude_none=True)}],
117+
}
118+
119+
66120
@mock_aws
67121
@mock_repository
68122
def test_search_document_reference_accession_number_in_pointer(
@@ -680,6 +734,65 @@ def test_search_document_reference_invalid_type(
680734
}
681735

682736

737+
@mock_aws
738+
@mock_repository
739+
@patch("nrlf.core.decorators.get_pointer_permissions_v2")
740+
def test_search_document_reference_invalid_type_v2(
741+
get_pointer_permissions_mock, repository: DocumentPointerRepository
742+
):
743+
v2_headers = create_headers(
744+
additional_headers={
745+
"nhsd-end-user-organisation-ods": "Y05868",
746+
"nhsd-nrl-app-id": "Y05868-TestApp-12345678",
747+
}
748+
)
749+
v2_headers.pop("nhsd-client-rp-details")
750+
751+
get_pointer_permissions_mock.return_value = {
752+
"access_controls": [],
753+
"types": ["http://snomed.info/sct|736253002"],
754+
}
755+
756+
event = create_test_api_gateway_event(
757+
headers=v2_headers,
758+
query_string_parameters={
759+
"subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191",
760+
"type": "http://snomed.info/sct|861421000000109",
761+
},
762+
)
763+
764+
result = handler(event, create_mock_context())
765+
body = result.pop("body")
766+
767+
assert result == {
768+
"statusCode": "400",
769+
"headers": default_response_headers(),
770+
"isBase64Encoded": False,
771+
}
772+
773+
parsed_body = json.loads(body)
774+
assert parsed_body == {
775+
"resourceType": "OperationOutcome",
776+
"issue": [
777+
{
778+
"severity": "error",
779+
"code": "code-invalid",
780+
"details": {
781+
"coding": [
782+
{
783+
"code": "INVALID_CODE_SYSTEM",
784+
"display": "Invalid code system",
785+
"system": "https://fhir.nhs.uk/CodeSystem/Spine-ErrorOrWarningCode",
786+
}
787+
]
788+
},
789+
"diagnostics": "Invalid query parameter (The provided type does not match the allowed types for this organisation)",
790+
"expression": ["type"],
791+
}
792+
],
793+
}
794+
795+
683796
@mock_aws
684797
@mock_repository
685798
def test_search_document_reference_invalid_category(

api/consumer/searchPostDocumentReference/search_post_document_reference.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,17 @@ def handler(
5050
base_url = f"https://{config.ENVIRONMENT}.api.service.nhs.uk/"
5151
self_link = f"{base_url}record-locator/consumer/FHIR/R4/DocumentReference?subject:identifier=https://fhir.nhs.uk/Id/nhs-number|{body.nhs_number}"
5252

53-
if not validate_type(body.type, metadata.pointer_types):
53+
allowed_types = (
54+
metadata.nrl_permissions_policy.types
55+
if metadata.nrl_permissions_policy
56+
else metadata.pointer_types
57+
)
58+
59+
if not validate_type(body.type, allowed_types):
5460
logger.log(
5561
LogReference.CONPOSTSEARCH002,
5662
type=body.type,
57-
pointer_types=metadata.pointer_types,
63+
pointer_types=allowed_types,
5864
)
5965
return SpineErrorResponse.INVALID_CODE_SYSTEM(
6066
diagnostics="The provided type does not match the allowed types for this organisation",
@@ -80,7 +86,7 @@ def handler(
8086
if custodian_id:
8187
self_link += f"&custodian:identifier=https://fhir.nhs.uk/Id/ods-organization-code|{custodian_id}"
8288

83-
pointer_types = [body.type.root] if body.type else metadata.pointer_types
89+
pointer_types = [body.type.root] if body.type else allowed_types
8490
if body.type:
8591
self_link += f"&type={body.type.root}"
8692

0 commit comments

Comments
 (0)