davinci_crd_test_kit 0.9.0 → 0.9.1
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/lib/davinci_crd_test_kit/card_responses/propose_alternate_request.json +2 -52
- data/lib/davinci_crd_test_kit/card_responses/request_form_completion.json +46 -31
- data/lib/davinci_crd_test_kit/cards_validation.rb +8 -4
- data/lib/davinci_crd_test_kit/client_hooks_group.rb +22 -660
- data/lib/davinci_crd_test_kit/client_tests/appointment_book_receive_request_test.rb +17 -6
- data/lib/davinci_crd_test_kit/client_tests/client_appointment_book_group.rb +70 -0
- data/lib/davinci_crd_test_kit/client_tests/client_encounter_discharge_group.rb +71 -0
- data/lib/davinci_crd_test_kit/client_tests/client_encounter_start_group.rb +70 -0
- data/lib/davinci_crd_test_kit/client_tests/client_order_dispatch_group.rb +70 -0
- data/lib/davinci_crd_test_kit/client_tests/client_order_select_group.rb +72 -0
- data/lib/davinci_crd_test_kit/client_tests/client_order_sign_group.rb +71 -0
- data/lib/davinci_crd_test_kit/client_tests/decode_auth_token_test.rb +43 -23
- data/lib/davinci_crd_test_kit/client_tests/encounter_discharge_receive_request_test.rb +19 -6
- data/lib/davinci_crd_test_kit/client_tests/encounter_start_receive_request_test.rb +18 -6
- data/lib/davinci_crd_test_kit/client_tests/hook_request_optional_fields_test.rb +26 -10
- data/lib/davinci_crd_test_kit/client_tests/hook_request_required_fields_test.rb +20 -11
- data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_context_test.rb +14 -10
- data/lib/davinci_crd_test_kit/client_tests/hook_request_valid_prefetch_test.rb +27 -110
- data/lib/davinci_crd_test_kit/client_tests/order_dispatch_receive_request_test.rb +18 -6
- data/lib/davinci_crd_test_kit/client_tests/order_select_receive_request_test.rb +18 -6
- data/lib/davinci_crd_test_kit/client_tests/order_sign_receive_request_test.rb +18 -6
- data/lib/davinci_crd_test_kit/client_tests/retrieve_jwks_test.rb +66 -29
- data/lib/davinci_crd_test_kit/client_tests/submitted_response_validation.rb +44 -0
- data/lib/davinci_crd_test_kit/client_tests/token_header_test.rb +45 -14
- data/lib/davinci_crd_test_kit/client_tests/token_payload_test.rb +43 -26
- data/lib/davinci_crd_test_kit/crd_client_suite.rb +0 -4
- data/lib/davinci_crd_test_kit/hook_request_field_validation.rb +240 -50
- data/lib/davinci_crd_test_kit/mock_service_response.rb +134 -120
- data/lib/davinci_crd_test_kit/routes/hook_request_endpoint.rb +26 -42
- data/lib/davinci_crd_test_kit/server_encounter_discharge_group.rb +24 -0
- data/lib/davinci_crd_test_kit/server_encounter_start_group.rb +24 -0
- data/lib/davinci_crd_test_kit/server_order_select_group.rb +24 -0
- data/lib/davinci_crd_test_kit/server_tests/coverage_information_system_action_received_test.rb +4 -1
- data/lib/davinci_crd_test_kit/server_tests/service_request_optional_fields_validation_test.rb +8 -10
- data/lib/davinci_crd_test_kit/server_tests/service_request_required_fields_validation_test.rb +5 -10
- data/lib/davinci_crd_test_kit/tags.rb +6 -6
- data/lib/davinci_crd_test_kit/version.rb +1 -1
- metadata +9 -2
@@ -1,5 +1,9 @@
|
|
1
|
+
require_relative '../client_hook_request_validation'
|
2
|
+
|
1
3
|
module DaVinciCRDTestKit
|
2
4
|
class RetrieveJWKSTest < Inferno::Test
|
5
|
+
include ClientHookRequestValidation
|
6
|
+
|
3
7
|
id :crd_retrieve_jwks
|
4
8
|
title 'JWKS can be retrieved'
|
5
9
|
description %(
|
@@ -10,7 +14,7 @@ module DaVinciCRDTestKit
|
|
10
14
|
submit the jwk_set as an input to the test.
|
11
15
|
)
|
12
16
|
|
13
|
-
input :
|
17
|
+
input :auth_token_headers_json
|
14
18
|
input :jwk_set,
|
15
19
|
title: "The Client's JWK Set containing it's public key",
|
16
20
|
description: %(
|
@@ -20,46 +24,79 @@ module DaVinciCRDTestKit
|
|
20
24
|
type: 'textarea',
|
21
25
|
optional: true
|
22
26
|
output :crd_jwks_json, :crd_jwks_keys_json
|
23
|
-
makes_request :crd_client_jwks
|
24
27
|
|
25
28
|
run do
|
26
|
-
|
27
|
-
|
29
|
+
auth_token_headers = JSON.parse(auth_token_headers_json)
|
30
|
+
skip_if auth_token_headers.empty?, 'No Authorization tokens produced from the previous test.'
|
28
31
|
|
29
|
-
|
30
|
-
|
32
|
+
crd_jwks_json = []
|
33
|
+
crd_jwks_keys_json = []
|
34
|
+
auth_token_headers.each_with_index do |token_header, index|
|
35
|
+
@request_number = index + 1
|
31
36
|
|
32
|
-
|
33
|
-
|
34
|
-
|
37
|
+
jku = JSON.parse(token_header)['jku']
|
38
|
+
if jku.present?
|
39
|
+
get(jku)
|
35
40
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
+
if response[:status] != 200
|
42
|
+
add_message('error', %(
|
43
|
+
#{request_number}Unexpected response status: expected 200, but received
|
44
|
+
#{response[:status]}))
|
45
|
+
next
|
46
|
+
end
|
41
47
|
|
42
|
-
|
43
|
-
|
48
|
+
@request_number = index + 1
|
49
|
+
jwks = json_parse(response[:body])
|
50
|
+
next if jwks.blank?
|
44
51
|
|
45
|
-
|
46
|
-
assert keys.is_a?(Array), 'JWKS `keys` field must be an array'
|
52
|
+
crd_jwks_json << response[:body]
|
47
53
|
|
48
|
-
|
54
|
+
jwks = JSON.parse(response[:body])
|
55
|
+
else
|
56
|
+
skip_if jwk_set.blank?,
|
57
|
+
%(#{request_number}JWK Set must be inputted if Client's JWK Set is not available via a URL
|
58
|
+
identified by the jku header field)
|
49
59
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
60
|
+
jwks = JSON.parse(jwk_set)
|
61
|
+
end
|
62
|
+
|
63
|
+
keys = jwks['keys']
|
64
|
+
unless keys.is_a?(Array)
|
65
|
+
add_message('error', "#{request_number}JWKS `keys` field must be an array")
|
66
|
+
next
|
67
|
+
end
|
55
68
|
|
56
|
-
|
57
|
-
|
69
|
+
if keys.blank?
|
70
|
+
add_message('error', "#{request_number}The JWK set returned contains no public keys")
|
71
|
+
next
|
72
|
+
end
|
73
|
+
|
74
|
+
keys.each do |jwk|
|
75
|
+
JWT::JWK.import(jwk.deep_symbolize_keys)
|
76
|
+
rescue StandardError
|
77
|
+
add_message('error', "#{request_number}Invalid JWK: #{jwk.to_json}")
|
78
|
+
end
|
79
|
+
|
80
|
+
kid_presence = keys.all? { |key| key['kid'].present? }
|
81
|
+
if kid_presence.blank?
|
82
|
+
add_message('error',
|
83
|
+
"#{request_number}`kid` field must be present in each key if JWKS contains multiple keys")
|
84
|
+
next
|
85
|
+
end
|
86
|
+
|
87
|
+
kid_uniqueness = keys.map { |key| key['kid'] }.uniq.length == keys.length
|
88
|
+
if kid_uniqueness.blank?
|
89
|
+
add_message('error', "#{request_number}`kid` must be unique within the client's JWK Set.")
|
90
|
+
next
|
91
|
+
end
|
92
|
+
|
93
|
+
crd_jwks_keys_json << keys.to_json
|
94
|
+
end
|
58
95
|
|
59
|
-
|
60
|
-
|
96
|
+
output crd_jwks_json: crd_jwks_json.to_json,
|
97
|
+
crd_jwks_keys_json: crd_jwks_keys_json.to_json
|
61
98
|
|
62
|
-
|
99
|
+
no_error_validation('Retrieving JWKS failed.')
|
63
100
|
end
|
64
101
|
end
|
65
102
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module DaVinciCRDTestKit
|
2
|
+
class SubmittedResponseValidationTest < Inferno::Test
|
3
|
+
include CardsValidation
|
4
|
+
|
5
|
+
title 'Custom CDS Service Response is valid'
|
6
|
+
id :crd_submitted_response_validation
|
7
|
+
|
8
|
+
input :custom_response, optional: true
|
9
|
+
|
10
|
+
def hook_name
|
11
|
+
config.options[:hook_name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def response_label(_index = nil)
|
15
|
+
'Custom response'
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid_cards
|
19
|
+
@valid_cards ||= []
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate_system_actions(system_actions)
|
23
|
+
return if system_actions.nil?
|
24
|
+
|
25
|
+
system_actions.each do |action|
|
26
|
+
action_fields_validation(action)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
run do
|
31
|
+
omit_if custom_response.blank?, 'Custom response was not provided'
|
32
|
+
|
33
|
+
assert_valid_json custom_response
|
34
|
+
|
35
|
+
custom_response_hash = JSON.parse(custom_response)
|
36
|
+
|
37
|
+
perform_cards_validation(custom_response_hash['cards'])
|
38
|
+
|
39
|
+
validate_system_actions(custom_response_hash['systemActions'])
|
40
|
+
|
41
|
+
no_error_validation('Custom response is not valid. Check messages for issues found.')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,5 +1,9 @@
|
|
1
|
+
require_relative '../client_hook_request_validation'
|
2
|
+
|
1
3
|
module DaVinciCRDTestKit
|
2
4
|
class TokenHeaderTest < Inferno::Test
|
5
|
+
include ClientHookRequestValidation
|
6
|
+
|
3
7
|
id :crd_token_header
|
4
8
|
title 'Authorization token header contains required information'
|
5
9
|
description %(
|
@@ -8,27 +12,54 @@ module DaVinciCRDTestKit
|
|
8
12
|
that the key used to sign the token can be identified in the JWKS.
|
9
13
|
)
|
10
14
|
|
11
|
-
input :
|
12
|
-
output :
|
15
|
+
input :auth_token_headers_json, :crd_jwks_keys_json
|
16
|
+
output :auth_tokens_jwk_json
|
13
17
|
|
14
18
|
run do
|
15
|
-
|
19
|
+
auth_token_headers = JSON.parse(auth_token_headers_json)
|
20
|
+
crd_jwks_keys = JSON.parse(crd_jwks_keys_json)
|
21
|
+
skip_if auth_token_headers.empty?, 'No Authorization tokens produced from the previous tests.'
|
22
|
+
skip_if crd_jwks_keys.empty?, 'No JWKS keys produced from the previous test.'
|
23
|
+
|
24
|
+
auth_tokens_jwk_json = []
|
25
|
+
auth_token_headers.each_with_index do |token_header, index|
|
26
|
+
@request_number = index + 1
|
27
|
+
|
28
|
+
header = JSON.parse(token_header)
|
29
|
+
algorithm = header['alg']
|
30
|
+
|
31
|
+
add_message('error', "#{request_number}Token header must have the `alg` field") if algorithm.blank?
|
32
|
+
|
33
|
+
add_message('error', "#{request_number}Token header `alg` field cannot be set to none") if algorithm == 'none'
|
34
|
+
|
35
|
+
if header['typ'].blank?
|
36
|
+
add_message('error', "#{request_number}Token header must have the `typ` field")
|
37
|
+
elsif header['typ'] != 'JWT'
|
38
|
+
add_message('error', %(
|
39
|
+
#{request_number}Token header `typ` field must be set to 'JWT', instead was
|
40
|
+
#{header['typ']}))
|
41
|
+
end
|
42
|
+
|
43
|
+
if header['kid'].blank?
|
44
|
+
add_message('error', "#{request_number}Token header must have the `kid` field")
|
45
|
+
next
|
46
|
+
end
|
16
47
|
|
17
|
-
|
18
|
-
|
19
|
-
assert algorithm != 'none', 'Token header `alg` field cannot be set to none'
|
48
|
+
kid = header['kid']
|
49
|
+
keys = JSON.parse(crd_jwks_keys[index])
|
20
50
|
|
21
|
-
|
22
|
-
|
51
|
+
jwk = keys.find { |key| key['kid'] == kid }
|
52
|
+
if jwk.blank?
|
53
|
+
add_message('error', "#{request_number}JWKS did not contain a public key with an id of `#{kid}`")
|
54
|
+
next
|
55
|
+
end
|
23
56
|
|
24
|
-
|
25
|
-
|
26
|
-
keys = JSON.parse(crd_jwks_keys_json)
|
57
|
+
auth_tokens_jwk_json << jwk.to_json
|
58
|
+
end
|
27
59
|
|
28
|
-
|
29
|
-
assert jwk.present?, "JWKS did not contain a public key with an id of `#{kid}`"
|
60
|
+
output auth_tokens_jwk_json: auth_tokens_jwk_json.to_json
|
30
61
|
|
31
|
-
|
62
|
+
no_error_validation('Token headers missing required information.')
|
32
63
|
end
|
33
64
|
end
|
34
65
|
end
|
@@ -1,5 +1,8 @@
|
|
1
|
+
require_relative '../client_hook_request_validation'
|
2
|
+
|
1
3
|
module DaVinciCRDTestKit
|
2
4
|
class TokenPayloadTest < Inferno::Test
|
5
|
+
include ClientHookRequestValidation
|
3
6
|
include URLs
|
4
7
|
id :crd_token_payload
|
5
8
|
title 'Authorization token payload has required claims and a valid signature'
|
@@ -25,37 +28,51 @@ module DaVinciCRDTestKit
|
|
25
28
|
base_url + config.options[:hook_path]
|
26
29
|
end
|
27
30
|
|
28
|
-
input :
|
29
|
-
:
|
31
|
+
input :auth_tokens,
|
32
|
+
:auth_tokens_jwk_json,
|
30
33
|
:iss
|
31
34
|
|
32
35
|
run do
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
true,
|
41
|
-
algorithms: [jwk[:alg]],
|
42
|
-
exp_leeway: 60,
|
43
|
-
iss:,
|
44
|
-
aud: hook_url,
|
45
|
-
verify_not_before: false,
|
46
|
-
verify_iat: false,
|
47
|
-
verify_jti: true,
|
48
|
-
verify_iss: true,
|
49
|
-
verify_aud: true
|
50
|
-
)
|
51
|
-
rescue StandardError => e
|
52
|
-
assert false, "Token validation error: #{e.message}"
|
53
|
-
end
|
36
|
+
auth_tokens_list = JSON.parse(auth_tokens)
|
37
|
+
auth_tokens_jwk = JSON.parse(auth_tokens_jwk_json)
|
38
|
+
skip_if auth_tokens_list.empty?, 'No Authorization tokens produced from the previous tests.'
|
39
|
+
skip_if auth_tokens_jwk.empty?, 'No Authorization token JWK produced from the previous test.'
|
40
|
+
|
41
|
+
auth_tokens_jwk.each_with_index do |auth_token_jwk, index|
|
42
|
+
@request_number = index + 1
|
54
43
|
|
55
|
-
|
56
|
-
|
44
|
+
begin
|
45
|
+
jwk = JSON.parse(auth_token_jwk).deep_symbolize_keys
|
57
46
|
|
58
|
-
|
47
|
+
payload, =
|
48
|
+
JWT.decode(
|
49
|
+
auth_tokens_list[index],
|
50
|
+
JWT::JWK.import(jwk).public_key,
|
51
|
+
true,
|
52
|
+
algorithms: [jwk[:alg]],
|
53
|
+
exp_leeway: 60,
|
54
|
+
iss:,
|
55
|
+
aud: hook_url,
|
56
|
+
verify_not_before: false,
|
57
|
+
verify_iat: false,
|
58
|
+
verify_jti: true,
|
59
|
+
verify_iss: true,
|
60
|
+
verify_aud: true
|
61
|
+
)
|
62
|
+
rescue StandardError => e
|
63
|
+
add_message('error', "#{request_number}Token validation error: #{e.message}")
|
64
|
+
next
|
65
|
+
end
|
66
|
+
|
67
|
+
missing_claims = required_claims - payload.keys
|
68
|
+
missing_claims_string = missing_claims.map { |claim| "`#{claim}`" }.join(', ')
|
69
|
+
|
70
|
+
unless missing_claims.empty?
|
71
|
+
add_message('error', "#{request_number}JWT payload missing required claims: #{missing_claims_string}")
|
72
|
+
next
|
73
|
+
end
|
74
|
+
end
|
75
|
+
no_error_validation('Token payload is missing required claims or does not have a valid signiture.')
|
59
76
|
end
|
60
77
|
end
|
61
78
|
end
|