ruby-saml 1.10.1 → 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.

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

Files changed (159) hide show
  1. checksums.yaml +7 -7
  2. data/.travis.yml +21 -20
  3. data/README.md +95 -26
  4. data/changelog.md +40 -0
  5. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  6. data/lib/onelogin/ruby-saml/authrequest.rb +11 -6
  7. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +70 -24
  8. data/lib/onelogin/ruby-saml/logging.rb +3 -3
  9. data/lib/onelogin/ruby-saml/logoutrequest.rb +11 -5
  10. data/lib/onelogin/ruby-saml/logoutresponse.rb +21 -2
  11. data/lib/onelogin/ruby-saml/metadata.rb +11 -3
  12. data/lib/onelogin/ruby-saml/response.rb +64 -23
  13. data/lib/onelogin/ruby-saml/saml_message.rb +6 -0
  14. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  15. data/lib/onelogin/ruby-saml/settings.rb +72 -7
  16. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +20 -1
  17. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +31 -17
  18. data/lib/onelogin/ruby-saml/utils.rb +69 -0
  19. data/lib/onelogin/ruby-saml/version.rb +1 -1
  20. data/lib/xml_security.rb +34 -6
  21. data/ruby-saml.gemspec +9 -5
  22. metadata +161 -383
  23. data/test/certificates/certificate.der +0 -0
  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 -587
  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 -86
  52. data/test/logoutrequest_test.rb +0 -260
  53. data/test/logoutresponse_test.rb +0 -409
  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_descriptor_4.xml +0 -72
  58. data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
  59. data/test/metadata/idp_metadata_multi_certs.xml +0 -75
  60. data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
  61. data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
  62. data/test/metadata/idp_multiple_descriptors.xml +0 -59
  63. data/test/metadata/idp_multiple_descriptors_2.xml +0 -59
  64. data/test/metadata/no_idp_descriptor.xml +0 -21
  65. data/test/metadata_test.rb +0 -331
  66. data/test/request_test.rb +0 -340
  67. data/test/response_test.rb +0 -1620
  68. data/test/responses/adfs_response_sha1.xml +0 -46
  69. data/test/responses/adfs_response_sha256.xml +0 -46
  70. data/test/responses/adfs_response_sha384.xml +0 -46
  71. data/test/responses/adfs_response_sha512.xml +0 -46
  72. data/test/responses/adfs_response_xmlns.xml +0 -45
  73. data/test/responses/attackxee.xml +0 -13
  74. data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
  75. data/test/responses/invalids/empty_destination.xml.base64 +0 -1
  76. data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
  77. data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
  78. data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
  79. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  80. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  81. data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
  82. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
  83. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
  84. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
  85. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
  86. data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
  87. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  88. data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
  89. data/test/responses/invalids/no_conditions.xml.base64 +0 -1
  90. data/test/responses/invalids/no_id.xml.base64 +0 -1
  91. data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
  92. data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
  93. data/test/responses/invalids/no_nameid.xml.base64 +0 -1
  94. data/test/responses/invalids/no_saml2.xml.base64 +0 -1
  95. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  96. data/test/responses/invalids/no_status.xml.base64 +0 -1
  97. data/test/responses/invalids/no_status_code.xml.base64 +0 -1
  98. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
  99. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
  100. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
  101. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  102. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  103. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  104. data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
  105. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
  106. data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
  107. data/test/responses/no_signature_ns.xml +0 -48
  108. data/test/responses/open_saml_response.xml +0 -56
  109. data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
  110. data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
  111. data/test/responses/response_double_status_code.xml.base64 +0 -1
  112. data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
  113. data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
  114. data/test/responses/response_eval.xml +0 -7
  115. data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
  116. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  117. data/test/responses/response_node_text_attack2.xml.base64 +0 -1
  118. data/test/responses/response_node_text_attack3.xml.base64 +0 -1
  119. data/test/responses/response_unsigned_xml_base64 +0 -1
  120. data/test/responses/response_with_ampersands.xml +0 -139
  121. data/test/responses/response_with_ampersands.xml.base64 +0 -93
  122. data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
  123. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  124. data/test/responses/response_with_multiple_attribute_values.xml +0 -67
  125. data/test/responses/response_with_retrieval_method.xml +0 -26
  126. data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
  127. data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
  128. data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
  129. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  130. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  131. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  132. data/test/responses/response_without_attributes.xml.base64 +0 -79
  133. data/test/responses/response_without_reference_uri.xml.base64 +0 -1
  134. data/test/responses/response_wrapped.xml.base64 +0 -150
  135. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
  136. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  137. data/test/responses/signed_nameid_in_atts.xml +0 -47
  138. data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
  139. data/test/responses/simple_saml_php.xml +0 -71
  140. data/test/responses/starfield_response.xml.base64 +0 -1
  141. data/test/responses/test_sign.xml +0 -43
  142. data/test/responses/unsigned_encrypted_adfs.xml +0 -23
  143. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
  144. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
  145. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
  146. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
  147. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
  148. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
  149. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  150. data/test/responses/valid_response.xml.base64 +0 -1
  151. data/test/responses/valid_response_with_formatted_x509certificate.xml.base64 +0 -1
  152. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  153. data/test/saml_message_test.rb +0 -56
  154. data/test/settings_test.rb +0 -329
  155. data/test/slo_logoutrequest_test.rb +0 -448
  156. data/test/slo_logoutresponse_test.rb +0 -233
  157. data/test/test_helper.rb +0 -331
  158. data/test/utils_test.rb +0 -259
  159. data/test/xml_security_test.rb +0 -421
@@ -3,6 +3,7 @@ require "rexml/document"
3
3
  require "onelogin/ruby-saml/logging"
4
4
  require "onelogin/ruby-saml/saml_message"
5
5
  require "onelogin/ruby-saml/utils"
6
+ require "onelogin/ruby-saml/setting_error"
6
7
 
7
8
  # Only supports SAML 2.0
8
9
  module OneLogin
@@ -23,6 +24,10 @@ module OneLogin
23
24
  @uuid = OneLogin::RubySaml::Utils.uuid
24
25
  end
25
26
 
27
+ def request_id
28
+ @uuid
29
+ end
30
+
26
31
  # Creates the AuthNRequest string.
27
32
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
28
33
  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
@@ -30,14 +35,14 @@ module OneLogin
30
35
  #
31
36
  def create(settings, params = {})
32
37
  params = create_params(settings, params)
33
- params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?'
38
+ params_prefix = (settings.idp_sso_service_url =~ /\?/) ? '&' : '?'
34
39
  saml_request = CGI.escape(params.delete("SAMLRequest"))
35
40
  request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
36
41
  params.each_pair do |key, value|
37
42
  request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
38
43
  end
39
- raise "Invalid settings, idp_sso_target_url is not set!" if settings.idp_sso_target_url.nil?
40
- @login_url = settings.idp_sso_target_url + request_params
44
+ raise SettingError.new "Invalid settings, idp_sso_service_url is not set!" if settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
45
+ @login_url = settings.idp_sso_service_url + request_params
41
46
  end
42
47
 
43
48
  # Creates the Get parameters for the request.
@@ -107,7 +112,7 @@ module OneLogin
107
112
  root.attributes['ID'] = uuid
108
113
  root.attributes['IssueInstant'] = time
109
114
  root.attributes['Version'] = "2.0"
110
- root.attributes['Destination'] = settings.idp_sso_target_url unless settings.idp_sso_target_url.nil?
115
+ root.attributes['Destination'] = settings.idp_sso_service_url unless settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
111
116
  root.attributes['IsPassive'] = settings.passive unless settings.passive.nil?
112
117
  root.attributes['ProtocolBinding'] = settings.protocol_binding unless settings.protocol_binding.nil?
113
118
  root.attributes["AttributeConsumingServiceIndex"] = settings.attributes_index unless settings.attributes_index.nil?
@@ -117,9 +122,9 @@ module OneLogin
117
122
  if settings.assertion_consumer_service_url != nil
118
123
  root.attributes["AssertionConsumerServiceURL"] = settings.assertion_consumer_service_url
119
124
  end
120
- if settings.issuer != nil
125
+ if settings.sp_entity_id != nil
121
126
  issuer = root.add_element "saml:Issuer"
122
- issuer.text = settings.issuer
127
+ issuer.text = settings.sp_entity_id
123
128
  end
124
129
 
125
130
  if settings.name_identifier_value_requested != nil
@@ -15,10 +15,10 @@ module OneLogin
15
15
 
16
16
  module SamlMetadata
17
17
  module Vocabulary
18
- METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
19
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
20
- NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
21
- SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
18
+ METADATA = "urn:oasis:names:tc:SAML:2.0:metadata".freeze
19
+ DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
20
+ NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*".freeze
21
+ SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
22
22
  end
23
23
 
24
24
  NAMESPACE = {
@@ -26,7 +26,7 @@ module OneLogin
26
26
  "NameFormat" => Vocabulary::NAME_FORMAT,
27
27
  "saml" => Vocabulary::SAML_ASSERTION,
28
28
  "ds" => Vocabulary::DSIG
29
- }
29
+ }.freeze
30
30
  end
31
31
 
32
32
  include SamlMetadata::Vocabulary
@@ -34,6 +34,16 @@ module OneLogin
34
34
  attr_reader :response
35
35
  attr_reader :options
36
36
 
37
+ # fetch IdP descriptors from a metadata document
38
+ def self.get_idps(metadata_document, only_entity_id=nil)
39
+ path = "//md:EntityDescriptor#{only_entity_id && '[@entityID="' + only_entity_id + '"]'}/md:IDPSSODescriptor"
40
+ REXML::XPath.match(
41
+ metadata_document,
42
+ path,
43
+ SamlMetadata::NAMESPACE
44
+ )
45
+ end
46
+
37
47
  # Parse the Identity Provider metadata and update the settings with the
38
48
  # IdP values
39
49
  #
@@ -103,6 +113,16 @@ module OneLogin
103
113
  def parse(idp_metadata, options = {})
104
114
  parsed_metadata = parse_to_hash(idp_metadata, options)
105
115
 
116
+ unless parsed_metadata[:cache_duration].nil?
117
+ cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration])
118
+ if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until], Time.now.utc).to_i
119
+ parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
120
+ end
121
+ end
122
+ # Remove the cache_duration because on the settings
123
+ # we only gonna suppot valid_until
124
+ parsed_metadata.delete(:cache_duration)
125
+
106
126
  settings = options[:settings]
107
127
 
108
128
  if settings.nil?
@@ -139,17 +159,21 @@ module OneLogin
139
159
  #
140
160
  # @return [Array<Hash>]
141
161
  def parse_to_array(idp_metadata, options = {})
162
+ parse_to_idp_metadata_array(idp_metadata, options).map{|idp_md| idp_md.to_hash(options)}
163
+ end
164
+
165
+ def parse_to_idp_metadata_array(idp_metadata, options = {})
142
166
  @document = REXML::Document.new(idp_metadata)
143
167
  @options = options
144
168
 
145
- idpsso_descriptors = IdpMetadata::get_idps(@document, options[:entity_id])
169
+ idpsso_descriptors = self.class.get_idps(@document, options[:entity_id])
146
170
  if !idpsso_descriptors.any?
147
171
  raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
148
172
  end
149
173
 
150
- return idpsso_descriptors.map{|id| IdpMetadata.new(id, id.parent.attributes["entityID"]).to_hash(options)}
174
+ return idpsso_descriptors.map{|id| IdpMetadata.new(id, id.parent.attributes["entityID"])}
151
175
  end
152
-
176
+
153
177
  private
154
178
 
155
179
  # Retrieve the remote IdP metadata from the URL or a cached copy.
@@ -175,6 +199,7 @@ module OneLogin
175
199
  end
176
200
 
177
201
  get = Net::HTTP::Get.new(uri.request_uri)
202
+ get.basic_auth uri.user, uri.password if uri.user
178
203
  @response = http.request(get)
179
204
  return response.body if response.is_a? Net::HTTPSuccess
180
205
 
@@ -184,15 +209,8 @@ module OneLogin
184
209
  end
185
210
 
186
211
  class IdpMetadata
187
- def self.get_idps(metadata_document, only_entity_id=nil)
188
- path = "//md:EntityDescriptor#{only_entity_id && '[@entityID="' + only_entity_id + '"]'}/md:IDPSSODescriptor"
189
- REXML::XPath.match(
190
- metadata_document,
191
- path,
192
- SamlMetadata::NAMESPACE
193
- )
194
- end
195
-
212
+ attr_reader :idpsso_descriptor, :entity_id
213
+
196
214
  def initialize(idpsso_descriptor, entity_id)
197
215
  @idpsso_descriptor = idpsso_descriptor
198
216
  @entity_id = entity_id
@@ -202,12 +220,15 @@ module OneLogin
202
220
  {
203
221
  :idp_entity_id => @entity_id,
204
222
  :name_identifier_format => idp_name_id_format,
205
- :idp_sso_target_url => single_signon_service_url(options),
206
- :idp_slo_target_url => single_logout_service_url(options),
223
+ :idp_sso_service_url => single_signon_service_url(options),
224
+ :idp_slo_service_url => single_logout_service_url(options),
225
+ :idp_slo_response_service_url => single_logout_response_service_url(options),
207
226
  :idp_attribute_names => attribute_names,
208
227
  :idp_cert => nil,
209
228
  :idp_cert_fingerprint => nil,
210
- :idp_cert_multi => nil
229
+ :idp_cert_multi => nil,
230
+ :valid_until => valid_until,
231
+ :cache_duration => cache_duration,
211
232
  }.tap do |response_hash|
212
233
  merge_certificates_into(response_hash) unless certificates.nil?
213
234
  end
@@ -224,6 +245,20 @@ module OneLogin
224
245
  Utils.element_text(node)
225
246
  end
226
247
 
248
+ # @return [String|nil] 'validUntil' attribute of metadata
249
+ #
250
+ def valid_until
251
+ root = @idpsso_descriptor.root
252
+ root.attributes['validUntil'] if root && root.attributes
253
+ end
254
+
255
+ # @return [String|nil] 'cacheDuration' attribute of metadata
256
+ #
257
+ def cache_duration
258
+ root = @idpsso_descriptor.root
259
+ root.attributes['cacheDuration'] if root && root.attributes
260
+ end
261
+
227
262
  # @param binding_priority [Array]
228
263
  # @return [String|nil] SingleSignOnService binding if exists
229
264
  #
@@ -288,6 +323,21 @@ module OneLogin
288
323
  return node.value if node
289
324
  end
290
325
 
326
+ # @param options [Hash]
327
+ # @return [String|nil] SingleLogoutService response url if exists
328
+ #
329
+ def single_logout_response_service_url(options = {})
330
+ binding = single_logout_service_binding(options[:slo_binding])
331
+ return if binding.nil?
332
+
333
+ node = REXML::XPath.first(
334
+ @idpsso_descriptor,
335
+ "md:SingleLogoutService[@Binding=\"#{binding}\"]/@ResponseLocation",
336
+ SamlMetadata::NAMESPACE
337
+ )
338
+ return node.value if node
339
+ end
340
+
291
341
  # @return [String|nil] Unformatted Certificate if exists
292
342
  #
293
343
  def certificates
@@ -385,10 +435,6 @@ module OneLogin
385
435
 
386
436
  settings
387
437
  end
388
-
389
- if self.respond_to?(:private_constant)
390
- private_constant :SamlMetadata, :IdpMetadata
391
- end
392
438
  end
393
439
  end
394
440
  end
@@ -8,9 +8,9 @@ module OneLogin
8
8
 
9
9
  def self.logger
10
10
  @logger ||= begin
11
- (defined?(::Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
12
- DEFAULT_LOGGER
13
- end
11
+ (defined?(::Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
12
+ DEFAULT_LOGGER
13
+ end
14
14
  end
15
15
 
16
16
  def self.logger=(logger)
@@ -1,6 +1,7 @@
1
1
  require "onelogin/ruby-saml/logging"
2
2
  require "onelogin/ruby-saml/saml_message"
3
3
  require "onelogin/ruby-saml/utils"
4
+ require "onelogin/ruby-saml/setting_error"
4
5
 
5
6
  # Only supports SAML 2.0
6
7
  module OneLogin
@@ -20,6 +21,10 @@ module OneLogin
20
21
  @uuid = OneLogin::RubySaml::Utils.uuid
21
22
  end
22
23
 
24
+ def request_id
25
+ @uuid
26
+ end
27
+
23
28
  # Creates the Logout Request string.
24
29
  # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
25
30
  # @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
@@ -27,13 +32,14 @@ module OneLogin
27
32
  #
28
33
  def create(settings, params={})
29
34
  params = create_params(settings, params)
30
- params_prefix = (settings.idp_slo_target_url =~ /\?/) ? '&' : '?'
35
+ params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
31
36
  saml_request = CGI.escape(params.delete("SAMLRequest"))
32
37
  request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
33
38
  params.each_pair do |key, value|
34
39
  request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
35
40
  end
36
- @logout_url = settings.idp_slo_target_url + request_params
41
+ raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty?
42
+ @logout_url = settings.idp_slo_service_url + request_params
37
43
  end
38
44
 
39
45
  # Creates the Get parameters for the logout request.
@@ -103,11 +109,11 @@ module OneLogin
103
109
  root.attributes['ID'] = uuid
104
110
  root.attributes['IssueInstant'] = time
105
111
  root.attributes['Version'] = "2.0"
106
- root.attributes['Destination'] = settings.idp_slo_target_url unless settings.idp_slo_target_url.nil?
112
+ root.attributes['Destination'] = settings.idp_slo_service_url unless settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty?
107
113
 
108
- if settings.issuer
114
+ if settings.sp_entity_id
109
115
  issuer = root.add_element "saml:Issuer"
110
- issuer.text = settings.issuer
116
+ issuer.text = settings.sp_entity_id
111
117
  end
112
118
 
113
119
  nameid = root.add_element "saml:NameID"
@@ -47,6 +47,10 @@ 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
@@ -163,7 +167,7 @@ module OneLogin
163
167
 
164
168
  return append_error("No settings on logout response") if settings.nil?
165
169
 
166
- 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?
167
171
 
168
172
  if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? && settings.idp_cert_multi.nil?
169
173
  return append_error("No fingerprint or certificate on settings of the logout response")
@@ -228,13 +232,19 @@ module OneLogin
228
232
  :raw_sig_alg => options[:raw_get_params]['SigAlg']
229
233
  )
230
234
 
235
+ expired = false
231
236
  if idp_certs.nil? || idp_certs[:signing].empty?
232
237
  valid = OneLogin::RubySaml::Utils.verify_signature(
233
- :cert => settings.get_idp_cert,
238
+ :cert => idp_cert,
234
239
  :sig_alg => options[:get_params]['SigAlg'],
235
240
  :signature => options[:get_params]['Signature'],
236
241
  :query_string => query_string
237
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
238
248
  else
239
249
  valid = false
240
250
  idp_certs[:signing].each do |signing_idp_cert|
@@ -245,11 +255,20 @@ module OneLogin
245
255
  :query_string => query_string
246
256
  )
247
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
248
263
  break
249
264
  end
250
265
  end
251
266
  end
252
267
 
268
+ if expired
269
+ error_msg = "IdP x509 certificate expired"
270
+ return append_error(error_msg)
271
+ end
253
272
  unless valid
254
273
  error_msg = "Invalid Signature on Logout Response"
255
274
  return append_error(error_msg)
@@ -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", {
@@ -34,7 +34,7 @@ module OneLogin
34
34
  # This is not a whitelist to allow people extending OneLogin::RubySaml:Response
35
35
  # and pass custom options
36
36
  AVAILABLE_OPTIONS = [
37
- :allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_authnstatement, :skip_conditions,
37
+ :allowed_clock_drift, :check_duplicated_attributes, :matches_request_id, :settings, :skip_audience, :skip_authnstatement, :skip_conditions,
38
38
  :skip_destination, :skip_recipient_check, :skip_subject_confirmation
39
39
  ]
40
40
  # TODO: Update the comment on initialize to describe every option
@@ -47,6 +47,8 @@ module OneLogin
47
47
  # or :matches_request_id that will validate that the response matches the ID of the request,
48
48
  # or skip the subject confirmation validation with the :skip_subject_confirmation option
49
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
+ #
50
52
  def initialize(response, options = {})
51
53
  raise ArgumentError.new("Response cannot be nil") if response.nil?
52
54
 
@@ -341,6 +343,28 @@ module OneLogin
341
343
  return options[:allowed_clock_drift].to_f
342
344
  end
343
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
366
+ end
367
+
344
368
  private
345
369
 
346
370
  # Validates the SAML Response (calls several validation methods)
@@ -435,7 +459,7 @@ module OneLogin
435
459
  # @return [Boolean] True if the SAML Response contains an ID, otherwise returns False
436
460
  #
437
461
  def validate_id
438
- unless id(document)
462
+ unless response_id
439
463
  return append_error("Missing ID attribute on SAML Response")
440
464
  end
441
465
 
@@ -584,16 +608,18 @@ module OneLogin
584
608
  end
585
609
 
586
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,
587
612
  # If fails, the error is added to the errors array
588
613
  # @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True
589
614
  # @raise [ValidationError] if soft == false and validation fails
590
615
  #
591
616
  def validate_audience
592
- 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?
593
619
 
594
- unless audiences.include? settings.issuer
620
+ unless audiences.include? settings.sp_entity_id
595
621
  s = audiences.count > 1 ? 's' : '';
596
- error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.issuer}"
622
+ error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.sp_entity_id}"
597
623
  return append_error(error_msg)
598
624
  end
599
625
 
@@ -781,8 +807,8 @@ module OneLogin
781
807
  return append_error("An empty NameID value found")
782
808
  end
783
809
 
784
- unless settings.issuer.nil? || settings.issuer.empty? || name_id_spnamequalifier.nil? || name_id_spnamequalifier.empty?
785
- 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
786
812
  return append_error("The SPNameQualifier value mistmatch the SP entityID value.")
787
813
  end
788
814
  end
@@ -802,7 +828,7 @@ module OneLogin
802
828
  # otherwise, review if the decrypted assertion contains a signature
803
829
  sig_elements = REXML::XPath.match(
804
830
  document,
805
- "/p:Response[@ID=$id]/ds:Signature]",
831
+ "/p:Response[@ID=$id]/ds:Signature",
806
832
  { "p" => PROTOCOL, "ds" => DSIG },
807
833
  { 'id' => document.signed_element_id }
808
834
  )
@@ -821,7 +847,7 @@ module OneLogin
821
847
  end
822
848
 
823
849
  if sig_elements.size != 1
824
- if sig_elements.size == 0
850
+ if sig_elements.size == 0
825
851
  append_error("Signed element id ##{doc.signed_element_id} is not found")
826
852
  else
827
853
  append_error("Signed element id ##{doc.signed_element_id} is found more than once")
@@ -829,25 +855,51 @@ module OneLogin
829
855
  return append_error(error_msg)
830
856
  end
831
857
 
858
+ old_errors = @errors.clone
859
+
832
860
  idp_certs = settings.get_idp_cert_multi
833
861
  if idp_certs.nil? || idp_certs[:signing].empty?
834
862
  opts = {}
835
863
  opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
836
- opts[:cert] = settings.get_idp_cert
864
+ idp_cert = settings.get_idp_cert
837
865
  fingerprint = settings.get_fingerprint
866
+ opts[:cert] = idp_cert
838
867
 
839
- 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
840
876
  return append_error(error_msg)
841
877
  end
842
878
  else
843
879
  valid = false
880
+ expired = false
844
881
  idp_certs[:signing].each do |idp_cert|
845
- valid = doc.validate_document_with_cert(idp_cert)
882
+ valid = doc.validate_document_with_cert(idp_cert, true)
846
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
847
892
  break
848
893
  end
894
+
895
+ end
896
+ if expired
897
+ error_msg = "IdP x509 certificate expired"
898
+ return append_error(error_msg)
849
899
  end
850
900
  unless valid
901
+ # Remove duplicated errors
902
+ @errors = @errors.uniq
851
903
  return append_error(error_msg)
852
904
  end
853
905
  end
@@ -948,17 +1000,6 @@ module OneLogin
948
1000
  XMLSecurity::SignedDocument.new(response_node.to_s)
949
1001
  end
950
1002
 
951
- # Checks if the SAML Response contains or not an EncryptedAssertion element
952
- # @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
953
- #
954
- def assertion_encrypted?
955
- ! REXML::XPath.first(
956
- document,
957
- "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
958
- { "p" => PROTOCOL, "a" => ASSERTION }
959
- ).nil?
960
- end
961
-
962
1003
  # Decrypts an EncryptedAssertion element
963
1004
  # @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element
964
1005
  # @return [REXML::Document] The decrypted EncryptedAssertion element