udap_security_test_kit 0.11.2 → 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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/UDAP_RunClientAgainstServer.json.erb +20 -0
  3. data/config/presets/UDAP_RunServerAgainstClient.json.erb +272 -0
  4. data/lib/udap_security_test_kit/client_credentials_token_exchange_test.rb +1 -1
  5. data/lib/udap_security_test_kit/client_suite/access_ac_group.rb +25 -0
  6. data/lib/udap_security_test_kit/client_suite/access_ac_interaction_test.rb +59 -0
  7. data/lib/udap_security_test_kit/client_suite/access_cc_group.rb +23 -0
  8. data/lib/udap_security_test_kit/client_suite/access_cc_interaction_test.rb +49 -0
  9. data/lib/udap_security_test_kit/client_suite/authorization_request_verification_test.rb +83 -0
  10. data/lib/udap_security_test_kit/client_suite/client_descriptions.rb +70 -0
  11. data/lib/udap_security_test_kit/client_suite/client_options.rb +20 -0
  12. data/lib/udap_security_test_kit/client_suite/oidc_jwks.json +32 -0
  13. data/lib/udap_security_test_kit/client_suite/oidc_jwks.rb +27 -0
  14. data/lib/udap_security_test_kit/client_suite/registration_ac_group.rb +18 -0
  15. data/lib/udap_security_test_kit/client_suite/registration_ac_verification_test.rb +38 -0
  16. data/lib/udap_security_test_kit/client_suite/registration_cc_group.rb +18 -0
  17. data/lib/udap_security_test_kit/client_suite/registration_cc_verification_test.rb +38 -0
  18. data/lib/udap_security_test_kit/client_suite/registration_interaction_test.rb +57 -0
  19. data/lib/udap_security_test_kit/client_suite/registration_request_verification.rb +242 -0
  20. data/lib/udap_security_test_kit/client_suite/token_request_ac_verification_test.rb +49 -0
  21. data/lib/udap_security_test_kit/client_suite/token_request_cc_verification_test.rb +49 -0
  22. data/lib/udap_security_test_kit/client_suite/token_request_verification.rb +223 -0
  23. data/lib/udap_security_test_kit/client_suite/token_use_verification_test.rb +40 -0
  24. data/lib/udap_security_test_kit/client_suite.rb +107 -0
  25. data/lib/udap_security_test_kit/docs/demo/FHIR Request.postman_collection.json +81 -0
  26. data/lib/udap_security_test_kit/docs/udap_client_suite_description.md +163 -0
  27. data/lib/udap_security_test_kit/endpoints/echoing_fhir_responder_endpoint.rb +96 -0
  28. data/lib/udap_security_test_kit/endpoints/mock_udap_server/authorization_endpoint.rb +28 -0
  29. data/lib/udap_security_test_kit/endpoints/mock_udap_server/registration_endpoint.rb +31 -0
  30. data/lib/udap_security_test_kit/endpoints/mock_udap_server/token_endpoint.rb +56 -0
  31. data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_authorization_response_creation.rb +63 -0
  32. data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_registration_response_creation.rb +28 -0
  33. data/lib/udap_security_test_kit/endpoints/mock_udap_server/udap_token_response_creation.rb +218 -0
  34. data/lib/udap_security_test_kit/endpoints/mock_udap_server.rb +382 -0
  35. data/lib/udap_security_test_kit/metadata.rb +4 -4
  36. data/lib/udap_security_test_kit/tags.rb +12 -0
  37. data/lib/udap_security_test_kit/urls.rb +52 -0
  38. data/lib/udap_security_test_kit/version.rb +2 -2
  39. data/lib/udap_security_test_kit.rb +8 -2
  40. metadata +36 -2
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../tags'
4
+
5
+ module UDAPSecurityTestKit
6
+ module UDAPClientOptions
7
+ module_function
8
+
9
+ UDAP_AUTHORIZATION_CODE = "#{UDAP_TAG},#{AUTHORIZATION_CODE_TAG}".freeze
10
+ UDAP_CLIENT_CREDENTIALS = "#{UDAP_TAG},#{CLIENT_CREDENTIALS_TAG}".freeze
11
+
12
+ def oauth_flow(suite_options)
13
+ if suite_options[:client_type].include?(AUTHORIZATION_CODE_TAG)
14
+ AUTHORIZATION_CODE_TAG
15
+ elsif suite_options[:client_type].include?(CLIENT_CREDENTIALS_TAG)
16
+ CLIENT_CREDENTIALS_TAG
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ {
2
+ "keys": [
3
+ {
4
+ "kty": "RSA",
5
+ "use": "sig",
6
+ "key_ops": [
7
+ "verify"
8
+ ],
9
+ "alg": "RS256",
10
+ "kid": "618e76fc7eb7c3401c7fd47b9af5e94d",
11
+ "n": "uoYzdYZQDp7yHTfY4XL1PFCFPfijG1rP2dNgMmsqKbm9qpMjiUuXCpdoOXWE838pcu3Ahi6L3dGgGS3Aeys3YrnF6h9uRTD1DHf-UWlOq4iPCzk26a-bBuyVZDVqS7Y5HsyF7i_UvYV1W0EaHSpAF911Vo_W_FVyiSkAZP1yN0ECbbjnSqxhaa44SGw90oh6XnyNBLu7a11aLCURkaRJOKXHj_vf8UyaD-IA9YkvM80St7QF6u6LRVYmCShbpfWRmejh_Swf4zdqcPl-duUsZE-OBysoTq92FaXr605igM30Rdkx8hs-HTwZtjn5hB3XNTiG2Wka1cUXKNyH59u8Yw",
12
+ "e": "AQAB"
13
+ },
14
+ {
15
+ "kty": "RSA",
16
+ "use": "sig",
17
+ "key_ops": [
18
+ "sign"
19
+ ],
20
+ "alg": "RS256",
21
+ "kid": "618e76fc7eb7c3401c7fd47b9af5e94d",
22
+ "d": "k2SxDVHRuXwIvuX-0EjTWZIXeF0eJuOgE_VgsvbUHpzUMBKNplTBSnFSvvUK1o_J5TPTSzVE-UhJRxxMWghQgAdlShkEPlDtk6jOou6gaBRFVQ0lQ4ys6M_TTZiYIrQgdyIPQ6Uwa4MmtbHAPQPCGhm6O2j27fdnxtNLqIJO2zF9OzF4VP5_v63bbMfwbKECvB_bbcGLmJrq2ClI1iTOw-GawQ3I8sfwf52D3mb-qDJyQwtomNxf7EhvmGBC9u_8JVY48qsCCYWr2KAauID02WtCVepxM4ltKOLJb6BL5QpKmJ8svYLWjSZvNJSdk-EoB9grqechyDOIa2DuU9g-QQ",
23
+ "n": "uoYzdYZQDp7yHTfY4XL1PFCFPfijG1rP2dNgMmsqKbm9qpMjiUuXCpdoOXWE838pcu3Ahi6L3dGgGS3Aeys3YrnF6h9uRTD1DHf-UWlOq4iPCzk26a-bBuyVZDVqS7Y5HsyF7i_UvYV1W0EaHSpAF911Vo_W_FVyiSkAZP1yN0ECbbjnSqxhaa44SGw90oh6XnyNBLu7a11aLCURkaRJOKXHj_vf8UyaD-IA9YkvM80St7QF6u6LRVYmCShbpfWRmejh_Swf4zdqcPl-duUsZE-OBysoTq92FaXr605igM30Rdkx8hs-HTwZtjn5hB3XNTiG2Wka1cUXKNyH59u8Yw",
24
+ "e": "AQAB",
25
+ "p": "x3syoDXXDJne_-aSeHMm3muQ1KfIGewMhklvGNZ6LVTIJWUE5UYiGshABmgn86GfS-uZc_oUo3WcXiCLUlZSBqZFTIyXgy9y979VWA0whPCYcVSLzSGOiUtv3Ys4VGYCmGSuJnyLDp0CNN63Gf_iGpjCRbaPCPdRnyBgfSEvETs",
26
+ "q": "718z8hC_AQXrhQxvTsCEv7FXSX0Ev10Tjdq94DcsD91g34KTbrF9K9wtPfBUQaUx5Z2rMOTdLxbtHKvP-YQ4YMoQioA9qsclcnwdvKoRcRWim9oBnLa3Iuqttdwc9U2FWEQ4wqv16rsQw7URU4qlYkiVjrHvRJb8Nx_AJsA8Tvk",
27
+ "dp": "waDGDVj1exfIq-ClgCFWM0N5-9E4nGDR729MVXGqemH3PMUHsX0YEaMa8p0bWpMhStJPy5GNgvTgaUVxtuRvDmFKlvlJAF-IWw7vyl5TIFdhwW_tm5nc_0uoNAW1EcdK8Z2YpWbym6avw54DYUtNr79jo8OGp49ZPPpybkNNqo0",
28
+ "dq": "ge3mL02Br9d7yKNAQ7niFH75Ry1yB0FJXOVPzUWFSDM84vVoe1wh-k2vzQAHa_50ABO-GXMQz_-cwsRLxj9LrtXfdp43Wtxv6h2OspqJjx1UP05tM5hF_dDua1lH6qqiZ4_YU2qtuDTD28cL2ZHXRWrqqyLQIiXmTzGPxjjwQ1k",
29
+ "qi": "n3fMznRlCCwuoCq0kv4tL0cravVhcg33rA-CQXlIxgqj3h1yiBJ4p76pHceV6tvYsPyX3YsUKNOBP9HcxuHK3I3kFqMN5Y4C4r8dTE4fbJK9QT_guo_k_GKijNp0NDNCCeKxU78SRYvA527VY-3Z6TUC_K6O-5Pd6aPJN1QDtoA"
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,27 @@
1
+ require 'jwt'
2
+
3
+ module UDAPSecurityTestKit
4
+ class OIDCJWKS
5
+ class << self
6
+ def jwks_json
7
+ @jwks_json ||=
8
+ JSON.pretty_generate(
9
+ { keys: jwks.export[:keys].select { |key| key[:key_ops]&.include?('verify') } }
10
+ )
11
+ end
12
+
13
+ def default_jwks_path
14
+ @default_jwks_path ||= File.join(__dir__, 'oidc_jwks.json')
15
+ end
16
+
17
+ def jwks_path
18
+ @jwks_path ||=
19
+ ENV.fetch('SIMULATED_OIDC_JWKS_PATH', default_jwks_path)
20
+ end
21
+
22
+ def jwks
23
+ @jwks ||= JWT::JWK::Set.new(JSON.parse(File.read(jwks_path)))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'registration_interaction_test'
2
+ require_relative 'registration_ac_verification_test'
3
+
4
+ module UDAPSecurityTestKit
5
+ class UDAPClientRegistrationAuthorizationCode < Inferno::TestGroup
6
+ id :udap_client_registration_ac
7
+ title 'Client Registration'
8
+ description %(
9
+ During these tests, the client system will dynamically register with Inferno's
10
+ simulated UDAP Server to use the authorization_code flow. At any time, the client
11
+ may perform UDAP discovery on the simulated Inferno UDAP server.
12
+ )
13
+ run_as_group
14
+
15
+ test from: :udap_client_registration_interaction
16
+ test from: :udap_client_registration_ac_verification
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ require_relative '../tags'
2
+ require_relative '../urls'
3
+ require_relative '../endpoints/mock_udap_server'
4
+ require_relative 'registration_request_verification'
5
+
6
+ module UDAPSecurityTestKit
7
+ class UDAPClientRegistrationAuthorizationCodeVerification < Inferno::Test
8
+ include URLs
9
+ include RegistrationRequestVerification
10
+
11
+ id :udap_client_registration_ac_verification
12
+ title 'Verify UDAP Authorization Code Registration'
13
+ description %(
14
+ During this test, Inferno will verify that the client's UDAP
15
+ registration request is conformant.
16
+ )
17
+ input :udap_client_uri
18
+ output :udap_registration_jwt
19
+
20
+ def client_suite_id
21
+ return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
22
+
23
+ UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
24
+ end
25
+
26
+ run do
27
+ client_registration_requests = load_registration_requests_for_client_uri(udap_client_uri)
28
+ skip_if client_registration_requests.empty?,
29
+ "No UDAP Registration Requests made for client uri '#{udap_client_uri}'."
30
+
31
+ verify_registration_request(AUTHORIZATION_CODE_TAG, client_registration_requests.last) # most recent if several
32
+
33
+ assert messages.none? { |msg|
34
+ msg[:type] == 'error'
35
+ }, 'Invalid registration request. See messages for details.'
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'registration_interaction_test'
2
+ require_relative 'registration_cc_verification_test'
3
+
4
+ module UDAPSecurityTestKit
5
+ class UDAPClientRegistrationClientCredentials < Inferno::TestGroup
6
+ id :udap_client_registration_cc
7
+ title 'Client Registration'
8
+ description %(
9
+ During these tests, the client system will dynamically register with Inferno's
10
+ simulated UDAP Server to use the client_credentials flow. At any time, the client
11
+ may perform UDAP discovery on the simulated Inferno UDAP server.
12
+ )
13
+ run_as_group
14
+
15
+ test from: :udap_client_registration_interaction
16
+ test from: :udap_client_registration_cc_verification
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ require_relative '../tags'
2
+ require_relative '../urls'
3
+ require_relative '../endpoints/mock_udap_server'
4
+ require_relative 'registration_request_verification'
5
+
6
+ module UDAPSecurityTestKit
7
+ class UDAPClientRegistrationClientCredentialsVerification < Inferno::Test
8
+ include URLs
9
+ include RegistrationRequestVerification
10
+
11
+ id :udap_client_registration_cc_verification
12
+ title 'Verify UDAP Client Credentials Registration'
13
+ description %(
14
+ During this test, Inferno will verify that the client's UDAP
15
+ registration request is conformant.
16
+ )
17
+ input :udap_client_uri
18
+ output :udap_registration_jwt
19
+
20
+ def client_suite_id
21
+ return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
22
+
23
+ UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
24
+ end
25
+
26
+ run do
27
+ client_registration_requests = load_registration_requests_for_client_uri(udap_client_uri)
28
+ skip_if client_registration_requests.empty?,
29
+ "No UDAP Registration Requests made for client uri '#{udap_client_uri}'."
30
+
31
+ verify_registration_request(CLIENT_CREDENTIALS_TAG, client_registration_requests.last) # most recent if several
32
+
33
+ assert messages.none? { |msg|
34
+ msg[:type] == 'error'
35
+ }, 'Invalid registration request. See messages for details.'
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,57 @@
1
+ require_relative '../urls'
2
+ require_relative '../endpoints/mock_udap_server'
3
+
4
+ module UDAPSecurityTestKit
5
+ class UDAPClientRegistrationInteraction < Inferno::Test
6
+ include URLs
7
+
8
+ id :udap_client_registration_interaction
9
+ title 'Perform UDAP Registration'
10
+ description %(
11
+ During this test, Inferno will wait for the client to register
12
+ themselves as a UDAP client with Inferno's simulated UDAP server
13
+ using UDAP dynamic registration.
14
+ )
15
+ input :udap_client_uri,
16
+ title: 'UDAP Client URI',
17
+ type: 'text',
18
+ description: %(
19
+ The UDAP Client URI that will be used to register with Inferno's simulated UDAP server.
20
+ )
21
+
22
+ output :client_id
23
+
24
+ def client_suite_id
25
+ return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
26
+
27
+ UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
28
+ end
29
+
30
+ run do
31
+ generated_client_id = MockUDAPServer.client_uri_to_client_id(udap_client_uri)
32
+ output client_id: generated_client_id
33
+
34
+ wait(
35
+ identifier: generated_client_id,
36
+ message: %(
37
+ **UDAP Registration**
38
+
39
+ Make a UDAP dyanmic registration request to the UDAP-protected FHIR Server at
40
+
41
+ `#{client_fhir_base_url}`
42
+
43
+ For Client URI
44
+
45
+ `#{udap_client_uri}`
46
+
47
+ Metadata on Inferno's simulated UDAP server can be found at
48
+
49
+ `#{client_udap_discovery_url}`
50
+
51
+ [Click here](#{client_resume_pass_url}?token=#{generated_client_id}) once you have
52
+ succesfully completed the registration.
53
+ )
54
+ )
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,242 @@
1
+ require_relative '../tags'
2
+ require_relative '../urls'
3
+ require_relative '../endpoints/mock_udap_server'
4
+
5
+ module UDAPSecurityTestKit
6
+ module RegistrationRequestVerification
7
+ def load_registration_requests_for_client_uri(client_uri)
8
+ load_tagged_requests(UDAP_TAG, REGISTRATION_TAG)
9
+ requests.select do |reg_request|
10
+ registered_uri = MockUDAPServer.udap_client_uri_from_registration_payload(
11
+ MockUDAPServer.parsed_request_body(reg_request)
12
+ )
13
+ client_uri == registered_uri
14
+ end
15
+ end
16
+
17
+ def verify_registration_request(oauth_flow, verified_request)
18
+ parsed_body = MockUDAPServer.parsed_request_body(verified_request)
19
+ assert parsed_body.present?, 'Registration request body is not valid JSON.'
20
+
21
+ check_request_body(parsed_body)
22
+ check_software_statement(oauth_flow, parsed_body['software_statement'], verified_request.created_at)
23
+ output udap_registration_jwt: parsed_body['software_statement']
24
+ end
25
+
26
+ def check_request_body(request_body)
27
+ if request_body['udap'].blank?
28
+ add_message('error', '`udap` key with a value of `1` missing in the registration request')
29
+ elsif request_body['udap'] != '1'
30
+ add_message('error',
31
+ 'The registration request contained an incorrect `udap` value: expected `1`, ' \
32
+ "got `#{request_body['udap']}`")
33
+ end
34
+
35
+ return unless request_body['certifications'].present?
36
+
37
+ request_body['certifications'].each_with_index do |certification_jwt, index|
38
+ JWT.decode(certification_jwt, nil, false)
39
+ rescue StandardError => e
40
+ add_message('error',
41
+ "Certification #{index + 1} in the registration request is not a valid signed jwt: #{e}")
42
+ end
43
+ end
44
+
45
+ def check_software_statement(oauth_flow, software_statement_jwt, request_time)
46
+ unless software_statement_jwt.present?
47
+ add_message('error',
48
+ 'Registration is missing a `software_statement` key')
49
+ return
50
+ end
51
+
52
+ claims, _headers = begin
53
+ JWT.decode(software_statement_jwt, nil, false)
54
+ rescue StandardError => e
55
+ add_message('error',
56
+ "Registration software statement does not follow the jwt structure: #{e}")
57
+ return
58
+ end
59
+
60
+ # headers checked with signature
61
+ check_software_statement_claims(oauth_flow, claims, request_time)
62
+ check_jwt_signature(software_statement_jwt)
63
+ end
64
+
65
+ def check_software_statement_claims(oauth_flow, claims, request_time) # rubocop:disable Metrics/CyclomaticComplexity
66
+ unless claims['iss'] == udap_client_uri
67
+ add_message('error',
68
+ 'Registration software statement `iss` claim is incorrect: ' \
69
+ "expected '#{udap_client_uri}', got '#{claims['iss']}'")
70
+ end
71
+ unless claims['sub'] == udap_client_uri
72
+ add_message('error',
73
+ 'Registration software statement `sub` claim is incorrect: ' \
74
+ "expected '#{udap_client_uri}', got '#{claims['sub']}'")
75
+ end
76
+ unless claims['aud'] == client_registration_url
77
+ add_message('error',
78
+ 'Registration software statement `aud` claim is incorrect: ' \
79
+ "expected '#{client_registration_url}', got '#{claims['aud']}'")
80
+ end
81
+
82
+ check_software_statement_grant_types(oauth_flow, claims)
83
+ MockUDAPServer.check_jwt_timing(claims['iat'], claims['exp'], request_time)
84
+
85
+ add_message('error', 'Registration software statement `jti` claim is missing.') unless claims['jti'].present?
86
+ unless claims['client_name'].present?
87
+ add_message('error', 'Registration software statement `client_name` claim is missing.')
88
+ end
89
+ check_software_statement_contacts(claims['contacts'])
90
+ unless claims['token_endpoint_auth_method'] == 'private_key_jwt'
91
+ add_message('error', 'Registration software statement `token_endpoint_auth_method` claim is incorrect: ' \
92
+ "expected `token_endpoint_auth_method`, got #{claims['token_endpoint_auth_method']}.")
93
+ end
94
+ add_message('error', 'Registration software statement `scope` claim is missing.') unless claims['scope'].present?
95
+
96
+ nil
97
+ end
98
+
99
+ def check_software_statement_contacts(contacts)
100
+ unless contacts.present?
101
+ add_message('error', 'Registration software statement `contacts` claim is missing.')
102
+ return
103
+ end
104
+ unless contacts.is_a?(Array)
105
+ add_message('error', 'Registration software statement `contacts` claim is missing.')
106
+ return
107
+ end
108
+ unless contacts.find { |contact| valid_uri?(contact, required_scheme: 'mailto') }.present?
109
+ add_message('error', 'Registration software statement `contacts` claim has no ' \
110
+ 'valid `mailto` uri entry.')
111
+ end
112
+
113
+ nil
114
+ end
115
+
116
+ def check_software_statement_grant_types(oauth_flow, claims) # rubocop:disable Metrics/CyclomaticComplexity
117
+ unless claims['grant_types'].present?
118
+ add_message('error', 'Registration software statement `grant_types` claim is missing')
119
+ return
120
+ end
121
+
122
+ unless claims['grant_types'].is_a?(Array)
123
+ add_message('error', 'Registration software statement `grant_types` claim must be a list.')
124
+ return
125
+ end
126
+
127
+ has_client_credentials = claims['grant_types'].include?('client_credentials')
128
+ has_authorization_code = claims['grant_types'].include?('authorization_code')
129
+
130
+ unless has_client_credentials || has_authorization_code
131
+ add_message('error', 'Registration software statement `grant_types` claim must contain one of ' \
132
+ "'authorization_code' or 'client_credentials'")
133
+ return
134
+ end
135
+
136
+ if has_client_credentials && has_authorization_code
137
+ add_message('error', 'Registration software statement `grant_types` claim cannot contain both ' \
138
+ "'authorization_code' and 'client_credentials'")
139
+ end
140
+
141
+ extra_grants = claims['grant_types'].reject do |grant|
142
+ ['client_credentials', 'authorization_code', 'refresh_token'].include?(grant)
143
+ end
144
+ unless extra_grants.blank?
145
+ add_message('error', 'Registration software statement `grant_types` claim cannot contain values beyond ' \
146
+ "'authorization_code', 'client_credentials', and 'refresh_token")
147
+ end
148
+
149
+ if oauth_flow == CLIENT_CREDENTIALS_TAG && !has_client_credentials
150
+ add_message('error', 'Registration software statement `grant_types` must contain ' \
151
+ "''client_credentials' when testing the client credentials flow.")
152
+ end
153
+ if oauth_flow == AUTHORIZATION_CODE_TAG && !has_authorization_code
154
+ add_message('error', 'Registration software statement `grant_types` must contain ' \
155
+ "''authorization_code' when testing the authorization code flow.")
156
+ end
157
+ check_client_credentials_software_statement(claims) if has_client_credentials
158
+ check_authorization_code_software_statement(claims) if has_authorization_code
159
+
160
+ nil
161
+ end
162
+
163
+ def check_authorization_code_software_statement(claims) # rubocop:disable Metrics/CyclomaticComplexity
164
+ if claims['redirect_uris'].blank?
165
+ add_message('error', 'Registration software statement `redirect_uris` must be present when ' \
166
+ "the 'authorization_code' `grant_type` is requested.")
167
+ elsif !claims['redirect_uris'].is_a?(Array)
168
+ add_message('error', 'Registration software statement `redirect_uris` must be a list when ' \
169
+ "the 'authorization_code' `grant_type` is requested.")
170
+ else
171
+ claims['redirect_uris'].each_with_index do |redirect_uri, index|
172
+ unless valid_uri?(redirect_uri, required_scheme: 'https')
173
+ add_message('error', "Registration software statement `redirect_uris` entry #{index + 1} is invalid: " \
174
+ "'#{redirect_uri}' is not a valid https uri.")
175
+ end
176
+ end
177
+ end
178
+
179
+ if claims['logo_uri'].blank?
180
+ add_message('error', 'Registration software statement `logo_uri` must be present when ' \
181
+ "the 'authorization_code' `grant_type` is requested.")
182
+ else
183
+ unless valid_uri?(claims['logo_uri'], required_scheme: 'https')
184
+ add_message('error', 'Registration software statement `logo_uri` is invalid: ' \
185
+ "'#{claims['logo_uri']}' is not a valid https uri.")
186
+ end
187
+ unless ['gif', 'jpg', 'jpeg', 'png'].include?(claims['logo_uri'].split('.').last.downcase)
188
+ add_message('error', 'Registration software statement `logo_uri` is invalid: it must point to a ' \
189
+ 'PNG, JPG, or GIF file.')
190
+ end
191
+ end
192
+
193
+ if claims['response_types'].blank?
194
+ add_message('error', 'Registration software statement `response_types` must be present when ' \
195
+ "the 'authorization_code' `grant_type` is requested.")
196
+ else
197
+ unless claims['response_types'].is_a?(Array) &&
198
+ claims['response_types'].size == 1 &&
199
+ claims['response_types'][0] == 'code'
200
+ add_message('error', 'Registration software statement `response_types` claim is invalid: ' \
201
+ "must contain exactly one entry with the value 'code'.")
202
+ end
203
+ end
204
+
205
+ nil
206
+ end
207
+
208
+ def check_client_credentials_software_statement(claims)
209
+ unless claims['redirect_uris'].nil?
210
+ add_message('error', 'Registration software statement `redirect_uris` must not be present when ' \
211
+ "the 'client_credentials' `grant_type` is requested.")
212
+ end
213
+
214
+ unless claims['response_types'].nil?
215
+ add_message('error', 'Registration software statement `response_types` must not be present when ' \
216
+ "the 'client_credentials' `grant_type` is requested.")
217
+ end
218
+
219
+ if claims['grant_types'].include?('refresh_token')
220
+ add_message('error', "Registration software statement `response_types` cannot contain 'refresh_token' when " \
221
+ "the 'client_credentials' `grant_type` is requested.")
222
+ end
223
+
224
+ nil
225
+ end
226
+
227
+ def check_jwt_signature(jwt)
228
+ error = MockUDAPServer.udap_reg_signature_verification(jwt)
229
+
230
+ return unless error.present?
231
+
232
+ add_message('error', "Signature validation failed on registration request: #{error}")
233
+ end
234
+
235
+ def valid_uri?(url, required_scheme: nil)
236
+ uri = URI.parse(url)
237
+ required_scheme.blank? || uri.scheme == required_scheme
238
+ rescue URI::InvalidURIError
239
+ false
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,49 @@
1
+ require_relative '../tags'
2
+ require_relative '../urls'
3
+ require_relative '../endpoints/mock_udap_server'
4
+ require_relative 'client_descriptions'
5
+ require_relative 'client_options'
6
+ require_relative 'token_request_verification'
7
+
8
+ module UDAPSecurityTestKit
9
+ class UDAPClientTokenRequestAuthorizationCodeVerification < Inferno::Test
10
+ include URLs
11
+ include TokenRequestVerification
12
+
13
+ id :udap_client_token_request_ac_verification
14
+ title 'Verify UDAP Authorization Code Token Requests'
15
+ description %(
16
+ Check that UDAP token requests are conformant.
17
+ )
18
+
19
+ input :client_id,
20
+ title: 'Client Id',
21
+ type: 'text',
22
+ locked: true,
23
+ description: INPUT_CLIENT_ID_DESCRIPTION_LOCKED
24
+ input :udap_registration_jwt,
25
+ title: 'Registered UDAP Software Statement',
26
+ type: 'textarea',
27
+ locked: 'true',
28
+ description: INPUT_UDAP_REGISTRATION_JWT_DESCRIPTION_LOCKED
29
+ output :udap_tokens
30
+
31
+ def client_suite_id
32
+ return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
33
+
34
+ UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
35
+ end
36
+
37
+ run do
38
+ load_tagged_requests(TOKEN_TAG, UDAP_TAG, AUTHORIZATION_CODE_TAG)
39
+ skip_if requests.blank?, 'No UDAP token requests made.'
40
+ load_tagged_requests(TOKEN_TAG, UDAP_TAG, REFRESH_TOKEN_TAG) # verify refresh_requests as well
41
+
42
+ verify_token_requests(AUTHORIZATION_CODE_TAG)
43
+
44
+ assert messages.none? { |msg|
45
+ msg[:type] == 'error'
46
+ }, 'Invalid token requests received. See messages for details.'
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ require_relative '../tags'
2
+ require_relative '../urls'
3
+ require_relative '../endpoints/mock_udap_server'
4
+ require_relative 'client_descriptions'
5
+ require_relative 'client_options'
6
+ require_relative 'token_request_verification'
7
+
8
+ module UDAPSecurityTestKit
9
+ class UDAPClientTokenRequestClientCredentialsVerification < Inferno::Test
10
+ include URLs
11
+ include TokenRequestVerification
12
+
13
+ id :udap_client_token_request_cc_verification
14
+ title 'Verify UDAP Client Credentials Token Requests'
15
+ description %(
16
+ Check that UDAP token requests are conformant.
17
+ )
18
+
19
+ input :client_id,
20
+ title: 'Client Id',
21
+ type: 'text',
22
+ locked: true,
23
+ description: INPUT_CLIENT_ID_DESCRIPTION_LOCKED
24
+ input :udap_registration_jwt,
25
+ title: 'Registered UDAP Software Statement',
26
+ type: 'textarea',
27
+ locked: 'true',
28
+ description: INPUT_UDAP_REGISTRATION_JWT_DESCRIPTION_LOCKED
29
+ output :udap_tokens
30
+
31
+ def client_suite_id
32
+ return config.options[:endpoint_suite_id] if config.options[:endpoint_suite_id].present?
33
+
34
+ UDAPSecurityTestKit::UDAPSecurityClientTestSuite.id
35
+ end
36
+
37
+ run do
38
+ load_tagged_requests(TOKEN_TAG, UDAP_TAG, CLIENT_CREDENTIALS_TAG)
39
+ skip_if requests.blank?, 'No UDAP token requests made.'
40
+ load_tagged_requests(TOKEN_TAG, UDAP_TAG, REFRESH_TOKEN_TAG) # verify refresh_requests as well (shouldn't be any)
41
+
42
+ verify_token_requests(CLIENT_CREDENTIALS_TAG)
43
+
44
+ assert messages.none? { |msg|
45
+ msg[:type] == 'error'
46
+ }, 'Invalid token requests received. See messages for details.'
47
+ end
48
+ end
49
+ end