davinci_crd_test_kit 0.9.0 → 0.9.1.rc
Sign up to get free protection for your applications and to get access to all the features.
- 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
|