smart_app_launch_test_kit 0.6.1 → 0.6.2

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/SMART_RunClientAgainstServer.json.erb +58 -10
  3. data/config/presets/SMART_RunServerAgainstClient_ConfidentialAsymmetric.json.erb +183 -0
  4. data/config/presets/SMART_RunServerAgainstClient_ConfidentialSymmetric.json.erb +157 -0
  5. data/config/presets/SMART_RunServerAgainstClient_Public.json.erb +155 -0
  6. data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +1 -1
  7. data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +1 -1
  8. data/lib/smart_app_launch/client_stu2_2_suite.rb +60 -19
  9. data/lib/smart_app_launch/client_suite/access_alca_interaction_test.rb +75 -0
  10. data/lib/smart_app_launch/client_suite/access_alcs_interaction_test.rb +75 -0
  11. data/lib/smart_app_launch/client_suite/access_alp_interaction_test.rb +75 -0
  12. data/lib/smart_app_launch/client_suite/access_bsca_interaction_test.rb +46 -0
  13. data/lib/smart_app_launch/client_suite/access_group.rb +85 -0
  14. data/lib/smart_app_launch/client_suite/authentication_verification.rb +86 -0
  15. data/lib/smart_app_launch/client_suite/authorization_request_verification_test.rb +108 -0
  16. data/lib/smart_app_launch/client_suite/client_descriptions.rb +114 -0
  17. data/lib/smart_app_launch/client_suite/client_options.rb +35 -0
  18. data/lib/smart_app_launch/client_suite/oidc_jwks.json +32 -0
  19. data/lib/smart_app_launch/client_suite/oidc_jwks.rb +27 -0
  20. data/lib/smart_app_launch/client_suite/registration_alca_group.rb +15 -0
  21. data/lib/smart_app_launch/client_suite/registration_alca_verification_test.rb +57 -0
  22. data/lib/smart_app_launch/client_suite/registration_alcs_group.rb +15 -0
  23. data/lib/smart_app_launch/client_suite/registration_alcs_verification_test.rb +56 -0
  24. data/lib/smart_app_launch/client_suite/registration_alp_group.rb +16 -0
  25. data/lib/smart_app_launch/client_suite/registration_alp_verification_test.rb +50 -0
  26. data/lib/smart_app_launch/client_suite/registration_bsca_group.rb +15 -0
  27. data/lib/smart_app_launch/client_suite/registration_bsca_verification_test.rb +40 -0
  28. data/lib/smart_app_launch/client_suite/registration_verification.rb +58 -0
  29. data/lib/smart_app_launch/client_suite/token_request_alca_verification_test.rb +53 -0
  30. data/lib/smart_app_launch/client_suite/token_request_alcs_verification_test.rb +53 -0
  31. data/lib/smart_app_launch/client_suite/token_request_alp_verification_test.rb +48 -0
  32. data/lib/smart_app_launch/client_suite/token_request_bsca_verification_test.rb +53 -0
  33. data/lib/smart_app_launch/client_suite/token_request_verification.rb +116 -0
  34. data/lib/smart_app_launch/client_suite/{client_token_use_verification_test.rb → token_use_verification_test.rb} +1 -8
  35. data/lib/smart_app_launch/docs/smart_stu2_2_client_suite_description.md +128 -41
  36. data/lib/smart_app_launch/endpoints/echoing_fhir_responder_endpoint.rb +96 -0
  37. data/lib/smart_app_launch/endpoints/mock_smart_server/authorization_endpoint.rb +27 -0
  38. data/lib/smart_app_launch/endpoints/mock_smart_server/introspection_endpoint.rb +33 -0
  39. data/lib/smart_app_launch/endpoints/mock_smart_server/smart_authorization_response_creation.rb +30 -0
  40. data/lib/smart_app_launch/endpoints/mock_smart_server/smart_introspection_response_creation.rb +46 -0
  41. data/lib/smart_app_launch/endpoints/mock_smart_server/smart_token_response_creation.rb +250 -0
  42. data/lib/smart_app_launch/endpoints/mock_smart_server/token_endpoint.rb +58 -0
  43. data/lib/smart_app_launch/endpoints/mock_smart_server.rb +128 -67
  44. data/lib/smart_app_launch/metadata.rb +19 -14
  45. data/lib/smart_app_launch/tags.rb +9 -1
  46. data/lib/smart_app_launch/token_payload_validation.rb +2 -2
  47. data/lib/smart_app_launch/urls.rb +12 -0
  48. data/lib/smart_app_launch/version.rb +2 -2
  49. metadata +38 -11
  50. data/config/presets/SMART_RunServerAgainstClient.json.erb +0 -42
  51. data/lib/smart_app_launch/client_suite/client_access_group.rb +0 -26
  52. data/lib/smart_app_launch/client_suite/client_access_interaction_test.rb +0 -64
  53. data/lib/smart_app_launch/client_suite/client_registration_group.rb +0 -15
  54. data/lib/smart_app_launch/client_suite/client_registration_verification_test.rb +0 -52
  55. data/lib/smart_app_launch/client_suite/client_token_request_verification_test.rb +0 -146
  56. data/lib/smart_app_launch/endpoints/echoing_fhir_responder.rb +0 -52
  57. data/lib/smart_app_launch/endpoints/mock_smart_server/token.rb +0 -27
@@ -5,9 +5,9 @@ module SMARTAppLaunch
5
5
  id :smart_app_launch_test_kit
6
6
  title 'SMART App Launch Test Kit'
7
7
  description <<~DESCRIPTION
8
- The SMART App Launch Test Kit primarily validates the conformance of an
9
- authorization server implementation to a specified version of the [SMART
10
- Application Launch Framework Implementation
8
+ The SMART App Launch Test Kit validates the conformance of authorization server
9
+ implementations and clients that interact with them to a specified version of the
10
+ [SMART Application Launch Framework Implementation
11
11
  Guide](http://hl7.org/fhir/smart-app-launch/index.html). This Test Kit also
12
12
  provides Brand Bundle Publisher testing for the User-access Brands and Endpoints
13
13
  specification. This Test Kit supports following versions of the SMART App
@@ -25,21 +25,26 @@ module SMARTAppLaunch
25
25
  kits for any FHIR-based data exchange.
26
26
 
27
27
  To run tests for a SMART App Launch authorization server, select one of the
28
- "SMART App Launch" suites. To run tests for a Brand Bundle Publisher, select
28
+ "SMART App Launch" suites. To run tests for a SMART App Launch or backend
29
+ services client, select the "SMART App Launch STU2.2 Client" suite and choose
30
+ the type of client. To run tests for a Brand Bundle Publisher, select
29
31
  the "SMART User-access Brands and Endpoints" suite.
30
32
 
31
33
  ## Status
32
34
 
33
- The SMART App Launch Test Kit primarily verifies that systems correctly
34
- implement the SMART App Launch IG for providing authorization and/or
35
- authentication services to client applications accessing HL7 FHIR APIs.
36
-
37
- The test kit currently tests the following requirements:
38
- - Standalone Launch
39
- - EHR Launch
40
-
41
- It also tests the ability of a Brand Bundle Publisher to publish a valid brand
42
- bundle as described in the User-access Brands and Endpoints specification.
35
+ The SMART App Launch Test Kit provides five suites that verify that systems
36
+ correctly implement different aspects and versions of the SMART App Launch IG.
37
+ - Three server suites (SMART App Launch STU1, STU2, and STU2.2) verifying that SMART server implementations
38
+ can provide authorization and/or authentication services to
39
+ client applications accessing HL7 FHIR APIs. Thes
40
+ - Standalone Launch
41
+ - EHR Launch
42
+ - Backend Services (STU2 and STU2.2 only)
43
+ - Token Introspection (STU2 and STU2.2 only)
44
+ - A client suite (SMART App Launch STU2.2 Client) verifying that a SMART STU2.2
45
+ client can obtain and use an access token using the SMART flows and authentication options.
46
+ - A suite verifying a server's compliance with the User-access Brands and Endpoints
47
+ specification.
43
48
 
44
49
  See the test descriptions within the test kit for detail on the specific
45
50
  validations performed as part of testing these requirements.
@@ -2,6 +2,14 @@
2
2
 
3
3
  module SMARTAppLaunch
4
4
  TOKEN_TAG = 'token'
5
- SMART_TAG = 'smart'
5
+ AUTHORIZATION_TAG = 'authorization'
6
+ INTROSPECTION_TAG = 'introspection'
7
+ SMART_TAG = 'SMART'
6
8
  ACCESS_TAG = 'access'
9
+ CLIENT_CREDENTIALS_TAG = 'client_credentials'
10
+ AUTHORIZATION_CODE_TAG = 'authorization_code'
11
+ REFRESH_TOKEN_TAG = 'refresh_token'
12
+ PUBLIC_TAG = 'public'
13
+ CONFIDENTIAL_SYMMETRIC_TAG = 'confidential_symmetric'
14
+ CONFIDENTIAL_ASYMMETRIC_TAG = 'confidential_asymmetric'
7
15
  end
@@ -78,8 +78,8 @@ module SMARTAppLaunch
78
78
  end
79
79
 
80
80
  def check_for_missing_scopes(requested_scopes, body)
81
- expected_scopes = requested_scopes.split
82
- new_scopes = body['scope'].split
81
+ expected_scopes = requested_scopes&.split || []
82
+ new_scopes = body['scope']&.split || []
83
83
  missing_scopes = expected_scopes - new_scopes
84
84
 
85
85
  warning do
@@ -6,7 +6,11 @@ module SMARTAppLaunch
6
6
  RESUME_FAIL_PATH = '/resume_fail'
7
7
  AUTH_SERVER_PATH = '/auth'
8
8
  SMART_DISCOVERY_PATH = "#{FHIR_PATH}/.well-known/smart-configuration".freeze
9
+ OIDC_DISCOVERY_PATH = "#{FHIR_PATH}/.well-known/openid-configuration".freeze
10
+ OIDC_JWKS_PATH = "#{FHIR_PATH}/.well-known/jwks.json".freeze
9
11
  TOKEN_PATH = "#{AUTH_SERVER_PATH}/token".freeze
12
+ AUTHORIZATION_PATH = "#{AUTH_SERVER_PATH}/authorization".freeze
13
+ INTROSPECTION_PATH = "#{AUTH_SERVER_PATH}/introspect".freeze
10
14
 
11
15
  module URLs
12
16
  def client_base_url
@@ -33,6 +37,14 @@ module SMARTAppLaunch
33
37
  @client_token_url ||= client_base_url + TOKEN_PATH
34
38
  end
35
39
 
40
+ def client_authorization_url
41
+ @client_authorization_url ||= client_base_url + AUTHORIZATION_PATH
42
+ end
43
+
44
+ def client_introspection_url
45
+ @client_introspection_url ||= client_base_url + INTROSPECTION_PATH
46
+ end
47
+
36
48
  def client_suite_id
37
49
  SMARTAppLaunch::SMARTClientSTU22Suite.id
38
50
  end
@@ -1,4 +1,4 @@
1
1
  module SMARTAppLaunch
2
- VERSION = '0.6.1'.freeze
3
- LAST_UPDATED = '2025-04-07'.freeze
2
+ VERSION = '0.6.2'.freeze
3
+ LAST_UPDATED = '2025-05-02'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_app_launch_test_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen MacVicar
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-07 00:00:00.000000000 Z
11
+ date: 2025-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inferno_core
@@ -146,7 +146,9 @@ extra_rdoc_files: []
146
146
  files:
147
147
  - LICENSE
148
148
  - config/presets/SMART_RunClientAgainstServer.json.erb
149
- - config/presets/SMART_RunServerAgainstClient.json.erb
149
+ - config/presets/SMART_RunServerAgainstClient_ConfidentialAsymmetric.json.erb
150
+ - config/presets/SMART_RunServerAgainstClient_ConfidentialSymmetric.json.erb
151
+ - config/presets/SMART_RunServerAgainstClient_Public.json.erb
150
152
  - config/presets/inferno_reference_server_preset.json
151
153
  - config/presets/inferno_reference_server_stu2_2_preset.json
152
154
  - config/presets/inferno_reference_server_stu2_preset.json
@@ -167,12 +169,32 @@ files:
167
169
  - lib/smart_app_launch/backend_services_invalid_jwt_test.rb
168
170
  - lib/smart_app_launch/client_assertion_builder.rb
169
171
  - lib/smart_app_launch/client_stu2_2_suite.rb
170
- - lib/smart_app_launch/client_suite/client_access_group.rb
171
- - lib/smart_app_launch/client_suite/client_access_interaction_test.rb
172
- - lib/smart_app_launch/client_suite/client_registration_group.rb
173
- - lib/smart_app_launch/client_suite/client_registration_verification_test.rb
174
- - lib/smart_app_launch/client_suite/client_token_request_verification_test.rb
175
- - lib/smart_app_launch/client_suite/client_token_use_verification_test.rb
172
+ - lib/smart_app_launch/client_suite/access_alca_interaction_test.rb
173
+ - lib/smart_app_launch/client_suite/access_alcs_interaction_test.rb
174
+ - lib/smart_app_launch/client_suite/access_alp_interaction_test.rb
175
+ - lib/smart_app_launch/client_suite/access_bsca_interaction_test.rb
176
+ - lib/smart_app_launch/client_suite/access_group.rb
177
+ - lib/smart_app_launch/client_suite/authentication_verification.rb
178
+ - lib/smart_app_launch/client_suite/authorization_request_verification_test.rb
179
+ - lib/smart_app_launch/client_suite/client_descriptions.rb
180
+ - lib/smart_app_launch/client_suite/client_options.rb
181
+ - lib/smart_app_launch/client_suite/oidc_jwks.json
182
+ - lib/smart_app_launch/client_suite/oidc_jwks.rb
183
+ - lib/smart_app_launch/client_suite/registration_alca_group.rb
184
+ - lib/smart_app_launch/client_suite/registration_alca_verification_test.rb
185
+ - lib/smart_app_launch/client_suite/registration_alcs_group.rb
186
+ - lib/smart_app_launch/client_suite/registration_alcs_verification_test.rb
187
+ - lib/smart_app_launch/client_suite/registration_alp_group.rb
188
+ - lib/smart_app_launch/client_suite/registration_alp_verification_test.rb
189
+ - lib/smart_app_launch/client_suite/registration_bsca_group.rb
190
+ - lib/smart_app_launch/client_suite/registration_bsca_verification_test.rb
191
+ - lib/smart_app_launch/client_suite/registration_verification.rb
192
+ - lib/smart_app_launch/client_suite/token_request_alca_verification_test.rb
193
+ - lib/smart_app_launch/client_suite/token_request_alcs_verification_test.rb
194
+ - lib/smart_app_launch/client_suite/token_request_alp_verification_test.rb
195
+ - lib/smart_app_launch/client_suite/token_request_bsca_verification_test.rb
196
+ - lib/smart_app_launch/client_suite/token_request_verification.rb
197
+ - lib/smart_app_launch/client_suite/token_use_verification_test.rb
176
198
  - lib/smart_app_launch/code_received_test.rb
177
199
  - lib/smart_app_launch/cors_metadata_request_test.rb
178
200
  - lib/smart_app_launch/cors_openid_fhir_user_claim_test.rb
@@ -186,9 +208,14 @@ files:
186
208
  - lib/smart_app_launch/ehr_launch_group.rb
187
209
  - lib/smart_app_launch/ehr_launch_group_stu2.rb
188
210
  - lib/smart_app_launch/ehr_launch_group_stu2_2.rb
189
- - lib/smart_app_launch/endpoints/echoing_fhir_responder.rb
211
+ - lib/smart_app_launch/endpoints/echoing_fhir_responder_endpoint.rb
190
212
  - lib/smart_app_launch/endpoints/mock_smart_server.rb
191
- - lib/smart_app_launch/endpoints/mock_smart_server/token.rb
213
+ - lib/smart_app_launch/endpoints/mock_smart_server/authorization_endpoint.rb
214
+ - lib/smart_app_launch/endpoints/mock_smart_server/introspection_endpoint.rb
215
+ - lib/smart_app_launch/endpoints/mock_smart_server/smart_authorization_response_creation.rb
216
+ - lib/smart_app_launch/endpoints/mock_smart_server/smart_introspection_response_creation.rb
217
+ - lib/smart_app_launch/endpoints/mock_smart_server/smart_token_response_creation.rb
218
+ - lib/smart_app_launch/endpoints/mock_smart_server/token_endpoint.rb
192
219
  - lib/smart_app_launch/jwks.rb
193
220
  - lib/smart_app_launch/launch_received_test.rb
194
221
  - lib/smart_app_launch/metadata.rb
@@ -1,42 +0,0 @@
1
- {
2
- "title": "Demo: Run Against the SMART Client Suite",
3
- "id": "smart_run_server_against_client_v2_2",
4
- "test_suite_id": "smart_stu2_2",
5
- "inputs": [
6
- {
7
- "name": "url",
8
- "description": "URL of the FHIR endpoint used by SMART applications",
9
- "title": "FHIR Endpoint",
10
- "type": "text",
11
- "value": "<%= Inferno::Application['base_url'] %>/custom/smart_client_stu2_2/fhir"
12
- },
13
- {
14
- "name": "backend_services_smart_auth_info",
15
- "options": {
16
- "mode": "auth",
17
- "components": [
18
- {
19
- "name": "auth_type",
20
- "default": "backend_services",
21
- "locked": "true"
22
- },
23
- {
24
- "name": "use_discovery",
25
- "locked": true
26
- }
27
- ]
28
- },
29
- "title": "Backend Services Credentials",
30
- "type": "auth_info",
31
- "value": {
32
- "encryption_algorithm": "ES384",
33
- "auth_type": "backend_services",
34
- "use_discovery": "true",
35
- "token_url": "<%= Inferno::Application['base_url'] %>/custom/smart_client_stu2_2/auth/token",
36
- "requested_scopes": "system/*.rs",
37
- "client_id": "smart_client_test_demo"
38
- },
39
- "default": {}
40
- }
41
- ]
42
- }
@@ -1,26 +0,0 @@
1
- require_relative 'client_access_interaction_test'
2
- require_relative 'client_token_request_verification_test'
3
- require_relative 'client_token_use_verification_test'
4
-
5
- module SMARTAppLaunch
6
- class SMARTClientAccess < Inferno::TestGroup
7
- id :smart_client_access
8
- title 'Client Access'
9
- description %(
10
- During these tests, the client system will access Inferno's simulated
11
- FHIR server by requesting an access token and making a FHIR request.
12
- Inferno will then verify that any token requests made were conformant
13
- and that a token returned from a token request was used on an access request.
14
- )
15
-
16
- run_as_group
17
-
18
- input :smart_jwk_set,
19
- optional: true,
20
- locked: true
21
-
22
- test from: :smart_client_access_interaction
23
- test from: :smart_client_token_request_verification
24
- test from: :smart_client_token_use_verification
25
- end
26
- end
@@ -1,64 +0,0 @@
1
- require_relative '../urls'
2
- require_relative '../endpoints/mock_smart_server'
3
-
4
- module SMARTAppLaunch
5
- class SMARTClientAccessInteraction < Inferno::Test
6
- include URLs
7
-
8
- id :smart_client_access_interaction
9
- title 'Perform SMART-secured Access'
10
- description %(
11
- During this test, Inferno will wait for the client to access data
12
- using a SMART token obtained during earlier tests.
13
- )
14
- input :client_id,
15
- title: 'Client Id',
16
- type: 'text',
17
- locked: true,
18
- description: %(
19
- The registered Client Id for use in obtaining access tokens.
20
- Create a new session if you need to change this value.
21
- )
22
- input :smart_jwk_set,
23
- title: 'JSON Web Key Set (JWKS)',
24
- type: 'textarea',
25
- optional: true,
26
- locked: true,
27
- description: %(
28
- The SMART client's JSON Web Key Set in the form of either a publicly accessible url
29
- containing the JWKS, or the raw JWKS JSON. Must include the key(s) Inferno will need to
30
- verify signatures on token requests made by the client.
31
- Create a new session if you need to change this value.
32
- )
33
- input :echoed_fhir_response,
34
- title: 'FHIR Response to Echo',
35
- type: 'textarea',
36
- description: %(
37
- JSON representation of a FHIR resource for Inferno to echo when a request
38
- is made to the simulated FHIR server. The provided content will be echoed
39
- back exactly and no check will be made that it is appropriate for the request
40
- made. If nothing is provided, an OperationOutcome will be returned.
41
- ),
42
- optional: true
43
-
44
- run do
45
- wait(
46
- identifier: client_id,
47
- message: %(
48
- **Access**
49
-
50
- Use the registered client id (#{client_id}) to obtain an access
51
- token using SMART Backend Services
52
- and use that token to access a FHIR endpoint under the simulated server's base URL
53
-
54
- `#{client_fhir_base_url}`
55
-
56
- Inferno will echo the response provided in the **FHIR Response to Echo** input.
57
-
58
- [Click here](#{client_resume_pass_url}?token=#{client_id}) once you performed
59
- the access.
60
- )
61
- )
62
- end
63
- end
64
- end
@@ -1,15 +0,0 @@
1
- require_relative 'client_registration_verification_test'
2
-
3
- module SMARTAppLaunch
4
- class SMARTClientRegistration < Inferno::TestGroup
5
- id :smart_client_registration
6
- title 'Client Registration'
7
- description %(
8
- During these tests, Inferno will verify the registration details provided as inputs,
9
- including the client's JSON Web Key Set.
10
- )
11
- run_as_group
12
-
13
- test from: :smart_client_registration_verification
14
- end
15
- end
@@ -1,52 +0,0 @@
1
- require_relative '../tags'
2
- require_relative '../endpoints/mock_smart_server'
3
-
4
- module SMARTAppLaunch
5
- class SMARTClientRegistrationVerification < Inferno::Test
6
-
7
- id :smart_client_registration_verification
8
- title 'Verify SMART Registration'
9
- description %(
10
- During this test, Inferno will verify that the SMART registration details
11
- provided are conformant.
12
- )
13
- input :smart_jwk_set,
14
- title: 'SMART JSON Web Key Set (JWKS)',
15
- type: 'textarea',
16
- description: %(
17
- The SMART client's JSON Web Key Set including the key(s) Inferno will need to
18
- verify signatures on token requests made by the client. May be provided as either
19
- a publicly accessible url containing the JWKS, or the raw JWKS JSON.
20
- )
21
- input :client_id,
22
- title: 'Client Id',
23
- type: 'text',
24
- optional: true,
25
- description: %(
26
- If a particular client id is desired, put it here. Otherwise a
27
- default of the Inferno session id will be used.
28
- )
29
-
30
- output :client_id
31
-
32
- run do
33
- omit_if smart_jwk_set.blank?, # for re-use: mark the smart_jwk_set input as optional when importing to enable
34
- 'Not configured for SMART authentication.'
35
-
36
- if client_id.blank?
37
- client_id = test_session_id
38
- output(client_id:)
39
- end
40
-
41
- jwks_warnings = []
42
- parsed_smart_jwk_set = MockSMARTServer.jwk_set(smart_jwk_set, jwks_warnings)
43
- jwks_warnings.each { |warning| add_message('warning', warning) }
44
-
45
- assert parsed_smart_jwk_set.length.positive?, 'JWKS content does not include any valid keys.'
46
-
47
- # TODO: add key-specific verification per end of https://build.fhir.org/ig/HL7/smart-app-launch/client-confidential-asymmetric.html#registering-a-client-communicating-public-keys
48
-
49
- assert messages.none? { |msg| msg[:type] == 'error' }, 'Invalid key set provided. See messages for details'
50
- end
51
- end
52
- end
@@ -1,146 +0,0 @@
1
- require_relative '../tags'
2
- require_relative '../urls'
3
- require_relative '../endpoints/mock_smart_server'
4
-
5
- module SMARTAppLaunch
6
- class SMARTClientTokenRequestVerification < Inferno::Test
7
- include URLs
8
-
9
- id :smart_client_token_request_verification
10
- title 'Verify SMART Token Requests'
11
- description %(
12
- Check that SMART token requests are conformant.
13
- )
14
-
15
- input :client_id,
16
- title: 'Client Id',
17
- type: 'text',
18
- optional: false,
19
- locked: true,
20
- description: %(
21
- The registered Client Id for use in obtaining access tokens.
22
- Create a new session if you need to change this value.
23
- )
24
- input :smart_jwk_set,
25
- title: 'JSON Web Key Set (JWKS)',
26
- type: 'textarea',
27
- optional: false,
28
- locked: true,
29
- description: %(
30
- The SMART client's JSON Web Key Set in the form of either a publicly accessible url
31
- containing the JWKS, or the raw JWKS JSON. Must include the key(s) Inferno will need to
32
- verify signatures on token requests made by the client.
33
- Create a new session if you need to change this value.
34
- )
35
- output :smart_tokens
36
-
37
- run do
38
- omit_if smart_jwk_set.blank?, # for re-use: mark the smart_jwk_set input as optional when importing to enable
39
- 'SMART Backend Services authentication not demonstrated as a part of this test session.'
40
-
41
- load_tagged_requests(TOKEN_TAG, SMART_TAG)
42
- skip_if requests.blank?, 'No SMART token requests made.'
43
-
44
- jti_list = []
45
- token_list = []
46
- requests.each_with_index do |token_request, index|
47
- request_params = URI.decode_www_form(token_request.request_body).to_h
48
- check_request_params(request_params, index + 1)
49
- check_client_assertion(request_params['client_assertion'], index + 1, jti_list)
50
- token_list << extract_token_from_response(token_request)
51
- end
52
-
53
- output smart_tokens: token_list.compact.join("\n")
54
-
55
- assert messages.none? { |msg|
56
- msg[:type] == 'error'
57
- }, 'Invalid token requests detected. See messages for details.'
58
- end
59
-
60
- def check_request_params(params, request_num)
61
- if params['grant_type'] != 'client_credentials'
62
- add_message('error',
63
- "Token request #{request_num} had an incorrect `grant_type`: expected 'client_credentials', " \
64
- "but got '#{params['grant_type']}'")
65
- end
66
- if params['client_assertion_type'] != 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
67
- add_message('error',
68
- "Token request #{request_num} had an incorrect `client_assertion_type`: " \
69
- "expected 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', " \
70
- "but got '#{params['client_assertion_type']}'")
71
- end
72
- return unless params['scope'].blank?
73
-
74
- add_message('error', "Token request #{request_num} did not include the requested `scope`")
75
- end
76
-
77
- def check_client_assertion(assertion, request_num, jti_list)
78
- decoded_token =
79
- begin
80
- JWT::EncodedToken.new(assertion)
81
- rescue StandardError => e
82
- add_message('error', "Token request #{request_num} contained an invalid client assertion jwt: #{e}")
83
- nil
84
- end
85
-
86
- return unless decoded_token.present?
87
-
88
- check_jwt_header(decoded_token.header, request_num)
89
- check_jwt_payload(decoded_token.payload, request_num, jti_list)
90
- check_jwt_signature(decoded_token, request_num)
91
- end
92
-
93
- def check_jwt_header(header, request_num)
94
- return unless header['typ'] != 'JWT'
95
-
96
- add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `typ` header: " \
97
- "expected 'JWT', got '#{header['typ']}'")
98
- end
99
-
100
- def check_jwt_payload(claims, request_num, jti_list)
101
- if claims['iss'] != client_id
102
- add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `iss` claim: " \
103
- "expected '#{client_id}', got '#{claims['iss']}'")
104
- end
105
-
106
- if claims['sub'] != client_id
107
- add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `sub` claim: " \
108
- "expected '#{client_id}', got '#{claims['sub']}'")
109
- end
110
-
111
- if claims['aud'] != client_token_url
112
- add_message('error', "client assertion jwt on token request #{request_num} has an incorrect `aud` claim: " \
113
- "expected '#{client_token_url}', got '#{claims['aud']}'")
114
- end
115
-
116
- if claims['exp'].blank?
117
- add_message('error', "client assertion jwt on token request #{request_num} is missing the `exp` claim.")
118
- end
119
-
120
- if claims['jti'].blank?
121
- add_message('error', "client assertion jwt on token request #{request_num} is missing the `jti` claim.")
122
- elsif jti_list.include?(claims['jti'])
123
- add_message('error', "client assertion jwt on token request #{request_num} has a `jti` claim that was " \
124
- "previouly used: '#{claims['jti']}'.")
125
- else
126
- jti_list << claims['jti']
127
- end
128
- end
129
-
130
- def check_jwt_signature(encoded_token, request_num)
131
- error = MockSMARTServer.smart_assertion_signature_verification(encoded_token, smart_jwk_set)
132
-
133
- return unless error.present?
134
-
135
- add_message('error', "Signature validation failed on token request #{request_num}: #{error}")
136
- end
137
-
138
- def extract_token_from_response(request)
139
- return unless request.status == 200
140
-
141
- JSON.parse(request.response_body)&.dig('access_token')
142
- rescue
143
- nil
144
- end
145
- end
146
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../urls'
4
- require_relative '../tags'
5
- require_relative 'mock_smart_server'
6
-
7
- module SMARTAppLaunch
8
- class EchoingFHIRResponderEndpoint < Inferno::DSL::SuiteEndpoint
9
- def test_run_identifier
10
- MockSMARTServer.token_to_client_id(request.headers['authorization']&.delete_prefix('Bearer '))
11
- end
12
-
13
- def make_response
14
- return if response.status == 401 # set in update_result (expired token handling there)
15
-
16
- response.content_type = 'application/fhir+json'
17
-
18
- # If the tester provided a response, echo it
19
- # otherwise, operation outcome
20
- echo_response = JSON.parse(result.input_json)
21
- .find { |input| input['name'].include?('echoed_fhir_response') }
22
- &.dig('value')
23
-
24
- unless echo_response.present?
25
- response.status = 400
26
- response.body = FHIR::OperationOutcome.new(
27
- issue: FHIR::OperationOutcome::Issue.new(
28
- severity: 'fatal', code: 'required',
29
- details: FHIR::CodeableConcept.new(text: 'No response provided to echo.')
30
- )
31
- ).to_json
32
- return
33
- end
34
-
35
- response.status = 200
36
- response.body = echo_response
37
- end
38
-
39
- def update_result
40
- if MockSMARTServer.request_has_expired_token?(request)
41
- MockSMARTServer.update_response_for_expired_token(response)
42
- return
43
- end
44
-
45
- nil # never update for now
46
- end
47
-
48
- def tags
49
- [ACCESS_TAG]
50
- end
51
- end
52
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../urls'
4
- require_relative '../../tags'
5
- require_relative '../mock_smart_server'
6
-
7
- module SMARTAppLaunch
8
- module MockSMARTServer
9
- class TokenEndpoint < Inferno::DSL::SuiteEndpoint
10
- def test_run_identifier
11
- MockSMARTServer.client_id_from_client_assertion(request.params[:client_assertion])
12
- end
13
-
14
- def make_response
15
- MockSMARTServer.make_smart_token_response(request, response, result)
16
- end
17
-
18
- def update_result
19
- nil # never update for now
20
- end
21
-
22
- def tags
23
- [TOKEN_TAG, SMART_TAG]
24
- end
25
- end
26
- end
27
- end