smart_app_launch_test_kit 0.4.2 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/smart_app_launch/backend_services_authorization_request_success_test.rb +3 -2
- data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +3 -2
- data/lib/smart_app_launch/backend_services_invalid_grant_type_test.rb +3 -2
- data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +3 -2
- 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/smart_stu2_suite.rb +5 -5
- 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_refresh_stu2_group.rb +46 -0
- data/lib/smart_app_launch/token_refresh_stu2_test.rb +46 -0
- data/lib/smart_app_launch/token_refresh_test.rb +10 -6
- 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 +21 -3
@@ -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
|
@@ -7,7 +7,7 @@ require_relative 'standalone_launch_group_stu2'
|
|
7
7
|
require_relative 'ehr_launch_group_stu2'
|
8
8
|
require_relative 'openid_connect_group'
|
9
9
|
require_relative 'token_introspection_group'
|
10
|
-
require_relative '
|
10
|
+
require_relative 'token_refresh_stu2_group'
|
11
11
|
require_relative 'backend_services_authorization_group'
|
12
12
|
|
13
13
|
module SMARTAppLaunch
|
@@ -103,7 +103,7 @@ module SMARTAppLaunch
|
|
103
103
|
}
|
104
104
|
}
|
105
105
|
|
106
|
-
group from: :
|
106
|
+
group from: :smart_token_refresh_stu2,
|
107
107
|
id: :smart_standalone_refresh_without_scopes,
|
108
108
|
title: 'SMART Token Refresh Without Scopes',
|
109
109
|
config: {
|
@@ -123,7 +123,7 @@ module SMARTAppLaunch
|
|
123
123
|
}
|
124
124
|
}
|
125
125
|
|
126
|
-
group from: :
|
126
|
+
group from: :smart_token_refresh_stu2,
|
127
127
|
id: :smart_standalone_refresh_with_scopes,
|
128
128
|
title: 'SMART Token Refresh With Scopes',
|
129
129
|
config: {
|
@@ -179,7 +179,7 @@ module SMARTAppLaunch
|
|
179
179
|
}
|
180
180
|
}
|
181
181
|
|
182
|
-
group from: :
|
182
|
+
group from: :smart_token_refresh_stu2,
|
183
183
|
id: :smart_ehr_refresh_without_scopes,
|
184
184
|
title: 'SMART Token Refresh Without Scopes',
|
185
185
|
config: {
|
@@ -199,7 +199,7 @@ module SMARTAppLaunch
|
|
199
199
|
}
|
200
200
|
}
|
201
201
|
|
202
|
-
group from: :
|
202
|
+
group from: :smart_token_refresh_stu2,
|
203
203
|
id: :smart_ehr_refresh_with_scopes,
|
204
204
|
title: 'SMART Token Refresh With Scopes',
|
205
205
|
config: {
|
@@ -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
|