Skip to content

Commit f2ab1ba

Browse files
nhsd-jack-wainwrightpca-nhs
authored andcommitted
[ERSSUP-89806]-[MW]-[Updated app-restricted business function validation to use new business functions]-[JW]
1 parent 1799a13 commit f2ab1ba

9 files changed

+172
-6
lines changed

proxies/live/apiproxy/policies/AssignMessage.OperationOutcomeErrorResponse.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<AssignMessage async="false" continueOnError="false" enabled="true" name="AssignMessage.OperationOutcomeErrorResponse">
22
<Set>
33
<StatusCode>{status_code}</StatusCode>
4-
<ReasonPhrase>Unauthorized</ReasonPhrase>
4+
<ReasonPhrase>{reason_phrase}</ReasonPhrase>
55
<Payload contentType="application/fhir+json" variablePrefix="%" variableSuffix="#">{ "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#" } ] }</Payload>
66
</Set>
77
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<AssignMessage enabled="true" name="AssignMessage.SetOperationOutcomeInvalidBusinessFunction">
2+
<AssignVariable>
3+
<Name>op_outcome_issue_code</Name>
4+
<Value>forbidden</Value>
5+
</AssignVariable>
6+
<AssignVariable>
7+
<Name>faultstring</Name>
8+
<Value>User does not have the required Business Function at the specified Organisation.</Value>
9+
</AssignVariable>
10+
<AssignVariable>
11+
<Name>status_code</Name>
12+
<Value>403</Value>
13+
</AssignVariable>
14+
<AssignVariable>
15+
<Name>reason_phrase</Name>
16+
<Value>Forbidden</Value>
17+
</AssignVariable>
18+
</AssignMessage>

proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeIssueCodeLogin.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
<Name>status_code</Name>
44
<Value>401</Value>
55
</AssignVariable>
6+
<AssignVariable>
7+
<Name>reason_phrase</Name>
8+
<Value>Unauthorized</Value>
9+
</AssignVariable>
610
<AssignVariable>
711
<Name>op_outcome_issue_code</Name>
812
<Value>login</Value>

proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeIssueIal.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
<Name>status_code</Name>
44
<Value>401</Value>
55
</AssignVariable>
6+
<AssignVariable>
7+
<Name>reason_phrase</Name>
8+
<Value>Unauthorized</Value>
9+
</AssignVariable>
610
<AssignVariable>
711
<Name>op_outcome_issue_code</Name>
812
<Value>forbidden</Value>

proxies/live/apiproxy/policies/AssignMessage.SetOperationOutcomeMissingAsid.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@
1111
<Name>status_code</Name>
1212
<Value>403</Value>
1313
</AssignVariable>
14+
<AssignVariable>
15+
<Name>reason_phrase</Name>
16+
<Value>Forbidden</Value>
17+
</AssignVariable>
1418
</AssignMessage>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<RaiseFault async="false" continueOnError="false" enabled="true" name="RaiseFault.InvalidBusinessFunction">
2+
<FaultResponse>
3+
<Set>
4+
<Payload contentType="text/plain"/>
5+
<StatusCode>403</StatusCode>
6+
<ReasonPhrase>Forbidden</ReasonPhrase>
7+
</Set>
8+
</FaultResponse>
9+
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
10+
</RaiseFault>

proxies/live/apiproxy/targets/ers-target.xml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,23 @@
178178
</Step>
179179
<Condition>(raisefault.RaiseFault.InvalidBusinessFunction.failed = true)</Condition>
180180
</FaultRule>
181+
<FaultRule name="invalid_business_function">
182+
<Step>
183+
<Condition>(isFhirR4Path = true)</Condition>
184+
<Name>AssignMessage.SetOperationOutcomeVariablesR4</Name>
185+
</Step>
186+
<Step>
187+
<Condition>(isFhirR4Path = false)</Condition>
188+
<Name>AssignMessage.SetOperationOutcomeVariablesPreR4</Name>
189+
</Step>
190+
<Step>
191+
<Name>AssignMessage.SetOperationOutcomeInvalidBusinessFunction</Name>
192+
</Step>
193+
<Step>
194+
<Name>AssignMessage.OperationOutcomeErrorResponse</Name>
195+
</Step>
196+
<Condition>(raisefault.RaiseFault.InvalidBusinessFunction.failed = true)</Condition>
197+
</FaultRule>
181198
</FaultRules>
182199
<PreFlow>
183200
<Request>
@@ -238,7 +255,7 @@
238255
<Request><!--AUTHORISED_APPLICATION business functions are not supported in user restricted flow --><Step>
239256
<Name>RaiseFault.InvalidBusinessFunction</Name>
240257
<Condition>(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")</Condition>
241-
</Step><Step>
258+
</Step> <Step>
242259
<Name>AssignMessage.Set.x-ers-access-mode-header-user-restricted</Name>
243260
</Step><Step>
244261
<Name>AssignMessage.Set.x-ers-user-id-header-user-restricted</Name>
@@ -268,17 +285,17 @@
268285
<Name>AssignMessage.Set.x-ers-amr-header</Name>
269286
</Step><Step>
270287
<Name>AssignMessage.Set.x-ers-id-assurance-level-header</Name>
271-
</Step> <Step>
288+
</Step><Step>
272289
<Condition>(request.header.x-ers-id-assurance-level LesserThan 3)</Condition>
273290
<Name>RaiseFault.401InsufficientIal</Name>
274291
</Step> {% if ALLOW_ECHO_TARGET | default(false) == true %}<Step>
275292
<Name>AssignMessage.SetEchoTarget</Name>
276293
<Condition>(request.header.echo)</Condition>
277-
</Step> {% endif %} {% if '--ft-' in (ERS_TARGET_SERVER | default('e-referrals-service-api')) %}<Step>
294+
</Step> {% endif %} {% if '--ft-' in (ERS_TARGET_SERVER | default('e-referrals-service-api')) %} <Step>
278295
<Name>AssignMessage.SetTruststore</Name>
279296
<!--Condition is implemented this way around to account for isEchoCall being null (https://docs.apigee.com/api-platform/reference/conditions-reference#behaviorofnulloperandsinconditionalstatements)-->
280297
<Condition>(isEchoCall != true )</Condition>
281-
</Step> <Step>
298+
</Step><Step>
282299
<Name>AssignMessage.SetEchoTruststore</Name>
283300
<Condition>(isEchoCall == true)</Condition>
284301
</Step> {% endif %} <Step>

tests/integration/test_app_restricted.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,42 @@ async def test_authorised_application_not_supported_for_user_restricted(
4646
f"{service_url}{_SPECIALTY_REF_DATA_URL}", headers=client_request_headers
4747
)
4848

49-
assert_error_response_with_body(response, _EXPECTED_CORRELATION_ID, 403)
49+
assert response.status_code == 403, (
50+
"Expected 403 response when calling the endpoint. But instead received a "
51+
+ response.status_code
52+
)
53+
54+
assert (
55+
response.headers[RenamedHeader.CORRELATION_ID.original]
56+
== _EXPECTED_CORRELATION_ID
57+
)
58+
59+
for renamed_header in RenamedHeader:
60+
assert renamed_header.renamed not in response.headers
61+
62+
# Verify the OperationOutcome payload
63+
response_data = response.json()
64+
assert response_data["resourceType"] == "OperationOutcome"
65+
assert response_data["meta"]["lastUpdated"] is not None
66+
assert len(response_data["meta"]["profile"]) == 1
67+
assert (
68+
response_data["meta"]["profile"][0]
69+
== "https://fhir.nhs.uk/STU3/StructureDefinition/eRS-OperationOutcome-1"
70+
)
71+
assert len(response_data["issue"]) == 1
72+
issue = response_data["issue"][0]
73+
assert issue["severity"] == "error"
74+
assert issue["code"] == "forbidden"
75+
assert issue["diagnostics"] == (
76+
"User does not have the required Business Function at the specified Organisation."
77+
)
78+
assert len(issue["details"]["coding"]) == 1
79+
issue_details = issue["details"]["coding"][0]
80+
assert (
81+
issue_details["system"]
82+
== "https://fhir.nhs.uk/STU3/CodeSystem/eRS-APIErrorCode-1"
83+
)
84+
assert issue_details["code"] == "NO_ACCESS"
5085

5186
def test_authorised_application_supported_for_app_restricted(
5287
self, app_restricted_access_code, service_url

tests/integration/test_headers.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,78 @@ async def test_headers_on_echo_target(
7777
expected_amr,
7878
)
7979

80+
@pytest.mark.asyncio
81+
@pytest.mark.parametrize(
82+
"business_function,endpoint_url,is_r4",
83+
[
84+
("PROVIDER_AUTHORISED_APPLICATION", "", False),
85+
("REFERRER_AUTHORISED_APPLICATION", "", False),
86+
("AUTHORISED_APPLICATION", "", False),
87+
("PROVIDER_AUTHORISED_APPLICATION", "/FHIR/STU3/", False),
88+
("REFERRER_AUTHORISED_APPLICATION", "/FHIR/STU3/", False),
89+
("AUTHORISED_APPLICATION", "/FHIR/STU3/", False),
90+
("PROVIDER_AUTHORISED_APPLICATION", "/FHIR/R4/", True),
91+
("REFERRER_AUTHORISED_APPLICATION", "/FHIR/R4/", True),
92+
("AUTHORISED_APPLICATION", "/FHIR/R4/", True),
93+
],
94+
)
95+
async def test_headers_on_echo_target_with_app_restricted_business_function(
96+
self, business_function, endpoint_url, is_r4, authenticate_user, service_url
97+
):
98+
user = Actor.RC
99+
access_code = await authenticate_user(user)
100+
client_request_headers = {
101+
_HEADER_ECHO: "", # enable echo target
102+
_HEADER_AUTHORIZATION: "Bearer " + access_code,
103+
_HEADER_REQUEST_ID: "DUMMY-VALUE",
104+
RenamedHeader.REFERRAL_ID.original: _EXPECTED_REFERRAL_ID,
105+
RenamedHeader.CORRELATION_ID.original: _EXPECTED_CORRELATION_ID,
106+
RenamedHeader.BUSINESS_FUNCTION.original: business_function,
107+
RenamedHeader.ODS_CODE.original: user.org_code,
108+
RenamedHeader.FILENAME.original: _EXPECTED_FILENAME,
109+
RenamedHeader.COMM_RULE_ORG.original: _EXPECTED_COMM_RULE_ORG,
110+
RenamedHeader.OBO_USER_ID.original: _EXPECTED_OBO_USER_ID,
111+
}
112+
113+
# Make the API call
114+
response = requests.get(
115+
service_url + endpoint_url, headers=client_request_headers
116+
)
117+
118+
assert response.status_code == 403, (
119+
"Expected a 403 response when attempting to call the endpoint, but instead received a "
120+
+ str(response.status_code)
121+
)
122+
123+
assert response.reason == "Forbidden"
124+
125+
# Verify the OperationOutcome payload
126+
response_data = response.json()
127+
assert response_data["resourceType"] == "OperationOutcome"
128+
assert response_data["meta"]["lastUpdated"] is not None
129+
assert len(response_data["meta"]["profile"]) == 1
130+
assert response_data["meta"]["profile"][0] == (
131+
"https://www.hl7.org/fhir/R4/operationoutcome.html"
132+
if is_r4
133+
else "https://fhir.nhs.uk/STU3/StructureDefinition/eRS-OperationOutcome-1"
134+
)
135+
assert len(response_data["issue"]) == 1
136+
issue = response_data["issue"][0]
137+
assert issue["severity"] == "error"
138+
assert issue["code"] == "forbidden"
139+
assert issue["diagnostics"] == (
140+
"User does not have the required Business Function at the specified Organisation."
141+
)
142+
assert len(issue["details"]["coding"]) == 1
143+
issue_details = issue["details"]["coding"][0]
144+
assert (
145+
issue_details["system"]
146+
== "https://fhir.nhs.uk/CodeSystem/NHSD-API-ErrorOrWarningCode"
147+
if is_r4
148+
else "https://fhir.nhs.uk/STU3/CodeSystem/eRS-APIErrorCode-1"
149+
)
150+
assert issue_details["code"] == "ACCESS_DENIED" if is_r4 else "NO_ACCESS"
151+
80152
@pytest.mark.asyncio
81153
@pytest.mark.parametrize(
82154
"endpoint_url,is_r4",
@@ -442,6 +514,8 @@ def test_unknown_access_code(
442514
response.status_code
443515
)
444516

517+
assert response.reason == "Unauthorized"
518+
445519
if not is_operation_outcome:
446520
assert len(response.content) == 0
447521
else:

0 commit comments

Comments
 (0)