ruby-saml 1.10.0 → 1.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +28 -14
  3. data/README.md +96 -26
  4. data/changelog.md +37 -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 +71 -22
  8. data/lib/onelogin/ruby-saml/logging.rb +3 -3
  9. data/lib/onelogin/ruby-saml/logoutrequest.rb +9 -3
  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 +68 -22
  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 +29 -16
  18. data/lib/onelogin/ruby-saml/utils.rb +74 -1
  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 +36 -282
  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 -1619
  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
@@ -12,12 +12,13 @@ module OneLogin
12
12
  # Auxiliary class to retrieve and parse the Identity Provider Metadata
13
13
  #
14
14
  class IdpMetadataParser
15
+
15
16
  module SamlMetadata
16
17
  module Vocabulary
17
- METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
18
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
19
- NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
20
- 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
21
22
  end
22
23
 
23
24
  NAMESPACE = {
@@ -25,7 +26,7 @@ module OneLogin
25
26
  "NameFormat" => Vocabulary::NAME_FORMAT,
26
27
  "saml" => Vocabulary::SAML_ASSERTION,
27
28
  "ds" => Vocabulary::DSIG
28
- }
29
+ }.freeze
29
30
  end
30
31
 
31
32
  include SamlMetadata::Vocabulary
@@ -33,6 +34,16 @@ module OneLogin
33
34
  attr_reader :response
34
35
  attr_reader :options
35
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
+
36
47
  # Parse the Identity Provider metadata and update the settings with the
37
48
  # IdP values
38
49
  #
@@ -102,6 +113,16 @@ module OneLogin
102
113
  def parse(idp_metadata, options = {})
103
114
  parsed_metadata = parse_to_hash(idp_metadata, options)
104
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
+
105
126
  settings = options[:settings]
106
127
 
107
128
  if settings.nil?
@@ -138,17 +159,21 @@ module OneLogin
138
159
  #
139
160
  # @return [Array<Hash>]
140
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 = {})
141
166
  @document = REXML::Document.new(idp_metadata)
142
167
  @options = options
143
168
 
144
- idpsso_descriptors = IdpMetadata::get_idps(@document, options[:entity_id])
169
+ idpsso_descriptors = self.class.get_idps(@document, options[:entity_id])
145
170
  if !idpsso_descriptors.any?
146
171
  raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
147
172
  end
148
173
 
149
- 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"])}
150
175
  end
151
-
176
+
152
177
  private
153
178
 
154
179
  # Retrieve the remote IdP metadata from the URL or a cached copy.
@@ -174,6 +199,7 @@ module OneLogin
174
199
  end
175
200
 
176
201
  get = Net::HTTP::Get.new(uri.request_uri)
202
+ get.basic_auth uri.user, uri.password if uri.user
177
203
  @response = http.request(get)
178
204
  return response.body if response.is_a? Net::HTTPSuccess
179
205
 
@@ -183,15 +209,8 @@ module OneLogin
183
209
  end
184
210
 
185
211
  class IdpMetadata
186
- def self.get_idps(metadata_document, only_entity_id=nil)
187
- path = "//md:EntityDescriptor#{only_entity_id && '[@entityID="' + only_entity_id + '"]'}/md:IDPSSODescriptor"
188
- REXML::XPath.match(
189
- metadata_document,
190
- path,
191
- SamlMetadata::NAMESPACE
192
- )
193
- end
194
-
212
+ attr_reader :idpsso_descriptor, :entity_id
213
+
195
214
  def initialize(idpsso_descriptor, entity_id)
196
215
  @idpsso_descriptor = idpsso_descriptor
197
216
  @entity_id = entity_id
@@ -201,12 +220,15 @@ module OneLogin
201
220
  {
202
221
  :idp_entity_id => @entity_id,
203
222
  :name_identifier_format => idp_name_id_format,
204
- :idp_sso_target_url => single_signon_service_url(options),
205
- :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),
206
226
  :idp_attribute_names => attribute_names,
207
227
  :idp_cert => nil,
208
228
  :idp_cert_fingerprint => nil,
209
- :idp_cert_multi => nil
229
+ :idp_cert_multi => nil,
230
+ :valid_until => valid_until,
231
+ :cache_duration => cache_duration,
210
232
  }.tap do |response_hash|
211
233
  merge_certificates_into(response_hash) unless certificates.nil?
212
234
  end
@@ -223,6 +245,20 @@ module OneLogin
223
245
  Utils.element_text(node)
224
246
  end
225
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
+
226
262
  # @param binding_priority [Array]
227
263
  # @return [String|nil] SingleSignOnService binding if exists
228
264
  #
@@ -287,6 +323,21 @@ module OneLogin
287
323
  return node.value if node
288
324
  end
289
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
+
290
341
  # @return [String|nil] Unformatted Certificate if exists
291
342
  #
292
343
  def certificates
@@ -384,8 +435,6 @@ module OneLogin
384
435
 
385
436
  settings
386
437
  end
387
-
388
- private_constant :SamlMetadata, :IdpMetadata
389
438
  end
390
439
  end
391
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
@@ -33,6 +38,7 @@ module OneLogin
33
38
  params.each_pair do |key, value|
34
39
  request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
35
40
  end
41
+ raise SettingError.new "Invalid settings, idp_slo_target_url is not set!" if settings.idp_slo_target_url.nil? or settings.idp_slo_target_url.empty?
36
42
  @logout_url = settings.idp_slo_target_url + request_params
37
43
  end
38
44
 
@@ -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_target_url unless settings.idp_slo_target_url.nil? or settings.idp_slo_target_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,28 +847,59 @@ module OneLogin
821
847
  end
822
848
 
823
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
824
855
  return append_error(error_msg)
825
856
  end
826
857
 
858
+ old_errors = @errors.clone
859
+
827
860
  idp_certs = settings.get_idp_cert_multi
828
861
  if idp_certs.nil? || idp_certs[:signing].empty?
829
862
  opts = {}
830
863
  opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
831
- opts[:cert] = settings.get_idp_cert
864
+ idp_cert = settings.get_idp_cert
832
865
  fingerprint = settings.get_fingerprint
866
+ opts[:cert] = idp_cert
833
867
 
834
- 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
835
876
  return append_error(error_msg)
836
877
  end
837
878
  else
838
879
  valid = false
880
+ expired = false
839
881
  idp_certs[:signing].each do |idp_cert|
840
- valid = doc.validate_document_with_cert(idp_cert)
882
+ valid = doc.validate_document_with_cert(idp_cert, true)
841
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
842
892
  break
843
893
  end
894
+
895
+ end
896
+ if expired
897
+ error_msg = "IdP x509 certificate expired"
898
+ return append_error(error_msg)
844
899
  end
845
900
  unless valid
901
+ # Remove duplicated errors
902
+ @errors = @errors.uniq
846
903
  return append_error(error_msg)
847
904
  end
848
905
  end
@@ -943,17 +1000,6 @@ module OneLogin
943
1000
  XMLSecurity::SignedDocument.new(response_node.to_s)
944
1001
  end
945
1002
 
946
- # Checks if the SAML Response contains or not an EncryptedAssertion element
947
- # @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
948
- #
949
- def assertion_encrypted?
950
- ! REXML::XPath.first(
951
- document,
952
- "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)",
953
- { "p" => PROTOCOL, "a" => ASSERTION }
954
- ).nil?
955
- end
956
-
957
1003
  # Decrypts an EncryptedAssertion element
958
1004
  # @param encrypted_assertion_node [REXML::Element] The EncryptedAssertion element
959
1005
  # @return [REXML::Document] The decrypted EncryptedAssertion element