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.
- checksums.yaml +4 -4
- data/config/presets/SMART_RunClientAgainstServer.json.erb +79 -0
- data/config/presets/SMART_RunServerAgainstClient_ConfidentialAsymmetric.json.erb +183 -0
- data/config/presets/SMART_RunServerAgainstClient_ConfidentialSymmetric.json.erb +157 -0
- data/config/presets/SMART_RunServerAgainstClient_Public.json.erb +155 -0
- data/lib/smart_app_launch/backend_services_authorization_group.rb +0 -2
- data/lib/smart_app_launch/backend_services_authorization_request_success_test.rb +5 -2
- data/lib/smart_app_launch/backend_services_authorization_response_body_test.rb +6 -2
- data/lib/smart_app_launch/backend_services_invalid_client_assertion_test.rb +1 -1
- data/lib/smart_app_launch/backend_services_invalid_jwt_test.rb +1 -1
- data/lib/smart_app_launch/client_stu2_2_suite.rb +120 -0
- data/lib/smart_app_launch/client_suite/access_alca_interaction_test.rb +75 -0
- data/lib/smart_app_launch/client_suite/access_alcs_interaction_test.rb +75 -0
- data/lib/smart_app_launch/client_suite/access_alp_interaction_test.rb +75 -0
- data/lib/smart_app_launch/client_suite/access_bsca_interaction_test.rb +46 -0
- data/lib/smart_app_launch/client_suite/access_group.rb +85 -0
- data/lib/smart_app_launch/client_suite/authentication_verification.rb +86 -0
- data/lib/smart_app_launch/client_suite/authorization_request_verification_test.rb +108 -0
- data/lib/smart_app_launch/client_suite/client_descriptions.rb +114 -0
- data/lib/smart_app_launch/client_suite/client_options.rb +35 -0
- data/lib/smart_app_launch/client_suite/oidc_jwks.json +32 -0
- data/lib/smart_app_launch/client_suite/oidc_jwks.rb +27 -0
- data/lib/smart_app_launch/client_suite/registration_alca_group.rb +15 -0
- data/lib/smart_app_launch/client_suite/registration_alca_verification_test.rb +57 -0
- data/lib/smart_app_launch/client_suite/registration_alcs_group.rb +15 -0
- data/lib/smart_app_launch/client_suite/registration_alcs_verification_test.rb +56 -0
- data/lib/smart_app_launch/client_suite/registration_alp_group.rb +16 -0
- data/lib/smart_app_launch/client_suite/registration_alp_verification_test.rb +50 -0
- data/lib/smart_app_launch/client_suite/registration_bsca_group.rb +15 -0
- data/lib/smart_app_launch/client_suite/registration_bsca_verification_test.rb +40 -0
- data/lib/smart_app_launch/client_suite/registration_verification.rb +58 -0
- data/lib/smart_app_launch/client_suite/token_request_alca_verification_test.rb +53 -0
- data/lib/smart_app_launch/client_suite/token_request_alcs_verification_test.rb +53 -0
- data/lib/smart_app_launch/client_suite/token_request_alp_verification_test.rb +48 -0
- data/lib/smart_app_launch/client_suite/token_request_bsca_verification_test.rb +53 -0
- data/lib/smart_app_launch/client_suite/token_request_verification.rb +116 -0
- data/lib/smart_app_launch/client_suite/token_use_verification_test.rb +40 -0
- data/lib/smart_app_launch/docs/demo/FHIR Request.postman_collection.json +81 -0
- data/lib/smart_app_launch/docs/smart_stu2_2_client_suite_description.md +208 -0
- data/lib/smart_app_launch/endpoints/echoing_fhir_responder_endpoint.rb +96 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/authorization_endpoint.rb +27 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/introspection_endpoint.rb +33 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/smart_authorization_response_creation.rb +30 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/smart_introspection_response_creation.rb +46 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/smart_token_response_creation.rb +250 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server/token_endpoint.rb +58 -0
- data/lib/smart_app_launch/endpoints/mock_smart_server.rb +278 -0
- data/lib/smart_app_launch/metadata.rb +21 -16
- data/lib/smart_app_launch/smart_stu2_2_suite.rb +2 -1
- data/lib/smart_app_launch/smart_stu2_suite.rb +2 -1
- data/lib/smart_app_launch/tags.rb +15 -0
- data/lib/smart_app_launch/token_introspection_response_group.rb +1 -1
- data/lib/smart_app_launch/token_payload_validation.rb +2 -2
- data/lib/smart_app_launch/urls.rb +52 -0
- data/lib/smart_app_launch/version.rb +2 -2
- data/lib/smart_app_launch_test_kit.rb +1 -0
- 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
|
9
|
-
|
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
|
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
|
34
|
-
implement the SMART App Launch IG
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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: '
|
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
|
82
|
-
new_scopes = body['scope']
|
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
|
@@ -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.
|
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-
|
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
|