udap_security_test_kit 0.11.3 → 0.11.5

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/UDAP_RunServerAgainstClient.json.erb +4 -4
  3. data/lib/udap_security_test_kit/client_suite/access_ac_group.rb +25 -0
  4. data/lib/udap_security_test_kit/client_suite/access_ac_interaction_test.rb +59 -0
  5. data/lib/udap_security_test_kit/client_suite/access_cc_group.rb +23 -0
  6. data/lib/udap_security_test_kit/client_suite/access_cc_interaction_test.rb +49 -0
  7. data/lib/udap_security_test_kit/client_suite/authorization_request_verification_test.rb +83 -0
  8. data/lib/udap_security_test_kit/client_suite/client_descriptions.rb +70 -0
  9. data/lib/udap_security_test_kit/client_suite/client_options.rb +20 -0
  10. data/lib/udap_security_test_kit/client_suite/oidc_jwks.json +32 -0
  11. data/lib/udap_security_test_kit/client_suite/oidc_jwks.rb +27 -0
  12. data/lib/udap_security_test_kit/client_suite/registration_ac_group.rb +18 -0
  13. data/lib/udap_security_test_kit/client_suite/registration_ac_verification_test.rb +38 -0
  14. data/lib/udap_security_test_kit/client_suite/registration_cc_group.rb +18 -0
  15. data/lib/udap_security_test_kit/client_suite/registration_cc_verification_test.rb +38 -0
  16. data/lib/udap_security_test_kit/client_suite/{client_registration_interaction_test.rb → registration_interaction_test.rb} +11 -4
  17. data/lib/udap_security_test_kit/client_suite/{client_registration_verification_test.rb → registration_request_verification.rb} +38 -40
  18. data/lib/udap_security_test_kit/client_suite/token_request_ac_verification_test.rb +49 -0
  19. data/lib/udap_security_test_kit/client_suite/token_request_cc_verification_test.rb +49 -0
  20. data/lib/udap_security_test_kit/client_suite/{client_token_request_verification_test.rb → token_request_verification.rb} +91 -46
  21. data/lib/udap_security_test_kit/client_suite/{client_token_use_verification_test.rb → token_use_verification_test.rb} +0 -3
  22. data/lib/udap_security_test_kit/client_suite.rb +48 -17
  23. data/lib/udap_security_test_kit/docs/udap_client_suite_description.md +74 -31
  24. data/lib/udap_security_test_kit/endpoints/echoing_fhir_responder_endpoint.rb +96 -0
  25. data/lib/udap_security_test_kit/endpoints/mock_udap_server/authorization_endpoint.rb +28 -0
  26. data/lib/udap_security_test_kit/endpoints/mock_udap_server/introspection_endpoint.rb +34 -0
  27. data/lib/udap_security_test_kit/endpoints/mock_udap_server/registration_endpoint.rb +31 -0
  28. data/lib/udap_security_test_kit/endpoints/mock_udap_server/token_endpoint.rb +56 -0
  29. data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_authorization_response_creation.rb +63 -0
  30. data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_introspection_response_creation.rb +71 -0
  31. data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_registration_response_creation.rb +28 -0
  32. data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_token_response_creation.rb +218 -0
  33. data/lib/udap_security_test_kit/endpoints/mock_udap_server.rb +117 -33
  34. data/lib/udap_security_test_kit/metadata.rb +1 -1
  35. data/lib/udap_security_test_kit/tags.rb +5 -0
  36. data/lib/udap_security_test_kit/urls.rb +20 -8
  37. data/lib/udap_security_test_kit/version.rb +2 -2
  38. metadata +30 -12
  39. data/lib/udap_security_test_kit/client_suite/client_access_group.rb +0 -22
  40. data/lib/udap_security_test_kit/client_suite/client_access_interaction_test.rb +0 -53
  41. data/lib/udap_security_test_kit/client_suite/client_registration_group.rb +0 -26
  42. data/lib/udap_security_test_kit/endpoints/echoing_fhir_responder.rb +0 -52
  43. data/lib/udap_security_test_kit/endpoints/mock_udap_server/registration.rb +0 -57
  44. data/lib/udap_security_test_kit/endpoints/mock_udap_server/token.rb +0 -27
@@ -0,0 +1,218 @@
1
+ require_relative '../../urls'
2
+ require_relative '../../tags'
3
+ require_relative '../mock_udap_server'
4
+ require_relative '../../client_suite/oidc_jwks'
5
+
6
+ module UDAPSecurityTestKit
7
+ module MockUDAPServer
8
+ module UDAPTokenResponseCreation
9
+ def make_udap_authorization_code_token_response # rubocop:disable Metrics/CyclomaticComplexity
10
+ authorization_code = request.params[:code]
11
+ client_id = MockUDAPServer.issued_token_to_client_id(authorization_code)
12
+ software_statement = MockUDAPServer.udap_registration_software_statement(test_run.test_session_id)
13
+ return unless udap_authenticated?(request.params[:client_assertion], software_statement)
14
+
15
+ if MockUDAPServer.token_expired?(authorization_code)
16
+ MockUDAPServer.update_response_for_expired_token(response, 'Authorization code')
17
+ return
18
+ end
19
+
20
+ return if request.params[:code_verifier].present? && !udap_pkce_valid?(authorization_code)
21
+
22
+ exp_min = 60
23
+ response_body = {
24
+ access_token: MockUDAPServer.client_id_to_token(client_id, exp_min),
25
+ token_type: 'Bearer',
26
+ expires_in: 60 * exp_min
27
+ }
28
+
29
+ launch_context =
30
+ begin
31
+ input_string = JSON.parse(result.input_json)&.find do |input|
32
+ input['name'] == 'launch_context'
33
+ end&.dig('value')
34
+ JSON.parse(input_string) if input_string.present?
35
+ rescue JSON::ParserError
36
+ nil
37
+ end
38
+ additional_context = udap_requested_scope_context(udap_registered_scope(software_statement), authorization_code,
39
+ launch_context)
40
+
41
+ response.body = additional_context.merge(response_body).to_json # response body values take priority
42
+ response.headers['Cache-Control'] = 'no-store'
43
+ response.headers['Pragma'] = 'no-cache'
44
+ response.headers['Access-Control-Allow-Origin'] = '*'
45
+ response.content_type = 'application/json'
46
+ response.status = 200
47
+ end
48
+
49
+ def make_udap_refresh_token_response # rubocop:disable Metrics/CyclomaticComplexity
50
+ refresh_token = request.params[:refresh_token]
51
+ authorization_code = MockUDAPServer.refresh_token_to_authorization_code(refresh_token)
52
+ client_id = MockUDAPServer.issued_token_to_client_id(authorization_code)
53
+ software_statement = MockUDAPServer.udap_registration_software_statement(test_run.test_session_id)
54
+ return unless udap_authenticated?(request.params[:client_assertion], software_statement)
55
+
56
+ # no expiration checks for refresh tokens
57
+
58
+ authorization_request = MockUDAPServer.authorization_request_for_code(authorization_code,
59
+ test_run.test_session_id)
60
+ if authorization_request.blank?
61
+ MockUDAPServer.update_response_for_error(
62
+ response,
63
+ "no authorization request found for refresh token #{refresh_token}"
64
+ )
65
+ return
66
+ end
67
+ auth_code_request_inputs = MockUDAPServer.authorization_code_request_details(authorization_request)
68
+ if auth_code_request_inputs.blank?
69
+ MockUDAPServer.update_response_for_error(
70
+ response,
71
+ 'invalid authorization request details'
72
+ )
73
+ return
74
+ end
75
+
76
+ exp_min = 60
77
+ response_body = {
78
+ access_token: MockUDAPServer.client_id_to_token(client_id, exp_min),
79
+ token_type: 'Bearer',
80
+ expires_in: 60 * exp_min
81
+ }
82
+
83
+ launch_context =
84
+ begin
85
+ input_string = JSON.parse(result.input_json)&.find do |input|
86
+ input['name'] == 'launch_context'
87
+ end&.dig('value')
88
+ JSON.parse(input_string) if input_string.present?
89
+ rescue JSON::ParserError
90
+ nil
91
+ end
92
+ additional_context = udap_requested_scope_context(udap_registered_scope(software_statement), authorization_code,
93
+ launch_context)
94
+
95
+ response.body = additional_context.merge(response_body).to_json # response body values take priority
96
+ response.headers['Cache-Control'] = 'no-store'
97
+ response.headers['Pragma'] = 'no-cache'
98
+ response.headers['Access-Control-Allow-Origin'] = '*'
99
+ response.content_type = 'application/json'
100
+ response.status = 200
101
+ end
102
+
103
+ def make_udap_client_credential_token_response
104
+ assertion = request.params[:client_assertion]
105
+ client_id = MockUDAPServer.client_id_from_client_assertion(assertion)
106
+ software_statement = MockUDAPServer.udap_registration_software_statement(test_run.test_session_id)
107
+ return unless udap_authenticated?(request.params[:client_assertion], software_statement)
108
+
109
+ exp_min = 60
110
+ response_body = {
111
+ access_token: MockUDAPServer.client_id_to_token(client_id, exp_min),
112
+ token_type: 'Bearer',
113
+ expires_in: 60 * exp_min
114
+ }
115
+
116
+ response.body = response_body.to_json
117
+ response.headers['Cache-Control'] = 'no-store'
118
+ response.headers['Pragma'] = 'no-cache'
119
+ response.headers['Access-Control-Allow-Origin'] = '*'
120
+ response.content_type = 'application/json'
121
+ response.status = 200
122
+ end
123
+
124
+ def udap_authenticated?(assertion, software_statement)
125
+ signature_error = MockUDAPServer.udap_token_signature_verification(assertion, software_statement)
126
+
127
+ if signature_error.present?
128
+ MockUDAPServer.update_response_for_error(response, signature_error)
129
+ return false
130
+ end
131
+
132
+ true
133
+ end
134
+
135
+ def udap_requested_scope_context(requested_scopes, authorization_code, launch_context)
136
+ context = launch_context.present? ? launch_context : {}
137
+ scopes_list = requested_scopes&.split || []
138
+
139
+ if scopes_list.include?('offline_access') || scopes_list.include?('online_access')
140
+ context[:refresh_token] = MockUDAPServer.authorization_code_to_refresh_token(authorization_code)
141
+ end
142
+
143
+ context[:id_token] = udap_construct_id_token(scopes_list.include?('fhirUser')) if scopes_list.include?('openid')
144
+
145
+ context
146
+ end
147
+
148
+ def udap_construct_id_token(include_fhir_user) # rubocop:disable Metrics/CyclomaticComplexity
149
+ client_id = JSON.parse(result.input_json)&.find do |input|
150
+ input['name'] == 'client_id'
151
+ end&.dig('value')
152
+ fhir_user_relative_reference = JSON.parse(result.input_json)&.find do |input|
153
+ input['name'] == 'fhir_user_relative_reference'
154
+ end&.dig('value')
155
+
156
+ subject_id = if fhir_user_relative_reference.present?
157
+ fhir_user_relative_reference.downcase.gsub('/', '-')
158
+ else
159
+ SecureRandom.uuid
160
+ end
161
+
162
+ claims = {
163
+ iss: client_fhir_base_url,
164
+ sub: subject_id,
165
+ aud: client_id,
166
+ exp: 1.year.from_now.to_i,
167
+ iat: Time.now.to_i
168
+ }
169
+ if include_fhir_user && fhir_user_relative_reference.present?
170
+ claims[:fhirUser] = "#{client_fhir_base_url}/#{fhir_user_relative_reference}"
171
+ end
172
+
173
+ algorithm = 'RS256'
174
+ private_key = OIDCJWKS.jwks
175
+ .select { |key| key[:key_ops]&.include?('sign') }
176
+ .select { |key| key[:alg] == algorithm }
177
+ .first
178
+
179
+ JWT.encode claims, private_key.signing_key, algorithm, { alg: algorithm, kid: private_key.kid, typ: 'JWT' }
180
+ end
181
+
182
+ def udap_pkce_valid?(authorization_code)
183
+ authorization_request = MockUDAPServer.authorization_request_for_code(authorization_code,
184
+ test_run.test_session_id)
185
+ if authorization_request.blank?
186
+ MockUDAPServer.update_response_for_error(
187
+ response,
188
+ "Could not check code_verifier: no authorization request found that returned code #{authorization_code}"
189
+ )
190
+ return false
191
+ end
192
+ auth_code_request_inputs = MockUDAPServer.authorization_code_request_details(authorization_request)
193
+ if auth_code_request_inputs.blank?
194
+ MockUDAPServer.update_response_for_error(
195
+ response,
196
+ "Could not check code_verifier: invalid authorization request details for code #{authorization_code}"
197
+ )
198
+ return false
199
+ end
200
+
201
+ verifier = request.params[:code_verifier]
202
+ challenge = auth_code_request_inputs&.dig('code_challenge')
203
+ method = auth_code_request_inputs&.dig('code_challenge_method')
204
+ MockUDAPServer.pkce_valid?(verifier, challenge, method, response)
205
+ end
206
+
207
+ def udap_registered_scope(software_statement_jwt)
208
+ claims, _headers = begin
209
+ JWT.decode(software_statement_jwt, nil, false)
210
+ rescue StandardError
211
+ return nil
212
+ end
213
+
214
+ claims['scope']
215
+ end
216
+ end
217
+ end
218
+ end
@@ -1,6 +1,7 @@
1
1
  require 'jwt'
2
2
  require 'faraday'
3
3
  require 'time'
4
+ require 'rack/utils'
4
5
  require_relative '../urls'
5
6
  require_relative '../tags'
6
7
  require_relative '../udap_jwt_builder'
@@ -20,44 +21,34 @@ module UDAPSecurityTestKit
20
21
  udap_authorization_extensions_required: [],
21
22
  udap_certifications_supported: [],
22
23
  udap_certifications_required: [],
23
- grant_types_supported: ['client_credentials'],
24
+ grant_types_supported: ['authorization_code', 'client_credentials', 'refresh_token'],
24
25
  scopes_supported: SUPPORTED_SCOPES,
26
+ registration_endpoint: base_url + REGISTRATION_PATH,
27
+ registration_endpoint_jwt_signing_alg_values_supported: ['RS256', 'RS384', 'ES384'],
28
+ authorization_endpoint: base_url + AUTHORIZATION_PATH,
25
29
  token_endpoint: base_url + TOKEN_PATH,
26
30
  token_endpoint_auth_methods_supported: ['private_key_jwt'],
27
31
  token_endpoint_auth_signing_alg_values_supported: ['RS256', 'RS384', 'ES384'],
28
- registration_endpoint: base_url + REGISTRATION_PATH,
29
- registration_endpoint_jwt_signing_alg_values_supported: ['RS256', 'RS384', 'ES384'],
32
+ introspection_endpoint: base_url + INTROSPECTION_PATH,
30
33
  signed_metadata: udap_signed_metadata_jwt(base_url)
31
34
  }.to_json
32
35
 
33
36
  [200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
34
37
  end
35
38
 
36
- def make_udap_token_response(request, response, test_session_id)
37
- assertion = request.params[:client_assertion]
38
- client_id = client_id_from_client_assertion(assertion)
39
-
40
- software_statement = udap_registration_software_statement(test_session_id)
41
- signature_error = udap_token_signature_verification(assertion, software_statement)
42
-
43
- if signature_error.present?
44
- update_response_for_invalid_assertion(response, signature_error)
45
- return
46
- end
47
-
48
- exp_min = 60
39
+ def openid_connect_metadata(suite_id)
40
+ base_url = "#{Inferno::Application['base_url']}/custom/#{suite_id}"
49
41
  response_body = {
50
- access_token: client_id_to_token(client_id, exp_min),
51
- token_type: 'Bearer',
52
- expires_in: 60 * exp_min
53
- }
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
54
50
 
55
- response.body = response_body.to_json
56
- response.headers['Cache-Control'] = 'no-store'
57
- response.headers['Pragma'] = 'no-cache'
58
- response.headers['Access-Control-Allow-Origin'] = '*'
59
- response.content_type = 'application/json'
60
- response.status = 200
51
+ [200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
61
52
  end
62
53
 
63
54
  def udap_signed_metadata_jwt(base_url)
@@ -68,6 +59,7 @@ module UDAPSecurityTestKit
68
59
  iat: Time.now.to_i,
69
60
  jti: SecureRandom.hex(32),
70
61
  token_endpoint: base_url + TOKEN_PATH,
62
+ authorization_endpoint: base_url + AUTHORIZATION_PATH,
71
63
  registration_endpoint: base_url + REGISTRATION_PATH
72
64
  }.compact
73
65
 
@@ -132,6 +124,21 @@ module UDAPSecurityTestKit
132
124
  JWT.decode(encoded_jwt, nil, false)[0]
133
125
  end
134
126
 
127
+ def udap_client_uri_from_registration_payload(reg_body)
128
+ udap_claim_from_registration_payload(reg_body, 'iss')
129
+ end
130
+
131
+ def udap_claim_from_registration_payload(reg_body, claim_key)
132
+ software_statement_jwt = udap_software_statement_jwt(reg_body)
133
+ return unless software_statement_jwt.present?
134
+
135
+ jwt_claims(software_statement_jwt)&.dig(claim_key)
136
+ end
137
+
138
+ def udap_software_statement_jwt(reg_body)
139
+ reg_body&.dig('software_statement')
140
+ end
141
+
135
142
  def client_uri_to_client_id(client_uri)
136
143
  Base64.urlsafe_encode64(client_uri, padding: false)
137
144
  end
@@ -151,31 +158,56 @@ module UDAPSecurityTestKit
151
158
  end
152
159
 
153
160
  def decode_token(token)
154
- JSON.parse(Base64.urlsafe_decode64(token))
155
- rescue JSON::ParserError
161
+ token_to_decode =
162
+ if issued_token_is_refresh_token(token)
163
+ refresh_token_to_authorization_code(token)
164
+ else
165
+ token
166
+ end
167
+ return unless token_to_decode.present?
168
+
169
+ JSON.parse(Base64.urlsafe_decode64(token_to_decode))
170
+ rescue StandardError
156
171
  nil
157
172
  end
158
173
 
159
- def token_to_client_id(token)
174
+ def issued_token_to_client_id(token)
160
175
  decode_token(token)&.dig('client_id')
161
176
  end
162
177
 
178
+ def issued_token_is_refresh_token(token)
179
+ token.end_with?('_rt')
180
+ end
181
+
182
+ def authorization_code_to_refresh_token(code)
183
+ "#{code}_rt"
184
+ end
185
+
186
+ def refresh_token_to_authorization_code(refresh_token)
187
+ refresh_token[..-4]
188
+ end
189
+
163
190
  def request_has_expired_token?(request)
164
191
  return false if request.params[:session_path].present?
165
192
 
166
193
  token = request.headers['authorization']&.delete_prefix('Bearer ')
194
+ token_expired?(token)
195
+ end
196
+
197
+ def token_expired?(token, check_time = nil)
167
198
  decoded_token = decode_token(token)
168
199
  return false unless decoded_token&.dig('expiration').present?
169
200
 
170
- decoded_token['expiration'] < Time.now.to_i
201
+ check_time = Time.now.to_i unless check_time.present?
202
+ decoded_token['expiration'] < check_time
171
203
  end
172
204
 
173
- def update_response_for_expired_token(response)
205
+ def update_response_for_expired_token(response, type)
174
206
  response.status = 401
175
207
  response.format = :json
176
208
  response.body = FHIR::OperationOutcome.new(
177
209
  issue: FHIR::OperationOutcome::Issue.new(severity: 'fatal', code: 'expired',
178
- details: FHIR::CodeableConcept.new(text: 'Bearer token has expired'))
210
+ details: FHIR::CodeableConcept.new(text: "#{type} has expired"))
179
211
  ).to_json
180
212
  end
181
213
 
@@ -244,7 +276,7 @@ module UDAPSecurityTestKit
244
276
  parsed_body&.dig('software_statement')
245
277
  end
246
278
 
247
- def update_response_for_invalid_assertion(response, error_message)
279
+ def update_response_for_error(response, error_message)
248
280
  response.status = 401
249
281
  response.format = :json
250
282
  response.body = { error: 'invalid_client', error_description: error_message }.to_json
@@ -297,5 +329,57 @@ module UDAPSecurityTestKit
297
329
 
298
330
  nil
299
331
  end
332
+
333
+ def pkce_error(verifier, challenge, method)
334
+ if verifier.blank?
335
+ 'pkce check failed: no verifier provided'
336
+ elsif challenge.blank?
337
+ 'pkce check failed: no challenge code provided'
338
+ elsif method == 'S256'
339
+ return nil unless challenge != calculate_s256_challenge(verifier)
340
+
341
+ "invalid S256 pkce verifier: got '#{calculate_s256_challenge(verifier)}' " \
342
+ "expected '#{challenge}'"
343
+ else
344
+ "invalid pkce challenge method '#{method}'"
345
+ end
346
+ end
347
+
348
+ def pkce_valid?(verifier, challenge, method, response)
349
+ pkce_error = pkce_error(verifier, challenge, method)
350
+
351
+ if pkce_error.present?
352
+ update_response_for_error(response, pkce_error)
353
+ false
354
+ else
355
+ true
356
+ end
357
+ end
358
+
359
+ def calculate_s256_challenge(verifier)
360
+ Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
361
+ end
362
+
363
+ def authorization_request_for_code(code, test_session_id)
364
+ authorization_requests = Inferno::Repositories::Requests.new.tagged_requests(test_session_id, [AUTHORIZATION_TAG])
365
+ authorization_requests.find do |request|
366
+ location_header = request.response_headers.find { |header| header.name.downcase == 'location' }
367
+ if location_header.present? && location_header.value.present?
368
+ Rack::Utils.parse_query(URI(location_header.value)&.query)&.dig('code') == code
369
+ else
370
+ false
371
+ end
372
+ end
373
+ end
374
+
375
+ def authorization_code_request_details(inferno_request)
376
+ return unless inferno_request.present?
377
+
378
+ if inferno_request.verb.downcase == 'get'
379
+ Rack::Utils.parse_query(URI(inferno_request.url)&.query)
380
+ elsif inferno_request.verb.downcase == 'post'
381
+ Rack::Utils.parse_query(inferno_request.request_body)
382
+ end
383
+ end
300
384
  end
301
385
  end
@@ -9,7 +9,7 @@ module UDAPSecurityTestKit
9
9
  STU 1.0 IG](https://hl7.org/fhir/us/udap-security/STU1/index.html)
10
10
  <!-- break -->
11
11
  Specifically, this test
12
- kit assesses the required capabilities from the following sections:
12
+ kit assesses the required capabilities for clients and servers from the following sections:
13
13
  - [JSON Web Token (JWT) Requirements](https://hl7.org/fhir/us/udap-security/STU1/index.html)
14
14
  - [Discovery](https://hl7.org/fhir/us/udap-security/STU1/discovery.html)
15
15
  - [Dynamic Client Registration](https://hl7.org/fhir/us/udap-security/STU1/registration.html)
@@ -2,7 +2,12 @@
2
2
 
3
3
  module UDAPSecurityTestKit
4
4
  REGISTRATION_TAG = 'registration'
5
+ AUTHORIZATION_TAG = 'authorization'
6
+ INTROSPECTION_TAG = 'introspection'
5
7
  TOKEN_TAG = 'token'
6
8
  UDAP_TAG = 'udap'
7
9
  ACCESS_TAG = 'access'
10
+ CLIENT_CREDENTIALS_TAG = 'client_credentials'
11
+ AUTHORIZATION_CODE_TAG = 'authorization_code'
12
+ REFRESH_TOKEN_TAG = 'refresh_token'
8
13
  end
@@ -2,12 +2,16 @@
2
2
 
3
3
  module UDAPSecurityTestKit
4
4
  FHIR_PATH = '/fhir'
5
- RESUME_PASS_PATH = '/resume_pass'
6
- RESUME_FAIL_PATH = '/resume_fail'
7
- AUTH_SERVER_PATH = '/auth'
5
+ OIDC_DISCOVERY_PATH = "#{FHIR_PATH}/.well-known/openid-configuration".freeze
6
+ OIDC_JWKS_PATH = "#{FHIR_PATH}/.well-known/jwks.json".freeze
8
7
  UDAP_DISCOVERY_PATH = "#{FHIR_PATH}/.well-known/udap".freeze
9
- TOKEN_PATH = "#{AUTH_SERVER_PATH}/token".freeze
8
+ AUTH_SERVER_PATH = '/auth'
10
9
  REGISTRATION_PATH = "#{AUTH_SERVER_PATH}/register".freeze
10
+ AUTHORIZATION_PATH = "#{AUTH_SERVER_PATH}/authorization".freeze
11
+ INTROSPECTION_PATH = "#{AUTH_SERVER_PATH}/introspect".freeze
12
+ TOKEN_PATH = "#{AUTH_SERVER_PATH}/token".freeze
13
+ RESUME_PASS_PATH = '/resume_pass'
14
+ RESUME_FAIL_PATH = '/resume_fail'
11
15
 
12
16
  module URLs
13
17
  def client_base_url
@@ -30,14 +34,22 @@ module UDAPSecurityTestKit
30
34
  @client_udap_discovery_url ||= client_base_url + UDAP_DISCOVERY_PATH
31
35
  end
32
36
 
33
- def client_token_url
34
- @client_token_url ||= client_base_url + TOKEN_PATH
35
- end
36
-
37
37
  def client_registration_url
38
38
  @client_registration_url ||= client_base_url + REGISTRATION_PATH
39
39
  end
40
40
 
41
+ def client_authorization_url
42
+ @client_authorization_url ||= client_base_url + AUTHORIZATION_PATH
43
+ end
44
+
45
+ def client_introspection_url
46
+ @client_introspection_url ||= client_base_url + INTROSPECTION_PATH
47
+ end
48
+
49
+ def client_token_url
50
+ @client_token_url ||= client_base_url + TOKEN_PATH
51
+ end
52
+
41
53
  def client_suite_id
42
54
  UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
43
55
  end
@@ -1,4 +1,4 @@
1
1
  module UDAPSecurityTestKit
2
- VERSION = '0.11.3'.freeze
3
- LAST_UPDATED = '2025-04-07'.freeze
2
+ VERSION = '0.11.5'.freeze
3
+ LAST_UPDATED = '2025-05-14'.freeze
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: udap_security_test_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.3
4
+ version: 0.11.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen MacVicar
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2025-04-07 00:00:00.000000000 Z
12
+ date: 2025-05-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: inferno_core
@@ -65,23 +65,41 @@ files:
65
65
  - lib/udap_security_test_kit/client_credentials_group.rb
66
66
  - lib/udap_security_test_kit/client_credentials_token_exchange_test.rb
67
67
  - lib/udap_security_test_kit/client_suite.rb
68
- - lib/udap_security_test_kit/client_suite/client_access_group.rb
69
- - lib/udap_security_test_kit/client_suite/client_access_interaction_test.rb
70
- - lib/udap_security_test_kit/client_suite/client_registration_group.rb
71
- - lib/udap_security_test_kit/client_suite/client_registration_interaction_test.rb
72
- - lib/udap_security_test_kit/client_suite/client_registration_verification_test.rb
73
- - lib/udap_security_test_kit/client_suite/client_token_request_verification_test.rb
74
- - lib/udap_security_test_kit/client_suite/client_token_use_verification_test.rb
68
+ - lib/udap_security_test_kit/client_suite/access_ac_group.rb
69
+ - lib/udap_security_test_kit/client_suite/access_ac_interaction_test.rb
70
+ - lib/udap_security_test_kit/client_suite/access_cc_group.rb
71
+ - lib/udap_security_test_kit/client_suite/access_cc_interaction_test.rb
72
+ - lib/udap_security_test_kit/client_suite/authorization_request_verification_test.rb
73
+ - lib/udap_security_test_kit/client_suite/client_descriptions.rb
74
+ - lib/udap_security_test_kit/client_suite/client_options.rb
75
+ - lib/udap_security_test_kit/client_suite/oidc_jwks.json
76
+ - lib/udap_security_test_kit/client_suite/oidc_jwks.rb
77
+ - lib/udap_security_test_kit/client_suite/registration_ac_group.rb
78
+ - lib/udap_security_test_kit/client_suite/registration_ac_verification_test.rb
79
+ - lib/udap_security_test_kit/client_suite/registration_cc_group.rb
80
+ - lib/udap_security_test_kit/client_suite/registration_cc_verification_test.rb
81
+ - lib/udap_security_test_kit/client_suite/registration_interaction_test.rb
82
+ - lib/udap_security_test_kit/client_suite/registration_request_verification.rb
83
+ - lib/udap_security_test_kit/client_suite/token_request_ac_verification_test.rb
84
+ - lib/udap_security_test_kit/client_suite/token_request_cc_verification_test.rb
85
+ - lib/udap_security_test_kit/client_suite/token_request_verification.rb
86
+ - lib/udap_security_test_kit/client_suite/token_use_verification_test.rb
75
87
  - lib/udap_security_test_kit/common_assertions.rb
76
88
  - lib/udap_security_test_kit/default_cert_file_loader.rb
77
89
  - lib/udap_security_test_kit/discovery_group.rb
78
90
  - lib/udap_security_test_kit/docs/demo/FHIR Request.postman_collection.json
79
91
  - lib/udap_security_test_kit/docs/udap_client_suite_description.md
80
92
  - lib/udap_security_test_kit/dynamic_client_registration_group.rb
81
- - lib/udap_security_test_kit/endpoints/echoing_fhir_responder.rb
93
+ - lib/udap_security_test_kit/endpoints/echoing_fhir_responder_endpoint.rb
82
94
  - lib/udap_security_test_kit/endpoints/mock_udap_server.rb
83
- - lib/udap_security_test_kit/endpoints/mock_udap_server/registration.rb
84
- - lib/udap_security_test_kit/endpoints/mock_udap_server/token.rb
95
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/authorization_endpoint.rb
96
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/introspection_endpoint.rb
97
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/registration_endpoint.rb
98
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/token_endpoint.rb
99
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/udap_authorization_response_creation.rb
100
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/udap_introspection_response_creation.rb
101
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/udap_registration_response_creation.rb
102
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/udap_token_response_creation.rb
85
103
  - lib/udap_security_test_kit/grant_types_supported_field_test.rb
86
104
  - lib/udap_security_test_kit/igs/put_ig_package_dot_tgz_here
87
105
  - lib/udap_security_test_kit/metadata.rb
@@ -1,22 +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 UDAPSecurityTestKit
6
- class UDAPClientAccess < Inferno::TestGroup
7
- id :udap_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
- test from: :udap_client_access_interaction
19
- test from: :udap_client_token_request_verification
20
- test from: :udap_client_token_use_verification
21
- end
22
- end
@@ -1,53 +0,0 @@
1
- require_relative '../urls'
2
- require_relative '../endpoints/mock_udap_server'
3
-
4
- module UDAPSecurityTestKit
5
- class UDAPClientAccessInteraction < Inferno::Test
6
- include URLs
7
-
8
- id :udap_client_access_interaction
9
- title 'Perform UDAP-secured Access'
10
- description %(
11
- During this test, Inferno will wait for the client to access data
12
- using a UDAP token obtained during an earlier test.
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 :echoed_fhir_response,
23
- title: 'FHIR Response to Echo',
24
- type: 'textarea',
25
- description: %(
26
- JSON representation of a FHIR resource for Inferno to echo when a request
27
- is made to the simulated FHIR server. The provided content will be echoed
28
- back exactly and no check will be made that it is appropriate for the request
29
- made. If nothing is provided, an OperationOutcome will be returned.
30
- ),
31
- optional: true
32
-
33
- run do
34
- wait(
35
- identifier: client_id,
36
- message: %(
37
- **Access**
38
-
39
- Use the registered client id (#{client_id}) to obtain an access
40
- token using the [UDAP B2B client credentials flow](https://hl7.org/fhir/us/udap-security/STU1/b2b.html)
41
- and use that token to access a FHIR endpoint under the simulated server's base URL
42
-
43
- `#{client_fhir_base_url}`
44
-
45
- Inferno will echo the response provided in the **FHIR Response to Echo** input.
46
-
47
- [Click here](#{client_resume_pass_url}?token=#{client_id}) once you performed
48
- the access.
49
- )
50
- )
51
- end
52
- end
53
- end