smart_app_launch_test_kit 0.5.1 → 0.6.1
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 +4 -4
- data/config/presets/SMART_RunClientAgainstServer.json.erb +31 -0
- data/config/presets/SMART_RunServerAgainstClient.json.erb +42 -0
- data/config/presets/inferno_reference_server_preset.json +15 -86
- data/config/presets/inferno_reference_server_stu2_2_preset.json +20 -69
- data/config/presets/inferno_reference_server_stu2_preset.json +20 -69
- data/lib/smart_app_launch/app_redirect_test.rb +12 -44
- data/lib/smart_app_launch/app_redirect_test_stu2.rb +2 -17
- data/lib/smart_app_launch/backend_services_authorization_group.rb +33 -59
- data/lib/smart_app_launch/backend_services_authorization_request_builder.rb +22 -9
- data/lib/smart_app_launch/backend_services_authorization_request_success_test.rb +32 -24
- data/lib/smart_app_launch/backend_services_authorization_response_body_test.rb +23 -5
- data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +30 -25
- data/lib/smart_app_launch/backend_services_invalid_grant_type_test.rb +30 -24
- data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +31 -26
- data/lib/smart_app_launch/client_assertion_builder.rb +27 -12
- data/lib/smart_app_launch/client_stu2_2_suite.rb +79 -0
- data/lib/smart_app_launch/client_suite/client_access_group.rb +26 -0
- data/lib/smart_app_launch/client_suite/client_access_interaction_test.rb +64 -0
- data/lib/smart_app_launch/client_suite/client_registration_group.rb +15 -0
- data/lib/smart_app_launch/client_suite/client_registration_verification_test.rb +52 -0
- data/lib/smart_app_launch/client_suite/client_token_request_verification_test.rb +146 -0
- data/lib/smart_app_launch/client_suite/client_token_use_verification_test.rb +47 -0
- data/lib/smart_app_launch/cors_openid_fhir_user_claim_test.rb +2 -2
- data/lib/smart_app_launch/cors_token_exchange_test.rb +2 -2
- data/lib/smart_app_launch/discovery_stu1_group.rb +6 -2
- data/lib/smart_app_launch/docs/demo/FHIR Request.postman_collection.json +81 -0
- data/lib/smart_app_launch/docs/smart_stu2_2_client_suite_description.md +121 -0
- data/lib/smart_app_launch/ehr_launch_group.rb +41 -24
- data/lib/smart_app_launch/ehr_launch_group_stu2.rb +26 -10
- data/lib/smart_app_launch/ehr_launch_group_stu2_2.rb +0 -16
- data/lib/smart_app_launch/endpoints/echoing_fhir_responder.rb +52 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/token.rb +27 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server.rb +217 -0
- data/lib/smart_app_launch/metadata.rb +2 -2
- data/lib/smart_app_launch/openid_fhir_user_claim_test.rb +5 -4
- data/lib/smart_app_launch/openid_token_payload_test.rb +6 -8
- data/lib/smart_app_launch/smart_stu1_suite.rb +32 -24
- data/lib/smart_app_launch/smart_stu2_2_suite.rb +57 -30
- data/lib/smart_app_launch/smart_stu2_suite.rb +57 -31
- data/lib/smart_app_launch/smart_tls_test.rb +14 -0
- data/lib/smart_app_launch/standalone_launch_group.rb +42 -25
- data/lib/smart_app_launch/standalone_launch_group_stu2.rb +26 -10
- data/lib/smart_app_launch/standalone_launch_group_stu2_2.rb +0 -16
- data/lib/smart_app_launch/tags.rb +7 -0
- data/lib/smart_app_launch/token_exchange_stu2_2_test.rb +5 -17
- data/lib/smart_app_launch/token_exchange_stu2_test.rb +8 -67
- data/lib/smart_app_launch/token_exchange_test.rb +18 -38
- data/lib/smart_app_launch/token_introspection_access_token_group.rb +12 -4
- data/lib/smart_app_launch/token_introspection_access_token_group_stu2_2.rb +9 -1
- data/lib/smart_app_launch/token_introspection_group.rb +2 -4
- data/lib/smart_app_launch/token_introspection_request_group.rb +2 -4
- data/lib/smart_app_launch/token_introspection_response_group.rb +64 -49
- data/lib/smart_app_launch/token_refresh_body_test.rb +9 -2
- data/lib/smart_app_launch/token_refresh_stu2_test.rb +10 -17
- data/lib/smart_app_launch/token_refresh_test.rb +19 -20
- data/lib/smart_app_launch/token_response_body_test.rb +14 -4
- data/lib/smart_app_launch/token_response_body_test_stu2_2.rb +3 -2
- data/lib/smart_app_launch/urls.rb +40 -0
- data/lib/smart_app_launch/version.rb +2 -2
- data/lib/smart_app_launch/well_known_endpoint_test.rb +11 -1
- data/lib/smart_app_launch_test_kit.rb +1 -0
- metadata +21 -4
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative '../urls'
|
2
|
+
require_relative '../endpoints/mock_smart_server'
|
3
|
+
|
4
|
+
module SMARTAppLaunch
|
5
|
+
class SMARTClientAccessInteraction < Inferno::Test
|
6
|
+
include URLs
|
7
|
+
|
8
|
+
id :smart_client_access_interaction
|
9
|
+
title 'Perform SMART-secured Access'
|
10
|
+
description %(
|
11
|
+
During this test, Inferno will wait for the client to access data
|
12
|
+
using a SMART token obtained during earlier tests.
|
13
|
+
)
|
14
|
+
input :client_id,
|
15
|
+
title: 'Client Id',
|
16
|
+
type: 'text',
|
17
|
+
locked: true,
|
18
|
+
description: %(
|
19
|
+
The registered Client Id for use in obtaining access tokens.
|
20
|
+
Create a new session if you need to change this value.
|
21
|
+
)
|
22
|
+
input :smart_jwk_set,
|
23
|
+
title: 'JSON Web Key Set (JWKS)',
|
24
|
+
type: 'textarea',
|
25
|
+
optional: true,
|
26
|
+
locked: true,
|
27
|
+
description: %(
|
28
|
+
The SMART client's JSON Web Key Set in the form of either a publicly accessible url
|
29
|
+
containing the JWKS, or the raw JWKS JSON. Must include the key(s) Inferno will need to
|
30
|
+
verify signatures on token requests made by the client.
|
31
|
+
Create a new session if you need to change this value.
|
32
|
+
)
|
33
|
+
input :echoed_fhir_response,
|
34
|
+
title: 'FHIR Response to Echo',
|
35
|
+
type: 'textarea',
|
36
|
+
description: %(
|
37
|
+
JSON representation of a FHIR resource for Inferno to echo when a request
|
38
|
+
is made to the simulated FHIR server. The provided content will be echoed
|
39
|
+
back exactly and no check will be made that it is appropriate for the request
|
40
|
+
made. If nothing is provided, an OperationOutcome will be returned.
|
41
|
+
),
|
42
|
+
optional: true
|
43
|
+
|
44
|
+
run do
|
45
|
+
wait(
|
46
|
+
identifier: client_id,
|
47
|
+
message: %(
|
48
|
+
**Access**
|
49
|
+
|
50
|
+
Use the registered client id (#{client_id}) to obtain an access
|
51
|
+
token using SMART Backend Services
|
52
|
+
and use that token to access a FHIR endpoint under the simulated server's base URL
|
53
|
+
|
54
|
+
`#{client_fhir_base_url}`
|
55
|
+
|
56
|
+
Inferno will echo the response provided in the **FHIR Response to Echo** input.
|
57
|
+
|
58
|
+
[Click here](#{client_resume_pass_url}?token=#{client_id}) once you performed
|
59
|
+
the access.
|
60
|
+
)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'client_registration_verification_test'
|
2
|
+
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class SMARTClientRegistration < Inferno::TestGroup
|
5
|
+
id :smart_client_registration
|
6
|
+
title 'Client Registration'
|
7
|
+
description %(
|
8
|
+
During these tests, Inferno will verify the registration details provided as inputs,
|
9
|
+
including the client's JSON Web Key Set.
|
10
|
+
)
|
11
|
+
run_as_group
|
12
|
+
|
13
|
+
test from: :smart_client_registration_verification
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../endpoints/mock_smart_server'
|
3
|
+
|
4
|
+
module SMARTAppLaunch
|
5
|
+
class SMARTClientRegistrationVerification < Inferno::Test
|
6
|
+
|
7
|
+
id :smart_client_registration_verification
|
8
|
+
title 'Verify SMART Registration'
|
9
|
+
description %(
|
10
|
+
During this test, Inferno will verify that the SMART registration details
|
11
|
+
provided are conformant.
|
12
|
+
)
|
13
|
+
input :smart_jwk_set,
|
14
|
+
title: 'SMART JSON Web Key Set (JWKS)',
|
15
|
+
type: 'textarea',
|
16
|
+
description: %(
|
17
|
+
The SMART client's JSON Web Key Set including the key(s) Inferno will need to
|
18
|
+
verify signatures on token requests made by the client. May be provided as either
|
19
|
+
a publicly accessible url containing the JWKS, or the raw JWKS JSON.
|
20
|
+
)
|
21
|
+
input :client_id,
|
22
|
+
title: 'Client Id',
|
23
|
+
type: 'text',
|
24
|
+
optional: true,
|
25
|
+
description: %(
|
26
|
+
If a particular client id is desired, put it here. Otherwise a
|
27
|
+
default of the Inferno session id will be used.
|
28
|
+
)
|
29
|
+
|
30
|
+
output :client_id
|
31
|
+
|
32
|
+
run do
|
33
|
+
omit_if smart_jwk_set.blank?, # for re-use: mark the smart_jwk_set input as optional when importing to enable
|
34
|
+
'Not configured for SMART authentication.'
|
35
|
+
|
36
|
+
if client_id.blank?
|
37
|
+
client_id = test_session_id
|
38
|
+
output(client_id:)
|
39
|
+
end
|
40
|
+
|
41
|
+
jwks_warnings = []
|
42
|
+
parsed_smart_jwk_set = MockSMARTServer.jwk_set(smart_jwk_set, jwks_warnings)
|
43
|
+
jwks_warnings.each { |warning| add_message('warning', warning) }
|
44
|
+
|
45
|
+
assert parsed_smart_jwk_set.length.positive?, 'JWKS content does not include any valid keys.'
|
46
|
+
|
47
|
+
# TODO: add key-specific verification per end of https://build.fhir.org/ig/HL7/smart-app-launch/client-confidential-asymmetric.html#registering-a-client-communicating-public-keys
|
48
|
+
|
49
|
+
assert messages.none? { |msg| msg[:type] == 'error' }, 'Invalid key set provided. See messages for details'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../urls'
|
3
|
+
require_relative '../endpoints/mock_smart_server'
|
4
|
+
|
5
|
+
module SMARTAppLaunch
|
6
|
+
class SMARTClientTokenRequestVerification < Inferno::Test
|
7
|
+
include URLs
|
8
|
+
|
9
|
+
id :smart_client_token_request_verification
|
10
|
+
title 'Verify SMART Token Requests'
|
11
|
+
description %(
|
12
|
+
Check that SMART token requests are conformant.
|
13
|
+
)
|
14
|
+
|
15
|
+
input :client_id,
|
16
|
+
title: 'Client Id',
|
17
|
+
type: 'text',
|
18
|
+
optional: false,
|
19
|
+
locked: true,
|
20
|
+
description: %(
|
21
|
+
The registered Client Id for use in obtaining access tokens.
|
22
|
+
Create a new session if you need to change this value.
|
23
|
+
)
|
24
|
+
input :smart_jwk_set,
|
25
|
+
title: 'JSON Web Key Set (JWKS)',
|
26
|
+
type: 'textarea',
|
27
|
+
optional: false,
|
28
|
+
locked: true,
|
29
|
+
description: %(
|
30
|
+
The SMART client's JSON Web Key Set in the form of either a publicly accessible url
|
31
|
+
containing the JWKS, or the raw JWKS JSON. Must include the key(s) Inferno will need to
|
32
|
+
verify signatures on token requests made by the client.
|
33
|
+
Create a new session if you need to change this value.
|
34
|
+
)
|
35
|
+
output :smart_tokens
|
36
|
+
|
37
|
+
run do
|
38
|
+
omit_if smart_jwk_set.blank?, # for re-use: mark the smart_jwk_set input as optional when importing to enable
|
39
|
+
'SMART Backend Services authentication not demonstrated as a part of this test session.'
|
40
|
+
|
41
|
+
load_tagged_requests(TOKEN_TAG, SMART_TAG)
|
42
|
+
skip_if requests.blank?, 'No SMART token requests made.'
|
43
|
+
|
44
|
+
jti_list = []
|
45
|
+
token_list = []
|
46
|
+
requests.each_with_index do |token_request, index|
|
47
|
+
request_params = URI.decode_www_form(token_request.request_body).to_h
|
48
|
+
check_request_params(request_params, index + 1)
|
49
|
+
check_client_assertion(request_params['client_assertion'], index + 1, jti_list)
|
50
|
+
token_list << extract_token_from_response(token_request)
|
51
|
+
end
|
52
|
+
|
53
|
+
output smart_tokens: token_list.compact.join("\n")
|
54
|
+
|
55
|
+
assert messages.none? { |msg|
|
56
|
+
msg[:type] == 'error'
|
57
|
+
}, 'Invalid token requests detected. See messages for details.'
|
58
|
+
end
|
59
|
+
|
60
|
+
def check_request_params(params, request_num)
|
61
|
+
if params['grant_type'] != 'client_credentials'
|
62
|
+
add_message('error',
|
63
|
+
"Token request #{request_num} had an incorrect `grant_type`: expected 'client_credentials', " \
|
64
|
+
"but got '#{params['grant_type']}'")
|
65
|
+
end
|
66
|
+
if params['client_assertion_type'] != 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
|
67
|
+
add_message('error',
|
68
|
+
"Token request #{request_num} had an incorrect `client_assertion_type`: " \
|
69
|
+
"expected 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', " \
|
70
|
+
"but got '#{params['client_assertion_type']}'")
|
71
|
+
end
|
72
|
+
return unless params['scope'].blank?
|
73
|
+
|
74
|
+
add_message('error', "Token request #{request_num} did not include the requested `scope`")
|
75
|
+
end
|
76
|
+
|
77
|
+
def check_client_assertion(assertion, request_num, jti_list)
|
78
|
+
decoded_token =
|
79
|
+
begin
|
80
|
+
JWT::EncodedToken.new(assertion)
|
81
|
+
rescue StandardError => e
|
82
|
+
add_message('error', "Token request #{request_num} contained an invalid client assertion jwt: #{e}")
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
return unless decoded_token.present?
|
87
|
+
|
88
|
+
check_jwt_header(decoded_token.header, request_num)
|
89
|
+
check_jwt_payload(decoded_token.payload, request_num, jti_list)
|
90
|
+
check_jwt_signature(decoded_token, request_num)
|
91
|
+
end
|
92
|
+
|
93
|
+
def check_jwt_header(header, request_num)
|
94
|
+
return unless header['typ'] != 'JWT'
|
95
|
+
|
96
|
+
add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `typ` header: " \
|
97
|
+
"expected 'JWT', got '#{header['typ']}'")
|
98
|
+
end
|
99
|
+
|
100
|
+
def check_jwt_payload(claims, request_num, jti_list)
|
101
|
+
if claims['iss'] != client_id
|
102
|
+
add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `iss` claim: " \
|
103
|
+
"expected '#{client_id}', got '#{claims['iss']}'")
|
104
|
+
end
|
105
|
+
|
106
|
+
if claims['sub'] != client_id
|
107
|
+
add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `sub` claim: " \
|
108
|
+
"expected '#{client_id}', got '#{claims['sub']}'")
|
109
|
+
end
|
110
|
+
|
111
|
+
if claims['aud'] != client_token_url
|
112
|
+
add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `aud` claim: " \
|
113
|
+
"expected '#{client_token_url}', got '#{claims['aud']}'")
|
114
|
+
end
|
115
|
+
|
116
|
+
if claims['exp'].blank?
|
117
|
+
add_message('error', "client assertion jwt on token request #{request_num} is missing the `exp` claim.")
|
118
|
+
end
|
119
|
+
|
120
|
+
if claims['jti'].blank?
|
121
|
+
add_message('error', "client assertion jwt on token request #{request_num} is missing the `jti` claim.")
|
122
|
+
elsif jti_list.include?(claims['jti'])
|
123
|
+
add_message('error', "client assertion jwt on token request #{request_num} has a `jti` claim that was " \
|
124
|
+
"previouly used: '#{claims['jti']}'.")
|
125
|
+
else
|
126
|
+
jti_list << claims['jti']
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def check_jwt_signature(encoded_token, request_num)
|
131
|
+
error = MockSMARTServer.smart_assertion_signature_verification(encoded_token, smart_jwk_set)
|
132
|
+
|
133
|
+
return unless error.present?
|
134
|
+
|
135
|
+
add_message('error', "Signature validation failed on token request #{request_num}: #{error}")
|
136
|
+
end
|
137
|
+
|
138
|
+
def extract_token_from_response(request)
|
139
|
+
return unless request.status == 200
|
140
|
+
|
141
|
+
JSON.parse(request.response_body)&.dig('access_token')
|
142
|
+
rescue
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../tags'
|
2
|
+
require_relative '../endpoints/mock_smart_server'
|
3
|
+
|
4
|
+
module SMARTAppLaunch
|
5
|
+
class SMARTClientTokenUseVerification < Inferno::Test
|
6
|
+
|
7
|
+
id :smart_client_token_use_verification
|
8
|
+
title 'Verify SMART Token Use'
|
9
|
+
description %(
|
10
|
+
Check that a SMART token returned to the client was used for request
|
11
|
+
authentication.
|
12
|
+
)
|
13
|
+
|
14
|
+
input :smart_tokens,
|
15
|
+
optional: true # verified in the test to return a more specific error message
|
16
|
+
input :smart_jwk_set,
|
17
|
+
optional: false,
|
18
|
+
locked: true
|
19
|
+
|
20
|
+
def access_request_tags
|
21
|
+
return config.options[:access_request_tags] if config.options[:access_request_tags].present?
|
22
|
+
|
23
|
+
[ACCESS_TAG]
|
24
|
+
end
|
25
|
+
|
26
|
+
run do
|
27
|
+
omit_if smart_jwk_set.blank?, # for re-use: mark the smart_jwk_set input as optional when importing to enable
|
28
|
+
'SMART Authentication not demonstrated as a part of this test session.'
|
29
|
+
|
30
|
+
access_requests = access_request_tags.map do |access_request_tag|
|
31
|
+
load_tagged_requests(access_request_tag).reject { |access| access.status == 401 }
|
32
|
+
end.flatten
|
33
|
+
obtained_tokens = smart_tokens&.split("\n")
|
34
|
+
|
35
|
+
skip_if obtained_tokens.blank?, 'No token requests made.'
|
36
|
+
skip_if access_requests.blank?, 'No successful access requests made.'
|
37
|
+
|
38
|
+
used_tokens = access_requests.map do |access_request|
|
39
|
+
access_request.request_headers.find do |header|
|
40
|
+
header.name.downcase == 'authorization'
|
41
|
+
end&.value&.delete_prefix('Bearer ')
|
42
|
+
end.compact
|
43
|
+
|
44
|
+
assert (used_tokens & obtained_tokens).present?, 'Returned tokens never used in any requests.'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -16,11 +16,11 @@ module SMARTAppLaunch
|
|
16
16
|
optional
|
17
17
|
|
18
18
|
input :url, :id_token_fhir_user
|
19
|
-
input :
|
19
|
+
input :smart_auth_info, type: :auth_info
|
20
20
|
|
21
21
|
fhir_client do
|
22
22
|
url :url
|
23
|
-
|
23
|
+
auth_info :smart_auth_info
|
24
24
|
headers 'Origin' => Inferno::Application['inferno_host']
|
25
25
|
end
|
26
26
|
|
@@ -15,10 +15,10 @@ module SMARTAppLaunch
|
|
15
15
|
|
16
16
|
uses_request :cors_token_request
|
17
17
|
|
18
|
-
input :
|
18
|
+
input :smart_auth_info, type: :auth_info, options: { mode: 'auth' }
|
19
19
|
|
20
20
|
run do
|
21
|
-
omit_if
|
21
|
+
omit_if smart_auth_info.auth_type != 'public', %(
|
22
22
|
Client type is not public, Cross-Origin Resource Sharing (CORS) is not required to be supported for
|
23
23
|
non-public client types
|
24
24
|
)
|
@@ -40,8 +40,12 @@ module SMARTAppLaunch
|
|
40
40
|
* [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html)
|
41
41
|
)
|
42
42
|
|
43
|
-
test from: :well_known_endpoint
|
44
|
-
|
43
|
+
test from: :well_known_endpoint do
|
44
|
+
id 'Test01'
|
45
|
+
input :smart_auth_info,
|
46
|
+
type: :auth_info
|
47
|
+
end
|
48
|
+
|
45
49
|
test from: :well_known_capabilities_stu1,
|
46
50
|
id: 'Test02'
|
47
51
|
|
@@ -0,0 +1,81 @@
|
|
1
|
+
{
|
2
|
+
"info": {
|
3
|
+
"_postman_id": "22f52416-c6ae-4ffc-a388-54616465d149",
|
4
|
+
"name": "FHIR Request",
|
5
|
+
"description": "Make a simple FHIR request with a specific bearer token. Useful for security client tests like SMART and UDAP.\n\n- base_url: points to a running instance of inferno. Typical values will be\n \n - Inferno production: [https://inferno.healthit.gov/suites](https://inferno.healthit.gov/suites)\n \n - Inferno QA: [https://inferno-qa.healthit.gov/suites](https://inferno-qa.healthit.gov/suites)\n \n - Local docker: [http://localhost](http://localhost)\n \n - Local development: [http://localhost:4567](http://localhost:4567)",
|
6
|
+
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
7
|
+
"_exporter_id": "32597978"
|
8
|
+
},
|
9
|
+
"item": [
|
10
|
+
{
|
11
|
+
"name": "Patient Read",
|
12
|
+
"request": {
|
13
|
+
"auth": {
|
14
|
+
"type": "bearer",
|
15
|
+
"bearer": [
|
16
|
+
{
|
17
|
+
"key": "token",
|
18
|
+
"value": "{{bearer_token}}",
|
19
|
+
"type": "string"
|
20
|
+
}
|
21
|
+
]
|
22
|
+
},
|
23
|
+
"method": "GET",
|
24
|
+
"header": [],
|
25
|
+
"url": {
|
26
|
+
"raw": "{{base_url}}/custom/{{target_suite}}/fhir/Patient/example",
|
27
|
+
"host": [
|
28
|
+
"{{base_url}}"
|
29
|
+
],
|
30
|
+
"path": [
|
31
|
+
"custom",
|
32
|
+
"{{target_suite}}",
|
33
|
+
"fhir",
|
34
|
+
"Patient",
|
35
|
+
"example"
|
36
|
+
]
|
37
|
+
}
|
38
|
+
},
|
39
|
+
"response": []
|
40
|
+
}
|
41
|
+
],
|
42
|
+
"event": [
|
43
|
+
{
|
44
|
+
"listen": "prerequest",
|
45
|
+
"script": {
|
46
|
+
"type": "text/javascript",
|
47
|
+
"packages": {},
|
48
|
+
"exec": [
|
49
|
+
""
|
50
|
+
]
|
51
|
+
}
|
52
|
+
},
|
53
|
+
{
|
54
|
+
"listen": "test",
|
55
|
+
"script": {
|
56
|
+
"type": "text/javascript",
|
57
|
+
"packages": {},
|
58
|
+
"exec": [
|
59
|
+
""
|
60
|
+
]
|
61
|
+
}
|
62
|
+
}
|
63
|
+
],
|
64
|
+
"variable": [
|
65
|
+
{
|
66
|
+
"key": "base_url",
|
67
|
+
"value": "https://inferno.healthit.gov/suites",
|
68
|
+
"type": "string"
|
69
|
+
},
|
70
|
+
{
|
71
|
+
"key": "target_suite",
|
72
|
+
"value": "smart_client_stu2_2",
|
73
|
+
"type": "string"
|
74
|
+
},
|
75
|
+
{
|
76
|
+
"key": "bearer_token",
|
77
|
+
"value": "",
|
78
|
+
"type": "string"
|
79
|
+
}
|
80
|
+
]
|
81
|
+
}
|
@@ -0,0 +1,121 @@
|
|
1
|
+
## Overview
|
2
|
+
|
3
|
+
The SMART App Launch STU 2.2 Client Test Suite verifies the conformance of
|
4
|
+
client systems to the STU 2.2.0 version of the HL7® FHIR®
|
5
|
+
[SMART App Launch IG](https://hl7.org/fhir/smart-app-launch/STU2.2/).
|
6
|
+
|
7
|
+
## Scope
|
8
|
+
|
9
|
+
The SMART App Launch Client Test Suite verifies that systems correctly implement
|
10
|
+
the [SMART App Launch IG](http://hl7.org/fhir/smart-app-launch/STU2.2/)
|
11
|
+
for authorizating and/or authenticating with a server in order to gain
|
12
|
+
access to HL7® FHIR® APIs. At this time, the suite only contains tests for
|
13
|
+
the [Backend Services](https://hl7.org/fhir/smart-app-launch/STU2.2/backend-services.html)
|
14
|
+
flow.
|
15
|
+
|
16
|
+
These tests are a **DRAFT** intended to allow implementers to perform
|
17
|
+
preliminary checks of their systems against SMART requirements and
|
18
|
+
[provide feedback](https://github.com/inferno-framework/smart-app-launch-test-kit/issues)
|
19
|
+
on the tests. Future versions of these tests may verify other
|
20
|
+
requirements and may change the test verification logic.
|
21
|
+
|
22
|
+
## Test Methodology
|
23
|
+
|
24
|
+
For these tests Inferno simulates a SMART server that supports the backend services
|
25
|
+
flow. Testers will
|
26
|
+
1. Provide registration details as inputs, including a JSON Web Key Set (JWKS)
|
27
|
+
an optionally a client id if a specific one should be used.
|
28
|
+
2. Request an access token using the registered JWKS and client id.
|
29
|
+
3. Use that access token on a FHIR API request.
|
30
|
+
|
31
|
+
The simulated server is relatively permissive in the sense that it will often
|
32
|
+
provide successful responses even when the request is not conformant. When
|
33
|
+
requesting tokens, Inferno will return an access token as long as it can find
|
34
|
+
the client id and the signature is valid. This allows incomplete systems to
|
35
|
+
run the tests. However, these non-conformant requests will be flagged by
|
36
|
+
the tests as failures so that systems will not pass the tests without being
|
37
|
+
fully conformant.
|
38
|
+
|
39
|
+
## Running the Tests
|
40
|
+
|
41
|
+
### Quick Start
|
42
|
+
|
43
|
+
The following inputs must be provided by the tester at a minimum to execute
|
44
|
+
any tests in this suite:
|
45
|
+
1. **SMART JSON Web Key Set (JWKS)**: The SMART client's public JSON Web Key Set including
|
46
|
+
key(s) that Inferno will use to verify the signature on incoming token requests. May
|
47
|
+
be provided as either a publicly accessible url containing the JWKS, or the raw JWKS.
|
48
|
+
|
49
|
+
The *Additional Inputs* section below describes options available to customize
|
50
|
+
the behavior of Inferno's server simulation.
|
51
|
+
|
52
|
+
### Demonstration
|
53
|
+
|
54
|
+
To try out these tests without a SMART client implementation, these tests can be exercised
|
55
|
+
using the SMART App Launch server test suite and a simple HTTP request generator. The following
|
56
|
+
steps use [Postman](https://www.postman.com/) to generate the access request using
|
57
|
+
[this collection](https://github.com/inferno-framework/smart-app-launch-test-kit/blob/main/lib/smart_app_launch/docs/demo/FHIR%20Request.postman_collection.json). Install the app and import the collection before following these
|
58
|
+
steps.
|
59
|
+
|
60
|
+
1. Start an instance of the SMART App Launch STU2.2 Client test suite.
|
61
|
+
2. From the drop down in the upper left, select preset "Demo: Run Against the SMART Server Suite".
|
62
|
+
3. Click the "RUN ALL TESTS" button in the upper right and click "SUBMIT"
|
63
|
+
4. In a new tab, start an instance of the SMART App Launch STU2.2 Test Suite
|
64
|
+
5. From the drop down in the upper left, select preset "Demo: Run Against the SMART Client Suite"
|
65
|
+
6. Select test group **3** Backend Services from the left panel, click the "RUN TESTS" button
|
66
|
+
in the upper right, and click "SUBMIT"
|
67
|
+
7. Find the access token to use for the data access request by opening test **3.2.05** Authorization
|
68
|
+
request succeeds when supplied correct information, click on the "REQUESTS" tab, clicking on the "DETAILS"
|
69
|
+
button, and expanding the "Response Body". Copy the "access_token" value, which will be a ~100 character
|
70
|
+
string of letters and numbers (e.g., eyJjbGllbnRfaWQiOiJzbWFydF9jbGllbnRfdGVzdF9kZW1vIiwiZXhwaXJhdGlvbiI6MTc0MzUxNDk4Mywibm9uY2UiOiJlZDI5MWIwNmZhMTE4OTc4In0)
|
71
|
+
8. Open Postman and open the "FHIR Request" Collection. Click the "Variables" tab and add the copied access token
|
72
|
+
as the current value of the `bearer_token` variable. Also update the
|
73
|
+
`base_url` value for where the test is running (see details on the "Overview" tab).
|
74
|
+
Save the collection.
|
75
|
+
9. Select the "Patient Read" request and click "Send". A FHIR Patient resource should be returned.
|
76
|
+
10. Return to the client tests and click the link to continue and complete the tests.
|
77
|
+
|
78
|
+
The client tests should pass with the exception of test **1.2.02** Verify SMART Token Requests. This is
|
79
|
+
expected as the Server tests make several intentionally invalid token requests. Inferno's simulated SMART
|
80
|
+
server responds successfully to those requests when the client id can be identified, but flags them as
|
81
|
+
not conformant causing these expected failures. Because responding with an access token to non-conformant
|
82
|
+
token requests is itself not conformant there are corresponding failures on the server test in tests **3.2.02**,
|
83
|
+
**3.2.04**, and **3.2.04**. There may be other SMART server test failures due to an assumption that
|
84
|
+
servers support the app launch capabilities in addition to backend services.
|
85
|
+
|
86
|
+
### Additional Inputs
|
87
|
+
|
88
|
+
Two additional inputs are available to support testers
|
89
|
+
- **Client Id**: Testers may specify a client id for Inferno to use for the test session if they
|
90
|
+
have one already configured.
|
91
|
+
- **FHIR Response to Echo**: The focus of this test kit is on the auth protocol, so the
|
92
|
+
simulated FHIR server implemented in this test suite is very simple and will by default
|
93
|
+
return a FHIR OperationOutcome to any request made. Testers may provide a static
|
94
|
+
FHIR JSON body for Inferno to return instead. In this case, the simulation is a simple
|
95
|
+
echo and Inferno does not check that the response if appropriate for the request made.
|
96
|
+
|
97
|
+
## Current Limitations
|
98
|
+
|
99
|
+
This test kit is still in draft form and does not test all of the requirements and features
|
100
|
+
described in the SMART App Launch IG for clients. Notably, only the backend services flow
|
101
|
+
is tested at this time.
|
102
|
+
|
103
|
+
The following sections list other known gaps and limitations.
|
104
|
+
|
105
|
+
### SMART Server Simulation Limitations
|
106
|
+
|
107
|
+
This test suite contains a simulation of a SMART Backend Services server which is not fully
|
108
|
+
general and not all conformant clients may be able to interact with it. However, the intention
|
109
|
+
is not to prevent systems from passing for making conformant choices that Inferno's simulation
|
110
|
+
does not support. One specific example is that the SMART configuration metadata available at
|
111
|
+
`.well-known/smart-configuration` for the simulated server is fixed and cannot be changed by
|
112
|
+
testers at this time. Please report any issues that prevent conformant systems from passing in
|
113
|
+
the [github repository's issues page](https://github.com/inferno-framework/smart-app-launch-test-kit/issues/).
|
114
|
+
|
115
|
+
### FHIR Server Simulation Limitations
|
116
|
+
|
117
|
+
The FHIR server simulation used to support clients in demonstrating their ability to access
|
118
|
+
FHIR APIs using access tokens obtained using the SMART flows is very limited. Testers are currently
|
119
|
+
able to provide a single static response that will be echoed for any FHIR request made. While
|
120
|
+
Inferno will never implement a fully general FHIR server simulation, additional options may be added
|
121
|
+
in the future based on community feedback.
|