smart_app_launch_test_kit 0.6.0 → 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 +79 -0
  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_authorization_group.rb +0 -2
  7. data/lib/smart_app_launch/backend_services_authorization_request_success_test.rb +5 -2
  8. data/lib/smart_app_launch/backend_services_authorization_response_body_test.rb +6 -2
  9. data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +1 -1
  10. data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +1 -1
  11. data/lib/smart_app_launch/client_stu2_2_suite.rb +120 -0
  12. data/lib/smart_app_launch/client_suite/access_alca_interaction_test.rb +75 -0
  13. data/lib/smart_app_launch/client_suite/access_alcs_interaction_test.rb +75 -0
  14. data/lib/smart_app_launch/client_suite/access_alp_interaction_test.rb +75 -0
  15. data/lib/smart_app_launch/client_suite/access_bsca_interaction_test.rb +46 -0
  16. data/lib/smart_app_launch/client_suite/access_group.rb +85 -0
  17. data/lib/smart_app_launch/client_suite/authentication_verification.rb +86 -0
  18. data/lib/smart_app_launch/client_suite/authorization_request_verification_test.rb +108 -0
  19. data/lib/smart_app_launch/client_suite/client_descriptions.rb +114 -0
  20. data/lib/smart_app_launch/client_suite/client_options.rb +35 -0
  21. data/lib/smart_app_launch/client_suite/oidc_jwks.json +32 -0
  22. data/lib/smart_app_launch/client_suite/oidc_jwks.rb +27 -0
  23. data/lib/smart_app_launch/client_suite/registration_alca_group.rb +15 -0
  24. data/lib/smart_app_launch/client_suite/registration_alca_verification_test.rb +57 -0
  25. data/lib/smart_app_launch/client_suite/registration_alcs_group.rb +15 -0
  26. data/lib/smart_app_launch/client_suite/registration_alcs_verification_test.rb +56 -0
  27. data/lib/smart_app_launch/client_suite/registration_alp_group.rb +16 -0
  28. data/lib/smart_app_launch/client_suite/registration_alp_verification_test.rb +50 -0
  29. data/lib/smart_app_launch/client_suite/registration_bsca_group.rb +15 -0
  30. data/lib/smart_app_launch/client_suite/registration_bsca_verification_test.rb +40 -0
  31. data/lib/smart_app_launch/client_suite/registration_verification.rb +58 -0
  32. data/lib/smart_app_launch/client_suite/token_request_alca_verification_test.rb +53 -0
  33. data/lib/smart_app_launch/client_suite/token_request_alcs_verification_test.rb +53 -0
  34. data/lib/smart_app_launch/client_suite/token_request_alp_verification_test.rb +48 -0
  35. data/lib/smart_app_launch/client_suite/token_request_bsca_verification_test.rb +53 -0
  36. data/lib/smart_app_launch/client_suite/token_request_verification.rb +116 -0
  37. data/lib/smart_app_launch/client_suite/token_use_verification_test.rb +40 -0
  38. data/lib/smart_app_launch/docs/demo/FHIR Request.postman_collection.json +81 -0
  39. data/lib/smart_app_launch/docs/smart_stu2_2_client_suite_description.md +208 -0
  40. data/lib/smart_app_launch/endpoints/echoing_fhir_responder_endpoint.rb +96 -0
  41. data/lib/smart_app_launch/endpoints/mock_smart_server/authorization_endpoint.rb +27 -0
  42. data/lib/smart_app_launch/endpoints/mock_smart_server/introspection_endpoint.rb +33 -0
  43. data/lib/smart_app_launch/endpoints/mock_smart_server/smart_authorization_response_creation.rb +30 -0
  44. data/lib/smart_app_launch/endpoints/mock_smart_server/smart_introspection_response_creation.rb +46 -0
  45. data/lib/smart_app_launch/endpoints/mock_smart_server/smart_token_response_creation.rb +250 -0
  46. data/lib/smart_app_launch/endpoints/mock_smart_server/token_endpoint.rb +58 -0
  47. data/lib/smart_app_launch/endpoints/mock_smart_server.rb +278 -0
  48. data/lib/smart_app_launch/metadata.rb +21 -16
  49. data/lib/smart_app_launch/smart_stu2_2_suite.rb +2 -1
  50. data/lib/smart_app_launch/smart_stu2_suite.rb +2 -1
  51. data/lib/smart_app_launch/tags.rb +15 -0
  52. data/lib/smart_app_launch/token_introspection_response_group.rb +1 -1
  53. data/lib/smart_app_launch/token_payload_validation.rb +2 -2
  54. data/lib/smart_app_launch/urls.rb +52 -0
  55. data/lib/smart_app_launch/version.rb +2 -2
  56. data/lib/smart_app_launch_test_kit.rb +1 -0
  57. metadata +45 -2
@@ -0,0 +1,278 @@
1
+ require 'jwt'
2
+ require 'faraday'
3
+ require 'time'
4
+ require 'base64'
5
+ require 'rack/utils'
6
+ require_relative '../urls'
7
+ require_relative '../tags'
8
+ require_relative '../client_suite/client_options'
9
+
10
+ module SMARTAppLaunch
11
+ module MockSMARTServer
12
+ SUPPORTED_SCOPES = ['system/*.read', 'user/*.read', 'patient/*.read'].freeze
13
+
14
+ module_function
15
+
16
+ def smart_server_metadata(suite_id)
17
+ base_url = "#{Inferno::Application['base_url']}/custom/#{suite_id}"
18
+ response_body = {
19
+ token_endpoint_auth_signing_alg_values_supported: ['RS384', 'ES384'],
20
+ capabilities: ['client-confidential-asymmetric', 'launch-ehr' ,'launch-standalone', 'authorize-post',
21
+ 'client-public', 'client-confidential-symmetric', 'permission-offline', 'permission-online',
22
+ 'permission-patient', 'permission-user', 'permission-v1', 'permission-v2',
23
+ 'context-ehr-patient', 'context-ehr-encounter',
24
+ 'context-standalone-patient', 'context-standalone-encounter',
25
+ 'context-banner', 'context-style'],
26
+ code_challenge_methods_supported: ['S256'],
27
+ token_endpoint_auth_methods_supported: ['private_key_jwt', 'client_secret_basic', 'client_secret_post'],
28
+ issuer: base_url + FHIR_PATH,
29
+ grant_types_supported: ['client_credentials', 'authorization_code'],
30
+ scopes_supported: SUPPORTED_SCOPES,
31
+ authorization_endpoint: base_url + AUTHORIZATION_PATH,
32
+ token_endpoint: base_url + TOKEN_PATH,
33
+ introspection_endpoint: base_url + INTROSPECTION_PATH
34
+ }.to_json
35
+
36
+ [200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
37
+ end
38
+
39
+ def openid_connect_metadata(suite_id)
40
+ base_url = "#{Inferno::Application['base_url']}/custom/#{suite_id}"
41
+ response_body = {
42
+ issuer: base_url + FHIR_PATH,
43
+ authorization_endpoint: base_url + AUTHORIZATION_PATH,
44
+ token_endpoint: base_url + TOKEN_PATH,
45
+ jwks_uri: base_url + OIDC_JWKS_PATH,
46
+ response_types_supported: ['code', 'id_token', 'token id_token'],
47
+ subject_types_supported: ['pairwise', 'public'],
48
+ id_token_signing_alg_values_supported: ['RS256']
49
+ }.to_json
50
+
51
+ [200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
52
+ end
53
+
54
+ def client_id_from_client_assertion(client_assertion_jwt)
55
+ return unless client_assertion_jwt.present?
56
+
57
+ claims, _header = JWT.decode(client_assertion_jwt, nil, false)[0]
58
+ claims&.dig('iss')
59
+ end
60
+
61
+ def client_id_to_token(client_id, exp_min)
62
+ token_structure = {
63
+ client_id:,
64
+ expiration: exp_min.minutes.from_now.to_i,
65
+ nonce: SecureRandom.hex(8)
66
+ }.to_json
67
+
68
+ Base64.urlsafe_encode64(token_structure, padding: false)
69
+ end
70
+
71
+ def decode_token(token)
72
+ token_to_decode =
73
+ if issued_token_is_refresh_token(token)
74
+ refresh_token_to_authorization_code(token)
75
+ else
76
+ token
77
+ end
78
+ return unless token_to_decode.present?
79
+
80
+ JSON.parse(Base64.urlsafe_decode64(token_to_decode))
81
+ rescue JSON::ParserError
82
+ nil
83
+ end
84
+
85
+ def issued_token_to_client_id(token)
86
+ decode_token(token)&.dig('client_id')
87
+ end
88
+
89
+ def issued_token_is_refresh_token(token)
90
+ token.end_with?('_rt')
91
+ end
92
+
93
+ def authorization_code_to_refresh_token(code)
94
+ "#{code}_rt"
95
+ end
96
+
97
+ def refresh_token_to_authorization_code(refresh_token)
98
+ refresh_token[..-4]
99
+ end
100
+
101
+ def jwk_set(jku, warning_messages = []) # rubocop:disable Metrics/CyclomaticComplexity
102
+ jwk_set = JWT::JWK::Set.new
103
+
104
+ if jku.blank?
105
+ warning_messages << 'No key set input.'
106
+ return jwk_set
107
+ end
108
+
109
+ jwk_body = # try as raw jwk set
110
+ begin
111
+ JSON.parse(jku)
112
+ rescue JSON::ParserError
113
+ nil
114
+ end
115
+
116
+ if jwk_body.blank?
117
+ retrieved = Faraday.get(jku) # try as url pointing to a jwk set
118
+ jwk_body =
119
+ begin
120
+ JSON.parse(retrieved.body)
121
+ rescue JSON::ParserError
122
+ warning_messages << "Failed to fetch valid json from jwks uri #{jwk_set}."
123
+ nil
124
+ end
125
+ else
126
+ warning_messages << 'Providing the JWK Set directly is strongly discouraged.'
127
+ end
128
+
129
+ return jwk_set if jwk_body.blank?
130
+
131
+ jwk_body['keys']&.each_with_index do |key_hash, index|
132
+ parsed_key =
133
+ begin
134
+ JWT::JWK.new(key_hash)
135
+ rescue JWT::JWKError => e
136
+ id = key_hash['kid'] | index
137
+ warning_messages << "Key #{id} invalid: #{e}"
138
+ nil
139
+ end
140
+ jwk_set << parsed_key unless parsed_key.blank?
141
+ end
142
+
143
+ jwk_set
144
+ end
145
+
146
+ def request_has_expired_token?(request)
147
+ return false if request.params[:session_path].present?
148
+
149
+ token = request.headers['authorization']&.delete_prefix('Bearer ')
150
+ token_expired?(token)
151
+ end
152
+
153
+ def token_expired?(token, check_time = nil)
154
+ decoded_token = decode_token(token)
155
+ return false unless decoded_token&.dig('expiration').present?
156
+
157
+ check_time = Time.now.to_i unless check_time.present?
158
+ decoded_token['expiration'] < check_time
159
+ end
160
+
161
+ def update_response_for_expired_token(response, type)
162
+ response.status = 401
163
+ response.format = :json
164
+ response.body = FHIR::OperationOutcome.new(
165
+ issue: FHIR::OperationOutcome::Issue.new(severity: 'fatal', code: 'expired',
166
+ details: FHIR::CodeableConcept.new(text: "#{type}has expired"))
167
+ ).to_json
168
+ end
169
+
170
+ def smart_assertion_signature_verification(token, key_set_input) # rubocop:disable Metrics/CyclomaticComplexity
171
+ encoded_token = nil
172
+ if token.is_a?(JWT::EncodedToken)
173
+ encoded_token = token
174
+ else
175
+ begin
176
+ encoded_token = JWT::EncodedToken.new(token)
177
+ rescue StandardError => e
178
+ return "invalid token structure: #{e}"
179
+ end
180
+ end
181
+ return 'invalid token' unless encoded_token.present?
182
+ return 'missing `alg` header' if encoded_token.header['alg'].blank?
183
+ return 'missing `kid` header' if encoded_token.header['kid'].blank?
184
+
185
+ jwk = identify_smart_signing_key(encoded_token.header['kid'], encoded_token.header['jku'], key_set_input)
186
+ return "no key found with `kid` '#{encoded_token.header['kid']}'" if jwk.blank?
187
+
188
+ begin
189
+ encoded_token.verify_signature!(algorithm: encoded_token.header['alg'], key: jwk.verify_key)
190
+ rescue StandardError => e
191
+ return e
192
+ end
193
+
194
+ nil
195
+ end
196
+
197
+ def identify_smart_signing_key(kid, jku, key_set_input)
198
+ key_set = jku.present? ? jku : key_set_input
199
+ parsed_key_set = jwk_set(key_set)
200
+ parsed_key_set&.find { |key| key.kid == kid }
201
+ end
202
+
203
+ def update_response_for_error(response, error_message)
204
+ response.status = 401
205
+ response.format = :json
206
+ response.body = { error: 'invalid_client', error_description: error_message }.to_json
207
+ end
208
+
209
+ def confidential_symmetric_header_value_error(authorization_header_value, client_id, client_secret)
210
+ unless authorization_header_value.present?
211
+ return 'authorization header missing from confidential symmetric client request'
212
+ end
213
+ unless authorization_header_value.start_with?('Basic ')
214
+ return 'authorization header for confidential symmetric client request does not use Basic auth'
215
+ end
216
+
217
+ client_and_secret =
218
+ begin
219
+ Base64.strict_decode64(authorization_header_value.delete_prefix('Basic '))
220
+ rescue
221
+ return 'Basic authorization header could not be decoded'
222
+ end
223
+ expected_client_and_secret = "#{client_id}:#{client_secret}"
224
+ unless client_and_secret == expected_client_and_secret
225
+ return 'basic authorization header has the wrong decoded value - ' \
226
+ "expected '#{expected_client_and_secret}', got '#{client_and_secret}'"
227
+ end
228
+
229
+ nil
230
+ end
231
+
232
+ def pkce_error(verifier, challenge, method)
233
+ if verifier.blank?
234
+ 'pkce check failed: no verifier provided'
235
+ elsif challenge.blank?
236
+ 'pkce check failed: no challenge code provided'
237
+ elsif method == 'S256'
238
+ return nil unless challenge != AppRedirectTest.calculate_s256_challenge(verifier)
239
+
240
+ "invalid S256 pkce verifier: got '#{AppRedirectTest.calculate_s256_challenge(verifier)}' " \
241
+ "expected '#{challenge}'"
242
+ else
243
+ "invalid pkce challenge method '#{method}'"
244
+ end
245
+ end
246
+
247
+ def pkce_valid?(verifier, challenge, method, response)
248
+ pkce_error = pkce_error(verifier, challenge, method)
249
+
250
+ if pkce_error.present?
251
+ update_response_for_error(response, pkce_error)
252
+ false
253
+ else
254
+ true
255
+ end
256
+ end
257
+
258
+ def authorization_request_for_code(code, test_session_id)
259
+ authorization_requests = Inferno::Repositories::Requests.new.tagged_requests(test_session_id, [AUTHORIZATION_TAG])
260
+ authorization_requests.find do |request|
261
+ location_header = request.response_headers.find { |header| header.name.downcase == 'location' }
262
+ if location_header.present? && location_header.value.present?
263
+ Rack::Utils.parse_query(URI(location_header.value)&.query)&.dig('code') == code
264
+ else
265
+ false
266
+ end
267
+ end
268
+ end
269
+
270
+ def authorization_code_request_details(inferno_request)
271
+ if inferno_request.verb.downcase == 'get'
272
+ Rack::Utils.parse_query(URI(inferno_request.url)&.query)
273
+ elsif inferno_request.verb.downcase == 'post'
274
+ Rack::Utils.parse_query(inferno_request.request_body)
275
+ end
276
+ end
277
+ end
278
+ end
@@ -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.
@@ -64,12 +69,12 @@ module SMARTAppLaunch
64
69
  section](https://github.com/inferno-framework/smart-app-launch-test-kit/issues)
65
70
  of the repository.
66
71
  DESCRIPTION
67
- suite_ids [:smart, :smart_stu2, :smart_stu2_2, :smart_access_brands]
72
+ suite_ids [:smart, :smart_stu2, :smart_stu2_2, :smart_access_brands, :smart_client_stu2_2]
68
73
  tags ['SMART App Launch', 'Endpoint Publication']
69
74
  last_updated LAST_UPDATED
70
75
  version VERSION
71
76
  maturity 'Medium'
72
- authors ['Stephen MacVicar']
77
+ authors ['Stephen MacVicar', 'Karl Naden']
73
78
  repo 'https://github.com/inferno-framework/smart-app-launch-test-kit'
74
79
  end
75
80
  end
@@ -262,7 +262,8 @@ module SMARTAppLaunch
262
262
  smart_auth_info: { name: :backend_services_smart_auth_info }
263
263
  },
264
264
  outputs: {
265
- smart_auth_info: { name: :backend_services_smart_auth_info }
265
+ smart_auth_info: { name: :backend_services_smart_auth_info },
266
+ received_scopes: { name: :backend_services_received_scopes }
266
267
  }
267
268
  }
268
269
  end
@@ -260,7 +260,8 @@ module SMARTAppLaunch
260
260
  smart_auth_info: { name: :backend_services_smart_auth_info }
261
261
  },
262
262
  outputs: {
263
- smart_auth_info: { name: :backend_services_smart_auth_info }
263
+ smart_auth_info: { name: :backend_services_smart_auth_info },
264
+ received_scopes: { name: :backend_services_received_scopes }
264
265
  }
265
266
  }
266
267
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMARTAppLaunch
4
+ TOKEN_TAG = 'token'
5
+ AUTHORIZATION_TAG = 'authorization'
6
+ INTROSPECTION_TAG = 'introspection'
7
+ SMART_TAG = 'SMART'
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'
15
+ end
@@ -53,7 +53,7 @@ module SMARTAppLaunch
53
53
  introspection response and should match the claim in the ID token
54
54
  )
55
55
 
56
- input :standalone_smart_auth_info, type: :auth_info, options: { mode: 'auth' }
56
+ input :standalone_smart_auth_info, type: :auth_info, options: { mode: 'access' }
57
57
 
58
58
  input :standalone_received_scopes,
59
59
  title: 'Expected Introspection Response Value: scope',
@@ -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
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SMARTAppLaunch
4
+ FHIR_PATH = '/fhir'
5
+ RESUME_PASS_PATH = '/resume_pass'
6
+ RESUME_FAIL_PATH = '/resume_fail'
7
+ AUTH_SERVER_PATH = '/auth'
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
11
+ TOKEN_PATH = "#{AUTH_SERVER_PATH}/token".freeze
12
+ AUTHORIZATION_PATH = "#{AUTH_SERVER_PATH}/authorization".freeze
13
+ INTROSPECTION_PATH = "#{AUTH_SERVER_PATH}/introspect".freeze
14
+
15
+ module URLs
16
+ def client_base_url
17
+ @client_base_url ||= "#{Inferno::Application['base_url']}/custom/#{client_suite_id}"
18
+ end
19
+
20
+ def client_fhir_base_url
21
+ @client_fhir_base_url ||= client_base_url + FHIR_PATH
22
+ end
23
+
24
+ def client_resume_pass_url
25
+ @client_resume_pass_url ||= client_base_url + RESUME_PASS_PATH
26
+ end
27
+
28
+ def client_resume_fail_url
29
+ @client_resume_fail_url ||= client_base_url + RESUME_FAIL_PATH
30
+ end
31
+
32
+ def client_smart_discovery_url
33
+ @client_smart_discovery_url ||= client_base_url + SMART_DISCOVERY_PATH
34
+ end
35
+
36
+ def client_token_url
37
+ @client_token_url ||= client_base_url + TOKEN_PATH
38
+ end
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
+
48
+ def client_suite_id
49
+ SMARTAppLaunch::SMARTClientSTU22Suite.id
50
+ end
51
+ end
52
+ end
@@ -1,4 +1,4 @@
1
1
  module SMARTAppLaunch
2
- VERSION = '0.6.0'.freeze
3
- LAST_UPDATED = '2025-03-14'.freeze
2
+ VERSION = '0.6.2'.freeze
3
+ LAST_UPDATED = '2025-05-02'.freeze
4
4
  end
@@ -5,3 +5,4 @@ require_relative 'smart_app_launch/smart_stu1_suite'
5
5
  require_relative 'smart_app_launch/smart_stu2_suite'
6
6
  require_relative 'smart_app_launch/smart_stu2_2_suite'
7
7
  require_relative 'smart_app_launch/smart_access_brands_suite'
8
+ require_relative 'smart_app_launch/client_stu2_2_suite'
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.0
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-03-14 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
@@ -145,6 +145,10 @@ extensions: []
145
145
  extra_rdoc_files: []
146
146
  files:
147
147
  - LICENSE
148
+ - config/presets/SMART_RunClientAgainstServer.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
148
152
  - config/presets/inferno_reference_server_preset.json
149
153
  - config/presets/inferno_reference_server_stu2_2_preset.json
150
154
  - config/presets/inferno_reference_server_stu2_preset.json
@@ -164,6 +168,33 @@ files:
164
168
  - lib/smart_app_launch/backend_services_invalid_grant_type_test.rb
165
169
  - lib/smart_app_launch/backend_services_invalid_jwt_test.rb
166
170
  - lib/smart_app_launch/client_assertion_builder.rb
171
+ - lib/smart_app_launch/client_stu2_2_suite.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
167
198
  - lib/smart_app_launch/code_received_test.rb
168
199
  - lib/smart_app_launch/cors_metadata_request_test.rb
169
200
  - lib/smart_app_launch/cors_openid_fhir_user_claim_test.rb
@@ -172,9 +203,19 @@ files:
172
203
  - lib/smart_app_launch/discovery_stu1_group.rb
173
204
  - lib/smart_app_launch/discovery_stu2_2_group.rb
174
205
  - lib/smart_app_launch/discovery_stu2_group.rb
206
+ - lib/smart_app_launch/docs/demo/FHIR Request.postman_collection.json
207
+ - lib/smart_app_launch/docs/smart_stu2_2_client_suite_description.md
175
208
  - lib/smart_app_launch/ehr_launch_group.rb
176
209
  - lib/smart_app_launch/ehr_launch_group_stu2.rb
177
210
  - lib/smart_app_launch/ehr_launch_group_stu2_2.rb
211
+ - lib/smart_app_launch/endpoints/echoing_fhir_responder_endpoint.rb
212
+ - lib/smart_app_launch/endpoints/mock_smart_server.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
178
219
  - lib/smart_app_launch/jwks.rb
179
220
  - lib/smart_app_launch/launch_received_test.rb
180
221
  - lib/smart_app_launch/metadata.rb
@@ -207,6 +248,7 @@ files:
207
248
  - lib/smart_app_launch/standalone_launch_group.rb
208
249
  - lib/smart_app_launch/standalone_launch_group_stu2.rb
209
250
  - lib/smart_app_launch/standalone_launch_group_stu2_2.rb
251
+ - lib/smart_app_launch/tags.rb
210
252
  - lib/smart_app_launch/token_exchange_stu2_2_test.rb
211
253
  - lib/smart_app_launch/token_exchange_stu2_test.rb
212
254
  - lib/smart_app_launch/token_exchange_test.rb
@@ -226,6 +268,7 @@ files:
226
268
  - lib/smart_app_launch/token_response_body_test_stu2_2.rb
227
269
  - lib/smart_app_launch/token_response_headers_test.rb
228
270
  - lib/smart_app_launch/url_helpers.rb
271
+ - lib/smart_app_launch/urls.rb
229
272
  - lib/smart_app_launch/version.rb
230
273
  - lib/smart_app_launch/well_known_capabilities_stu1_test.rb
231
274
  - lib/smart_app_launch/well_known_capabilities_stu2_test.rb