Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e0f1027
NRL-1949 Add consumer & producer objects to auth store
sandyforresternhs Feb 26, 2026
1a3e838
Merge branch 'develop' into feature/SAFO6-NRL-1949-assign-pointer-typ…
sandyforresternhs Mar 3, 2026
7eeb57f
NRL-1949 Add V2 consumer pointer type feature tests
sandyforresternhs Mar 5, 2026
ac060ab
NRL-1949 Add todo
sandyforresternhs Mar 5, 2026
e5d4edd
NRL-1949 Add producer v2 WIP
sandyforresternhs Mar 5, 2026
db65883
NRL-1949 V2 Producer tests for pointer type
sandyforresternhs Mar 6, 2026
dddea42
NRL-1949 Tidy up client classes
sandyforresternhs Mar 6, 2026
833e404
NRL-1949 Tidy up write permissions
sandyforresternhs Mar 6, 2026
791f3a3
NRL-1949 Amend comments
sandyforresternhs Mar 6, 2026
2762827
Merge branch 'develop' into feature/SAFO6-NRL-1949-assign-pointer-typ…
sandyforresternhs Mar 6, 2026
2caf29a
NRL-1949 Address pr comments
sandyforresternhs Mar 9, 2026
c6ee2ea
Merge branch 'develop' into feature/SAFO6-NRL-1949-assign-pointer-typ…
sandyforresternhs Mar 9, 2026
ef4922b
Merge branch 'develop' into feature/SAFO6-NRL-1949-assign-pointer-typ…
sandyforresternhs Mar 9, 2026
5dcd0de
Merge branch 'develop' into feature/SAFO6-NRL-1949-assign-pointer-typ…
sandyforresternhs Mar 10, 2026
81ada46
NRL-1949 Add v2 perm policy to metadata & update producer endpoints
sandyforresternhs Mar 11, 2026
b128293
NRL-1949 Add Update consumer endpoints with v2 permission policy
sandyforresternhs Mar 11, 2026
b8820d0
NRL-1986 Add allow_all_types consumer test and v2 enums
sandyforresternhs Mar 11, 2026
6eb0ae6
NRL-1986 Add allow_all_types producer feature test
sandyforresternhs Mar 12, 2026
7ad3bb9
NRL-1986 Address PR comments
sandyforresternhs Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
NRL-1949 Add v2 perm policy to metadata & update producer endpoints
  • Loading branch information
sandyforresternhs committed Mar 11, 2026
commit 81ada466d04710671761e33d4fc8c19ea25b9bb0
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,18 @@ def _check_permissions(
expression="custodian.identifier.value",
)

if core_model.type not in metadata.pointer_types:
allowed_types = (
metadata.nrl_permissions_policy.types
if metadata.nrl_permissions_policy
else metadata.pointer_types
)

if core_model.type not in allowed_types:
logger.log(
LogReference.PROCREATE005,
ods_code=metadata.ods_code,
type=core_model.type,
pointer_types=metadata.pointer_types,
pointer_types=allowed_types,
)
return SpineErrorResponse.AUTHOR_CREDENTIALS_ERROR(
diagnostics="The type of the provided DocumentReference is not in the list of allowed types for this organisation",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,128 @@ def test_create_document_reference_pointer_type_not_allowed(
}


@mock_aws
@mock_repository
@freeze_time("2024-03-21T12:34:56.789")
@freeze_uuid("00000000-0000-0000-0000-000000000001")
@patch("nrlf.core.decorators.get_pointer_permissions_v2")
def test_create_document_reference_happy_path_v2(
get_pointer_permissions_mock, repository: DocumentPointerRepository
):
doc_ref_data = load_document_reference_data("Y05868-736253002-Valid")

v2_headers = create_headers(
additional_headers={
"nhsd-end-user-organisation-ods": "Y05868",
"nhsd-nrl-app-id": "Y05868-TestApp-12345678",
}
)
v2_headers.pop("nhsd-client-rp-details")

get_pointer_permissions_mock.return_value = {
"access_controls": [],
"types": ["http://snomed.info/sct|736253002"],
}

event = create_test_api_gateway_event(
headers=v2_headers,
body=doc_ref_data,
)

result = handler(event, create_mock_context())
body = result.pop("body")

assert result == {
"statusCode": "201",
"headers": {
"Location": "/DocumentReference/Y05868-00000000-0000-0000-0000-000000000001",
**default_response_headers(),
},
"isBase64Encoded": False,
}

parsed_body = json.loads(body)
assert parsed_body == {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "information",
"code": "informational",
"details": {
"coding": [
{
"code": "RESOURCE_CREATED",
"display": "Resource created",
"system": "https://fhir.nhs.uk/ValueSet/NRL-ResponseCode",
}
],
},
"diagnostics": "The document has been created",
}
],
}


@mock_aws
@mock_repository
@patch("nrlf.core.decorators.get_pointer_permissions_v2")
def test_create_document_reference_pointer_type_not_allowed_v2(
get_pointer_permissions_mock, repository: DocumentPointerRepository
):
doc_ref = load_document_reference("Y05868-736253002-Valid")

headers = create_headers(
additional_headers={
"nhsd-end-user-organisation-ods": "Y05868",
"nhsd-nrl-app-id": "Y05868-TestApp-12345678",
}
)
headers.pop("nhsd-client-rp-details")

# Return a type that does not match the document's type
get_pointer_permissions_mock.return_value = {
"access_controls": [],
"types": ["http://snomed.info/sct|736373009"],
}

event = create_test_api_gateway_event(
headers=headers,
body=doc_ref.model_dump_json(exclude_none=True),
)

result = handler(event, create_mock_context())
body = result.pop("body")

assert result == {
"statusCode": "403",
"headers": default_response_headers(),
"isBase64Encoded": False,
}

parsed_body = json.loads(body)

assert parsed_body == {
"resourceType": "OperationOutcome",
"issue": [
{
"severity": "error",
"code": "forbidden",
"details": {
"coding": [
{
"code": "AUTHOR_CREDENTIALS_ERROR",
"display": "Author credentials error",
"system": "https://fhir.nhs.uk/CodeSystem/Spine-ErrorOrWarningCode",
}
]
},
"diagnostics": "The type of the provided DocumentReference is not in the list of allowed types for this organisation",
"expression": ["type.coding[0].code"],
}
],
}


def test_create_document_reference_invalid_category_type():
doc_ref = load_document_reference("Y05868-736253002-Valid")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,17 @@ def handler(
expression="subject:identifier",
)

if not validate_type(params.type, metadata.pointer_types):
allowed_types = (
metadata.nrl_permissions_policy.types
if metadata.nrl_permissions_policy
else metadata.pointer_types
)

if not validate_type(params.type, allowed_types):
logger.log(
LogReference.PROSEARCH002,
type=params.type,
pointer_types=metadata.pointer_types,
pointer_types=allowed_types,
)
return SpineErrorResponse.INVALID_CODE_SYSTEM(
diagnostics="Invalid query parameter (The provided type does not match the allowed types for this organisation)",
Expand All @@ -74,7 +80,7 @@ def handler(
expression="category",
)

pointer_types = [params.type.root] if params.type else metadata.pointer_types
pointer_types = [params.type.root] if params.type else allowed_types
bundle = {"resourceType": "Bundle", "type": "searchset", "total": 0, "entry": []}

logger.log(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,55 @@ def test_search_document_reference_happy_path(
}


@mock_aws
@mock_repository
@patch("nrlf.core.decorators.get_pointer_permissions_v2")
def test_search_document_reference_happy_path_v2(
get_pointer_permissions_mock,
repository: DocumentPointerRepository,
):
doc_ref = load_document_reference("Y05868-736253002-Valid")
doc_pointer = DocumentPointer.from_document_reference(doc_ref)
repository.create(doc_pointer)

v2_headers = create_headers(
additional_headers={
"nhsd-end-user-organisation-ods": "Y05868",
"nhsd-nrl-app-id": "Y05868-TestApp-12345678",
}
)
v2_headers.pop("nhsd-client-rp-details")

get_pointer_permissions_mock.return_value = {
"access_controls": [],
"types": ["http://snomed.info/sct|736253002"],
}

event = create_test_api_gateway_event(
headers=v2_headers,
query_string_parameters={
"subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191",
},
)

result = handler(event, create_mock_context())
body = result.pop("body")

assert result == {
"statusCode": "200",
"headers": default_response_headers(),
"isBase64Encoded": False,
}

parsed_body = json.loads(body)
assert parsed_body == {
"resourceType": "Bundle",
"type": "searchset",
"total": 1,
"entry": [{"resource": doc_ref.model_dump(exclude_none=True)}],
}


@mock_aws
@mock_repository
def test_search_document_reference_no_results(
Expand Down Expand Up @@ -462,6 +511,60 @@ def test_search_document_reference_filters_by_pointer_types(
}


@mock_aws
@mock_repository
@patch("nrlf.core.decorators.get_pointer_permissions_v2")
def test_search_document_reference_filters_by_pointer_types_v2(
get_pointer_permissions_mock,
repository: DocumentPointerRepository,
):
allowed_doc_ref = load_document_reference("Y05868-736253002-Valid")
allowed_pointer = DocumentPointer.from_document_reference(allowed_doc_ref)
repository.create(allowed_pointer)

disallowed_doc_ref = load_document_reference("Y05868-736253002-Valid")
assert disallowed_doc_ref.type and disallowed_doc_ref.type.coding
disallowed_doc_ref.type.coding[0].code = "861421000000109"
disallowed_doc_ref.type.coding[0].system = "http://snomed.info/sct"
disallowed_pointer = DocumentPointer.from_document_reference(disallowed_doc_ref)
disallowed_pointer.id = "Y05868-disallowed-type"
disallowed_pointer.type = "http://snomed.info/sct|861421000000109"
repository.create(disallowed_pointer)

v2_headers = create_headers(
additional_headers={
"nhsd-end-user-organisation-ods": "Y05868",
"nhsd-nrl-app-id": "Y05868-TestApp-12345678",
}
)
v2_headers.pop("nhsd-client-rp-details")

get_pointer_permissions_mock.return_value = {
"access_controls": [],
"types": ["http://snomed.info/sct|736253002"],
}

event = create_test_api_gateway_event(
headers=v2_headers,
query_string_parameters={
"subject:identifier": "https://fhir.nhs.uk/Id/nhs-number|6700028191",
},
)

result = handler(event, create_mock_context())
body = result.pop("body")

assert result == {
"statusCode": "200",
"headers": default_response_headers(),
"isBase64Encoded": False,
}

parsed_body = json.loads(body)
assert parsed_body["total"] == 1
assert parsed_body["entry"][0]["resource"]["id"] == allowed_doc_ref.id


@mock_aws
@mock_repository
@patch("api.producer.searchDocumentReference.search_document_reference.logger")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,17 @@ def handler(
expression="subject:identifier",
)

if not validate_type(body.type, metadata.pointer_types):
allowed_types = (
metadata.nrl_permissions_policy.types
if metadata.nrl_permissions_policy
else metadata.pointer_types
)

if not validate_type(body.type, allowed_types):
logger.log(
LogReference.PROPOSTSEARCH002,
type=body.type,
pointer_types=metadata.pointer_types,
pointer_types=allowed_types,
)
return SpineErrorResponse.INVALID_CODE_SYSTEM(
diagnostics="The provided type does not match the allowed types for this organisation",
Expand All @@ -68,7 +74,7 @@ def handler(
expression="category",
)

pointer_types = [body.type.root] if body.type else metadata.pointer_types
pointer_types = [body.type.root] if body.type else allowed_types
bundle = {"resourceType": "Bundle", "type": "searchset", "total": 0, "entry": []}

logger.log(
Expand Down
Loading