smart_app_launch_test_kit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ae741dbca6b676c7c53c6be41a14f84292b4d620dbfecbf67f7472f5450c67fc
4
+ data.tar.gz: 4136c596fa810d8109c18df863e39eda7283c170a59c7441dc07e1bcb4ebdc1f
5
+ SHA512:
6
+ metadata.gz: dfa9bd45de2c2b917347724b9a706dde71d1287dc48a3e49a86371b1106c42257084f78f989dcb737fba425594fbc11f4562b3aab7bf37710a40cdff3fccb116
7
+ data.tar.gz: ce2e0ed22fd192d88c5de32f334ada292ee5813b72c184eb9161f122d2cec82155ff6e83de19f8fe2c2d7a2ea0cd52b5c4adcdcefef0a668ff079abb7107776d
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,25 @@
1
+ module SMARTAppLaunch
2
+ class AppLaunchTest < Inferno::Test
3
+ title 'EHR server redirects client browser to Inferno app launch URI'
4
+ description %(
5
+ Client browser sent from EHR server to app launch URI of client app as
6
+ described in SMART EHR Launch Sequence.
7
+ )
8
+ id :smart_app_launch
9
+
10
+ input :url
11
+ receives_request :launch
12
+
13
+ config options: { launch_uri: "#{Inferno::Application['inferno_host']}/custom/smart/launch" }
14
+
15
+ run do
16
+ wait(
17
+ identifier: url,
18
+ message: %(
19
+ Waiting to receive a request at
20
+ `#{config.options[:launch_uri]}` with an `iss` of `#{url}`.
21
+ )
22
+ )
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,61 @@
1
+ module SMARTAppLaunch
2
+ class AppRedirectTest < Inferno::Test
3
+ title 'OAuth server redirects client browser to app redirect URI'
4
+ description %(
5
+ Client browser redirected from OAuth server to redirect URI of client
6
+ app as described in SMART authorization sequence.
7
+ )
8
+ id :smart_app_redirect
9
+
10
+ input :client_id, :requested_scopes, :url, :smart_authorization_url
11
+
12
+ output :state
13
+ receives_request :redirect
14
+
15
+ config options: { redirect_uri: "#{Inferno::Application['inferno_host']}/custom/smart/redirect" }
16
+
17
+ run do
18
+ assert_valid_http_uri(
19
+ smart_authorization_url,
20
+ "OAuth2 Authorization Endpoint '#{smart_authorization_url}' is not a valid URI"
21
+ )
22
+
23
+ output state: SecureRandom.uuid
24
+
25
+ oauth2_params = {
26
+ 'response_type' => 'code',
27
+ 'client_id' => client_id,
28
+ 'redirect_uri' => config.options[:redirect_uri],
29
+ 'scope' => requested_scopes,
30
+ 'state' => state,
31
+ 'aud' => url
32
+ }
33
+
34
+ oauth2_params['launch'] = launch if self.class.inputs.include?(:launch)
35
+
36
+ authorization_url = smart_authorization_url
37
+
38
+ authorization_url +=
39
+ if authorization_url.include? '?'
40
+ '&'
41
+ else
42
+ '?'
43
+ end
44
+
45
+ oauth2_params.each do |key, value|
46
+ authorization_url += "#{key}=#{CGI.escape(value)}&"
47
+ end
48
+
49
+ authorization_url.chomp!('&')
50
+
51
+ wait(
52
+ identifier: state,
53
+ message: %(
54
+ [Follow this link to authorize with the SMART
55
+ server](#{authorization_url}). Waiting to receive a request at
56
+ `#{config.options[:redirect_uri]}` with a state of `#{state}`.
57
+ )
58
+ )
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,31 @@
1
+ module SMARTAppLaunch
2
+ class CodeReceivedTest < Inferno::Test
3
+ title 'OAuth server sends code parameter'
4
+ description %(
5
+ Code is a required querystring parameter on the redirect.
6
+ )
7
+ id :smart_code_received
8
+
9
+ output :code
10
+ uses_request :redirect
11
+
12
+ run do
13
+ code = request.query_parameters['code']
14
+ output code: code
15
+
16
+ assert code.present?, 'No `code` paramater received'
17
+
18
+ error = request.query_parameters['error']
19
+
20
+ pass_if error.blank?
21
+
22
+ error_message = "Error returned from authorization server. code: '#{error}'"
23
+ error_description = request.query_parameters['error_description']
24
+ error_uri = request.query_parameters['error_uri']
25
+ error_message += ", description: '#{error_description}'" if error_description.present?
26
+ error_message += ", uri: #{error_uri}" if error_uri.present?
27
+
28
+ assert false, error_message
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,229 @@
1
+ module SMARTAppLaunch
2
+ class DiscoveryGroup < Inferno::TestGroup
3
+ id :smart_discovery
4
+ title 'SMART on FHIR Discovery'
5
+ description %(
6
+ # Background
7
+
8
+ The #{title} Sequence test looks for authorization endpoints and SMART
9
+ capabilities as described by the [SMART App Launch
10
+ Framework](http://hl7.org/fhir/smart-app-launch/conformance/index.html).
11
+ The SMART launch framework uses OAuth 2.0 to *authorize* apps, like
12
+ Inferno, to access certain information on a FHIR server. The
13
+ authorization service accessed at the endpoint allows users to give
14
+ these apps permission without sharing their credentials with the
15
+ application itself. Instead, the application receives an access token
16
+ which allows it to access resources on the server. The access token
17
+ itself has a limited lifetime and permission scopes associated with it.
18
+ A refresh token may also be provided to the application in order to
19
+ obtain another access token. Unlike access tokens, a refresh token is
20
+ not shared with the resource server. If OpenID Connect is used, an id
21
+ token may be provided as well. The id token can be used to
22
+ *authenticate* the user. The id token is digitally signed and allows the
23
+ identity of the user to be verified.
24
+
25
+ # Test Methodology
26
+
27
+ This test suite will examine the SMART on FHIR configuration contained
28
+ in both the `/metadata` and `/.well-known/smart-configuration`
29
+ endpoints.
30
+
31
+ For more information see:
32
+
33
+ * [SMART App Launch Framework](http://hl7.org/fhir/smart-app-launch/index.html)
34
+ * [The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749)
35
+ * [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html)
36
+ )
37
+
38
+ test do
39
+ title 'FHIR server makes SMART configuration available from well-known endpoint'
40
+ description %(
41
+ The authorization endpoints accepted by a FHIR resource server can
42
+ be exposed as a Well-Known Uniform Resource Identifier
43
+ )
44
+ input :url,
45
+ title: 'FHIR Endpoint',
46
+ description: 'URL of the FHIR endpoint used by SMART applications',
47
+ default: 'https://inferno.healthit.gov/reference-server/r4'
48
+ output :well_known_configuration,
49
+ :well_known_authorization_url,
50
+ :well_known_introspection_url,
51
+ :well_known_management_url,
52
+ :well_known_registration_url,
53
+ :well_known_revocation_url,
54
+ :well_known_token_url
55
+ makes_request :smart_well_known_configuration
56
+
57
+ run do
58
+ well_known_configuration_url = "#{url.chomp('/')}/.well-known/smart-configuration"
59
+ get(well_known_configuration_url, name: :smart_well_known_configuration)
60
+
61
+ assert_response_status(200)
62
+
63
+ assert_valid_json(request.response_body)
64
+
65
+ config = JSON.parse(request.response_body)
66
+ output well_known_configuration: request.response_body,
67
+ well_known_authorization_url: config['authorization_endpoint'],
68
+ well_known_introspection_url: config['introspection_endpoint'],
69
+ well_known_management_url: config['management_endpoint'],
70
+ well_known_registration_url: config['registration_endpoint'],
71
+ well_known_revocation_url: config['revocation_endpoint'],
72
+ well_known_token_url: config['token_endpoint']
73
+
74
+ content_type = request.response_header('Content-Type')&.value
75
+
76
+ assert content_type.present?, 'No `Content-Type` header received.'
77
+ assert content_type.start_with?('application/json'),
78
+ "`Content-Type` must be `application/json`, but received: `#{content_type}`"
79
+ end
80
+ end
81
+
82
+ test do
83
+ title 'Well-known configuration contains required fields'
84
+ description %(
85
+ The JSON from .well-known/smart-configuration contains the following
86
+ required fields: `authorization_endpoint`, `token_endpoint`,
87
+ `capabilities`
88
+ )
89
+ input :well_known_configuration
90
+
91
+ run do
92
+ skip_if well_known_configuration.blank?, 'No well-known configuration found'
93
+ config = JSON.parse(well_known_configuration)
94
+
95
+ ['authorization_endpoint', 'token_endpoint', 'capabilities'].each do |key|
96
+ assert config.key?(key), "Well-known configuration does not include `#{key}`"
97
+ assert config[key].present?, "Well-known configuration field `#{key}` is blank"
98
+ end
99
+
100
+ assert config['authorization_endpoint'].is_a?(String),
101
+ 'Well-known `authorization_endpoint` field must be a string'
102
+ assert config['token_endpoint'].is_a?(String),
103
+ 'Well-known `token_endpoint` field must be a string'
104
+ assert config['capabilities'].is_a?(Array),
105
+ 'Well-known `capabilities` field must be an array'
106
+
107
+ non_string_capabilities = config['capabilities'].reject { |capability| capability.is_a? String }
108
+
109
+ assert non_string_capabilities.blank?, %(
110
+ Well-known `capabilities` field must be an array of strings, but found
111
+ non-string values:
112
+ #{non_string_capabilities.map { |value| "`#{value.nil? ? 'nil' : value}`" }.join(', ')}
113
+ )
114
+ end
115
+ end
116
+
117
+ test do
118
+ title 'Conformance/CapabilityStatement provides OAuth 2.0 endpoints'
119
+ description %(
120
+ If a server requires SMART on FHIR authorization for access, its
121
+ metadata must support automated discovery of OAuth2 endpoints.
122
+ )
123
+ input :url
124
+ output :capability_authorization_url,
125
+ :capability_introspection_url,
126
+ :capability_management_url,
127
+ :capability_registration_url,
128
+ :capability_revocation_url,
129
+ :capability_token_url
130
+
131
+ fhir_client do
132
+ url :url
133
+ end
134
+
135
+ run do
136
+ fhir_get_capability_statement
137
+
138
+ assert_response_status(200)
139
+
140
+ smart_extension =
141
+ resource
142
+ .rest
143
+ &.map(&:security)
144
+ &.find do |security|
145
+ security.service&.any? do |service|
146
+ service.coding&.any? do |coding|
147
+ coding.code == 'SMART-on-FHIR'
148
+ end
149
+ end
150
+ end
151
+ &.extension
152
+ &.find do |extension|
153
+ extension.url == 'http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris'
154
+ end
155
+
156
+ assert smart_extension.present?, 'No SMART extensions found in CapabilityStatement'
157
+
158
+ oauth_extension_urls = ['authorize', 'introspect', 'manage', 'register', 'revoke', 'token']
159
+
160
+ oauth_urls = oauth_extension_urls.each_with_object({}) do |url, urls|
161
+ urls[url] = smart_extension.extension.find { |extension| extension.url == url }&.valueUri
162
+ end
163
+
164
+ output capability_authorization_url: oauth_urls['authorize'],
165
+ capability_introspection_url: oauth_urls['introspect'],
166
+ capability_management_url: oauth_urls['manage'],
167
+ capability_registration_url: oauth_urls['register'],
168
+ capability_revocation_url: oauth_urls['revoke'],
169
+ capability_token_url: oauth_urls['token']
170
+
171
+ assert oauth_urls['authorize'].present?, 'No `authorize` extension found'
172
+ assert oauth_urls['token'].present?, 'No `token` extension found'
173
+ end
174
+ end
175
+
176
+ test do
177
+ title 'OAuth 2.0 Endpoints in the conformance statement match those from the well-known configuration'
178
+ description %(
179
+ The server SHALL convey the FHIR OAuth authorization endpoints that are listed
180
+ in the table below to app developers. The server SHALL use both a FHIR
181
+ CapabilityStatement and A Well-Known Uris JSON file.
182
+ )
183
+ input :well_known_authorization_url,
184
+ :well_known_introspection_url,
185
+ :well_known_management_url,
186
+ :well_known_registration_url,
187
+ :well_known_revocation_url,
188
+ :well_known_token_url,
189
+ :capability_authorization_url,
190
+ :capability_introspection_url,
191
+ :capability_management_url,
192
+ :capability_registration_url,
193
+ :capability_revocation_url,
194
+ :capability_token_url
195
+ output :smart_authorization_url,
196
+ :smart_introspection_url,
197
+ :smart_management_url,
198
+ :smart_registration_url,
199
+ :smart_revocation_url,
200
+ :smart_token_url
201
+
202
+ run do
203
+ mismatched_urls = []
204
+ ['authorization', 'token', 'introspection', 'management', 'registration', 'revocation'].each do |type|
205
+ well_known_url = send("well_known_#{type}_url")
206
+ capability_url = send("capability_#{type}_url")
207
+
208
+ output "smart_#{type}_url": well_known_url.presence || capability_url.presence
209
+
210
+ mismatched_urls << type if well_known_url != capability_url
211
+ end
212
+
213
+ pass_if mismatched_urls.empty?
214
+
215
+ error_message = 'The following urls do not match:'
216
+
217
+ mismatched_urls.each do |type|
218
+ well_known_url = send("well_known_#{type}_url")
219
+ capability_url = send("capability_#{type}_url")
220
+
221
+ error_message += "\n- #{type.capitalize}:"
222
+ error_message += "\n - Well-Known: #{well_known_url}\n - CapabilityStatement: #{capability_url}"
223
+ end
224
+
225
+ assert false, error_message
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,117 @@
1
+ require_relative 'app_launch_test'
2
+ require_relative 'app_redirect_test'
3
+ require_relative 'code_received_test'
4
+ require_relative 'launch_received_test'
5
+ require_relative 'token_exchange_test'
6
+ require_relative 'token_response_body_test'
7
+ require_relative 'token_response_headers_test'
8
+
9
+ module SMARTAppLaunch
10
+ class EHRLaunchGroup < Inferno::TestGroup
11
+ id :smart_ehr_launch
12
+ title 'SMART EHR Launch'
13
+
14
+ description %(
15
+ # Background
16
+
17
+ The [EHR
18
+ Launch](http://hl7.org/fhir/smart-app-launch/index.html#ehr-launch-sequence)
19
+ is one of two ways in which an app can be launched, the other being
20
+ Standalone launch. In an EHR launch, the app is launched from an
21
+ existing EHR session or portal by a redirect to the registered launch
22
+ URL. The EHR provides the app two parameters:
23
+
24
+ * `iss` - Which contains the FHIR server url
25
+ * `launch` - An identifier needed for authorization
26
+
27
+ # Test Methodology
28
+
29
+ Inferno will wait for the EHR server redirect upon execution. When the
30
+ redirect is received Inferno will check for the presence of the `iss`
31
+ and `launch` parameters. The security of the authorization endpoint is
32
+ then checked and authorization is attempted using the provided `launch`
33
+ identifier.
34
+
35
+ For more information on the #{title} see:
36
+
37
+ * [SMART EHR Launch Sequence](http://hl7.org/fhir/smart-app-launch/index.html#ehr-launch-sequence)
38
+ )
39
+
40
+ config(
41
+ inputs: {
42
+ client_id: {
43
+ name: :ehr_client_id,
44
+ title: 'EHR Launch Client ID',
45
+ description: 'Client ID provided during registration of Inferno as an EHR launch application',
46
+ default: 'SAMPLE_PUBLIC_CLIENT_ID'
47
+ },
48
+ client_secret: {
49
+ name: :ehr_client_secret,
50
+ title: 'EHR Launch Client Secret',
51
+ description: 'Client Secret provided during registration of Inferno as an EHR launch application'
52
+ },
53
+ requested_scopes: {
54
+ name: :ehr_requested_scopes,
55
+ title: 'EHR Launch Scope',
56
+ description: 'OAuth 2.0 scope provided by system to enable all required functionality',
57
+ type: 'textarea',
58
+ default: %(
59
+ launch openid fhirUser offline_access
60
+ patient/Medication.read patient/AllergyIntolerance.read
61
+ patient/CarePlan.read patient/CareTeam.read patient/Condition.read
62
+ patient/Device.read patient/DiagnosticReport.read
63
+ patient/DocumentReference.read patient/Encounter.read
64
+ patient/Goal.read patient/Immunization.read patient/Location.read
65
+ patient/MedicationRequest.read patient/Observation.read
66
+ patient/Organization.read patient/Patient.read
67
+ patient/Practitioner.read patient/Procedure.read
68
+ patient/Provenance.read patient/PractitionerRole.read
69
+ ).gsub(/\s{2,}/, ' ').strip
70
+ },
71
+ url: {
72
+ title: 'EHR Launch FHIR Endpoint',
73
+ description: 'URL of the FHIR endpoint used by EHR launched applications',
74
+ default: 'https://inferno.healthit.gov/reference-server/r4'
75
+ },
76
+ code: {
77
+ name: :ehr_code
78
+ },
79
+ state: {
80
+ name: :ehr_state
81
+ },
82
+ launch: {
83
+ name: :ehr_launch
84
+ }
85
+ },
86
+ outputs: {
87
+ launch: { name: :ehr_launch },
88
+ code: { name: :ehr_code },
89
+ token_retrieval_time: { name: :ehr_token_retrieval_time },
90
+ state: { name: :ehr_state },
91
+ id_token: { name: :ehr_id_token },
92
+ refresh_token: { name: :ehr_refresh_token },
93
+ access_token: { name: :ehr_access_token },
94
+ expires_in: { name: :ehr_expires_in },
95
+ patient_id: { name: :ehr_patient_id },
96
+ encounter_id: { name: :ehr_encounter_id },
97
+ received_scopes: { name: :ehr_received_scopes },
98
+ intent: { name: :ehr_intent }
99
+ },
100
+ requests: {
101
+ launch: { name: :ehr_launch },
102
+ redirect: { name: :ehr_redirect },
103
+ token: { name: :ehr_token }
104
+ }
105
+ )
106
+
107
+ test from: :smart_app_launch
108
+ test from: :smart_launch_received
109
+ test from: :smart_app_redirect do
110
+ input :launch
111
+ end
112
+ test from: :smart_code_received
113
+ test from: :smart_token_exchange
114
+ test from: :smart_token_response_body
115
+ test from: :smart_token_response_headers
116
+ end
117
+ end