Skip to content

Commit a7cae09

Browse files
Feature/safo6 nrl 1986 enable allow all types (#1168)
- PermissionsPolicy added to ConnectionMetadata to keep v2 permissions separate - Endpoints updated to factor in the new PermissionsPolicy object - V2 access control enabled for allow_all_types
1 parent dadf711 commit a7cae09

File tree

24 files changed

+1168
-56
lines changed

24 files changed

+1168
-56
lines changed

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: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import json
2+
from unittest.mock import patch
23

34
from moto import mock_aws
45

56
from api.consumer.readDocumentReference.read_document_reference import handler
7+
from nrlf.core.constants import CLIENT_RP_DETAILS, V2Headers
68
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
79
from nrlf.tests.data import load_document_reference
810
from nrlf.tests.dynamodb import mock_repository
@@ -41,6 +43,47 @@ def test_read_document_reference_happy_path(
4143
assert parsed_body == doc_ref.model_dump(exclude_none=True)
4244

4345

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

161204

205+
@mock_aws
206+
@mock_repository
207+
@patch("nrlf.core.decorators.get_pointer_permissions_v2")
208+
def test_read_document_reference_unauthorised_for_type_v2(
209+
get_pointer_permissions_mock, repository: DocumentPointerRepository
210+
):
211+
doc_ref = load_document_reference("Y05868-736253002-Valid")
212+
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
213+
repository.create(doc_pointer)
214+
215+
headers = create_headers(
216+
additional_headers={
217+
V2Headers.NHSD_END_USER_ORGANISATION_ODS: "Y05868",
218+
V2Headers.NHSD_NRL_APP_ID: "Y05868-TestApp-12345678",
219+
}
220+
)
221+
headers.pop("nhsd-client-rp-details")
222+
223+
get_pointer_permissions_mock.return_value = {
224+
"access_controls": [],
225+
"types": ["http://snomed.info/sct|736373009"],
226+
}
227+
228+
event = create_test_api_gateway_event(
229+
headers=headers,
230+
path_parameters={"id": doc_pointer.id},
231+
)
232+
233+
result = handler(event, create_mock_context())
234+
body = result.pop("body")
235+
236+
assert result == {
237+
"statusCode": "403",
238+
"headers": default_response_headers(),
239+
"isBase64Encoded": False,
240+
}
241+
242+
parsed_body = json.loads(body)
243+
assert parsed_body == {
244+
"resourceType": "OperationOutcome",
245+
"issue": [
246+
{
247+
"severity": "error",
248+
"code": "forbidden",
249+
"details": {
250+
"coding": [
251+
{
252+
"code": "ACCESS DENIED",
253+
"display": "Access has been denied to process this request",
254+
"system": "https://fhir.nhs.uk/CodeSystem/Spine-ErrorOrWarningCode",
255+
}
256+
]
257+
},
258+
"diagnostics": "The requested DocumentReference is not of a type that this organisation is allowed to access",
259+
}
260+
],
261+
}
262+
263+
162264
@mock_aws
163265
@mock_repository
164266
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: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
from nrlf.consumer.fhir.r4.model import CodeableConcept, Identifier
88
from nrlf.core.constants import (
99
CATEGORY_ATTRIBUTES,
10+
CLIENT_RP_DETAILS,
1011
TYPE_ATTRIBUTES,
1112
Categories,
1213
PointerTypes,
14+
V2Headers,
1315
)
1416
from nrlf.core.dynamodb.repository import DocumentPointer, DocumentPointerRepository
1517
from nrlf.tests.data import load_document_reference
@@ -63,6 +65,60 @@ def test_search_document_reference_happy_path(
6365
}
6466

6567

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

682738

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