ruby-saml 1.10.0 → 1.12.1

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 (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