diff --git a/proxies/live/apiproxy/policies/AssignMessage.OperationOutcomeErrorResponse.xml b/proxies/live/apiproxy/policies/AssignMessage.OperationOutcomeErrorResponse.xml
index ca205b0fc..037554370 100644
--- a/proxies/live/apiproxy/policies/AssignMessage.OperationOutcomeErrorResponse.xml
+++ b/proxies/live/apiproxy/policies/AssignMessage.OperationOutcomeErrorResponse.xml
@@ -1,7 +1,7 @@
{status_code}
- Unauthorized
+ {reason_phrase}
{ "resourceType": "OperationOutcome", "meta": { "lastUpdated": "%current_timestamp#", "profile" : [ "%op_outcome_fhir_profile#" ] }, "issue": [ { "severity": "error", "code": "%op_outcome_issue_code#", "details": { "coding": [ { "system": "%op_outcome_issue_details_coding_system#", "code": "%op_outcome_issue_details_coding_code#" } ] }, "diagnostics": "%faultstring#" } ] }
true
diff --git a/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeInvalidBusinessFunction.xml b/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeInvalidBusinessFunction.xml
new file mode 100644
index 000000000..ac2bd2178
--- /dev/null
+++ b/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeInvalidBusinessFunction.xml
@@ -0,0 +1,18 @@
+
+
+ op_outcome_issue_code
+ forbidden
+
+
+ faultstring
+ User does not have the required Business Function at the specified Organisation.
+
+
+ status_code
+ 403
+
+
+ reason_phrase
+ Forbidden
+
+
diff --git a/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeIssueCodeLogin.xml b/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeIssueCodeLogin.xml
index 288ca58e5..e193b2f79 100644
--- a/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeIssueCodeLogin.xml
+++ b/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeIssueCodeLogin.xml
@@ -3,6 +3,10 @@
status_code
401
+
+ reason_phrase
+ Unauthorized
+
op_outcome_issue_code
login
diff --git a/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeIssueIal.xml b/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeIssueIal.xml
index 4bed1bf31..2bbe9b7c6 100644
--- a/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeIssueIal.xml
+++ b/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeIssueIal.xml
@@ -3,6 +3,10 @@
status_code
401
+
+ reason_phrase
+ Unauthorized
+
op_outcome_issue_code
forbidden
diff --git a/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeMissingAsid.xml b/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeMissingAsid.xml
index 8e1082bd8..869c7bbfb 100644
--- a/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeMissingAsid.xml
+++ b/proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeMissingAsid.xml
@@ -11,4 +11,8 @@
status_code
403
+
+ reason_phrase
+ Forbidden
+
diff --git a/proxies/live/apiproxy/policies/RaiseFault.InvalidBusinessFunction.xml b/proxies/live/apiproxy/policies/RaiseFault.InvalidBusinessFunction.xml
new file mode 100644
index 000000000..27810cb06
--- /dev/null
+++ b/proxies/live/apiproxy/policies/RaiseFault.InvalidBusinessFunction.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ 403
+ Forbidden
+
+
+ true
+
diff --git a/proxies/live/apiproxy/targets/ers-target.xml b/proxies/live/apiproxy/targets/ers-target.xml
index 5ea639c61..4add6f9c2 100644
--- a/proxies/live/apiproxy/targets/ers-target.xml
+++ b/proxies/live/apiproxy/targets/ers-target.xml
@@ -106,6 +106,23 @@
(raisefault.RaiseFault.MissingAsid.failed = true)
+
+
+ (isFhirR4Path = true)
+ AssignMessage.SetOperationOutcomeVariablesR4
+
+
+ (isFhirR4Path = false)
+ AssignMessage.SetOperationOutcomeVariablesPreR4
+
+
+ AssignMessage.SetOperationOutcomeInvalidBusinessFunction
+
+
+ AssignMessage.OperationOutcomeErrorResponse
+
+ (raisefault.RaiseFault.InvalidBusinessFunction.failed = true)
+
@@ -154,9 +171,9 @@
(accesstoken.auth_type == "user")
-
- RaiseFault.403Forbidden
- (request.header.nhsd-ers-business-function == "AUTHORISED_APPLICATION")
+
+ RaiseFault.InvalidBusinessFunction
+ (request.header.nhsd-ers-business-function == "PROVIDER_AUTHORISED_APPLICATION") or (request.header.nhsd-ers-business-function == "REFERRER_AUTHORISED_APPLICATION") or (request.header.nhsd-ers-business-function == "AUTHORISED_APPLICATION")
AssignMessage.Set.x-ers-access-mode-header-user-restricted
diff --git a/tests/integration/test_app_restricted.py b/tests/integration/test_app_restricted.py
index 9a323f78b..1d6916d59 100644
--- a/tests/integration/test_app_restricted.py
+++ b/tests/integration/test_app_restricted.py
@@ -46,7 +46,42 @@ async def test_authorised_application_not_supported_for_user_restricted(
f"{service_url}{_SPECIALTY_REF_DATA_URL}", headers=client_request_headers
)
- assert_error_response_with_body(response, _EXPECTED_CORRELATION_ID, 403)
+ assert response.status_code == 403, (
+ "Expected 403 response when calling the endpoint. But instead received a "
+ + response.status_code
+ )
+
+ assert (
+ response.headers[RenamedHeader.CORRELATION_ID.original]
+ == _EXPECTED_CORRELATION_ID
+ )
+
+ for renamed_header in RenamedHeader:
+ assert renamed_header.renamed not in response.headers
+
+ # Verify the OperationOutcome payload
+ response_data = response.json()
+ assert response_data["resourceType"] == "OperationOutcome"
+ assert response_data["meta"]["lastUpdated"] is not None
+ assert len(response_data["meta"]["profile"]) == 1
+ assert (
+ response_data["meta"]["profile"][0]
+ == "https://fhir.nhs.uk/STU3/StructureDefinition/eRS-OperationOutcome-1"
+ )
+ assert len(response_data["issue"]) == 1
+ issue = response_data["issue"][0]
+ assert issue["severity"] == "error"
+ assert issue["code"] == "forbidden"
+ assert issue["diagnostics"] == (
+ "User does not have the required Business Function at the specified Organisation."
+ )
+ assert len(issue["details"]["coding"]) == 1
+ issue_details = issue["details"]["coding"][0]
+ assert (
+ issue_details["system"]
+ == "https://fhir.nhs.uk/STU3/CodeSystem/eRS-APIErrorCode-1"
+ )
+ assert issue_details["code"] == "NO_ACCESS"
def test_authorised_application_supported_for_app_restricted(
self, app_restricted_access_code, service_url
diff --git a/tests/integration/test_headers.py b/tests/integration/test_headers.py
index 849a6d9b4..c379500ee 100644
--- a/tests/integration/test_headers.py
+++ b/tests/integration/test_headers.py
@@ -73,6 +73,78 @@ async def test_headers_on_echo_target(
expected_amr,
)
+ @pytest.mark.asyncio
+ @pytest.mark.parametrize(
+ "business_function,endpoint_url,is_r4",
+ [
+ ("PROVIDER_AUTHORISED_APPLICATION", "", False),
+ ("REFERRER_AUTHORISED_APPLICATION", "", False),
+ ("AUTHORISED_APPLICATION", "", False),
+ ("PROVIDER_AUTHORISED_APPLICATION", "/FHIR/STU3/", False),
+ ("REFERRER_AUTHORISED_APPLICATION", "/FHIR/STU3/", False),
+ ("AUTHORISED_APPLICATION", "/FHIR/STU3/", False),
+ ("PROVIDER_AUTHORISED_APPLICATION", "/FHIR/R4/", True),
+ ("REFERRER_AUTHORISED_APPLICATION", "/FHIR/R4/", True),
+ ("AUTHORISED_APPLICATION", "/FHIR/R4/", True),
+ ],
+ )
+ async def test_headers_on_echo_target_with_app_restricted_business_function(
+ self, business_function, endpoint_url, is_r4, authenticate_user, service_url
+ ):
+ user = Actor.RC
+ access_code = await authenticate_user(user)
+ client_request_headers = {
+ _HEADER_ECHO: "", # enable echo target
+ _HEADER_AUTHORIZATION: "Bearer " + access_code,
+ _HEADER_REQUEST_ID: "DUMMY-VALUE",
+ RenamedHeader.REFERRAL_ID.original: _EXPECTED_REFERRAL_ID,
+ RenamedHeader.CORRELATION_ID.original: _EXPECTED_CORRELATION_ID,
+ RenamedHeader.BUSINESS_FUNCTION.original: business_function,
+ RenamedHeader.ODS_CODE.original: user.org_code,
+ RenamedHeader.FILENAME.original: _EXPECTED_FILENAME,
+ RenamedHeader.COMM_RULE_ORG.original: _EXPECTED_COMM_RULE_ORG,
+ RenamedHeader.OBO_USER_ID.original: _EXPECTED_OBO_USER_ID,
+ }
+
+ # Make the API call
+ response = requests.get(
+ service_url + endpoint_url, headers=client_request_headers
+ )
+
+ assert response.status_code == 403, (
+ "Expected a 403 response when attempting to call the endpoint, but instead received a "
+ + str(response.status_code)
+ )
+
+ assert response.reason == "Forbidden"
+
+ # Verify the OperationOutcome payload
+ response_data = response.json()
+ assert response_data["resourceType"] == "OperationOutcome"
+ assert response_data["meta"]["lastUpdated"] is not None
+ assert len(response_data["meta"]["profile"]) == 1
+ assert response_data["meta"]["profile"][0] == (
+ "https://www.hl7.org/fhir/R4/operationoutcome.html"
+ if is_r4
+ else "https://fhir.nhs.uk/STU3/StructureDefinition/eRS-OperationOutcome-1"
+ )
+ assert len(response_data["issue"]) == 1
+ issue = response_data["issue"][0]
+ assert issue["severity"] == "error"
+ assert issue["code"] == "forbidden"
+ assert issue["diagnostics"] == (
+ "User does not have the required Business Function at the specified Organisation."
+ )
+ assert len(issue["details"]["coding"]) == 1
+ issue_details = issue["details"]["coding"][0]
+ assert (
+ issue_details["system"]
+ == "https://fhir.nhs.uk/CodeSystem/NHSD-API-ErrorOrWarningCode"
+ if is_r4
+ else "https://fhir.nhs.uk/STU3/CodeSystem/eRS-APIErrorCode-1"
+ )
+ assert issue_details["code"] == "ACCESS_DENIED" if is_r4 else "NO_ACCESS"
+
@pytest.mark.asyncio
@pytest.mark.parametrize(
"endpoint_url,is_r4",
@@ -438,6 +510,8 @@ def test_unknown_access_code(
response.status_code
)
+ assert response.reason == "Unauthorized"
+
if not is_operation_outcome:
assert len(response.content) == 0
else:
@@ -475,7 +549,7 @@ def test_unknown_access_code(
assert renamed_header.renamed not in client_response_headers
@pytest.mark.asyncio
- @pytest.mark.parametrize("service_name", [(None)])
+ @pytest.mark.parametrize("service_name", [None])
async def test_access_code_not_supported(
self, referring_clinician, authenticate_user, service_url
):