Module: UDAPSecurityTestKit::RegistrationRequestVerification

Included in:
UDAPClientRegistrationAuthorizationCodeVerification, UDAPClientRegistrationClientCredentialsVerification
Defined in:
lib/udap_security_test_kit/client_suite/registration_request_verification.rb

Instance Method Summary collapse

Instance Method Details

#check_authorization_code_software_statement(claims) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 163

def check_authorization_code_software_statement(claims) # rubocop:disable Metrics/CyclomaticComplexity
  if claims['redirect_uris'].blank?
    add_message('error', 'Registration software statement `redirect_uris` must be present when ' \
                         "the 'authorization_code' `grant_type` is requested.")
  elsif !claims['redirect_uris'].is_a?(Array)
    add_message('error', 'Registration software statement `redirect_uris` must be a list when ' \
                         "the 'authorization_code' `grant_type` is requested.")
  else
    claims['redirect_uris'].each_with_index do |redirect_uri, index|
      unless valid_uri?(redirect_uri, required_scheme: 'https')
        add_message('error', "Registration software statement `redirect_uris` entry #{index + 1} is invalid: " \
                             "'#{redirect_uri}' is not a valid https uri.")
      end
    end
  end

  if claims['logo_uri'].blank?
    add_message('error', 'Registration software statement `logo_uri` must be present when ' \
                         "the 'authorization_code' `grant_type` is requested.")
  else
    unless valid_uri?(claims['logo_uri'], required_scheme: 'https')
      add_message('error', 'Registration software statement `logo_uri` is invalid: ' \
                           "'#{claims['logo_uri']}' is not a valid https uri.")
    end
    unless ['gif', 'jpg', 'jpeg', 'png'].include?(claims['logo_uri'].split('.').last.downcase)
      add_message('error', 'Registration software statement `logo_uri` is invalid: it must point to a ' \
                           'PNG, JPG, or GIF file.')
    end
  end

  if claims['response_types'].blank?
    add_message('error', 'Registration software statement `response_types` must be present when ' \
                         "the 'authorization_code' `grant_type` is requested.")
  else
    unless claims['response_types'].is_a?(Array) &&
           claims['response_types'].size == 1 &&
           claims['response_types'][0] == 'code'
      add_message('error', 'Registration software statement `response_types` claim is invalid: ' \
                           "must contain exactly one entry with the value 'code'.")
    end
  end

  nil
end

#check_client_credentials_software_statement(claims) ⇒ Object



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 208

def check_client_credentials_software_statement(claims)
  unless claims['redirect_uris'].nil?
    add_message('error', 'Registration software statement `redirect_uris` must not be present when ' \
                         "the 'client_credentials' `grant_type` is requested.")
  end

  unless claims['response_types'].nil?
    add_message('error', 'Registration software statement `response_types` must not be present when ' \
                         "the 'client_credentials' `grant_type` is requested.")
  end

  if claims['grant_types'].include?('refresh_token')
    add_message('error', "Registration software statement `response_types` cannot contain 'refresh_token' when " \
                         "the 'client_credentials' `grant_type` is requested.")
  end

  nil
end

#check_jwt_signature(jwt) ⇒ Object



227
228
229
230
231
232
233
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 227

def check_jwt_signature(jwt)
  error = MockUDAPServer.udap_reg_signature_verification(jwt)

  return unless error.present?

  add_message('error', "Signature validation failed on registration request: #{error}")
end

#check_request_body(request_body) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 26

def check_request_body(request_body)
  if request_body['udap'].blank?
    add_message('error', '`udap` key with a value of `1` missing in the registration request')
  elsif request_body['udap'] != '1'
    add_message('error',
                'The registration request contained an incorrect `udap` value: expected `1`, ' \
                "got `#{request_body['udap']}`")
  end

  return unless request_body['certifications'].present?

  request_body['certifications'].each_with_index do |certification_jwt, index|
    JWT.decode(certification_jwt, nil, false)
  rescue StandardError => e
    add_message('error',
                "Certification #{index + 1} in the registration request is not a valid signed jwt: #{e}")
  end
end

#check_software_statement(oauth_flow, software_statement_jwt, request_time) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 45

def check_software_statement(oauth_flow, software_statement_jwt, request_time)
  unless software_statement_jwt.present?
    add_message('error',
                'Registration is missing a `software_statement` key')
    return
  end

  claims, _headers = begin
    JWT.decode(software_statement_jwt, nil, false)
  rescue StandardError => e
    add_message('error',
                "Registration software statement does not follow the jwt structure: #{e}")
    return
  end

  # headers checked with signature
  check_software_statement_claims(oauth_flow, claims, request_time)
  check_jwt_signature(software_statement_jwt)
end

#check_software_statement_claims(oauth_flow, claims, request_time) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 65

def check_software_statement_claims(oauth_flow, claims, request_time) # rubocop:disable Metrics/CyclomaticComplexity
  unless claims['iss'] == udap_client_uri
    add_message('error',
                'Registration software statement `iss` claim is incorrect: ' \
                "expected '#{udap_client_uri}', got '#{claims['iss']}'")
  end
  unless claims['sub'] == udap_client_uri
    add_message('error',
                'Registration software statement `sub` claim is incorrect: ' \
                "expected '#{udap_client_uri}', got '#{claims['sub']}'")
  end
  unless claims['aud'] == client_registration_url
    add_message('error',
                'Registration software statement `aud` claim is incorrect: ' \
                "expected '#{client_registration_url}', got '#{claims['aud']}'")
  end

  check_software_statement_grant_types(oauth_flow, claims)
  MockUDAPServer.check_jwt_timing(claims['iat'], claims['exp'], request_time)

  add_message('error', 'Registration software statement `jti` claim is missing.') unless claims['jti'].present?
  unless claims['client_name'].present?
    add_message('error', 'Registration software statement `client_name` claim is missing.')
  end
  check_software_statement_contacts(claims['contacts'])
  unless claims['token_endpoint_auth_method'] == 'private_key_jwt'
    add_message('error', 'Registration software statement `token_endpoint_auth_method` claim is incorrect: ' \
                         "expected `token_endpoint_auth_method`, got #{claims['token_endpoint_auth_method']}.")
  end
  add_message('error', 'Registration software statement `scope` claim is missing.') unless claims['scope'].present?

  nil
end

#check_software_statement_contacts(contacts) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 99

def check_software_statement_contacts(contacts)
  unless contacts.present?
    add_message('error', 'Registration software statement `contacts` claim is missing.')
    return
  end
  unless contacts.is_a?(Array)
    add_message('error', 'Registration software statement `contacts` claim is missing.')
    return
  end
  unless contacts.find { |contact| valid_uri?(contact, required_scheme: 'mailto') }.present?
    add_message('error', 'Registration software statement `contacts` claim has no ' \
                         'valid `mailto` uri entry.')
  end

  nil
end

#check_software_statement_grant_types(oauth_flow, claims) ⇒ Object

rubocop:disable Metrics/CyclomaticComplexity



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 116

def check_software_statement_grant_types(oauth_flow, claims) # rubocop:disable Metrics/CyclomaticComplexity
  unless claims['grant_types'].present?
    add_message('error', 'Registration software statement `grant_types` claim is missing')
    return
  end

  unless claims['grant_types'].is_a?(Array)
    add_message('error', 'Registration software statement `grant_types` claim must be a list.')
    return
  end

  has_client_credentials = claims['grant_types'].include?('client_credentials')
  has_authorization_code = claims['grant_types'].include?('authorization_code')

  unless has_client_credentials || has_authorization_code
    add_message('error', 'Registration software statement `grant_types` claim must contain one of ' \
                         "'authorization_code' or 'client_credentials'")
    return
  end

  if has_client_credentials && has_authorization_code
    add_message('error', 'Registration software statement `grant_types` claim cannot contain both ' \
                         "'authorization_code' and 'client_credentials'")
  end

  extra_grants = claims['grant_types'].reject do |grant|
    ['client_credentials', 'authorization_code', 'refresh_token'].include?(grant)
  end
  unless extra_grants.blank?
    add_message('error', 'Registration software statement `grant_types` claim cannot contain values beyond ' \
                         "'authorization_code', 'client_credentials', and 'refresh_token")
  end

  if oauth_flow == CLIENT_CREDENTIALS_TAG && !has_client_credentials
    add_message('error', 'Registration software statement `grant_types` must contain ' \
                         "''client_credentials' when testing the client credentials flow.")
  end
  if oauth_flow == AUTHORIZATION_CODE_TAG && !has_authorization_code
    add_message('error', 'Registration software statement `grant_types` must contain ' \
                         "''authorization_code' when testing the authorization code flow.")
  end
  check_client_credentials_software_statement(claims) if has_client_credentials
  check_authorization_code_software_statement(claims) if has_authorization_code

  nil
end

#load_registration_requests_for_client_uri(client_uri) ⇒ Object



7
8
9
10
11
12
13
14
15
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 7

def load_registration_requests_for_client_uri(client_uri)
  load_tagged_requests(UDAP_TAG, REGISTRATION_TAG)
  requests.select do |reg_request|
    registered_uri = MockUDAPServer.udap_client_uri_from_registration_payload(
      MockUDAPServer.parsed_request_body(reg_request)
    )
    client_uri == registered_uri
  end
end

#valid_uri?(url, required_scheme: nil) ⇒ Boolean

Returns:

  • (Boolean)


235
236
237
238
239
240
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 235

def valid_uri?(url, required_scheme: nil)
  uri = URI.parse(url)
  required_scheme.blank? || uri.scheme == required_scheme
rescue URI::InvalidURIError
  false
end

#verify_registration_request(oauth_flow, verified_request) ⇒ Object



17
18
19
20
21
22
23
24
# File 'lib/udap_security_test_kit/client_suite/registration_request_verification.rb', line 17

def verify_registration_request(oauth_flow, verified_request)
  parsed_body = MockUDAPServer.parsed_request_body(verified_request)
  assert parsed_body.present?, 'Registration request body is not valid JSON.'

  check_request_body(parsed_body)
  check_software_statement(oauth_flow, parsed_body['software_statement'], verified_request.created_at)
  output udap_registration_jwt: parsed_body['software_statement']
end