Skip to content

Commit 5d75bb7

Browse files
[ERSSUP-68202]-[JW]-[Return 403 - Forbidden for missing ASID in application]-[DMW]
1 parent 1f42353 commit 5d75bb7

8 files changed

+273
-5
lines changed

proxies/live/apiproxy/policies/AssignMessage.AuthenticationOperationOutcomeErrorResponse.xml renamed to proxies/live/apiproxy/policies/AssignMessage.OperationOutcomeErrorResponse.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
<AssignMessage async="false" continueOnError="false" enabled="true" name="AssignMessage.AuthenticationOperationOutcomeErrorResponse">
1+
<AssignMessage async="false" continueOnError="false" enabled="true" name="AssignMessage.OperationOutcomeErrorResponse">
22
<Set>
3-
<StatusCode>401</StatusCode>
3+
<StatusCode>{status_code}</StatusCode>
44
<ReasonPhrase>Unauthorized</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>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<AssignMessage enabled="true" name="AssignMessage.SetOperationOutcomeIssueCodeLogin">
2+
<AssignVariable>
3+
<Name>status_code</Name>
4+
<Value>401</Value>
5+
</AssignVariable>
26
<AssignVariable>
37
<Name>op_outcome_issue_code</Name>
48
<Value>login</Value>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<AssignMessage enabled="true" name="AssignMessage.SetOperationOutcomeIssueIal">
2+
<AssignVariable>
3+
<Name>status_code</Name>
4+
<Value>401</Value>
5+
</AssignVariable>
26
<AssignVariable>
37
<Name>op_outcome_issue_code</Name>
48
<Value>forbidden</Value>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<AssignMessage enabled="true" name="AssignMessage.SetOperationOutcomeMissingAsid">
2+
<AssignVariable>
3+
<Name>op_outcome_issue_code</Name>
4+
<Value>forbidden</Value>
5+
</AssignVariable>
6+
<AssignVariable>
7+
<Name>faultstring</Name>
8+
<Value>ASID is not configured in the application</Value>
9+
</AssignVariable>
10+
<AssignVariable>
11+
<Name>status_code</Name>
12+
<Value>403</Value>
13+
</AssignVariable>
14+
</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.MissingAsid">
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: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<Name>AssignMessage.SetOperationOutcomeIssueCodeLogin</Name>
1616
</Step>
1717
<Step>
18-
<Name>AssignMessage.AuthenticationOperationOutcomeErrorResponse</Name>
18+
<Name>AssignMessage.OperationOutcomeErrorResponse</Name>
1919
</Step>
2020
<Condition>(oauthV2.OauthV2.VerifyAccessToken.failed = true) and (isFhirR4Path = true)</Condition>
2121
</FaultRule>
@@ -43,7 +43,7 @@
4343
<Condition>aalError != true</Condition>
4444
</Step>
4545
<Step>
46-
<Name>AssignMessage.AuthenticationOperationOutcomeErrorResponse</Name>
46+
<Name>AssignMessage.OperationOutcomeErrorResponse</Name>
4747
<Condition>aalError = true</Condition>
4848
</Step>
4949
<Condition>(oauthV2.OauthV2.VerifyAccessToken.failed = true) and (isFhirR4Path = false)</Condition>
@@ -85,10 +85,27 @@
8585
<Name>AssignMessage.SetOperationOutcomeIssueIal</Name>
8686
</Step>
8787
<Step>
88-
<Name>AssignMessage.AuthenticationOperationOutcomeErrorResponse</Name>
88+
<Name>AssignMessage.OperationOutcomeErrorResponse</Name>
8989
</Step>
9090
<Condition>(raisefault.RaiseFault.401InsufficientIal.failed = true)</Condition>
9191
</FaultRule>
92+
<FaultRule name="missing_asid">
93+
<Step>
94+
<Condition>(isFhirR4Path = true)</Condition>
95+
<Name>AssignMessage.SetOperationOutcomeVariablesR4</Name>
96+
</Step>
97+
<Step>
98+
<Condition>(isFhirR4Path = false)</Condition>
99+
<Name>AssignMessage.SetOperationOutcomeVariablesPreR4</Name>
100+
</Step>
101+
<Step>
102+
<Name>AssignMessage.SetOperationOutcomeMissingAsid</Name>
103+
</Step>
104+
<Step>
105+
<Name>AssignMessage.OperationOutcomeErrorResponse</Name>
106+
</Step>
107+
<Condition>(raisefault.RaiseFault.MissingAsid.failed = true)</Condition>
108+
</FaultRule>
92109
</FaultRules>
93110
<PreFlow>
94111
<Request>
@@ -101,6 +118,10 @@
101118
<Step>
102119
<Name>OauthV2.VerifyAccessToken</Name>
103120
</Step>
121+
<Step>
122+
<Name>RaiseFault.MissingAsid</Name>
123+
<Condition>(app.asid == null) Or (app.asid == "")</Condition>
124+
</Step>
104125
<Step>
105126
<Name>AssignMessage.PopulateAsidFromApp</Name>
106127
</Step>

tests/conftest.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,77 @@ def _update_function(append_scopes: Collection[str]):
167167
return _update_function
168168

169169

170+
@pytest.fixture
171+
def delete_user_restricted_app_attr(
172+
user_restricted_app, client: ApigeeClient
173+
) -> Callable[[Collection[str]], Generator[Dict[str, str], None, None]]:
174+
@contextmanager
175+
def _update_function(attr):
176+
app_api = DeveloperAppsAPI(client=client)
177+
app = app_api.get_app_by_name(
178+
179+
app_name=user_restricted_app["name"],
180+
)
181+
182+
warnings.warn(f"Existing app = {app}")
183+
184+
existing_attributes = app_api.get_app_attributes(
185+
email="[email protected]", app_name=app["name"]
186+
)
187+
188+
yield app_api.delete_app_attribute_by_name(
189+
190+
app_name=app["name"],
191+
attribute_name=attr,
192+
)
193+
194+
# reset the product once the context manager has been closed.
195+
196+
app_api.post_app_attributes(
197+
198+
app_name=app["name"],
199+
body=existing_attributes,
200+
)
201+
202+
return _update_function
203+
204+
205+
@pytest.fixture
206+
def update_user_restricted_app_attr(
207+
user_restricted_app, client: ApigeeClient
208+
) -> Callable[[Collection[str]], Generator[Dict[str, str], None, None]]:
209+
@contextmanager
210+
def _update_function(attr, value):
211+
app_api = DeveloperAppsAPI(client=client)
212+
app = app_api.get_app_by_name(
213+
214+
app_name=user_restricted_app["name"],
215+
)
216+
217+
warnings.warn(f"Existing app = {app}")
218+
219+
existing_attributes = app_api.get_app_attributes(
220+
email="[email protected]", app_name=app["name"]
221+
)
222+
223+
yield app_api.post_app_attribute_by_name(
224+
225+
app_name=app["name"],
226+
attribute_name=attr,
227+
body={"value": value},
228+
)
229+
230+
# reset the product once the context manager has been closed.
231+
232+
app_api.post_app_attributes(
233+
234+
app_name=app["name"],
235+
body=existing_attributes,
236+
)
237+
238+
return _update_function
239+
240+
170241
@pytest.fixture
171242
def make_product(client, environment, service_name):
172243
async def _make_product(product_scopes):

tests/integration/test_headers.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,3 +517,147 @@ async def test_access_code_not_supported(
517517

518518
for renamed_header in RenamedHeader:
519519
assert renamed_header.renamed not in client_response_headers
520+
521+
@pytest.mark.asyncio
522+
@pytest.mark.parametrize(
523+
"user, endpoint_url, is_fhir_4",
524+
[
525+
(Actor.RC, "/FHIR/R4/", True),
526+
(Actor.AAL2_USER, "/FHIR/R4/", True),
527+
(Actor.RC, "/FHIR/STU3/", False),
528+
(Actor.AAL2_USER, "/FHIR/STU3/", False),
529+
],
530+
)
531+
async def test_headers_on_echo_target_no_asid(
532+
self,
533+
authenticate_user,
534+
endpoint_url,
535+
is_fhir_4,
536+
service_url,
537+
delete_user_restricted_app_attr,
538+
user: Actor,
539+
):
540+
with delete_user_restricted_app_attr("asid"):
541+
542+
access_code = await authenticate_user(user)
543+
544+
client_request_headers = {
545+
_HEADER_ECHO: "", # enable echo target
546+
_HEADER_AUTHORIZATION: "Bearer " + access_code,
547+
_HEADER_REQUEST_ID: "DUMMY-VALUE",
548+
RenamedHeader.REFERRAL_ID.original: _EXPECTED_REFERRAL_ID,
549+
RenamedHeader.CORRELATION_ID.original: _EXPECTED_CORRELATION_ID,
550+
RenamedHeader.BUSINESS_FUNCTION.original: user.business_function,
551+
RenamedHeader.ODS_CODE.original: user.org_code,
552+
RenamedHeader.FILENAME.original: _EXPECTED_FILENAME,
553+
RenamedHeader.COMM_RULE_ORG.original: _EXPECTED_COMM_RULE_ORG,
554+
RenamedHeader.OBO_USER_ID.original: _EXPECTED_OBO_USER_ID,
555+
}
556+
557+
# Make the API call
558+
response = requests.get(
559+
f"{service_url}{endpoint_url}", headers=client_request_headers
560+
)
561+
# Verify the status
562+
assert (
563+
response.status_code == 403
564+
), "Expected a 403 when accessing the api but got " + str(
565+
response.status_code
566+
)
567+
568+
response_data = response.json()
569+
assert response_data["resourceType"] == "OperationOutcome"
570+
assert response_data["meta"]["lastUpdated"] is not None
571+
assert response_data["meta"]["profile"][0] == (
572+
"https://www.hl7.org/fhir/R4/operationoutcome.html"
573+
if is_fhir_4
574+
else "https://fhir.nhs.uk/STU3/StructureDefinition/eRS-OperationOutcome-1"
575+
)
576+
assert len(response_data["issue"]) == 1
577+
issue = response_data["issue"][0]
578+
assert issue["severity"] == "error"
579+
assert issue["code"] == "forbidden"
580+
assert issue["diagnostics"] == "ASID is not configured in the application"
581+
assert len(issue["details"]["coding"]) == 1
582+
issue_details = issue["details"]["coding"][0]
583+
assert (
584+
issue_details["system"]
585+
== "https://fhir.nhs.uk/CodeSystem/NHSD-API-ErrorOrWarningCode"
586+
if is_fhir_4
587+
else "https://fhir.nhs.uk/STU3/CodeSystem/eRS-APIErrorCode-1"
588+
)
589+
assert (
590+
issue_details["code"] == "ACCESS_DENIED" if is_fhir_4 else "NO_ACCESS"
591+
)
592+
593+
@pytest.mark.asyncio
594+
@pytest.mark.parametrize(
595+
"user, endpoint_url, is_fhir_4",
596+
[
597+
(Actor.RC, "/FHIR/R4/", True),
598+
(Actor.AAL2_USER, "/FHIR/R4/", True),
599+
(Actor.RC, "/FHIR/STU3/", False),
600+
(Actor.AAL2_USER, "/FHIR/STU3/", False),
601+
],
602+
)
603+
async def test_headers_on_echo_target_empty_asid(
604+
self,
605+
authenticate_user,
606+
endpoint_url,
607+
is_fhir_4,
608+
service_url,
609+
update_user_restricted_app_attr,
610+
user: Actor,
611+
):
612+
with update_user_restricted_app_attr("asid", ""):
613+
614+
access_code = await authenticate_user(user)
615+
616+
client_request_headers = {
617+
_HEADER_ECHO: "", # enable echo target
618+
_HEADER_AUTHORIZATION: "Bearer " + access_code,
619+
_HEADER_REQUEST_ID: "DUMMY-VALUE",
620+
RenamedHeader.REFERRAL_ID.original: _EXPECTED_REFERRAL_ID,
621+
RenamedHeader.CORRELATION_ID.original: _EXPECTED_CORRELATION_ID,
622+
RenamedHeader.BUSINESS_FUNCTION.original: user.business_function,
623+
RenamedHeader.ODS_CODE.original: user.org_code,
624+
RenamedHeader.FILENAME.original: _EXPECTED_FILENAME,
625+
RenamedHeader.COMM_RULE_ORG.original: _EXPECTED_COMM_RULE_ORG,
626+
RenamedHeader.OBO_USER_ID.original: _EXPECTED_OBO_USER_ID,
627+
}
628+
629+
# Make the API call
630+
response = requests.get(
631+
f"{service_url}{endpoint_url}", headers=client_request_headers
632+
)
633+
# Verify the status
634+
assert (
635+
response.status_code == 403
636+
), "Expected a 403 when accessing the api but got " + str(
637+
response.status_code
638+
)
639+
640+
response_data = response.json()
641+
assert response_data["resourceType"] == "OperationOutcome"
642+
assert response_data["meta"]["lastUpdated"] is not None
643+
assert response_data["meta"]["profile"][0] == (
644+
"https://www.hl7.org/fhir/R4/operationoutcome.html"
645+
if is_fhir_4
646+
else "https://fhir.nhs.uk/STU3/StructureDefinition/eRS-OperationOutcome-1"
647+
)
648+
assert len(response_data["issue"]) == 1
649+
issue = response_data["issue"][0]
650+
assert issue["severity"] == "error"
651+
assert issue["code"] == "forbidden"
652+
assert issue["diagnostics"] == "ASID is not configured in the application"
653+
assert len(issue["details"]["coding"]) == 1
654+
issue_details = issue["details"]["coding"][0]
655+
assert (
656+
issue_details["system"]
657+
== "https://fhir.nhs.uk/CodeSystem/NHSD-API-ErrorOrWarningCode"
658+
if is_fhir_4
659+
else "https://fhir.nhs.uk/STU3/CodeSystem/eRS-APIErrorCode-1"
660+
)
661+
assert (
662+
issue_details["code"] == "ACCESS_DENIED" if is_fhir_4 else "NO_ACCESS"
663+
)

0 commit comments

Comments
 (0)