udap_security_test_kit 0.11.3 → 0.11.4

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 (42) 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 +46 -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/registration_endpoint.rb +31 -0
  27. data/lib/udap_security_test_kit/endpoints/mock_udap_server/token_endpoint.rb +56 -0
  28. data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_authorization_response_creation.rb +63 -0
  29. data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_registration_response_creation.rb +28 -0
  30. data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_token_response_creation.rb +218 -0
  31. data/lib/udap_security_test_kit/endpoints/mock_udap_server.rb +112 -31
  32. data/lib/udap_security_test_kit/metadata.rb +1 -1
  33. data/lib/udap_security_test_kit/tags.rb +4 -0
  34. data/lib/udap_security_test_kit/urls.rb +15 -8
  35. data/lib/udap_security_test_kit/version.rb +2 -2
  36. metadata +28 -12
  37. data/lib/udap_security_test_kit/client_suite/client_access_group.rb +0 -22
  38. data/lib/udap_security_test_kit/client_suite/client_access_interaction_test.rb +0 -53
  39. data/lib/udap_security_test_kit/client_suite/client_registration_group.rb +0 -26
  40. data/lib/udap_security_test_kit/endpoints/echoing_fhir_responder.rb +0 -52
  41. data/lib/udap_security_test_kit/endpoints/mock_udap_server/registration.rb +0 -57
  42. 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,33 @@ 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'],
30
32
  signed_metadata: udap_signed_metadata_jwt(base_url)
31
33
  }.to_json
32
34
 
33
35
  [200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
34
36
  end
35
37
 
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
38
+ def openid_connect_metadata(suite_id)
39
+ base_url = "#{Inferno::Application['base_url']}/custom/#{suite_id}"
49
40
  response_body = {
50
- access_token: client_id_to_token(client_id, exp_min),
51
- token_type: 'Bearer',
52
- expires_in: 60 * exp_min
53
- }
41
+ issuer: base_url + FHIR_PATH,
42
+ authorization_endpoint: base_url + AUTHORIZATION_PATH,
43
+ token_endpoint: base_url + TOKEN_PATH,
44
+ jwks_uri: base_url + OIDC_JWKS_PATH,
45
+ response_types_supported: ['code', 'id_token', 'token id_token'],
46
+ subject_types_supported: ['pairwise', 'public'],
47
+ id_token_signing_alg_values_supported: ['RS256']
48
+ }.to_json
54
49
 
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
50
+ [200, { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*' }, [response_body]]
61
51
  end
62
52
 
63
53
  def udap_signed_metadata_jwt(base_url)
@@ -68,6 +58,7 @@ module UDAPSecurityTestKit
68
58
  iat: Time.now.to_i,
69
59
  jti: SecureRandom.hex(32),
70
60
  token_endpoint: base_url + TOKEN_PATH,
61
+ authorization_endpoint: base_url + AUTHORIZATION_PATH,
71
62
  registration_endpoint: base_url + REGISTRATION_PATH
72
63
  }.compact
73
64
 
@@ -132,6 +123,21 @@ module UDAPSecurityTestKit
132
123
  JWT.decode(encoded_jwt, nil, false)[0]
133
124
  end
134
125
 
126
+ def udap_client_uri_from_registration_payload(reg_body)
127
+ udap_claim_from_registration_payload(reg_body, 'iss')
128
+ end
129
+
130
+ def udap_claim_from_registration_payload(reg_body, claim_key)
131
+ software_statement_jwt = udap_software_statement_jwt(reg_body)
132
+ return unless software_statement_jwt.present?
133
+
134
+ jwt_claims(software_statement_jwt)&.dig(claim_key)
135
+ end
136
+
137
+ def udap_software_statement_jwt(reg_body)
138
+ reg_body&.dig('software_statement')
139
+ end
140
+
135
141
  def client_uri_to_client_id(client_uri)
136
142
  Base64.urlsafe_encode64(client_uri, padding: false)
137
143
  end
@@ -151,31 +157,56 @@ module UDAPSecurityTestKit
151
157
  end
152
158
 
153
159
  def decode_token(token)
160
+ token_to_decode =
161
+ if issued_token_is_refresh_token(token)
162
+ refresh_token_to_authorization_code(token)
163
+ else
164
+ token
165
+ end
166
+ return unless token_to_decode.present?
167
+
154
168
  JSON.parse(Base64.urlsafe_decode64(token))
155
169
  rescue JSON::ParserError
156
170
  nil
157
171
  end
158
172
 
159
- def token_to_client_id(token)
173
+ def issued_token_to_client_id(token)
160
174
  decode_token(token)&.dig('client_id')
161
175
  end
162
176
 
177
+ def issued_token_is_refresh_token(token)
178
+ token.end_with?('_rt')
179
+ end
180
+
181
+ def authorization_code_to_refresh_token(code)
182
+ "#{code}_rt"
183
+ end
184
+
185
+ def refresh_token_to_authorization_code(refresh_token)
186
+ refresh_token[..-4]
187
+ end
188
+
163
189
  def request_has_expired_token?(request)
164
190
  return false if request.params[:session_path].present?
165
191
 
166
192
  token = request.headers['authorization']&.delete_prefix('Bearer ')
193
+ token_expired?(token)
194
+ end
195
+
196
+ def token_expired?(token, check_time = nil)
167
197
  decoded_token = decode_token(token)
168
198
  return false unless decoded_token&.dig('expiration').present?
169
199
 
170
- decoded_token['expiration'] < Time.now.to_i
200
+ check_time = Time.now.to_i unless check_time.present?
201
+ decoded_token['expiration'] < check_time
171
202
  end
172
203
 
173
- def update_response_for_expired_token(response)
204
+ def update_response_for_expired_token(response, type)
174
205
  response.status = 401
175
206
  response.format = :json
176
207
  response.body = FHIR::OperationOutcome.new(
177
208
  issue: FHIR::OperationOutcome::Issue.new(severity: 'fatal', code: 'expired',
178
- details: FHIR::CodeableConcept.new(text: 'Bearer token has expired'))
209
+ details: FHIR::CodeableConcept.new(text: "#{type}has expired"))
179
210
  ).to_json
180
211
  end
181
212
 
@@ -244,7 +275,7 @@ module UDAPSecurityTestKit
244
275
  parsed_body&.dig('software_statement')
245
276
  end
246
277
 
247
- def update_response_for_invalid_assertion(response, error_message)
278
+ def update_response_for_error(response, error_message)
248
279
  response.status = 401
249
280
  response.format = :json
250
281
  response.body = { error: 'invalid_client', error_description: error_message }.to_json
@@ -297,5 +328,55 @@ module UDAPSecurityTestKit
297
328
 
298
329
  nil
299
330
  end
331
+
332
+ def pkce_error(verifier, challenge, method)
333
+ if verifier.blank?
334
+ 'pkce check failed: no verifier provided'
335
+ elsif challenge.blank?
336
+ 'pkce check failed: no challenge code provided'
337
+ elsif method == 'S256'
338
+ return nil unless challenge != calculate_s256_challenge(verifier)
339
+
340
+ "invalid S256 pkce verifier: got '#{calculate_s256_challenge(verifier)}' " \
341
+ "expected '#{challenge}'"
342
+ else
343
+ "invalid pkce challenge method '#{method}'"
344
+ end
345
+ end
346
+
347
+ def pkce_valid?(verifier, challenge, method, response)
348
+ pkce_error = pkce_error(verifier, challenge, method)
349
+
350
+ if pkce_error.present?
351
+ update_response_for_error(response, pkce_error)
352
+ false
353
+ else
354
+ true
355
+ end
356
+ end
357
+
358
+ def calculate_s256_challenge(verifier)
359
+ Base64.urlsafe_encode64(Digest::SHA256.digest(verifier), padding: false)
360
+ end
361
+
362
+ def authorization_request_for_code(code, test_session_id)
363
+ authorization_requests = Inferno::Repositories::Requests.new.tagged_requests(test_session_id, [AUTHORIZATION_TAG])
364
+ authorization_requests.find do |request|
365
+ location_header = request.response_headers.find { |header| header.name.downcase == 'location' }
366
+ if location_header.present? && location_header.value.present?
367
+ Rack::Utils.parse_query(URI(location_header.value)&.query)&.dig('code') == code
368
+ else
369
+ false
370
+ end
371
+ end
372
+ end
373
+
374
+ def authorization_code_request_details(inferno_request)
375
+ if inferno_request.verb.downcase == 'get'
376
+ Rack::Utils.parse_query(URI(inferno_request.url)&.query)
377
+ elsif inferno_request.verb.downcase == 'post'
378
+ Rack::Utils.parse_query(inferno_request.request_body)
379
+ end
380
+ end
300
381
  end
301
382
  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,11 @@
2
2
 
3
3
  module UDAPSecurityTestKit
4
4
  REGISTRATION_TAG = 'registration'
5
+ AUTHORIZATION_TAG = 'authorization'
5
6
  TOKEN_TAG = 'token'
6
7
  UDAP_TAG = 'udap'
7
8
  ACCESS_TAG = 'access'
9
+ CLIENT_CREDENTIALS_TAG = 'client_credentials'
10
+ AUTHORIZATION_CODE_TAG = 'authorization_code'
11
+ REFRESH_TOKEN_TAG = 'refresh_token'
8
12
  end
@@ -2,12 +2,15 @@
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
+ TOKEN_PATH = "#{AUTH_SERVER_PATH}/token".freeze
12
+ RESUME_PASS_PATH = '/resume_pass'
13
+ RESUME_FAIL_PATH = '/resume_fail'
11
14
 
12
15
  module URLs
13
16
  def client_base_url
@@ -30,14 +33,18 @@ module UDAPSecurityTestKit
30
33
  @client_udap_discovery_url ||= client_base_url + UDAP_DISCOVERY_PATH
31
34
  end
32
35
 
33
- def client_token_url
34
- @client_token_url ||= client_base_url + TOKEN_PATH
35
- end
36
-
37
36
  def client_registration_url
38
37
  @client_registration_url ||= client_base_url + REGISTRATION_PATH
39
38
  end
40
39
 
40
+ def client_authorization_url
41
+ @client_authorization_url ||= client_base_url + AUTHORIZATION_PATH
42
+ end
43
+
44
+ def client_token_url
45
+ @client_token_url ||= client_base_url + TOKEN_PATH
46
+ end
47
+
41
48
  def client_suite_id
42
49
  UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
43
50
  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.4'.freeze
3
+ LAST_UPDATED = '2025-05-02'.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.4
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-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: inferno_core
@@ -65,23 +65,39 @@ 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/registration_endpoint.rb
97
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/token_endpoint.rb
98
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/udap_authorization_response_creation.rb
99
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/udap_registration_response_creation.rb
100
+ - lib/udap_security_test_kit/endpoints/mock_udap_server/udap_token_response_creation.rb
85
101
  - lib/udap_security_test_kit/grant_types_supported_field_test.rb
86
102
  - lib/udap_security_test_kit/igs/put_ig_package_dot_tgz_here
87
103
  - 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