smart_app_launch_test_kit 0.4.3 → 0.4.4
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/lib/smart_app_launch/ehr_launch_group_stu2_2.rb +54 -0
- data/lib/smart_app_launch/smart_access_brands_examples/r4_capability_statement.json +198 -0
- data/lib/smart_app_launch/smart_access_brands_group.rb +59 -0
- data/lib/smart_app_launch/smart_access_brands_retrieval_group.rb +21 -0
- data/lib/smart_app_launch/smart_access_brands_retrieve_bundle_test.rb +31 -0
- data/lib/smart_app_launch/smart_access_brands_suite.rb +77 -0
- data/lib/smart_app_launch/smart_access_brands_validate_brands_test.rb +104 -0
- data/lib/smart_app_launch/smart_access_brands_validate_bundle_test.rb +45 -0
- data/lib/smart_app_launch/smart_access_brands_validate_endpoint_urls_test.rb +120 -0
- data/lib/smart_app_launch/smart_access_brands_validate_endpoints_test.rb +70 -0
- data/lib/smart_app_launch/smart_access_brands_validation_group.rb +24 -0
- data/lib/smart_app_launch/smart_stu2_2_suite.rb +243 -0
- data/lib/smart_app_launch/standalone_launch_group_stu2_2.rb +52 -0
- data/lib/smart_app_launch/token_introspection_access_token_group_stu2_2.rb +28 -0
- data/lib/smart_app_launch/token_introspection_group_stu2_2.rb +68 -0
- data/lib/smart_app_launch/token_payload_validation.rb +129 -60
- data/lib/smart_app_launch/token_response_body_test_stu2_2.rb +32 -0
- data/lib/smart_app_launch/version.rb +1 -1
- data/lib/smart_app_launch/well_known_endpoint_test.rb +5 -1
- data/lib/smart_app_launch_test_kit.rb +2 -0
- metadata +18 -2
@@ -0,0 +1,120 @@
|
|
1
|
+
module SMARTAppLaunch
|
2
|
+
class SMARTAccessBrandsValidateEndpointURLs < Inferno::Test
|
3
|
+
id :smart_access_brands_valid_endpoint_urls
|
4
|
+
title 'All Endpoint resource referenced URLS should be valid and available'
|
5
|
+
description %(
|
6
|
+
Verify that User Access Brands Bundle contains Endpoints that contain URLs that are both valid
|
7
|
+
and available.
|
8
|
+
)
|
9
|
+
|
10
|
+
input :user_access_brands_bundle,
|
11
|
+
optional: true
|
12
|
+
|
13
|
+
input :endpoint_availability_limit,
|
14
|
+
title: 'Endpoint Availability Limit',
|
15
|
+
description: %(
|
16
|
+
Input a number to cap the number of Endpoints that Inferno will send requests to check for availability.
|
17
|
+
This can help speed up validation when there are large number of endpoints in the Service Base URL Bundle.
|
18
|
+
),
|
19
|
+
optional: true
|
20
|
+
|
21
|
+
input :endpoint_availability_success_rate,
|
22
|
+
title: 'Endpoint Availability Success Rate',
|
23
|
+
description: %(
|
24
|
+
Select an option to choose how many Endpoints have to be available and send back a valid capability
|
25
|
+
statement for the Endpoint validation test to pass.
|
26
|
+
),
|
27
|
+
type: 'radio',
|
28
|
+
options: {
|
29
|
+
list_options: [
|
30
|
+
{
|
31
|
+
label: 'All',
|
32
|
+
value: 'all'
|
33
|
+
},
|
34
|
+
{
|
35
|
+
label: 'At Least 1',
|
36
|
+
value: 'at_least_1'
|
37
|
+
},
|
38
|
+
{
|
39
|
+
label: 'None',
|
40
|
+
value: 'none'
|
41
|
+
}
|
42
|
+
]
|
43
|
+
},
|
44
|
+
default: 'all'
|
45
|
+
|
46
|
+
def get_endpoint_availability_limit(endpoint_availability_limit)
|
47
|
+
return if endpoint_availability_limit.blank?
|
48
|
+
|
49
|
+
endpoint_availability_limit.to_i
|
50
|
+
end
|
51
|
+
|
52
|
+
def skip_message
|
53
|
+
%(
|
54
|
+
No User Access Brands request was made in the previous test, and no User Access Brands Bundle was provided as
|
55
|
+
input instead. Either provide a User Access Brands Publication URL to retrieve the Bundle via a HTTP GET
|
56
|
+
request, or provide the Bundle as an input.
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
run do
|
61
|
+
bundle_response = if user_access_brands_bundle.blank?
|
62
|
+
load_tagged_requests('smart_access_brands_bundle')
|
63
|
+
skip skip_message if requests.length != 1
|
64
|
+
requests.first.response_body
|
65
|
+
else
|
66
|
+
user_access_brands_bundle
|
67
|
+
end
|
68
|
+
|
69
|
+
skip_if bundle_response.blank?, 'No SMART Access Brands Bundle contained in the response'
|
70
|
+
|
71
|
+
assert_valid_json(bundle_response)
|
72
|
+
bundle_resource = FHIR.from_contents(bundle_response)
|
73
|
+
|
74
|
+
skip_if bundle_resource.entry.empty?, 'The given Bundle does not contain any resources'
|
75
|
+
|
76
|
+
endpoint_list = bundle_resource
|
77
|
+
.entry
|
78
|
+
.map(&:resource)
|
79
|
+
.select { |resource| resource.resourceType == 'Endpoint' }
|
80
|
+
.map(&:address)
|
81
|
+
.uniq
|
82
|
+
|
83
|
+
check_limit = get_endpoint_availability_limit(endpoint_availability_limit)
|
84
|
+
one_endpoint_valid = false
|
85
|
+
|
86
|
+
endpoint_list.each_with_index do |address, index|
|
87
|
+
assert_valid_http_uri(address)
|
88
|
+
|
89
|
+
next if endpoint_availability_success_rate == 'none' || (check_limit.present? && index >= check_limit)
|
90
|
+
|
91
|
+
address = address.delete_suffix('/')
|
92
|
+
get("#{address}/metadata", client: nil, headers: { Accept: 'application/fhir+json' })
|
93
|
+
|
94
|
+
if endpoint_availability_success_rate == 'all'
|
95
|
+
assert_response_status(200)
|
96
|
+
assert resource.present?, 'The content received does not appear to be a valid FHIR resource'
|
97
|
+
assert_resource_type(:capability_statement)
|
98
|
+
else
|
99
|
+
warning do
|
100
|
+
assert_response_status(200)
|
101
|
+
assert resource.present?, 'The content received does not appear to be a valid FHIR resource'
|
102
|
+
assert_resource_type(:capability_statement)
|
103
|
+
end
|
104
|
+
|
105
|
+
if !one_endpoint_valid && response[:status] == 200 && resource.present? &&
|
106
|
+
resource.resourceType == 'CapabilityStatement'
|
107
|
+
one_endpoint_valid = true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
if endpoint_availability_success_rate == 'at_least_1'
|
113
|
+
assert(one_endpoint_valid, %(
|
114
|
+
There were no Endpoints that were available and returned a valid Capability Statement in the Service Base
|
115
|
+
URL Bundle'
|
116
|
+
))
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module SMARTAppLaunch
|
2
|
+
class SMARTAccessBrandsValidateEndpoints < Inferno::Test
|
3
|
+
id :smart_access_brands_valid_endpoints
|
4
|
+
title 'SMART Access Brands Bundle contains valid User Access Endpoints'
|
5
|
+
description %(
|
6
|
+
Verify that Bundle of User Access Brands and Endpoints contains Endpoints that are valid
|
7
|
+
Endpoint resources according to the [User Access Endpoint Profile](https://hl7.org/fhir/smart-app-launch/STU2.2/StructureDefinition-user-access-endpoint.html).
|
8
|
+
|
9
|
+
Along with validating the Endpoint resources, this test also ensures that each endpoint contains a primary brand
|
10
|
+
by checking if it is referenced by at least 1 Organization resource.
|
11
|
+
)
|
12
|
+
|
13
|
+
def find_referenced_org(bundle_resource, endpoint_id)
|
14
|
+
bundle_resource
|
15
|
+
.entry
|
16
|
+
.map(&:resource)
|
17
|
+
.select { |resource| resource.resourceType == 'Organization' }
|
18
|
+
.map(&:endpoint)
|
19
|
+
.flatten
|
20
|
+
.map(&:reference)
|
21
|
+
.select { |reference| reference.include? endpoint_id }
|
22
|
+
end
|
23
|
+
|
24
|
+
def skip_message
|
25
|
+
%(
|
26
|
+
No User Access Brands request was made in the previous test, and no User Access Brands Bundle was provided as
|
27
|
+
input instead. Either provide a User Access Brands Publication URL to retrieve the Bundle via a HTTP GET
|
28
|
+
request, or provide the Bundle as an input.
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
input :user_access_brands_bundle,
|
33
|
+
optional: true
|
34
|
+
|
35
|
+
run do
|
36
|
+
bundle_response = if user_access_brands_bundle.blank?
|
37
|
+
load_tagged_requests('smart_access_brands_bundle')
|
38
|
+
skip skip_message if requests.length != 1
|
39
|
+
requests.first.response_body
|
40
|
+
else
|
41
|
+
user_access_brands_bundle
|
42
|
+
end
|
43
|
+
|
44
|
+
skip_if bundle_response.blank?, 'No SMART Access Brands Bundle contained in the response'
|
45
|
+
|
46
|
+
assert_valid_json(bundle_response)
|
47
|
+
bundle_resource = FHIR.from_contents(bundle_response)
|
48
|
+
|
49
|
+
skip_if bundle_resource.entry.empty?, 'The given Bundle does not contain any resources'
|
50
|
+
|
51
|
+
assert_valid_bundle_entries(bundle: bundle_resource,
|
52
|
+
resource_types: {
|
53
|
+
Endpoint: 'http://hl7.org/fhir/smart-app-launch/StructureDefinition/user-access-endpoint'
|
54
|
+
})
|
55
|
+
|
56
|
+
endpoint_ids =
|
57
|
+
bundle_resource
|
58
|
+
.entry
|
59
|
+
.map(&:resource)
|
60
|
+
.select { |resource| resource.resourceType == 'Endpoint' }
|
61
|
+
.map(&:id)
|
62
|
+
|
63
|
+
endpoint_ids.each do |endpoint_id|
|
64
|
+
endpoint_referenced_orgs = find_referenced_org(bundle_resource, endpoint_id)
|
65
|
+
assert !endpoint_referenced_orgs.empty?,
|
66
|
+
"Endpoint with id: #{endpoint_id} does not have any associated Organizations in the Bundle."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'smart_access_brands_validate_bundle_test'
|
2
|
+
require_relative 'smart_access_brands_validate_endpoints_test'
|
3
|
+
require_relative 'smart_access_brands_validate_endpoint_urls_test'
|
4
|
+
require_relative 'smart_access_brands_validate_brands_test'
|
5
|
+
|
6
|
+
module SMARTAppLaunch
|
7
|
+
class SMARTAccessBrandsValidationGroup < Inferno::TestGroup
|
8
|
+
id :smart_access_brands_validation
|
9
|
+
title 'Validate SMART Access Brands Bundle'
|
10
|
+
description %(
|
11
|
+
These tests ensure that the publisher's User Access Brands publication is in
|
12
|
+
a valid Bundle according to the [User Access Brand Bundle Profile](https://hl7.org/fhir/smart-app-launch/STU2.2/StructureDefinition-user-access-brands-bundle.html).
|
13
|
+
It ensures that this User Access Brand Bundle has its brand and endpoint
|
14
|
+
details contained in valid Endpoints according to the [User Access Endpoint Profile](https://hl7.org/fhir/smart-app-launch/STU2.2/StructureDefinition-user-access-endpoint.html)
|
15
|
+
and valid Brands (Organizations) according to the [User Access Brand Profile](https://hl7.org/fhir/smart-app-launch/STU2.2/StructureDefinition-user-access-brand.html).
|
16
|
+
)
|
17
|
+
run_as_group
|
18
|
+
|
19
|
+
test from: :smart_access_brands_valid_bundle
|
20
|
+
test from: :smart_access_brands_valid_endpoints
|
21
|
+
test from: :smart_access_brands_valid_endpoint_urls
|
22
|
+
test from: :smart_access_brands_valid_brands
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'tls_test_kit'
|
2
|
+
|
3
|
+
require_relative 'jwks'
|
4
|
+
require_relative 'version'
|
5
|
+
require_relative 'discovery_stu2_group'
|
6
|
+
require_relative 'standalone_launch_group_stu2_2'
|
7
|
+
require_relative 'ehr_launch_group_stu2_2'
|
8
|
+
require_relative 'openid_connect_group'
|
9
|
+
require_relative 'token_introspection_group_stu2_2'
|
10
|
+
require_relative 'token_refresh_stu2_group'
|
11
|
+
require_relative 'backend_services_authorization_group'
|
12
|
+
|
13
|
+
module SMARTAppLaunch
|
14
|
+
class SMARTSTU22Suite < Inferno::TestSuite
|
15
|
+
id 'smart_stu2_2'
|
16
|
+
title 'SMART App Launch STU2.2'
|
17
|
+
version VERSION
|
18
|
+
|
19
|
+
resume_test_route :get, '/launch' do |request|
|
20
|
+
request.query_parameters['iss']
|
21
|
+
end
|
22
|
+
|
23
|
+
resume_test_route :get, '/redirect' do |request|
|
24
|
+
request.query_parameters['state']
|
25
|
+
end
|
26
|
+
|
27
|
+
route(
|
28
|
+
:get,
|
29
|
+
'/.well-known/jwks.json',
|
30
|
+
->(_env) { [200, { 'Content-Type' => 'application/json' }, [JWKS.jwks_json]] }
|
31
|
+
)
|
32
|
+
|
33
|
+
@post_auth_page = File.read(File.join(__dir__, 'post_auth.html'))
|
34
|
+
post_auth_handler = proc { [200, {}, [@post_auth_page]] }
|
35
|
+
|
36
|
+
route :get, '/post_auth', post_auth_handler
|
37
|
+
|
38
|
+
config options: {
|
39
|
+
redirect_uri: "#{Inferno::Application['base_url']}/custom/smart_stu2_2/redirect",
|
40
|
+
launch_uri: "#{Inferno::Application['base_url']}/custom/smart_stu2_2/launch",
|
41
|
+
post_authorization_uri: "#{Inferno::Application['base_url']}/custom/smart_stu2_2/post_auth"
|
42
|
+
}
|
43
|
+
|
44
|
+
description <<~DESCRIPTION
|
45
|
+
The SMART App Launch Test Suite verifies that systems correctly implement
|
46
|
+
the [SMART App Launch IG](http://hl7.org/fhir/smart-app-launch/STU2.2/)
|
47
|
+
for providing authorization and/or authentication services to client
|
48
|
+
applications accessing HL7® FHIR® APIs. To get started, please first register
|
49
|
+
the Inferno client as a SMART App with the following information:
|
50
|
+
|
51
|
+
* SMART Launch URI: `#{config.options[:launch_uri]}`
|
52
|
+
* OAuth Redirect URI: `#{config.options[:redirect_uri]}`
|
53
|
+
|
54
|
+
If using asymmetric client authentication, register Inferno with the
|
55
|
+
following JWK Set URL:
|
56
|
+
|
57
|
+
* `#{Inferno::Application[:base_url]}/custom/smart_stu2_2/.well-known/jwks.json`
|
58
|
+
DESCRIPTION
|
59
|
+
|
60
|
+
input_instructions %(
|
61
|
+
When running tests at this level, the token introspection endpoint is not available as a manual input.
|
62
|
+
Instead, group 3 Token Introspection will assume the token introspection endpoint
|
63
|
+
will be output from group 1 Standalone Launch tests, specifically the SMART On FHIR Discovery tests that query
|
64
|
+
the .well-known/smart-configuration endpoint. However, including the token introspection
|
65
|
+
endpoint as part of the well-known ouput is NOT required and is not formally checked in the SMART On FHIR Discovery
|
66
|
+
tests. RFC-7662 on Token Introspection says that "The means by which the protected resource discovers the location of the introspection
|
67
|
+
endpoint are outside the scope of this specification" and the Token Introspection IG does not add any further
|
68
|
+
requirements to this.
|
69
|
+
|
70
|
+
If the token introspection endpoint of the system under test is NOT available at .well-known/smart-configuration,
|
71
|
+
please run the test groups individually and group 3 Token Introspection will include the introspection endpoint as a manual input.
|
72
|
+
)
|
73
|
+
|
74
|
+
group do
|
75
|
+
title 'Standalone Launch'
|
76
|
+
id :smart_full_standalone_launch
|
77
|
+
|
78
|
+
input_instructions <<~INSTRUCTIONS
|
79
|
+
Please register the Inferno client as a SMART App with the following
|
80
|
+
information:
|
81
|
+
|
82
|
+
* OAuth Redirect URI: `#{config.options[:redirect_uri]}`
|
83
|
+
|
84
|
+
If using asymmetric client authentication, register Inferno with the
|
85
|
+
following JWK Set URL:
|
86
|
+
|
87
|
+
* `#{Inferno::Application[:base_url]}/custom/smart_stu2_2/.well-known/jwks.json`
|
88
|
+
INSTRUCTIONS
|
89
|
+
|
90
|
+
run_as_group
|
91
|
+
|
92
|
+
group from: :smart_discovery_stu2
|
93
|
+
group from: :smart_standalone_launch_stu2_2
|
94
|
+
|
95
|
+
group from: :smart_openid_connect,
|
96
|
+
config: {
|
97
|
+
inputs: {
|
98
|
+
id_token: { name: :standalone_id_token },
|
99
|
+
client_id: { name: :standalone_client_id },
|
100
|
+
requested_scopes: { name: :standalone_requested_scopes },
|
101
|
+
access_token: { name: :standalone_access_token },
|
102
|
+
smart_credentials: { name: :standalone_smart_credentials }
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
group from: :smart_token_refresh_stu2,
|
107
|
+
id: :smart_standalone_refresh_without_scopes,
|
108
|
+
title: 'SMART Token Refresh Without Scopes',
|
109
|
+
config: {
|
110
|
+
inputs: {
|
111
|
+
refresh_token: { name: :standalone_refresh_token },
|
112
|
+
client_id: { name: :standalone_client_id },
|
113
|
+
client_secret: { name: :standalone_client_secret },
|
114
|
+
received_scopes: { name: :standalone_received_scopes }
|
115
|
+
},
|
116
|
+
outputs: {
|
117
|
+
refresh_token: { name: :standalone_refresh_token },
|
118
|
+
received_scopes: { name: :standalone_received_scopes },
|
119
|
+
access_token: { name: :standalone_access_token },
|
120
|
+
token_retrieval_time: { name: :standalone_token_retrieval_time },
|
121
|
+
expires_in: { name: :standalone_expires_in },
|
122
|
+
smart_credentials: { name: :standalone_smart_credentials }
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
group from: :smart_token_refresh_stu2,
|
127
|
+
id: :smart_standalone_refresh_with_scopes,
|
128
|
+
title: 'SMART Token Refresh With Scopes',
|
129
|
+
config: {
|
130
|
+
options: { include_scopes: true },
|
131
|
+
inputs: {
|
132
|
+
refresh_token: { name: :standalone_refresh_token },
|
133
|
+
client_id: { name: :standalone_client_id },
|
134
|
+
client_secret: { name: :standalone_client_secret },
|
135
|
+
received_scopes: { name: :standalone_received_scopes }
|
136
|
+
},
|
137
|
+
outputs: {
|
138
|
+
refresh_token: { name: :standalone_refresh_token },
|
139
|
+
received_scopes: { name: :standalone_received_scopes },
|
140
|
+
access_token: { name: :standalone_access_token },
|
141
|
+
token_retrieval_time: { name: :standalone_token_retrieval_time },
|
142
|
+
expires_in: { name: :standalone_expires_in },
|
143
|
+
smart_credentials: { name: :standalone_smart_credentials }
|
144
|
+
}
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
group do
|
149
|
+
title 'EHR Launch'
|
150
|
+
id :smart_full_ehr_launch
|
151
|
+
|
152
|
+
input_instructions <<~INSTRUCTIONS
|
153
|
+
Please register the Inferno client as a SMART App with the following
|
154
|
+
information:
|
155
|
+
|
156
|
+
* SMART Launch URI: `#{config.options[:launch_uri]}`
|
157
|
+
* OAuth Redirect URI: `#{config.options[:redirect_uri]}`
|
158
|
+
|
159
|
+
If using asymmetric client authentication, register Inferno with the
|
160
|
+
following JWK Set URL:
|
161
|
+
|
162
|
+
* `#{Inferno::Application[:base_url]}/custom/smart_stu2_2/.well-known/jwks.json`
|
163
|
+
INSTRUCTIONS
|
164
|
+
|
165
|
+
run_as_group
|
166
|
+
|
167
|
+
group from: :smart_discovery_stu2
|
168
|
+
|
169
|
+
group from: :smart_ehr_launch_stu2_2
|
170
|
+
|
171
|
+
group from: :smart_openid_connect,
|
172
|
+
config: {
|
173
|
+
inputs: {
|
174
|
+
id_token: { name: :ehr_id_token },
|
175
|
+
client_id: { name: :ehr_client_id },
|
176
|
+
requested_scopes: { name: :ehr_requested_scopes },
|
177
|
+
access_token: { name: :ehr_access_token },
|
178
|
+
smart_credentials: { name: :ehr_smart_credentials }
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
group from: :smart_token_refresh_stu2,
|
183
|
+
id: :smart_ehr_refresh_without_scopes,
|
184
|
+
title: 'SMART Token Refresh Without Scopes',
|
185
|
+
config: {
|
186
|
+
inputs: {
|
187
|
+
refresh_token: { name: :ehr_refresh_token },
|
188
|
+
client_id: { name: :ehr_client_id },
|
189
|
+
client_secret: { name: :ehr_client_secret },
|
190
|
+
received_scopes: { name: :ehr_received_scopes }
|
191
|
+
},
|
192
|
+
outputs: {
|
193
|
+
refresh_token: { name: :ehr_refresh_token },
|
194
|
+
received_scopes: { name: :ehr_received_scopes },
|
195
|
+
access_token: { name: :ehr_access_token },
|
196
|
+
token_retrieval_time: { name: :ehr_token_retrieval_time },
|
197
|
+
expires_in: { name: :ehr_expires_in },
|
198
|
+
smart_credentials: { name: :ehr_smart_credentials }
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
group from: :smart_token_refresh_stu2,
|
203
|
+
id: :smart_ehr_refresh_with_scopes,
|
204
|
+
title: 'SMART Token Refresh With Scopes',
|
205
|
+
config: {
|
206
|
+
options: { include_scopes: true },
|
207
|
+
inputs: {
|
208
|
+
refresh_token: { name: :ehr_refresh_token },
|
209
|
+
client_id: { name: :ehr_client_id },
|
210
|
+
client_secret: { name: :ehr_client_secret },
|
211
|
+
received_scopes: { name: :ehr_received_scopes }
|
212
|
+
},
|
213
|
+
outputs: {
|
214
|
+
refresh_token: { name: :ehr_refresh_token },
|
215
|
+
received_scopes: { name: :ehr_received_scopes },
|
216
|
+
access_token: { name: :ehr_access_token },
|
217
|
+
token_retrieval_time: { name: :ehr_token_retrieval_time },
|
218
|
+
expires_in: { name: :ehr_expires_in },
|
219
|
+
smart_credentials: { name: :ehr_smart_credentials }
|
220
|
+
}
|
221
|
+
}
|
222
|
+
end
|
223
|
+
|
224
|
+
group do
|
225
|
+
title 'Backend Services'
|
226
|
+
id :smart_backend_services
|
227
|
+
|
228
|
+
input_instructions <<~INSTRUCTIONS
|
229
|
+
Please register the Inferno client with the authorization services with the
|
230
|
+
following JWK Set URL:
|
231
|
+
|
232
|
+
* `#{Inferno::Application[:base_url]}/custom/smart_stu2_2/.well-known/jwks.json`
|
233
|
+
INSTRUCTIONS
|
234
|
+
|
235
|
+
run_as_group
|
236
|
+
|
237
|
+
group from: :smart_discovery_stu2
|
238
|
+
group from: :backend_services_authorization
|
239
|
+
end
|
240
|
+
|
241
|
+
group from: :smart_token_introspection_stu2_2
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative 'token_response_body_test_stu2_2'
|
2
|
+
require_relative 'standalone_launch_group_stu2'
|
3
|
+
|
4
|
+
module SMARTAppLaunch
|
5
|
+
class StandaloneLaunchGroupSTU22 < StandaloneLaunchGroupSTU2
|
6
|
+
id :smart_standalone_launch_stu2_2
|
7
|
+
description %(
|
8
|
+
# Background
|
9
|
+
|
10
|
+
The [Standalone
|
11
|
+
Launch Sequence](http://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html#launch-app-standalone-launch)
|
12
|
+
allows an app, like Inferno, to be launched independent of an
|
13
|
+
existing EHR session. It is one of the two launch methods described in
|
14
|
+
the SMART App Launch Framework alongside EHR Launch. The app will
|
15
|
+
request authorization for the provided scope from the authorization
|
16
|
+
endpoint, ultimately receiving an authorization token which can be used
|
17
|
+
to gain access to resources on the FHIR server.
|
18
|
+
|
19
|
+
# Test Methodology
|
20
|
+
|
21
|
+
Inferno will redirect the user to the the authorization endpoint so that
|
22
|
+
they may provide any required credentials and authorize the application.
|
23
|
+
Upon successful authorization, Inferno will exchange the authorization
|
24
|
+
code provided for an access token.
|
25
|
+
|
26
|
+
For more information on the #{title}:
|
27
|
+
|
28
|
+
* [Standalone Launch Sequence](http://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html#launch-app-standalone-launch)
|
29
|
+
)
|
30
|
+
|
31
|
+
config(
|
32
|
+
inputs: {
|
33
|
+
use_pkce: {
|
34
|
+
default: 'true',
|
35
|
+
locked: true
|
36
|
+
},
|
37
|
+
pkce_code_challenge_method: {
|
38
|
+
default: 'S256',
|
39
|
+
locked: true
|
40
|
+
},
|
41
|
+
requested_scopes: {
|
42
|
+
default: 'launch/patient openid fhirUser offline_access patient/*.rs'
|
43
|
+
}
|
44
|
+
}
|
45
|
+
)
|
46
|
+
|
47
|
+
test from: :smart_token_response_body_stu2_2
|
48
|
+
|
49
|
+
token_response_body_index = children.find_index { |child| child.id.to_s.end_with? 'token_response_body' }
|
50
|
+
children[token_response_body_index] = children.pop
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'standalone_launch_group_stu2_2'
|
2
|
+
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class SMARTTokenIntrospectionAccessTokenGroupSTU22 < SMARTTokenIntrospectionAccessTokenGroup
|
5
|
+
title 'Request New Access Token to Introspect'
|
6
|
+
run_as_group
|
7
|
+
|
8
|
+
id :smart_token_introspection_access_token_group_stu2_2
|
9
|
+
|
10
|
+
description %(
|
11
|
+
These tests are repeated from the Standalone Launch tests in order to receive a new, active access token that
|
12
|
+
will be provided for token introspection. This test group may be skipped if the tester can obtain an access token
|
13
|
+
__and__ the contents of the access token response body by some other means.
|
14
|
+
|
15
|
+
These tests are currently designed such that the token introspection URL must be present in the SMART well-known endpoint.
|
16
|
+
|
17
|
+
)
|
18
|
+
|
19
|
+
input_instructions %(
|
20
|
+
Register Inferno as a Standalone SMART App and provide the registration details below.
|
21
|
+
)
|
22
|
+
|
23
|
+
group from: :smart_standalone_launch_stu2_2
|
24
|
+
|
25
|
+
standalone_launch_index = children.find_index { |child| child.id.to_s.end_with? 'standalone_launch_stu2' }
|
26
|
+
children[standalone_launch_index] = children.pop
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require_relative 'token_introspection_access_token_group_stu2_2'
|
2
|
+
require_relative 'token_introspection_group'
|
3
|
+
|
4
|
+
module SMARTAppLaunch
|
5
|
+
class SMARTTokenIntrospectionGroupSTU22 < SMARTTokenIntrospectionGroup
|
6
|
+
title 'Token Introspection'
|
7
|
+
id :smart_token_introspection_stu2_2
|
8
|
+
description %(
|
9
|
+
# Background
|
10
|
+
|
11
|
+
OAuth 2.0 Token introspection, as described in [RFC-7662](https://datatracker.ietf.org/doc/html/rfc7662), allows
|
12
|
+
an authorized resource server to query an OAuth 2.0 authorization server for metadata on a token. The
|
13
|
+
[SMART App Launch STU2.2 Implementation Guide Section on Token Introspection](https://hl7.org/fhir/smart-app-launch/STU2.2/token-introspection.html)
|
14
|
+
states that "SMART on FHIR EHRs SHOULD support token introspection, which allows a broader ecosystem of resource servers
|
15
|
+
to leverage authorization decisions managed by a single authorization server."
|
16
|
+
|
17
|
+
# Test Methodology
|
18
|
+
|
19
|
+
In these tests, Inferno acts as an authorized resource server that queries the authorization server about an access
|
20
|
+
token, rather than a client to a FHIR resource server as in the previous SMART App Launch tests.
|
21
|
+
Ideally, Inferno should be registered with the authorization server as an authorized resource server
|
22
|
+
capable of accessing the token introspection endpoint through client credentials, per the SMART IG recommendations.
|
23
|
+
However, the SMART IG only formally REQUIRES "some form of authorization" to access
|
24
|
+
the token introspection endpoint and does not specifiy any one specific approach. As such, the token introspection tests are
|
25
|
+
broken up into three groups that each complete a discrete step in the token introspection process:
|
26
|
+
|
27
|
+
1. **Request Access Token Group** - optional but recommended, repeats a subset of Standalone Launch tests
|
28
|
+
in order to receive a new access token with an authorization code grant. If skipped, testers will need to
|
29
|
+
obtain an access token out-of-band and manually provide values from the access token response as inputs to
|
30
|
+
the Validate Token Response group.
|
31
|
+
2. **Issue Token Introspection Request Group** - optional but recommended, completes the introspection requests.
|
32
|
+
If skipped, testers will need to complete an introspection request out-of-band and manually provide the introspection
|
33
|
+
responses as inputs to the Validate Token Response group.
|
34
|
+
3. **Validate Token Introspection Response Group** - required, validates the contents of the introspection responses.
|
35
|
+
|
36
|
+
Running all three test groups in order is the simplest and is highly recommended if the environment under test
|
37
|
+
can support it, as outputs from one group will feed the inputs of the next group. However, test groups can be run
|
38
|
+
independently if needed.
|
39
|
+
|
40
|
+
See the individual test groups for more details and guidance.
|
41
|
+
)
|
42
|
+
group from: :smart_token_introspection_access_token_group_stu2_2
|
43
|
+
|
44
|
+
access_token_group_index = children.find_index { |child| child.id.to_s.end_with? 'access_token_group' }
|
45
|
+
children[access_token_group_index] = children.pop
|
46
|
+
|
47
|
+
input_order :url, :standalone_client_id, :standalone_client_secret,
|
48
|
+
:authorization_method, :use_pkce, :pkce_code_challenge_method,
|
49
|
+
:standalone_requested_scopes, :client_auth_encryption_method,
|
50
|
+
:client_auth_type, :custom_authorization_header,
|
51
|
+
:optional_introspection_request_params
|
52
|
+
input_instructions %(
|
53
|
+
Executing tests at this level will run all three Token Introspection groups back-to-back. If test groups need
|
54
|
+
to be run independently, exit this window and select a specific test group instead.
|
55
|
+
|
56
|
+
These tests are currently designed such that the token introspection URL must be present in the SMART well-known endpoint.
|
57
|
+
|
58
|
+
If the introspection endpoint is protected, testers must enter their own HTTP Authorization header for the introspection request. See
|
59
|
+
[RFC 7616 The 'Basic' HTTP Authentication Scheme](https://datatracker.ietf.org/doc/html/rfc7617) for the most common
|
60
|
+
approach that uses client credentials. Testers may also provide any additional parameters needed for their authorization
|
61
|
+
server to complete the introspection request.
|
62
|
+
|
63
|
+
**Note:** For both the Authorization header and request parameters, user-input
|
64
|
+
values will be sent exactly as entered and therefore the tester must
|
65
|
+
URI-encode any appropriate values.
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|