ruby-saml 1.7.2 → 1.12.2

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 (154) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +37 -15
  3. data/README.md +127 -25
  4. data/changelog.md +61 -0
  5. data/lib/onelogin/ruby-saml/attribute_service.rb +1 -1
  6. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  7. data/lib/onelogin/ruby-saml/authrequest.rb +29 -6
  8. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +239 -169
  9. data/lib/onelogin/ruby-saml/logging.rb +4 -1
  10. data/lib/onelogin/ruby-saml/logoutrequest.rb +27 -7
  11. data/lib/onelogin/ruby-saml/logoutresponse.rb +32 -16
  12. data/lib/onelogin/ruby-saml/metadata.rb +11 -3
  13. data/lib/onelogin/ruby-saml/response.rb +91 -30
  14. data/lib/onelogin/ruby-saml/saml_message.rb +15 -5
  15. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  16. data/lib/onelogin/ruby-saml/settings.rb +82 -9
  17. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +26 -7
  18. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +46 -18
  19. data/lib/onelogin/ruby-saml/utils.rb +87 -10
  20. data/lib/onelogin/ruby-saml/version.rb +1 -1
  21. data/lib/xml_security.rb +39 -12
  22. data/ruby-saml.gemspec +16 -8
  23. metadata +40 -274
  24. data/test/certificates/certificate1 +0 -12
  25. data/test/certificates/certificate_without_head_foot +0 -1
  26. data/test/certificates/formatted_certificate +0 -14
  27. data/test/certificates/formatted_chained_certificate +0 -42
  28. data/test/certificates/formatted_private_key +0 -12
  29. data/test/certificates/formatted_rsa_private_key +0 -12
  30. data/test/certificates/invalid_certificate1 +0 -1
  31. data/test/certificates/invalid_certificate2 +0 -1
  32. data/test/certificates/invalid_certificate3 +0 -12
  33. data/test/certificates/invalid_chained_certificate1 +0 -1
  34. data/test/certificates/invalid_private_key1 +0 -1
  35. data/test/certificates/invalid_private_key2 +0 -1
  36. data/test/certificates/invalid_private_key3 +0 -10
  37. data/test/certificates/invalid_rsa_private_key1 +0 -1
  38. data/test/certificates/invalid_rsa_private_key2 +0 -1
  39. data/test/certificates/invalid_rsa_private_key3 +0 -10
  40. data/test/certificates/ruby-saml-2.crt +0 -15
  41. data/test/certificates/ruby-saml.crt +0 -14
  42. data/test/certificates/ruby-saml.key +0 -15
  43. data/test/idp_metadata_parser_test.rb +0 -568
  44. data/test/logging_test.rb +0 -62
  45. data/test/logout_requests/invalid_slo_request.xml +0 -6
  46. data/test/logout_requests/slo_request.xml +0 -4
  47. data/test/logout_requests/slo_request.xml.base64 +0 -1
  48. data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
  49. data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
  50. data/test/logout_requests/slo_request_with_session_index.xml +0 -5
  51. data/test/logout_responses/logoutresponse_fixtures.rb +0 -67
  52. data/test/logoutrequest_test.rb +0 -212
  53. data/test/logoutresponse_test.rb +0 -402
  54. data/test/metadata/idp_descriptor.xml +0 -26
  55. data/test/metadata/idp_descriptor_2.xml +0 -56
  56. data/test/metadata/idp_descriptor_3.xml +0 -14
  57. data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
  58. data/test/metadata/idp_metadata_multi_certs.xml +0 -75
  59. data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
  60. data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
  61. data/test/metadata/idp_multiple_descriptors.xml +0 -53
  62. data/test/metadata/no_idp_descriptor.xml +0 -21
  63. data/test/metadata_test.rb +0 -331
  64. data/test/request_test.rb +0 -296
  65. data/test/response_test.rb +0 -1535
  66. data/test/responses/adfs_response_sha1.xml +0 -46
  67. data/test/responses/adfs_response_sha256.xml +0 -46
  68. data/test/responses/adfs_response_sha384.xml +0 -46
  69. data/test/responses/adfs_response_sha512.xml +0 -46
  70. data/test/responses/adfs_response_xmlns.xml +0 -45
  71. data/test/responses/attackxee.xml +0 -13
  72. data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
  73. data/test/responses/invalids/empty_destination.xml.base64 +0 -1
  74. data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
  75. data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
  76. data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
  77. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  78. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  79. data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
  80. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
  81. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
  82. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
  83. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
  84. data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
  85. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  86. data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
  87. data/test/responses/invalids/no_conditions.xml.base64 +0 -1
  88. data/test/responses/invalids/no_id.xml.base64 +0 -1
  89. data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
  90. data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
  91. data/test/responses/invalids/no_nameid.xml.base64 +0 -1
  92. data/test/responses/invalids/no_saml2.xml.base64 +0 -1
  93. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  94. data/test/responses/invalids/no_status.xml.base64 +0 -1
  95. data/test/responses/invalids/no_status_code.xml.base64 +0 -1
  96. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
  97. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
  98. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
  99. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  100. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  101. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  102. data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
  103. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
  104. data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
  105. data/test/responses/no_signature_ns.xml +0 -48
  106. data/test/responses/open_saml_response.xml +0 -56
  107. data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
  108. data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
  109. data/test/responses/response_double_status_code.xml.base64 +0 -1
  110. data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
  111. data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
  112. data/test/responses/response_eval.xml +0 -7
  113. data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
  114. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  115. data/test/responses/response_unsigned_xml_base64 +0 -1
  116. data/test/responses/response_with_ampersands.xml +0 -139
  117. data/test/responses/response_with_ampersands.xml.base64 +0 -93
  118. data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
  119. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  120. data/test/responses/response_with_multiple_attribute_values.xml +0 -67
  121. data/test/responses/response_with_retrieval_method.xml +0 -26
  122. data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
  123. data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
  124. data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
  125. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  126. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  127. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  128. data/test/responses/response_without_attributes.xml.base64 +0 -79
  129. data/test/responses/response_without_reference_uri.xml.base64 +0 -1
  130. data/test/responses/response_wrapped.xml.base64 +0 -150
  131. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
  132. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  133. data/test/responses/signed_nameid_in_atts.xml +0 -47
  134. data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
  135. data/test/responses/simple_saml_php.xml +0 -71
  136. data/test/responses/starfield_response.xml.base64 +0 -1
  137. data/test/responses/test_sign.xml +0 -43
  138. data/test/responses/unsigned_encrypted_adfs.xml +0 -23
  139. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
  140. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
  141. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
  142. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
  143. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
  144. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
  145. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  146. data/test/responses/valid_response.xml.base64 +0 -1
  147. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  148. data/test/saml_message_test.rb +0 -56
  149. data/test/settings_test.rb +0 -301
  150. data/test/slo_logoutrequest_test.rb +0 -448
  151. data/test/slo_logoutresponse_test.rb +0 -185
  152. data/test/test_helper.rb +0 -323
  153. data/test/utils_test.rb +0 -254
  154. data/test/xml_security_test.rb +0 -421
@@ -24,7 +24,7 @@ module OneLogin
24
24
  # Constructs the Logout Response. A Logout Response Object that is an extension of the SamlMessage class.
25
25
  # @param response [String] A UUEncoded logout response from the IdP.
26
26
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
27
- # @param options [Hash] Extra parameters.
27
+ # @param options [Hash] Extra parameters.
28
28
  # :matches_request_id It will validate that the logout response matches the ID of the request.
29
29
  # :get_params GET Parameters, including the SAMLResponse
30
30
  # :relax_signature_validation to accept signatures if no idp certificate registered on settings
@@ -47,15 +47,16 @@ module OneLogin
47
47
  @document = XMLSecurity::SignedDocument.new(@response)
48
48
  end
49
49
 
50
+ def response_id
51
+ id(document)
52
+ end
53
+
50
54
  # Checks if the Status has the "Success" code
51
55
  # @return [Boolean] True if the StatusCode is Sucess
52
56
  # @raise [ValidationError] if soft == false and validation fails
53
- #
57
+ #
54
58
  def success?
55
- unless status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
56
- return append_error("Bad status code. Expected <urn:oasis:names:tc:SAML:2.0:status:Success>, but was: <#@status_code>")
57
- end
58
- true
59
+ return status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
59
60
  end
60
61
 
61
62
  # @return [String|nil] Gets the InResponseTo attribute from the Logout Response if exists.
@@ -65,7 +66,7 @@ module OneLogin
65
66
  node = REXML::XPath.first(
66
67
  document,
67
68
  "/p:LogoutResponse",
68
- { "p" => PROTOCOL, "a" => ASSERTION }
69
+ { "p" => PROTOCOL }
69
70
  )
70
71
  node.nil? ? nil : node.attributes['InResponseTo']
71
72
  end
@@ -88,7 +89,7 @@ module OneLogin
88
89
  #
89
90
  def status_code
90
91
  @status_code ||= begin
91
- node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION })
92
+ node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL })
92
93
  node.nil? ? nil : node.attributes["Value"]
93
94
  end
94
95
  end
@@ -98,7 +99,7 @@ module OneLogin
98
99
  node = REXML::XPath.first(
99
100
  document,
100
101
  "/p:LogoutResponse/p:Status/p:StatusMessage",
101
- { "p" => PROTOCOL, "a" => ASSERTION }
102
+ { "p" => PROTOCOL }
102
103
  )
103
104
  Utils.element_text(node)
104
105
  end
@@ -146,7 +147,7 @@ module OneLogin
146
147
 
147
148
  # Validates the Logout Response against the specified schema.
148
149
  # @return [Boolean] True if the XML is valid, otherwise False if soft=True
149
- # @raise [ValidationError] if soft == false and validation fails
150
+ # @raise [ValidationError] if soft == false and validation fails
150
151
  #
151
152
  def validate_structure
152
153
  unless valid_saml?(document, soft)
@@ -166,7 +167,7 @@ module OneLogin
166
167
 
167
168
  return append_error("No settings on logout response") if settings.nil?
168
169
 
169
- return append_error("No issuer in settings of the logout response") if settings.issuer.nil?
170
+ return append_error("No sp_entity_id in settings of the logout response") if settings.sp_entity_id.nil?
170
171
 
171
172
  if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? && settings.idp_cert_multi.nil?
172
173
  return append_error("No fingerprint or certificate on settings of the logout response")
@@ -205,7 +206,7 @@ module OneLogin
205
206
  # Validates the Signature if it exists and the GET parameters are provided
206
207
  # @return [Boolean] True if not contains a Signature or if the Signature is valid, otherwise False if soft=True
207
208
  # @raise [ValidationError] if soft == false and validation fails
208
- #
209
+ #
209
210
  def validate_signature
210
211
  return true unless !options.nil?
211
212
  return true unless options.has_key? :get_params
@@ -231,33 +232,48 @@ module OneLogin
231
232
  :raw_sig_alg => options[:raw_get_params]['SigAlg']
232
233
  )
233
234
 
235
+ expired = false
234
236
  if idp_certs.nil? || idp_certs[:signing].empty?
235
237
  valid = OneLogin::RubySaml::Utils.verify_signature(
236
- :cert => settings.get_idp_cert,
238
+ :cert => idp_cert,
237
239
  :sig_alg => options[:get_params]['SigAlg'],
238
240
  :signature => options[:get_params]['Signature'],
239
241
  :query_string => query_string
240
242
  )
243
+ if valid && settings.security[:check_idp_cert_expiration]
244
+ if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
245
+ expired = true
246
+ end
247
+ end
241
248
  else
242
249
  valid = false
243
- idp_certs[:signing].each do |idp_cert|
250
+ idp_certs[:signing].each do |signing_idp_cert|
244
251
  valid = OneLogin::RubySaml::Utils.verify_signature(
245
- :cert => idp_cert,
252
+ :cert => signing_idp_cert,
246
253
  :sig_alg => options[:get_params]['SigAlg'],
247
254
  :signature => options[:get_params]['Signature'],
248
255
  :query_string => query_string
249
256
  )
250
257
  if valid
258
+ if settings.security[:check_idp_cert_expiration]
259
+ if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
260
+ expired = true
261
+ end
262
+ end
251
263
  break
252
264
  end
253
265
  end
254
266
  end
255
267
 
268
+ if expired
269
+ error_msg = "IdP x509 certificate expired"
270
+ return append_error(error_msg)
271
+ end
256
272
  unless valid
257
273
  error_msg = "Invalid Signature on Logout Response"
258
274
  return append_error(error_msg)
259
275
  end
260
- true
276
+ true
261
277
  end
262
278
 
263
279
  end
@@ -15,9 +15,11 @@ module OneLogin
15
15
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
16
16
  # @param pretty_print [Boolean] Pretty print or not the response
17
17
  # (No pretty print if you gonna validate the signature)
18
+ # @param valid_until [DateTime] Metadata's valid time
19
+ # @param cache_duration [Integer] Duration of the cache in seconds
18
20
  # @return [String] XML Metadata of the Service Provider
19
21
  #
20
- def generate(settings, pretty_print=false)
22
+ def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil)
21
23
  meta_doc = XMLSecurity::Document.new
22
24
  namespaces = {
23
25
  "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
@@ -57,8 +59,14 @@ module OneLogin
57
59
  end
58
60
 
59
61
  root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
60
- if settings.issuer
61
- root.attributes["entityID"] = settings.issuer
62
+ if settings.sp_entity_id
63
+ root.attributes["entityID"] = settings.sp_entity_id
64
+ end
65
+ if valid_until
66
+ root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z')
67
+ end
68
+ if cache_duration
69
+ root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S"
62
70
  end
63
71
  if settings.single_logout_service_url
64
72
  sp_sso.add_element "md:SingleLogoutService", {
@@ -30,6 +30,15 @@ module OneLogin
30
30
 
31
31
  attr_accessor :soft
32
32
 
33
+ # Response available options
34
+ # This is not a whitelist to allow people extending OneLogin::RubySaml:Response
35
+ # and pass custom options
36
+ AVAILABLE_OPTIONS = [
37
+ :allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_audience, :skip_authnstatement, :skip_conditions,
38
+ :skip_destination, :skip_recipient_check, :skip_subject_confirmation
39
+ ]
40
+ # TODO: Update the comment on initialize to describe every option
41
+
33
42
  # Constructs the SAML Response. A Response Object that is an extension of the SamlMessage class.
34
43
  # @param response [String] A UUEncoded SAML response from the IdP.
35
44
  # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
@@ -38,6 +47,8 @@ module OneLogin
38
47
  # or :matches_request_id that will validate that the response matches the ID of the request,
39
48
  # or skip the subject confirmation validation with the :skip_subject_confirmation option
40
49
  # or skip the recipient validation of the subject confirmation element with :skip_recipient_check option
50
+ # or skip the audience validation with :skip_audience option
51
+ #
41
52
  def initialize(response, options = {})
42
53
  raise ArgumentError.new("Response cannot be nil") if response.nil?
43
54
 
@@ -162,9 +173,10 @@ module OneLogin
162
173
  # identify the subject in an SP rather than email or other less opaque attributes
163
174
  # NameQualifier, if present is prefixed with a "/" to the value
164
175
  else
165
- REXML::XPath.match(e,'a:NameID', { "a" => ASSERTION }).collect{|n|
166
- (n.attributes['NameQualifier'] ? n.attributes['NameQualifier'] +"/" : '') + Utils.element_text(n)
167
- }
176
+ REXML::XPath.match(e,'a:NameID', { "a" => ASSERTION }).collect do |n|
177
+ base_path = n.attributes['NameQualifier'] ? "#{n.attributes['NameQualifier']}/" : ''
178
+ "#{base_path}#{Utils.element_text(n)}"
179
+ end
168
180
  end
169
181
  }
170
182
 
@@ -212,8 +224,8 @@ module OneLogin
212
224
  "/p:Response/p:Status/p:StatusCode/p:StatusCode",
213
225
  { "p" => PROTOCOL }
214
226
  )
215
- statuses = nodes.collect do |node|
216
- node.attributes["Value"]
227
+ statuses = nodes.collect do |inner_node|
228
+ inner_node.attributes["Value"]
217
229
  end
218
230
  extra_code = statuses.join(" | ")
219
231
  if extra_code
@@ -279,7 +291,6 @@ module OneLogin
279
291
  raise ValidationError.new(error_msg)
280
292
  end
281
293
 
282
- doc = decrypted_document.nil? ? document : decrypted_document
283
294
  issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
284
295
  unless issuer_assertion_nodes.size == 1
285
296
  error_msg = "Issuer of the Assertion not found or multiple."
@@ -329,7 +340,29 @@ module OneLogin
329
340
  # returns the allowed clock drift on timing validation
330
341
  # @return [Integer]
331
342
  def allowed_clock_drift
332
- return options[:allowed_clock_drift] || 0
343
+ return options[:allowed_clock_drift].to_f
344
+ end
345
+
346
+ # Checks if the SAML Response contains or not an EncryptedAssertion element
347
+ # @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
348
+ #
349
+ def assertion_encrypted?
350
+ ! REXML::XPath.first(
351
+ document,
352
+ "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
353
+ { "p" => PROTOCOL, "a" => ASSERTION }
354
+ ).nil?
355
+ end
356
+
357
+ def response_id
358
+ id(document)
359
+ end
360
+
361
+ def assertion_id
362
+ @assertion_id ||= begin
363
+ node = xpath_first_from_signed_assertion("")
364
+ node.nil? ? nil : node.attributes['ID']
365
+ end
333
366
  end
334
367
 
335
368
  private
@@ -426,7 +459,7 @@ module OneLogin
426
459
  # @return [Boolean] True if the SAML Response contains an ID, otherwise returns False
427
460
  #
428
461
  def validate_id
429
- unless id(document)
462
+ unless response_id
430
463
  return append_error("Missing ID attribute on SAML Response")
431
464
  end
432
465
 
@@ -575,15 +608,18 @@ module OneLogin
575
608
  end
576
609
 
577
610
  # Validates the Audience, (If the Audience match the Service Provider EntityID)
611
+ # If the response was initialized with the :skip_audience option, this validation is skipped,
578
612
  # If fails, the error is added to the errors array
579
613
  # @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True
580
614
  # @raise [ValidationError] if soft == false and validation fails
581
615
  #
582
616
  def validate_audience
583
- return true if audiences.empty? || settings.issuer.nil? || settings.issuer.empty?
617
+ return true if options[:skip_audience]
618
+ return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
584
619
 
585
- unless audiences.include? settings.issuer
586
- error_msg = "#{settings.issuer} is not a valid audience for this Response - Valid audiences: #{audiences.join(',')}"
620
+ unless audiences.include? settings.sp_entity_id
621
+ s = audiences.count > 1 ? 's' : '';
622
+ error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.sp_entity_id}"
587
623
  return append_error(error_msg)
588
624
  end
589
625
 
@@ -615,10 +651,13 @@ module OneLogin
615
651
  end
616
652
 
617
653
  # Checks that the samlp:Response/saml:Assertion/saml:Conditions element exists and is unique.
654
+ # (If the response was initialized with the :skip_conditions option, this validation is skipped)
618
655
  # If fails, the error is added to the errors array
619
656
  # @return [Boolean] True if there is a conditions element and is unique
620
657
  #
621
658
  def validate_one_conditions
659
+ return true if options[:skip_conditions]
660
+
622
661
  conditions_nodes = xpath_from_signed_assertion('/a:Conditions')
623
662
  unless conditions_nodes.size == 1
624
663
  error_msg = "The Assertion must include one Conditions element"
@@ -633,6 +672,8 @@ module OneLogin
633
672
  # @return [Boolean] True if there is a authnstatement element and is unique
634
673
  #
635
674
  def validate_one_authnstatement
675
+ return true if options[:skip_authnstatement]
676
+
636
677
  authnstatement_nodes = xpath_from_signed_assertion('/a:AuthnStatement')
637
678
  unless authnstatement_nodes.size == 1
638
679
  error_msg = "The Assertion must include one AuthnStatement element"
@@ -694,7 +735,7 @@ module OneLogin
694
735
  # this time validation is relaxed by the allowed_clock_drift value)
695
736
  # If fails, the error is added to the errors array
696
737
  # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not)
697
- # @return [Boolean] True if the SessionNotOnOrAfter of the AttributeStatement is valid, otherwise (when expired) False if soft=True
738
+ # @return [Boolean] True if the SessionNotOnOrAfter of the AuthnStatement is valid, otherwise (when expired) False if soft=True
698
739
  # @raise [ValidationError] if soft == false and validation fails
699
740
  #
700
741
  def validate_session_expiration(soft = true)
@@ -702,7 +743,7 @@ module OneLogin
702
743
 
703
744
  now = Time.now.utc
704
745
  unless (session_expires_at + allowed_clock_drift) > now
705
- error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response"
746
+ error_msg = "The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response"
706
747
  return append_error(error_msg)
707
748
  end
708
749
 
@@ -766,8 +807,8 @@ module OneLogin
766
807
  return append_error("An empty NameID value found")
767
808
  end
768
809
 
769
- unless settings.issuer.nil? || settings.issuer.empty? || name_id_spnamequalifier.nil? || name_id_spnamequalifier.empty?
770
- if name_id_spnamequalifier != settings.issuer
810
+ unless settings.sp_entity_id.nil? || settings.sp_entity_id.empty? || name_id_spnamequalifier.nil? || name_id_spnamequalifier.empty?
811
+ if name_id_spnamequalifier != settings.sp_entity_id
771
812
  return append_error("The SPNameQualifier value mistmatch the SP entityID value.")
772
813
  end
773
814
  end
@@ -787,7 +828,7 @@ module OneLogin
787
828
  # otherwise, review if the decrypted assertion contains a signature
788
829
  sig_elements = REXML::XPath.match(
789
830
  document,
790
- "/p:Response[@ID=$id]/ds:Signature]",
831
+ "/p:Response[@ID=$id]/ds:Signature",
791
832
  { "p" => PROTOCOL, "ds" => DSIG },
792
833
  { 'id' => document.signed_element_id }
793
834
  )
@@ -806,28 +847,59 @@ module OneLogin
806
847
  end
807
848
 
808
849
  if sig_elements.size != 1
850
+ if sig_elements.size == 0
851
+ append_error("Signed element id ##{doc.signed_element_id} is not found")
852
+ else
853
+ append_error("Signed element id ##{doc.signed_element_id} is found more than once")
854
+ end
809
855
  return append_error(error_msg)
810
856
  end
811
857
 
858
+ old_errors = @errors.clone
859
+
812
860
  idp_certs = settings.get_idp_cert_multi
813
861
  if idp_certs.nil? || idp_certs[:signing].empty?
814
862
  opts = {}
815
863
  opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
816
- opts[:cert] = settings.get_idp_cert
864
+ idp_cert = settings.get_idp_cert
817
865
  fingerprint = settings.get_fingerprint
866
+ opts[:cert] = idp_cert
818
867
 
819
- unless fingerprint && doc.validate_document(fingerprint, @soft, opts)
868
+ if fingerprint && doc.validate_document(fingerprint, @soft, opts)
869
+ if settings.security[:check_idp_cert_expiration]
870
+ if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
871
+ error_msg = "IdP x509 certificate expired"
872
+ return append_error(error_msg)
873
+ end
874
+ end
875
+ else
820
876
  return append_error(error_msg)
821
877
  end
822
878
  else
823
879
  valid = false
880
+ expired = false
824
881
  idp_certs[:signing].each do |idp_cert|
825
- valid = doc.validate_document_with_cert(idp_cert)
882
+ valid = doc.validate_document_with_cert(idp_cert, true)
826
883
  if valid
884
+ if settings.security[:check_idp_cert_expiration]
885
+ if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
886
+ expired = true
887
+ end
888
+ end
889
+
890
+ # At least one certificate is valid, restore the old accumulated errors
891
+ @errors = old_errors
827
892
  break
828
893
  end
894
+
895
+ end
896
+ if expired
897
+ error_msg = "IdP x509 certificate expired"
898
+ return append_error(error_msg)
829
899
  end
830
900
  unless valid
901
+ # Remove duplicated errors
902
+ @errors = @errors.uniq
831
903
  return append_error(error_msg)
832
904
  end
833
905
  end
@@ -928,17 +1000,6 @@ module OneLogin
928
1000
  XMLSecurity::SignedDocument.new(response_node.to_s)
929
1001
  end
930
1002
 
931
- # Checks if the SAML Response contains or not an EncryptedAssertion element
932
- # @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
933
- #
934
- def assertion_encrypted?
935
- ! REXML::XPath.first(
936
- document,
937
- "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
938
- { "p" => PROTOCOL, "a" => ASSERTION }
939
- ).nil?
940
- end
941
-
942
1003
  # Decrypts an EncryptedAssertion element
943
1004
  # @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element
944
1005
  # @return [REXML::Document] The decrypted EncryptedAssertion element
@@ -22,6 +22,8 @@ module OneLogin
22
22
  BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
23
23
  @@mutex = Mutex.new
24
24
 
25
+ MAX_BYTE_SIZE = 250000
26
+
25
27
  # @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
26
28
  #
27
29
  def self.schema
@@ -62,7 +64,7 @@ module OneLogin
62
64
  # @param document [REXML::Document] The message that will be validated
63
65
  # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the message is invalid or not)
64
66
  # @return [Boolean] True if the XML is valid, otherwise False, if soft=True
65
- # @raise [ValidationError] if soft == false and validation fails
67
+ # @raise [ValidationError] if soft == false and validation fails
66
68
  #
67
69
  def valid_saml?(document, soft = true)
68
70
  begin
@@ -74,9 +76,9 @@ module OneLogin
74
76
  raise ValidationError.new("XML load failed: #{error.message}")
75
77
  end
76
78
 
77
- SamlMessage.schema.validate(xml).map do |error|
79
+ SamlMessage.schema.validate(xml).map do |schema_error|
78
80
  return false if soft
79
- raise ValidationError.new("#{error.message}\n\n#{xml.to_s}")
81
+ raise ValidationError.new("#{schema_error.message}\n\n#{xml.to_s}")
80
82
  end
81
83
  end
82
84
 
@@ -89,6 +91,10 @@ module OneLogin
89
91
  def decode_raw_saml(saml)
90
92
  return saml unless base64_encoded?(saml)
91
93
 
94
+ if saml.bytesize > MAX_BYTE_SIZE
95
+ raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
96
+ end
97
+
92
98
  decoded = decode(saml)
93
99
  begin
94
100
  inflate(decoded)
@@ -105,7 +111,7 @@ module OneLogin
105
111
  def encode_raw_saml(saml, settings)
106
112
  saml = deflate(saml) if settings.compress_request
107
113
 
108
- CGI.escape(Base64.encode64(saml))
114
+ CGI.escape(encode(saml))
109
115
  end
110
116
 
111
117
  # Base 64 decode method
@@ -121,7 +127,11 @@ module OneLogin
121
127
  # @return [String] The encoded string
122
128
  #
123
129
  def encode(string)
124
- Base64.encode64(string).gsub(/\n/, "")
130
+ if Base64.respond_to?('strict_encode64')
131
+ Base64.strict_encode64(string)
132
+ else
133
+ Base64.encode64(string).gsub(/\n/, "")
134
+ end
125
135
  end
126
136
 
127
137
  # Check if a string is base64 encoded
@@ -0,0 +1,6 @@
1
+ module OneLogin
2
+ module RubySaml
3
+ class SettingError < StandardError
4
+ end
5
+ end
6
+ end
@@ -1,6 +1,7 @@
1
1
  require "xml_security"
2
2
  require "onelogin/ruby-saml/attribute_service"
3
3
  require "onelogin/ruby-saml/utils"
4
+ require "onelogin/ruby-saml/validation_error"
4
5
 
5
6
  # Only supports SAML 2.0
6
7
  module OneLogin
@@ -9,8 +10,15 @@ module OneLogin
9
10
  # SAML2 Toolkit Settings
10
11
  #
11
12
  class Settings
12
- def initialize(overrides = {})
13
- config = DEFAULTS.merge(overrides)
13
+ def initialize(overrides = {}, keep_security_attributes = false)
14
+ if keep_security_attributes
15
+ security_attributes = overrides.delete(:security) || {}
16
+ config = DEFAULTS.merge(overrides)
17
+ config[:security] = DEFAULTS[:security].merge(security_attributes)
18
+ else
19
+ config = DEFAULTS.merge(overrides)
20
+ end
21
+
14
22
  config.each do |k,v|
15
23
  acc = "#{k.to_s}=".to_sym
16
24
  if respond_to? acc
@@ -23,21 +31,24 @@ module OneLogin
23
31
 
24
32
  # IdP Data
25
33
  attr_accessor :idp_entity_id
26
- attr_accessor :idp_sso_target_url
27
- attr_accessor :idp_slo_target_url
34
+
35
+ attr_accessor :idp_sso_service_url
36
+ attr_accessor :idp_slo_service_url
37
+ attr_accessor :idp_slo_response_service_url
28
38
  attr_accessor :idp_cert
29
39
  attr_accessor :idp_cert_fingerprint
30
40
  attr_accessor :idp_cert_fingerprint_algorithm
31
41
  attr_accessor :idp_cert_multi
32
42
  attr_accessor :idp_attribute_names
33
43
  attr_accessor :idp_name_qualifier
44
+ attr_accessor :valid_until
34
45
  # SP Data
35
- attr_accessor :issuer
36
46
  attr_accessor :assertion_consumer_service_url
37
47
  attr_accessor :assertion_consumer_service_binding
38
48
  attr_accessor :sp_name_qualifier
39
49
  attr_accessor :name_identifier_format
40
50
  attr_accessor :name_identifier_value
51
+ attr_accessor :name_identifier_value_requested
41
52
  attr_accessor :sessionindex
42
53
  attr_accessor :compress_request
43
54
  attr_accessor :compress_response
@@ -59,6 +70,58 @@ module OneLogin
59
70
  # Compability
60
71
  attr_accessor :assertion_consumer_logout_service_url
61
72
  attr_accessor :assertion_consumer_logout_service_binding
73
+ attr_accessor :issuer
74
+ attr_accessor :idp_sso_target_url
75
+ attr_accessor :idp_slo_target_url
76
+
77
+ # @return [String] IdP Single Sign On Service URL
78
+ #
79
+ def idp_sso_service_url
80
+ val = nil
81
+ if @idp_sso_service_url.nil?
82
+ if @idp_sso_target_url
83
+ val = @idp_sso_target_url
84
+ end
85
+ else
86
+ val = @idp_sso_service_url
87
+ end
88
+ val
89
+ end
90
+
91
+ # @return [String] IdP Single Logout Service URL
92
+ #
93
+ def idp_slo_service_url
94
+ val = nil
95
+ if @idp_slo_service_url.nil?
96
+ if @idp_slo_target_url
97
+ val = @idp_slo_target_url
98
+ end
99
+ else
100
+ val = @idp_slo_service_url
101
+ end
102
+ val
103
+ end
104
+
105
+ # @return [String] SP Entity ID
106
+ #
107
+ def sp_entity_id
108
+ val = nil
109
+ if @sp_entity_id.nil?
110
+ if @issuer
111
+ val = @issuer
112
+ end
113
+ else
114
+ val = @sp_entity_id
115
+ end
116
+ val
117
+ end
118
+
119
+ # Setter for SP Entity ID.
120
+ # @param val [String].
121
+ #
122
+ def sp_entity_id=(val)
123
+ @sp_entity_id = val
124
+ end
62
125
 
63
126
  # @return [String] Single Logout Service URL.
64
127
  #
@@ -158,7 +221,15 @@ module OneLogin
158
221
  return nil if certificate.nil? || certificate.empty?
159
222
 
160
223
  formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
161
- OpenSSL::X509::Certificate.new(formatted_cert)
224
+ cert = OpenSSL::X509::Certificate.new(formatted_cert)
225
+
226
+ if security[:check_sp_cert_expiration]
227
+ if OneLogin::RubySaml::Utils.is_cert_expired(cert)
228
+ raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
229
+ end
230
+ end
231
+
232
+ cert
162
233
  end
163
234
 
164
235
  # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
@@ -188,6 +259,7 @@ module OneLogin
188
259
  :compress_request => true,
189
260
  :compress_response => true,
190
261
  :soft => true,
262
+ :double_quote_xml_attribute_values => false,
191
263
  :security => {
192
264
  :authn_requests_signed => false,
193
265
  :logout_requests_signed => false,
@@ -198,9 +270,10 @@ module OneLogin
198
270
  :metadata_signed => false,
199
271
  :embed_sign => false,
200
272
  :digest_method => XMLSecurity::Document::SHA1,
201
- :signature_method => XMLSecurity::Document::RSA_SHA1
202
- }.freeze,
203
- :double_quote_xml_attribute_values => false,
273
+ :signature_method => XMLSecurity::Document::RSA_SHA1,
274
+ :check_idp_cert_expiration => false,
275
+ :check_sp_cert_expiration => false
276
+ }.freeze
204
277
  }.freeze
205
278
  end
206
279
  end