onc_certification_g10_test_kit 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/inferno/terminology/expected_manifest.yml +242 -29
  3. data/lib/inferno/terminology/fhir_package_manager.rb +27 -0
  4. data/lib/inferno/terminology/loader.rb +22 -1
  5. data/lib/inferno/terminology/tasks/create_value_set_validators.rb +1 -1
  6. data/lib/inferno/terminology/tasks/download_fhir_terminology.rb +5 -0
  7. data/lib/inferno/terminology/value_set.rb +51 -5
  8. data/lib/onc_certification_g10_test_kit/base_token_refresh_group.rb +5 -4
  9. data/lib/onc_certification_g10_test_kit/bulk_data_group_export_stu1.rb +5 -0
  10. data/lib/onc_certification_g10_test_kit/bulk_data_group_export_stu2.rb +2 -0
  11. data/lib/onc_certification_g10_test_kit/bulk_data_group_export_validation.rb +206 -28
  12. data/lib/onc_certification_g10_test_kit/bulk_export_validation_tester.rb +25 -40
  13. data/lib/onc_certification_g10_test_kit/encounter_context_test.rb +30 -0
  14. data/lib/onc_certification_g10_test_kit/feature.rb +5 -8
  15. data/lib/onc_certification_g10_test_kit/limited_scope_grant_test.rb +18 -5
  16. data/lib/onc_certification_g10_test_kit/profile_selector.rb +175 -0
  17. data/lib/onc_certification_g10_test_kit/restricted_resource_type_access_group.rb +54 -4
  18. data/lib/onc_certification_g10_test_kit/single_patient_us_core_5_api_group.rb +93 -0
  19. data/lib/onc_certification_g10_test_kit/smart_app_launch_invalid_aud_group.rb +50 -5
  20. data/lib/onc_certification_g10_test_kit/smart_ehr_patient_launch_group.rb +94 -0
  21. data/lib/onc_certification_g10_test_kit/smart_ehr_patient_launch_group_stu2.rb +94 -0
  22. data/lib/onc_certification_g10_test_kit/smart_ehr_practitioner_app_group.rb +197 -13
  23. data/lib/onc_certification_g10_test_kit/smart_invalid_pkce_group.rb +310 -0
  24. data/lib/onc_certification_g10_test_kit/smart_invalid_token_group_stu2.rb +211 -0
  25. data/lib/onc_certification_g10_test_kit/smart_limited_app_group.rb +135 -9
  26. data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group.rb +16 -4
  27. data/lib/onc_certification_g10_test_kit/smart_public_standalone_launch_group_stu2.rb +130 -0
  28. data/lib/onc_certification_g10_test_kit/smart_scopes_test.rb +134 -67
  29. data/lib/onc_certification_g10_test_kit/smart_standalone_patient_app_group.rb +166 -11
  30. data/lib/onc_certification_g10_test_kit/unrestricted_resource_type_access_group.rb +119 -135
  31. data/lib/onc_certification_g10_test_kit/version.rb +1 -1
  32. data/lib/onc_certification_g10_test_kit/visual_inspection_and_attestations_group.rb +19 -0
  33. data/lib/onc_certification_g10_test_kit/well_known_capabilities_test.rb +7 -1
  34. data/lib/onc_certification_g10_test_kit.rb +115 -74
  35. metadata +19 -11
  36. data/lib/onc_certification_g10_test_kit/profile_guesser.rb +0 -72
@@ -2,6 +2,7 @@ require_relative 'base_token_refresh_group'
2
2
  require_relative 'smart_scopes_test'
3
3
  require_relative 'unauthorized_access_test'
4
4
  require_relative 'well_known_capabilities_test'
5
+ require_relative 'encounter_context_test'
5
6
 
6
7
  module ONCCertificationG10TestKit
7
8
  class SmartEHRPractitionerAppGroup < Inferno::TestGroup
@@ -14,29 +15,35 @@ module ONCCertificationG10TestKit
14
15
  * Redirect URI: `#{SMARTAppLaunch::AppRedirectTest.config.options[:redirect_uri]}`
15
16
 
16
17
  Enter in the appropriate scope to enable user-level access to all relevant
17
- resources. In addition, support for the OpenID Connect (openid fhirUser),
18
- refresh tokens (offline_access), and EHR context (launch) are required. This
19
- test expects that the EHR will launch the application with a patient context.
18
+ resources. If using SMART v2, v2-style scopes must be used. In addition,
19
+ support for the OpenID Connect (openid fhirUser), refresh tokens
20
+ (offline_access), and EHR context (launch) are required. This test expects
21
+ that the EHR will launch the application with a patient context.
20
22
 
21
23
  After submit is pressed, Inferno will wait for the system under test to launch
22
24
  the application.
23
25
  )
24
26
 
25
27
  description %(
26
- Demonstrate the ability to perform an EHR launch to a [SMART on
27
- FHIR](https://hl7.org/fhir/smart-app-launch/1.0.0/) confidential client with
28
- patient context, refresh token, and [OpenID Connect
29
- (OIDC)](https://openid.net/specs/openid-connect-core-1_0.html) identity
30
- token. After launch, a simple Patient resource read is performed on the
31
- patient in context. The access token is then refreshed, and the Patient
32
- resource is read using the new access token to ensure that the refresh was
33
- successful. Finally, the authentication information provided by OpenID
34
- Connect is decoded and validated.
28
+ Demonstrate the ability to perform an EHR launch to a SMART on FHIR
29
+ confidential client with patient context, refresh token, OpenID Connect
30
+ (OIDC) identity token, and (SMART v2 only) use the POST HTTP method for
31
+ code exchange. After launch, a simple Patient resource read is performed
32
+ on the patient in context. The access token is then refreshed, and the
33
+ Patient resource is read using the new access token to ensure that the
34
+ refresh was successful. Finally, the authentication information provided
35
+ by OpenID Connect is decoded and validated.
35
36
 
36
37
  For EHRs that use Internet Explorer 11 to display embedded apps,
37
38
  please review [instructions on how to complete the EHR Practitioner App
38
39
  test](https://github.com/onc-healthit/onc-certification-g10-test-kit/wiki/Completing-EHR-Practitioner-App-test-in-Internet-Explorer/).
39
40
 
41
+ * [SMART on FHIR
42
+ (STU1)](http://www.hl7.org/fhir/smart-app-launch/1.0.0/)
43
+ * [SMART on FHIR
44
+ (STU2)](http://hl7.org/fhir/smart-app-launch/STU2)
45
+ * [OpenID Connect
46
+ (OIDC)](https://openid.net/specs/openid-connect-core-1_0.html)
40
47
  )
41
48
  id :g10_smart_ehr_practitioner_app
42
49
  run_as_group
@@ -52,6 +59,8 @@ module ONCCertificationG10TestKit
52
59
  input_order :url, :ehr_client_id, :ehr_client_secret
53
60
 
54
61
  group from: :smart_discovery do
62
+ required_suite_options(smart_app_launch_version: 'smart_app_launch_1')
63
+
55
64
  test from: 'g10_smart_well_known_capabilities',
56
65
  config: {
57
66
  options: {
@@ -69,7 +78,32 @@ module ONCCertificationG10TestKit
69
78
  }
70
79
  end
71
80
 
81
+ group from: :smart_discovery_stu2 do
82
+ required_suite_options(smart_app_launch_version: 'smart_app_launch_2')
83
+
84
+ test from: 'g10_smart_well_known_capabilities',
85
+ config: {
86
+ options: {
87
+ required_capabilities: [
88
+ 'launch-ehr',
89
+ 'client-confidential-symmetric',
90
+ 'sso-openid-connect',
91
+ 'context-banner',
92
+ 'context-style',
93
+ 'context-ehr-patient',
94
+ 'permission-offline',
95
+ 'permission-user',
96
+ 'authorize-post',
97
+ 'permission-v1',
98
+ 'permission-v2'
99
+ ]
100
+ }
101
+ }
102
+ end
103
+
72
104
  group from: :smart_ehr_launch do
105
+ required_suite_options(smart_app_launch_version: 'smart_app_launch_1')
106
+
73
107
  title 'EHR Launch With Practitioner Scope'
74
108
  input :client_secret,
75
109
  name: :ehr_client_secret,
@@ -101,6 +135,9 @@ module ONCCertificationG10TestKit
101
135
  inputs: {
102
136
  requested_scopes: { name: :ehr_requested_scopes },
103
137
  received_scopes: { name: :ehr_received_scopes }
138
+ },
139
+ options: {
140
+ scope_version: :v1
104
141
  }
105
142
  )
106
143
 
@@ -108,7 +145,7 @@ module ONCCertificationG10TestKit
108
145
  ['openid', 'fhirUser', 'launch', 'offline_access']
109
146
  end
110
147
 
111
- def scope_type
148
+ def required_scope_type
112
149
  'user'
113
150
  end
114
151
  end
@@ -128,6 +165,151 @@ module ONCCertificationG10TestKit
128
165
  }
129
166
  }
130
167
 
168
+ test from: :g10_encounter_context,
169
+ config: {
170
+ inputs: {
171
+ encounter_id: { name: :ehr_encounter_id },
172
+ access_token: { name: :ehr_access_token }
173
+ }
174
+ },
175
+ required_suite_options: { us_core_version: 'us_core_5' }
176
+
177
+ test do
178
+ title 'Launch context contains smart_style_url which links to valid JSON'
179
+ description %(
180
+ In order to mimic the style of the SMART host more closely, SMART apps
181
+ can check for the existence of this launch context parameter and
182
+ download the JSON file referenced by the URL value.
183
+ )
184
+ id :Test13
185
+ uses_request :token
186
+
187
+ run do
188
+ skip_if request.status != 200, 'No token response received'
189
+ assert_valid_json response[:body]
190
+
191
+ body = JSON.parse(response[:body])
192
+
193
+ assert body['smart_style_url'].present?,
194
+ 'Token response did not contain `smart_style_url`'
195
+
196
+ get(body['smart_style_url'])
197
+
198
+ assert_response_status(200)
199
+ assert_valid_json(response[:body])
200
+ end
201
+ end
202
+
203
+ test do
204
+ title 'Launch context contains need_patient_banner'
205
+ description %(
206
+ `need_patient_banner` is a boolean value indicating whether the app
207
+ was launched in a UX context where a patient banner is required (when
208
+ true) or not required (when false).
209
+ )
210
+ id :Test14
211
+ uses_request :token
212
+
213
+ run do
214
+ skip_if request.status != 200, 'No token response received'
215
+ assert_valid_json response[:body]
216
+
217
+ body = JSON.parse(response[:body])
218
+
219
+ assert body.key?('need_patient_banner'),
220
+ 'Token response did not contain `need_patient_banner`'
221
+ end
222
+ end
223
+ end
224
+
225
+ group from: :smart_ehr_launch_stu2,
226
+ config: {
227
+ inputs: {
228
+ use_pkce: {
229
+ default: 'true',
230
+ locked: true
231
+ },
232
+ pkce_code_challenge_method: {
233
+ locked: true
234
+ },
235
+ authorization_method: {
236
+ name: :ehr_authorization_method,
237
+ default: 'post',
238
+ locked: true
239
+ }
240
+ }
241
+ } do
242
+ required_suite_options(smart_app_launch_version: 'smart_app_launch_2')
243
+
244
+ title 'EHR Launch With Practitioner Scope'
245
+ input :client_secret,
246
+ name: :ehr_client_secret,
247
+ title: 'EHR Launch Client Secret',
248
+ description: 'Client Secret provided during registration of Inferno as an EHR launch application',
249
+ optional: false
250
+
251
+ config(
252
+ inputs: {
253
+ requested_scopes: {
254
+ default: %(
255
+ launch openid fhirUser offline_access user/Medication.rs
256
+ user/AllergyIntolerance.rs user/CarePlan.rs user/CareTeam.rs
257
+ user/Condition.rs user/Device.rs user/DiagnosticReport.rs
258
+ user/DocumentReference.rs user/Encounter.rs user/Goal.rs
259
+ user/Immunization.rs user/Location.rs user/MedicationRequest.rs
260
+ user/Observation.rs user/Organization.rs user/Patient.rs
261
+ user/Practitioner.rs user/Procedure.rs user/Provenance.rs
262
+ user/PractitionerRole.rs
263
+ ).gsub(/\s{2,}/, ' ').strip
264
+ }
265
+ }
266
+ )
267
+
268
+ test from: :g10_smart_scopes do
269
+ title 'User-level access with OpenID Connect and Refresh Token scopes used.'
270
+ config(
271
+ inputs: {
272
+ requested_scopes: { name: :ehr_requested_scopes },
273
+ received_scopes: { name: :ehr_received_scopes }
274
+ },
275
+ options: {
276
+ scope_version: :v2
277
+ }
278
+ )
279
+
280
+ def required_scopes
281
+ ['openid', 'fhirUser', 'launch', 'offline_access']
282
+ end
283
+
284
+ def required_scope_type
285
+ 'user'
286
+ end
287
+ end
288
+
289
+ test from: :g10_unauthorized_access,
290
+ config: {
291
+ inputs: {
292
+ patient_id: { name: :ehr_patient_id }
293
+ }
294
+ }
295
+
296
+ test from: :g10_patient_context,
297
+ config: {
298
+ inputs: {
299
+ patient_id: { name: :ehr_patient_id },
300
+ access_token: { name: :ehr_access_token }
301
+ }
302
+ }
303
+
304
+ test from: :g10_encounter_context,
305
+ config: {
306
+ inputs: {
307
+ encounter_id: { name: :ehr_encounter_id },
308
+ access_token: { name: :ehr_access_token }
309
+ }
310
+ },
311
+ required_suite_options: { us_core_version: 'us_core_5' }
312
+
131
313
  test do
132
314
  title 'Launch context contains smart_style_url which links to valid JSON'
133
315
  description %(
@@ -136,6 +318,7 @@ module ONCCertificationG10TestKit
136
318
  download the JSON file referenced by the URL value.
137
319
  )
138
320
  uses_request :token
321
+ id :g10_smart_style_url
139
322
 
140
323
  run do
141
324
  skip_if request.status != 200, 'No token response received'
@@ -161,6 +344,7 @@ module ONCCertificationG10TestKit
161
344
  true) or not required (when false).
162
345
  )
163
346
  uses_request :token
347
+ id :g10_smart_need_patient_banner
164
348
 
165
349
  run do
166
350
  skip_if request.status != 200, 'No token response received'
@@ -0,0 +1,310 @@
1
+ module ONCCertificationG10TestKit
2
+ class InvalidSMARTTokenRequestTest < Inferno::Test
3
+ title 'OAuth token exchange fails when supplied invalid code_verifier'
4
+ description %(
5
+ If the request failed verification or is invalid, the authorization
6
+ server returns an error response.
7
+ )
8
+ uses_request :redirect
9
+ id :invalid_pkce_request
10
+
11
+ input :code, :use_pkce, :pkce_code_verifier, :client_id, :client_secret, :smart_token_url
12
+
13
+ def modify_oauth_params(oauth_params)
14
+ oauth_params
15
+ end
16
+
17
+ run do
18
+ skip_if request.query_parameters['error'].present?, 'Error during authorization request'
19
+
20
+ oauth2_params = {
21
+ grant_type: 'authorization_code',
22
+ code: code,
23
+ redirect_uri: config.options[:redirect_uri]
24
+ }
25
+
26
+ oauth2_headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
27
+
28
+ if client_secret.present?
29
+ client_credentials = "#{client_id}:#{client_secret}"
30
+ oauth2_headers['Authorization'] = "Basic #{Base64.strict_encode64(client_credentials)}"
31
+ else
32
+ oauth2_params[:client_id] = client_id
33
+ end
34
+
35
+ modify_oauth_params(oauth2_params)
36
+
37
+ post(smart_token_url, body: oauth2_params, name: :token, headers: oauth2_headers)
38
+
39
+ assert_response_status([400, 401])
40
+ end
41
+ end
42
+
43
+ class SMARTInvalidPKCEGroup < Inferno::TestGroup
44
+ title 'SMART App Launch Error: Invalid PKCE Code Verifier'
45
+ short_title 'SMART Invalid PKCE Code Verifier'
46
+ input_instructions %(
47
+ Register Inferno as a standalone application using the following information:
48
+
49
+ * Redirect URI: `#{SMARTAppLaunch::AppRedirectTest.config.options[:redirect_uri]}`
50
+ )
51
+ description %(
52
+ # Background
53
+
54
+ The #{title} Group verifies that a SMART Launch Sequence, specifically the
55
+ [Standalone
56
+ Launch](http://hl7.org/fhir/smart-app-launch/STU2/app-launch.html#launch-app-standalone-launch)
57
+ Sequence, verifies that servers properly support PKCE. It does this by ensuring the launch fails
58
+ in the case where the client sends an invalid PKCE `code_verifier`.
59
+
60
+ This group performs four launches with various forms of an invalid `code_verifier`
61
+ (e.g. incorrect `code_verifier`, blank `code_identifier`) and verifies that these do
62
+ not result in a successful launch. Testers can expect to be prompted four times
63
+ that a redirect will occur in this test.
64
+
65
+ This test is not included as part of the Single Patient App group
66
+ because there is no way for a client to infer that PKCE is supported on the server
67
+ properly without performing extra launches. Attempting to verify this within the
68
+ same launch cannot be done because some servers may not accept an authorization code
69
+ after it has been used unsuccessfully in this manner.
70
+ )
71
+ id :g10_smart_invalid_pkce_code_verifier_group
72
+ run_as_group
73
+
74
+ input :use_pkce,
75
+ title: 'Proof Key for Code Exchange (PKCE)',
76
+ type: 'radio',
77
+ default: 'true',
78
+ locked: true,
79
+ options: {
80
+ list_options: [
81
+ {
82
+ label: 'Enabled',
83
+ value: 'true'
84
+ },
85
+ {
86
+ label: 'Disabled',
87
+ value: 'false'
88
+ }
89
+ ]
90
+ }
91
+ input :pkce_code_challenge_method,
92
+ optional: true,
93
+ title: 'PKCE Code Challenge Method',
94
+ type: 'radio',
95
+ default: 'S256',
96
+ locked: true,
97
+ options: {
98
+ list_options: [
99
+ {
100
+ label: 'S256',
101
+ value: 'S256'
102
+ },
103
+ {
104
+ label: 'Plain',
105
+ value: 'plain'
106
+ }
107
+ ]
108
+ }
109
+
110
+ input_order :url,
111
+ :standalone_client_id,
112
+ :standalone_client_secret,
113
+ :standalone_requested_scopes,
114
+ :use_pkce,
115
+ :pkce_code_challenge_method,
116
+ :smart_authorization_url,
117
+ :smart_token_url
118
+
119
+ config(
120
+ inputs: {
121
+ client_id: {
122
+ name: :standalone_client_id,
123
+ title: 'Standalone Client ID',
124
+ description: 'Client ID provided during registration of Inferno as a standalone application'
125
+ },
126
+ client_secret: {
127
+ name: :standalone_client_secret,
128
+ title: 'Standalone Client Secret',
129
+ description: 'Client Secret provided during registration of Inferno as a standalone application'
130
+ },
131
+ requested_scopes: {
132
+ name: :standalone_requested_scopes,
133
+ title: 'Standalone Scope',
134
+ description: 'OAuth 2.0 scope provided by system to enable all required functionality',
135
+ type: 'textarea',
136
+ default: %(
137
+ launch/patient openid fhirUser offline_access
138
+ patient/Medication.read patient/AllergyIntolerance.read
139
+ patient/CarePlan.read patient/CareTeam.read patient/Condition.read
140
+ patient/Device.read patient/DiagnosticReport.read
141
+ patient/DocumentReference.read patient/Encounter.read
142
+ patient/Goal.read patient/Immunization.read patient/Location.read
143
+ patient/MedicationRequest.read patient/Observation.read
144
+ patient/Organization.read patient/Patient.read
145
+ patient/Practitioner.read patient/Procedure.read
146
+ patient/Provenance.read patient/PractitionerRole.read
147
+ ).gsub(/\s{2,}/, ' ').strip
148
+ },
149
+ url: {
150
+ title: 'Standalone FHIR Endpoint',
151
+ description: 'URL of the FHIR endpoint used by standalone applications'
152
+ },
153
+ code: {
154
+ name: :invalid_token_code
155
+ },
156
+ state: {
157
+ name: :invalid_token_state
158
+ },
159
+ smart_authorization_url: {
160
+ title: 'OAuth 2.0 Authorize Endpoint',
161
+ description: 'OAuth 2.0 Authorize Endpoint provided during the patient standalone launch'
162
+ },
163
+ smart_token_url: {
164
+ title: 'OAuth 2.0 Token Endpoint',
165
+ description: 'OAuth 2.0 Token Endpoint provided during the patient standalone launch'
166
+ },
167
+ pkce_code_challenge: {
168
+ name: :invalid_token_pkce_code_challenge
169
+ },
170
+ pkce_code_verifier: {
171
+ name: :invalid_token_pkce_code_verifier
172
+ }
173
+ },
174
+ outputs: {
175
+ code: { name: :invalid_token_code },
176
+ state: { name: :invalid_token_state },
177
+ expires_in: { name: :invalid_token_expires_in },
178
+ pkce_code_challenge: { name: :invalid_token_pkce_code_challenge },
179
+ pkce_code_verifier: { name: :invalid_token_pkce_code_verifier }
180
+ },
181
+ requests: {
182
+ redirect: { name: :invalid_token_redirect },
183
+ token: { name: :invalid_token_token }
184
+ }
185
+ )
186
+
187
+ test from: :smart_app_redirect_stu2,
188
+ id: :smart_no_code_verifier_redirect,
189
+ config: {
190
+ options: {
191
+ redirect_message_proc: lambda do |auth_url|
192
+ %(
193
+ ### Invalid PKCE code_verifier 1/4
194
+
195
+ This launch does not provide a `code_verifier` and verifies the
196
+ server does not issue an access token.
197
+
198
+ [Follow this link to authorize with the SMART
199
+ server](#{auth_url}).
200
+
201
+ Tests will resume once Inferno receives a request at
202
+ `#{config.options[:redirect_uri]}` with a state of `#{state}`.
203
+ )
204
+ end
205
+ }
206
+ }
207
+ test from: :smart_code_received,
208
+ id: :smart_no_code_verifier_code_received
209
+ test from: :invalid_pkce_request do
210
+ title 'OAuth token exchange fails when no code_verifier is given'
211
+ id :smart_no_verifier_token_request
212
+ end
213
+
214
+ test from: :smart_app_redirect_stu2,
215
+ id: :smart_blank_code_verifier_redirect,
216
+ config: {
217
+ options: {
218
+ redirect_message_proc: lambda do |auth_url|
219
+ %(
220
+ ### Invalid PKCE code_verifier 2/4
221
+
222
+ This launch provides a blank `code_verifier` and verifies the
223
+ server does not issue an access token.
224
+
225
+ [Follow this link to authorize with the SMART
226
+ server](#{auth_url}).
227
+
228
+ Tests will resume once Inferno receives a request at
229
+ `#{config.options[:redirect_uri]}` with a state of `#{state}`.
230
+ )
231
+ end
232
+ }
233
+ }
234
+ test from: :smart_code_received,
235
+ id: :smart_blank_code_verifier_code_received
236
+ test from: :invalid_pkce_request do
237
+ title 'OAuth token exchange fails when code_verifier is blank'
238
+ id :smart_blank_verifier_token_request
239
+
240
+ def modify_oauth_params(oauth_params)
241
+ oauth_params.merge!(code_verifier: '')
242
+ end
243
+ end
244
+
245
+ test from: :smart_app_redirect_stu2,
246
+ id: :smart_bad_code_verifier_redirect,
247
+ config: {
248
+ options: {
249
+ redirect_message_proc: lambda do |auth_url|
250
+ %(
251
+ ### Invalid PKCE code_verifier 3/4
252
+
253
+ This launch provides an invalid `code_verifier` and verifies the
254
+ server does not issue an access token.
255
+
256
+ [Follow this link to authorize with the SMART
257
+ server](#{auth_url}).
258
+
259
+ Tests will resume once Inferno receives a request at
260
+ `#{config.options[:redirect_uri]}` with a state of `#{state}`.
261
+ )
262
+ end
263
+ }
264
+ }
265
+ test from: :smart_code_received,
266
+ id: :smart_bad_code_verifier_code_received
267
+ test from: :invalid_pkce_request do
268
+ title 'OAuth token exchange fails when code_verifier is incorrect'
269
+ id :smart_bad_code_verifier_token_request
270
+
271
+ def modify_oauth_params(oauth_params)
272
+ oauth_params.merge!(code_verifier: "#{SecureRandom.uuid}-#{SecureRandom.uuid}")
273
+ end
274
+ end
275
+
276
+ test from: :smart_app_redirect_stu2,
277
+ id: :smart_plain_code_verifier_redirect,
278
+ config: {
279
+ options: {
280
+ redirect_message_proc: lambda do |auth_url|
281
+ %(
282
+ ### Invalid PKCE code_verifier 4/4
283
+
284
+ This launch provides a `plain` `code_verifier` instead of one
285
+ encoded with `S256` and verifies the server does not issue an
286
+ access token.
287
+
288
+ [Follow this link to authorize with the SMART
289
+ server](#{auth_url}).
290
+
291
+ Tests will resume once Inferno receives a request at
292
+ `#{config.options[:redirect_uri]}` with a state of `#{state}`.
293
+ )
294
+ end
295
+ }
296
+ }
297
+ test from: :smart_code_received,
298
+ id: :smart_plain_code_verifier_code_received
299
+ test from: :invalid_pkce_request do
300
+ title 'OAuth token exchange fails when code_verifier matches code_challenge'
301
+ id :smart_plain_code_verifier_token_request
302
+
303
+ input :pkce_code_challenge
304
+
305
+ def modify_oauth_params(oauth_params)
306
+ oauth_params.merge!(code_verifier: pkce_code_challenge)
307
+ end
308
+ end
309
+ end
310
+ end