onc_certification_g10_test_kit 2.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/lib/inferno/exceptions.rb +31 -0
- data/lib/inferno/ext/bloomer.rb +24 -0
- data/lib/inferno/repositiories/validators.rb +17 -0
- data/lib/inferno/repositiories/value_sets.rb +26 -0
- data/lib/inferno/terminology/bcp47.rb +95 -0
- data/lib/inferno/terminology/bcp_13.rb +26 -0
- data/lib/inferno/terminology/codesystem.rb +49 -0
- data/lib/inferno/terminology/expected_manifest.yml +1123 -0
- data/lib/inferno/terminology/fhir_package_manager.rb +69 -0
- data/lib/inferno/terminology/loader.rb +298 -0
- data/lib/inferno/terminology/tasks/check_built_terminology.rb +77 -0
- data/lib/inferno/terminology/tasks/cleanup.rb +13 -0
- data/lib/inferno/terminology/tasks/cleanup_precursors.rb +23 -0
- data/lib/inferno/terminology/tasks/count_codes_in_value_set.rb +20 -0
- data/lib/inferno/terminology/tasks/create_value_set_validators.rb +34 -0
- data/lib/inferno/terminology/tasks/download_fhir_terminology.rb +27 -0
- data/lib/inferno/terminology/tasks/download_umls.rb +109 -0
- data/lib/inferno/terminology/tasks/download_umls_notice.rb +20 -0
- data/lib/inferno/terminology/tasks/expand_value_set_to_file.rb +36 -0
- data/lib/inferno/terminology/tasks/process_umls.rb +91 -0
- data/lib/inferno/terminology/tasks/process_umls_translations.rb +85 -0
- data/lib/inferno/terminology/tasks/run_umls_jar.rb +75 -0
- data/lib/inferno/terminology/tasks/temp_dir.rb +27 -0
- data/lib/inferno/terminology/tasks/unzip_umls.rb +42 -0
- data/lib/inferno/terminology/tasks/validate_code.rb +36 -0
- data/lib/inferno/terminology/tasks.rb +11 -0
- data/lib/inferno/terminology/terminology_configuration.rb +52 -0
- data/lib/inferno/terminology/terminology_validation.rb +42 -0
- data/lib/inferno/terminology/validator.rb +64 -0
- data/lib/inferno/terminology/value_set.rb +462 -0
- data/lib/inferno/terminology.rb +16 -0
- data/lib/onc_certification_g10_test_kit/authorization_request_builder.rb +87 -0
- data/lib/onc_certification_g10_test_kit/base_token_refresh_group.rb +48 -0
- data/lib/onc_certification_g10_test_kit/bulk_data_authorization.rb +235 -0
- data/lib/onc_certification_g10_test_kit/bulk_data_group_export.rb +255 -0
- data/lib/onc_certification_g10_test_kit/bulk_data_group_export_validation.rb +474 -0
- data/lib/onc_certification_g10_test_kit/bulk_data_jwks.json +58 -0
- data/lib/onc_certification_g10_test_kit/bulk_export_validation_tester.rb +171 -0
- data/lib/onc_certification_g10_test_kit/configuration_checker.rb +104 -0
- data/lib/onc_certification_g10_test_kit/export_kick_off_performer.rb +12 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyheight.json +3772 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodytemp.json +3772 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bodyweight.json +3772 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-bp.json +6034 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-heartrate.json +3756 -0
- data/lib/onc_certification_g10_test_kit/igs/StructureDefinition-resprate.json +3756 -0
- data/lib/onc_certification_g10_test_kit/limited_scope_grant_test.rb +66 -0
- data/lib/onc_certification_g10_test_kit/multi_patient_api.rb +43 -0
- data/lib/onc_certification_g10_test_kit/patient_context_test.rb +30 -0
- data/lib/onc_certification_g10_test_kit/profile_guesser.rb +69 -0
- data/lib/onc_certification_g10_test_kit/resource_access_test.rb +96 -0
- data/lib/onc_certification_g10_test_kit/restricted_access_test.rb +12 -0
- data/lib/onc_certification_g10_test_kit/restricted_resource_type_access_group.rb +303 -0
- data/lib/onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group.rb +136 -0
- data/lib/onc_certification_g10_test_kit/smart_ehr_practitioner_app_group.rb +209 -0
- data/lib/onc_certification_g10_test_kit/smart_invalid_token_group.rb +197 -0
- data/lib/onc_certification_g10_test_kit/smart_limited_app_group.rb +123 -0
- data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group.rb +113 -0
- data/lib/onc_certification_g10_test_kit/smart_scopes_test.rb +153 -0
- data/lib/onc_certification_g10_test_kit/smart_standalone_patient_app_group.rb +177 -0
- data/lib/onc_certification_g10_test_kit/terminology_binding_validator.rb +140 -0
- data/lib/onc_certification_g10_test_kit/token_revocation_group.rb +133 -0
- data/lib/onc_certification_g10_test_kit/unauthorized_access_test.rb +25 -0
- data/lib/onc_certification_g10_test_kit/unrestricted_resource_type_access_group.rb +375 -0
- data/lib/onc_certification_g10_test_kit/version.rb +3 -0
- data/lib/onc_certification_g10_test_kit/visual_inspection_and_attestations_group.rb +470 -0
- data/lib/onc_certification_g10_test_kit/well_known_capabilities_test.rb +37 -0
- data/lib/onc_certification_g10_test_kit.rb +223 -0
- metadata +310 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
require_relative 'authorization_request_builder'
|
|
2
|
+
|
|
3
|
+
module ONCCertificationG10TestKit
|
|
4
|
+
class BulkDataAuthorization < Inferno::TestGroup
|
|
5
|
+
title 'Bulk Data Authorization'
|
|
6
|
+
short_description 'Demonstrate SMART Backend Services Authorization for Bulk Data.'
|
|
7
|
+
description <<~DESCRIPTION
|
|
8
|
+
Bulk Data servers are required to authorize clients using the
|
|
9
|
+
[Backend Service Authorization](http://hl7.org/fhir/uv/bulkdata/STU1/authorization/)
|
|
10
|
+
specification as defined in the [FHIR Bulk Data Access IG v1.0.0](http://hl7.org/fhir/uv/bulkdata/STU1/).
|
|
11
|
+
|
|
12
|
+
In this set of tests, Inferno serves as a Bulk Data client that requests authorization
|
|
13
|
+
from the Bulk Data authorization server. It also performs a number of negative tests
|
|
14
|
+
to validate that the authorization service does not improperly authorize invalid
|
|
15
|
+
requests.
|
|
16
|
+
|
|
17
|
+
This test returns an access token.
|
|
18
|
+
DESCRIPTION
|
|
19
|
+
|
|
20
|
+
id :bulk_data_authorization
|
|
21
|
+
|
|
22
|
+
input :bulk_token_endpoint,
|
|
23
|
+
title: 'Backend Services Token Endpoint',
|
|
24
|
+
description: 'The OAuth 2.0 Token Endpoint used by the Backend Services specification
|
|
25
|
+
to provide bearer tokens.'
|
|
26
|
+
input :bulk_client_id,
|
|
27
|
+
title: 'Bulk Data Client ID',
|
|
28
|
+
description: 'Client ID provided at registration to the Inferno application.'
|
|
29
|
+
input :bulk_scope,
|
|
30
|
+
title: 'Bulk Data Scopes',
|
|
31
|
+
description: 'Bulk Data Scopes provided at registration to the Inferno application.',
|
|
32
|
+
default: 'system/*.read'
|
|
33
|
+
input :bulk_encryption_method,
|
|
34
|
+
title: 'Encryption Method',
|
|
35
|
+
description: 'The server is required to suport either ES384 or RS384 encryption methods
|
|
36
|
+
for JWT signature verification. Select which method to use.',
|
|
37
|
+
type: 'radio',
|
|
38
|
+
default: 'ES384',
|
|
39
|
+
options: {
|
|
40
|
+
list_options: [
|
|
41
|
+
{
|
|
42
|
+
label: 'ES384',
|
|
43
|
+
value: 'ES384'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: 'RS384',
|
|
47
|
+
value: 'RS384'
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
output :bearer_token
|
|
52
|
+
|
|
53
|
+
http_client :token_endpoint do
|
|
54
|
+
url :bulk_token_endpoint
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
test from: :tls_version_test do
|
|
58
|
+
title 'Authorization service token endpoint secured by transport layer security'
|
|
59
|
+
description <<~DESCRIPTION
|
|
60
|
+
[§170.315(g)(10) Test
|
|
61
|
+
Procedure](https://www.healthit.gov/test-method/standardized-api-patient-and-population-services)
|
|
62
|
+
requires that all exchanges described herein between a client and a
|
|
63
|
+
server SHALL be secured using Transport Layer Security (TLS) Protocol
|
|
64
|
+
Version 1.2 (RFC5246).
|
|
65
|
+
DESCRIPTION
|
|
66
|
+
id :g10_bulk_token_tls_version
|
|
67
|
+
|
|
68
|
+
config(
|
|
69
|
+
inputs: { url: { name: :bulk_token_endpoint } },
|
|
70
|
+
options: { minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION }
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
test do
|
|
75
|
+
title 'Authorization request fails when client supplies invalid grant_type'
|
|
76
|
+
description <<~DESCRIPTION
|
|
77
|
+
The Backend Service Authorization specification defines the required fields for the
|
|
78
|
+
authorization request, made via HTTP POST to authorization token endpoint.
|
|
79
|
+
This includes the `grant_type` parameter, where the value must be `client_credentials`.
|
|
80
|
+
|
|
81
|
+
The OAuth 2.0 Authorization Framework describes the proper response for an
|
|
82
|
+
invalid request in the client credentials grant flow:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
If the request failed client authentication or is invalid, the authorization server returns an
|
|
86
|
+
error response as described in [Section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2).
|
|
87
|
+
```
|
|
88
|
+
DESCRIPTION
|
|
89
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/authorization/index.html#protocol-details'
|
|
90
|
+
|
|
91
|
+
run do
|
|
92
|
+
post_request_content = AuthorizationRequestBuilder.build(encryption_method: bulk_encryption_method,
|
|
93
|
+
scope: bulk_scope,
|
|
94
|
+
iss: bulk_client_id,
|
|
95
|
+
sub: bulk_client_id,
|
|
96
|
+
aud: bulk_token_endpoint,
|
|
97
|
+
grant_type: 'not_a_grant_type')
|
|
98
|
+
|
|
99
|
+
post({ client: :token_endpoint }.merge(post_request_content))
|
|
100
|
+
|
|
101
|
+
assert_response_status(400)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
test do
|
|
106
|
+
title 'Authorization request fails when supplied invalid client_assertion_type'
|
|
107
|
+
description <<~DESCRIPTION
|
|
108
|
+
The Backend Service Authorization specification defines the required fields for the
|
|
109
|
+
authorization request, made via HTTP POST to authorization token endpoint.
|
|
110
|
+
This includes the `client_assertion_type` parameter, where the value must be `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`.
|
|
111
|
+
|
|
112
|
+
The OAuth 2.0 Authorization Framework describes the proper response for an
|
|
113
|
+
invalid request in the client credentials grant flow:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
If the request failed client authentication or is invalid, the authorization server returns an
|
|
117
|
+
error response as described in [Section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2).
|
|
118
|
+
```
|
|
119
|
+
DESCRIPTION
|
|
120
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/authorization/index.html#protocol-details'
|
|
121
|
+
|
|
122
|
+
run do
|
|
123
|
+
post_request_content = AuthorizationRequestBuilder.build(encryption_method: bulk_encryption_method,
|
|
124
|
+
scope: bulk_scope,
|
|
125
|
+
iss: bulk_client_id,
|
|
126
|
+
sub: bulk_client_id,
|
|
127
|
+
aud: bulk_token_endpoint,
|
|
128
|
+
client_assertion_type: 'not_an_assertion_type')
|
|
129
|
+
|
|
130
|
+
post({ client: :token_endpoint }.merge(post_request_content))
|
|
131
|
+
|
|
132
|
+
assert_response_status(400)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
test do
|
|
137
|
+
title 'Authorization request fails when client supplies invalid JWT token'
|
|
138
|
+
description <<~DESCRIPTION
|
|
139
|
+
The Backend Service Authorization specification defines the required fields for the
|
|
140
|
+
authorization request, made via HTTP POST to authorization token endpoint.
|
|
141
|
+
This includes the `client_assertion` parameter, where the value must be
|
|
142
|
+
a valid JWT. The JWT SHALL include the following claims, and SHALL be signed with the client’s private key.
|
|
143
|
+
|
|
144
|
+
| JWT Claim | Required? | Description |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| iss | required | Issuer of the JWT -- the client's client_id, as determined during registration with the FHIR authorization server (note that this is the same as the value for the sub claim) |
|
|
147
|
+
| sub | required | The service's client_id, as determined during registration with the FHIR authorization server (note that this is the same as the value for the iss claim) |
|
|
148
|
+
| aud | required | The FHIR authorization server's "token URL" (the same URL to which this authentication JWT will be posted) |
|
|
149
|
+
| exp | required | Expiration time integer for this authentication JWT, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC). This time SHALL be no more than five minutes in the future. |
|
|
150
|
+
| jti | required | A nonce string value that uniquely identifies this authentication JWT. |
|
|
151
|
+
|
|
152
|
+
The OAuth 2.0 Authorization Framework describes the proper response for an
|
|
153
|
+
invalid request in the client credentials grant flow:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
If the request failed client authentication or is invalid, the authorization server returns an
|
|
157
|
+
error response as described in [Section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2).
|
|
158
|
+
```
|
|
159
|
+
DESCRIPTION
|
|
160
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/authorization/index.html#protocol-details'
|
|
161
|
+
|
|
162
|
+
run do
|
|
163
|
+
post_request_content = AuthorizationRequestBuilder.build(encryption_method: bulk_encryption_method,
|
|
164
|
+
scope: bulk_scope,
|
|
165
|
+
iss: 'not_a_valid_iss',
|
|
166
|
+
sub: bulk_client_id,
|
|
167
|
+
aud: bulk_token_endpoint)
|
|
168
|
+
|
|
169
|
+
post({ client: :token_endpoint }.merge(post_request_content))
|
|
170
|
+
|
|
171
|
+
assert_response_status([400, 401])
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
test do
|
|
176
|
+
title 'Authorization request succeeds when supplied correct information'
|
|
177
|
+
description <<~DESCRIPTION
|
|
178
|
+
If the access token request is valid and authorized, the authorization server SHALL issue an access token in response.
|
|
179
|
+
DESCRIPTION
|
|
180
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/authorization/index.html#issuing-access-tokens'
|
|
181
|
+
|
|
182
|
+
output :authentication_response
|
|
183
|
+
|
|
184
|
+
run do
|
|
185
|
+
post_request_content = AuthorizationRequestBuilder.build(encryption_method: bulk_encryption_method,
|
|
186
|
+
scope: bulk_scope,
|
|
187
|
+
iss: bulk_client_id,
|
|
188
|
+
sub: bulk_client_id,
|
|
189
|
+
aud: bulk_token_endpoint)
|
|
190
|
+
|
|
191
|
+
authentication_response = post({ client: :token_endpoint }.merge(post_request_content))
|
|
192
|
+
|
|
193
|
+
assert_response_status([200, 201])
|
|
194
|
+
|
|
195
|
+
output authentication_response: authentication_response.response_body
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
test do
|
|
200
|
+
title 'Authorization request response body contains required information encoded in JSON'
|
|
201
|
+
description <<~DESCRIPTION
|
|
202
|
+
The access token response SHALL be a JSON object with the following properties:
|
|
203
|
+
|
|
204
|
+
| Token Property | Required? | Description |
|
|
205
|
+
| --- | --- | --- |
|
|
206
|
+
| access_token | required | The access token issued by the authorization server. |
|
|
207
|
+
| token_type | required | Fixed value: bearer. |
|
|
208
|
+
| expires_in | required | The lifetime in seconds of the access token. The recommended value is 300, for a five-minute token lifetime. |
|
|
209
|
+
| scope | required | Scope of access authorized. Note that this can be different from the scopes requested by the app. |
|
|
210
|
+
DESCRIPTION
|
|
211
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/authorization/index.html#issuing-access-tokens'
|
|
212
|
+
|
|
213
|
+
input :authentication_response
|
|
214
|
+
output :bearer_token
|
|
215
|
+
|
|
216
|
+
run do
|
|
217
|
+
skip_if authentication_response.blank?, 'No authentication response received.'
|
|
218
|
+
|
|
219
|
+
assert_valid_json(authentication_response)
|
|
220
|
+
response_body = JSON.parse(authentication_response)
|
|
221
|
+
|
|
222
|
+
access_token = response_body['access_token']
|
|
223
|
+
assert access_token.present?, 'Token response did not contain access_token as required'
|
|
224
|
+
|
|
225
|
+
output bearer_token: access_token
|
|
226
|
+
|
|
227
|
+
required_keys = ['token_type', 'expires_in', 'scope']
|
|
228
|
+
|
|
229
|
+
required_keys.each do |key|
|
|
230
|
+
assert response_body[key].present?, "Token response did not contain #{key} as required"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
require_relative 'export_kick_off_performer'
|
|
2
|
+
|
|
3
|
+
module ONCCertificationG10TestKit
|
|
4
|
+
class BulkDataGroupExport < Inferno::TestGroup
|
|
5
|
+
title 'Group Compartment Export Tests'
|
|
6
|
+
short_description 'Verify that the system supports Group compartment export.'
|
|
7
|
+
description <<~DESCRIPTION
|
|
8
|
+
Verify that system level export on the Bulk Data server follow the Bulk Data Access Implementation Guide
|
|
9
|
+
DESCRIPTION
|
|
10
|
+
|
|
11
|
+
id :bulk_data_group_export
|
|
12
|
+
|
|
13
|
+
input :bearer_token
|
|
14
|
+
input :bulk_server_url,
|
|
15
|
+
title: 'Bulk Data FHIR URL',
|
|
16
|
+
description: 'The URL of the Bulk FHIR server.'
|
|
17
|
+
input :group_id,
|
|
18
|
+
title: 'Group ID',
|
|
19
|
+
description: 'The Group ID associated with the group of patients to be exported.'
|
|
20
|
+
input :bulk_timeout,
|
|
21
|
+
title: 'Export Times Out after (1-600)',
|
|
22
|
+
description: 'While testing, Inferno waits for the server to complete the exporting task.
|
|
23
|
+
If the calculated totalTime is greater than the timeout value specified here,
|
|
24
|
+
Inferno bulk client stops testing. Please enter an integer for the maximum wait time in seconds.
|
|
25
|
+
If timeout is less than 1, Inferno uses default value 180.
|
|
26
|
+
If the timeout is greater than 600 (10 minutes), Inferno uses the maximum value 600.',
|
|
27
|
+
default: 180
|
|
28
|
+
|
|
29
|
+
output :requires_access_token, :status_output, :bulk_download_url
|
|
30
|
+
|
|
31
|
+
fhir_client :bulk_server do
|
|
32
|
+
url :bulk_server_url
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
http_client :bulk_server do
|
|
36
|
+
url :bulk_server_url
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
test from: :tls_version_test do
|
|
40
|
+
title 'Bulk Data Server is secured by transport layer security'
|
|
41
|
+
description <<~DESCRIPTION
|
|
42
|
+
[§170.315(g)(10) Test
|
|
43
|
+
Procedure](https://www.healthit.gov/test-method/standardized-api-patient-and-population-services)
|
|
44
|
+
requires that all exchanges described herein between a client and a
|
|
45
|
+
server SHALL be secured using Transport Layer Security (TLS) Protocol
|
|
46
|
+
Version 1.2 (RFC5246).
|
|
47
|
+
DESCRIPTION
|
|
48
|
+
id :g10_bulk_data_server_tls_version
|
|
49
|
+
|
|
50
|
+
config(
|
|
51
|
+
inputs: { url: { name: :bulk_server_url } },
|
|
52
|
+
options: { minimum_allowed_version: OpenSSL::SSL::TLS1_2_VERSION }
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
test do
|
|
57
|
+
title 'Bulk Data Server declares support for Group export operation in CapabilityStatement'
|
|
58
|
+
description <<~DESCRIPTION
|
|
59
|
+
The Bulk Data Server SHALL declare support for Group/[id]/$export operation in its server CapabilityStatement
|
|
60
|
+
DESCRIPTION
|
|
61
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/OperationDefinition-group-export.html'
|
|
62
|
+
|
|
63
|
+
run do
|
|
64
|
+
fhir_get_capability_statement(client: :bulk_server)
|
|
65
|
+
assert_response_status([200, 201])
|
|
66
|
+
|
|
67
|
+
assert_valid_json(request.response_body)
|
|
68
|
+
capability_statement = FHIR.from_contents(request.response_body)
|
|
69
|
+
|
|
70
|
+
has_export_operation = capability_statement&.rest&.any? do |rest|
|
|
71
|
+
rest.resource&.any? do |resource|
|
|
72
|
+
resource.type == 'Group' &&
|
|
73
|
+
resource.respond_to?(:operation) &&
|
|
74
|
+
resource.operation&.find do |operation|
|
|
75
|
+
operation.definition&.match(%r{^http://hl7.org/fhir/uv/bulkdata/OperationDefinition/group-export(\|\S+)?})
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
assert has_export_operation,
|
|
81
|
+
'Server CapabilityStatement did not declare support for export operation in Group resource'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
test do
|
|
86
|
+
title 'Bulk Data Server rejects $export request without authorization'
|
|
87
|
+
description <<~DESCRIPTION
|
|
88
|
+
The FHIR server SHALL limit the data returned to only those FHIR resources for which the client is authorized.
|
|
89
|
+
|
|
90
|
+
[FHIR R4 Security](http://build.fhir.org/security.html#AccessDenied) and
|
|
91
|
+
[The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750#section-3.1)
|
|
92
|
+
recommend using HTTP status code 401 for invalid token but also allow the actual result be controlled by policy and context.
|
|
93
|
+
DESCRIPTION
|
|
94
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/export/index.html#bulk-data-kick-off-request'
|
|
95
|
+
|
|
96
|
+
include ExportKickOffPerformer
|
|
97
|
+
|
|
98
|
+
run do
|
|
99
|
+
skip_if bearer_token.blank?, 'Could not verify this functionality when bearer token is not set'
|
|
100
|
+
|
|
101
|
+
perform_export_kick_off_request(use_token: false)
|
|
102
|
+
assert_response_status([400, 401])
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
test do
|
|
107
|
+
title 'Bulk Data Server returns "202 Accepted" and "Content-location" for $export operation'
|
|
108
|
+
description <<~DESCRIPTION
|
|
109
|
+
Response - Success
|
|
110
|
+
|
|
111
|
+
* HTTP Status Code of 202 Accepted
|
|
112
|
+
* Content-Location header with the absolute URL of an endpoint for subsequent status requests (polling location)
|
|
113
|
+
DESCRIPTION
|
|
114
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/export/index.html#response---success'
|
|
115
|
+
|
|
116
|
+
include ExportKickOffPerformer
|
|
117
|
+
|
|
118
|
+
output :polling_url
|
|
119
|
+
|
|
120
|
+
run do
|
|
121
|
+
perform_export_kick_off_request
|
|
122
|
+
assert_response_status(202)
|
|
123
|
+
|
|
124
|
+
polling_url = request.response_header('content-location')&.value
|
|
125
|
+
assert polling_url.present?, 'Export response headers did not include "Content-Location"'
|
|
126
|
+
|
|
127
|
+
output polling_url: polling_url
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
test do
|
|
132
|
+
title 'Bulk Data Server returns "202 Accepted" or "200 OK" for status check'
|
|
133
|
+
description <<~DESCRIPTION
|
|
134
|
+
Clients SHOULD follow an exponential backoff approach when polling for status. Servers SHOULD respond with
|
|
135
|
+
|
|
136
|
+
* In-Progress Status: HTTP Status Code of 202 Accepted
|
|
137
|
+
* Complete Status: HTTP status of 200 OK and Content-Type header of application/json
|
|
138
|
+
|
|
139
|
+
The JSON object of Complete Status SHALL contain these required field:
|
|
140
|
+
|
|
141
|
+
* transactionTime, request, requiresAccessToken, output, and error
|
|
142
|
+
DESCRIPTION
|
|
143
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/export/index.html#bulk-data-status-request'
|
|
144
|
+
|
|
145
|
+
input :polling_url
|
|
146
|
+
|
|
147
|
+
output :status_response, :requires_access_token
|
|
148
|
+
|
|
149
|
+
run do
|
|
150
|
+
skip 'Server response did not have Content-Location in header' unless polling_url.present?
|
|
151
|
+
|
|
152
|
+
timeout = bulk_timeout.to_i
|
|
153
|
+
|
|
154
|
+
if !timeout.positive?
|
|
155
|
+
timeout = 180
|
|
156
|
+
elsif timeout > 600
|
|
157
|
+
timeout = 600
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
wait_time = 1
|
|
161
|
+
start = Time.now
|
|
162
|
+
|
|
163
|
+
loop do
|
|
164
|
+
get(polling_url, headers: { authorization: "Bearer #{bearer_token}" })
|
|
165
|
+
|
|
166
|
+
retry_after_val = request.response_header('retry-after')&.value.to_i
|
|
167
|
+
|
|
168
|
+
wait_time = retry_after_val.positive? ? retry_after_val : wait_time *= 2
|
|
169
|
+
|
|
170
|
+
seconds_used = Time.now - start + wait_time
|
|
171
|
+
|
|
172
|
+
break if response[:status] != 202 || seconds_used > timeout
|
|
173
|
+
|
|
174
|
+
sleep wait_time
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
skip "Server took more than #{timeout} seconds to process the request." if response[:status] == 202
|
|
178
|
+
assert_response_status(200)
|
|
179
|
+
|
|
180
|
+
assert request.response_header('content-type')&.value&.include?('application/json'),
|
|
181
|
+
'Content-Type not application/json'
|
|
182
|
+
|
|
183
|
+
response_body = JSON.parse(response[:body])
|
|
184
|
+
|
|
185
|
+
['transactionTime', 'request', 'requiresAccessToken', 'output', 'error'].each do |key|
|
|
186
|
+
assert response_body.key?(key), "Complete Status response did not contain \"#{key}\" as required"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
output requires_access_token: response_body['requiresAccessToken'].to_s.downcase
|
|
190
|
+
output status_response: response[:body]
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
test do
|
|
195
|
+
title 'Bulk Data Server returns output with type and url for status complete'
|
|
196
|
+
description <<~DESCRIPTION
|
|
197
|
+
The value of output field is an array of file items with one entry for each generated file.
|
|
198
|
+
If no resources are returned from the kick-off request, the server SHOULD return an empty array.
|
|
199
|
+
|
|
200
|
+
Each file item SHALL contain the following fields:
|
|
201
|
+
|
|
202
|
+
* type - the FHIR resource type that is contained in the file.
|
|
203
|
+
|
|
204
|
+
Each file SHALL contain resources of only one type, but a server MAY create more than one file for each resource type returned.
|
|
205
|
+
|
|
206
|
+
* url - the path to the file. The format of the file SHOULD reflect that requested in the _outputFormat parameter of the initial kick-off request.
|
|
207
|
+
DESCRIPTION
|
|
208
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/export/index.html#response---complete-status'
|
|
209
|
+
|
|
210
|
+
input :status_response
|
|
211
|
+
|
|
212
|
+
output :status_output, :bulk_download_url
|
|
213
|
+
|
|
214
|
+
run do
|
|
215
|
+
assert status_response.present?, 'Bulk Data Server status response not found'
|
|
216
|
+
|
|
217
|
+
status_output = JSON.parse(status_response)['output']
|
|
218
|
+
assert status_output, 'Bulk Data Server status response does not contain output'
|
|
219
|
+
|
|
220
|
+
output status_output: status_output.to_json,
|
|
221
|
+
bulk_download_url: status_output[0]['url']
|
|
222
|
+
|
|
223
|
+
status_output.each do |file|
|
|
224
|
+
['type', 'url'].each do |key|
|
|
225
|
+
assert file.key?(key), "Output file did not contain \"#{key}\" as required"
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
test do
|
|
232
|
+
title 'Bulk Data Server returns "202 Accepted" for delete request'
|
|
233
|
+
description <<~DESCRIPTION
|
|
234
|
+
After a bulk data request has been started, a client MAY send a delete request to the URL provided in the Content-Location header to cancel the request.
|
|
235
|
+
Bulk Data Server MUST support client's delete request and return HTTP Status Code of "202 Accepted"
|
|
236
|
+
DESCRIPTION
|
|
237
|
+
# link 'http://hl7.org/fhir/uv/bulkdata/export/index.html#bulk-data-delete-request'
|
|
238
|
+
|
|
239
|
+
include ExportKickOffPerformer
|
|
240
|
+
|
|
241
|
+
run do
|
|
242
|
+
perform_export_kick_off_request
|
|
243
|
+
assert_response_status(202)
|
|
244
|
+
|
|
245
|
+
polling_url = request.response_header('content-location')&.value
|
|
246
|
+
assert polling_url.present?, 'Export response header did not include "Content-Location"'
|
|
247
|
+
|
|
248
|
+
headers = { accept: 'application/json', authorization: "Bearer #{bearer_token}" }
|
|
249
|
+
|
|
250
|
+
delete(polling_url, headers: headers)
|
|
251
|
+
assert_response_status(202)
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|