ruby-saml 1.7.2 → 1.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +37 -15
  3. data/README.md +127 -25
  4. data/changelog.md +61 -0
  5. data/lib/onelogin/ruby-saml/attribute_service.rb +1 -1
  6. data/lib/onelogin/ruby-saml/attributes.rb +24 -1
  7. data/lib/onelogin/ruby-saml/authrequest.rb +29 -6
  8. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +239 -169
  9. data/lib/onelogin/ruby-saml/logging.rb +4 -1
  10. data/lib/onelogin/ruby-saml/logoutrequest.rb +27 -7
  11. data/lib/onelogin/ruby-saml/logoutresponse.rb +32 -16
  12. data/lib/onelogin/ruby-saml/metadata.rb +11 -3
  13. data/lib/onelogin/ruby-saml/response.rb +91 -30
  14. data/lib/onelogin/ruby-saml/saml_message.rb +15 -5
  15. data/lib/onelogin/ruby-saml/setting_error.rb +6 -0
  16. data/lib/onelogin/ruby-saml/settings.rb +82 -9
  17. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +26 -7
  18. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +46 -18
  19. data/lib/onelogin/ruby-saml/utils.rb +87 -10
  20. data/lib/onelogin/ruby-saml/version.rb +1 -1
  21. data/lib/xml_security.rb +39 -12
  22. data/ruby-saml.gemspec +16 -8
  23. metadata +40 -274
  24. data/test/certificates/certificate1 +0 -12
  25. data/test/certificates/certificate_without_head_foot +0 -1
  26. data/test/certificates/formatted_certificate +0 -14
  27. data/test/certificates/formatted_chained_certificate +0 -42
  28. data/test/certificates/formatted_private_key +0 -12
  29. data/test/certificates/formatted_rsa_private_key +0 -12
  30. data/test/certificates/invalid_certificate1 +0 -1
  31. data/test/certificates/invalid_certificate2 +0 -1
  32. data/test/certificates/invalid_certificate3 +0 -12
  33. data/test/certificates/invalid_chained_certificate1 +0 -1
  34. data/test/certificates/invalid_private_key1 +0 -1
  35. data/test/certificates/invalid_private_key2 +0 -1
  36. data/test/certificates/invalid_private_key3 +0 -10
  37. data/test/certificates/invalid_rsa_private_key1 +0 -1
  38. data/test/certificates/invalid_rsa_private_key2 +0 -1
  39. data/test/certificates/invalid_rsa_private_key3 +0 -10
  40. data/test/certificates/ruby-saml-2.crt +0 -15
  41. data/test/certificates/ruby-saml.crt +0 -14
  42. data/test/certificates/ruby-saml.key +0 -15
  43. data/test/idp_metadata_parser_test.rb +0 -568
  44. data/test/logging_test.rb +0 -62
  45. data/test/logout_requests/invalid_slo_request.xml +0 -6
  46. data/test/logout_requests/slo_request.xml +0 -4
  47. data/test/logout_requests/slo_request.xml.base64 +0 -1
  48. data/test/logout_requests/slo_request_deflated.xml.base64 +0 -1
  49. data/test/logout_requests/slo_request_with_name_id_format.xml +0 -4
  50. data/test/logout_requests/slo_request_with_session_index.xml +0 -5
  51. data/test/logout_responses/logoutresponse_fixtures.rb +0 -67
  52. data/test/logoutrequest_test.rb +0 -212
  53. data/test/logoutresponse_test.rb +0 -402
  54. data/test/metadata/idp_descriptor.xml +0 -26
  55. data/test/metadata/idp_descriptor_2.xml +0 -56
  56. data/test/metadata/idp_descriptor_3.xml +0 -14
  57. data/test/metadata/idp_metadata_different_sign_and_encrypt_cert.xml +0 -72
  58. data/test/metadata/idp_metadata_multi_certs.xml +0 -75
  59. data/test/metadata/idp_metadata_multi_signing_certs.xml +0 -52
  60. data/test/metadata/idp_metadata_same_sign_and_encrypt_cert.xml +0 -71
  61. data/test/metadata/idp_multiple_descriptors.xml +0 -53
  62. data/test/metadata/no_idp_descriptor.xml +0 -21
  63. data/test/metadata_test.rb +0 -331
  64. data/test/request_test.rb +0 -296
  65. data/test/response_test.rb +0 -1535
  66. data/test/responses/adfs_response_sha1.xml +0 -46
  67. data/test/responses/adfs_response_sha256.xml +0 -46
  68. data/test/responses/adfs_response_sha384.xml +0 -46
  69. data/test/responses/adfs_response_sha512.xml +0 -46
  70. data/test/responses/adfs_response_xmlns.xml +0 -45
  71. data/test/responses/attackxee.xml +0 -13
  72. data/test/responses/invalids/duplicated_attributes.xml.base64 +0 -1
  73. data/test/responses/invalids/empty_destination.xml.base64 +0 -1
  74. data/test/responses/invalids/empty_nameid.xml.base64 +0 -1
  75. data/test/responses/invalids/encrypted_new_attack.xml.base64 +0 -1
  76. data/test/responses/invalids/invalid_audience.xml.base64 +0 -1
  77. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  78. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  79. data/test/responses/invalids/invalid_signature_position.xml.base64 +0 -1
  80. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +0 -1
  81. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +0 -1
  82. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +0 -1
  83. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +0 -1
  84. data/test/responses/invalids/multiple_assertions.xml.base64 +0 -2
  85. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  86. data/test/responses/invalids/no_authnstatement.xml.base64 +0 -1
  87. data/test/responses/invalids/no_conditions.xml.base64 +0 -1
  88. data/test/responses/invalids/no_id.xml.base64 +0 -1
  89. data/test/responses/invalids/no_issuer_assertion.xml.base64 +0 -1
  90. data/test/responses/invalids/no_issuer_response.xml.base64 +0 -1
  91. data/test/responses/invalids/no_nameid.xml.base64 +0 -1
  92. data/test/responses/invalids/no_saml2.xml.base64 +0 -1
  93. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  94. data/test/responses/invalids/no_status.xml.base64 +0 -1
  95. data/test/responses/invalids/no_status_code.xml.base64 +0 -1
  96. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +0 -1
  97. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +0 -1
  98. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +0 -1
  99. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  100. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  101. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  102. data/test/responses/invalids/status_code_responder.xml.base64 +0 -1
  103. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +0 -1
  104. data/test/responses/invalids/wrong_spnamequalifier.xml.base64 +0 -1
  105. data/test/responses/no_signature_ns.xml +0 -48
  106. data/test/responses/open_saml_response.xml +0 -56
  107. data/test/responses/response_assertion_wrapped.xml.base64 +0 -93
  108. data/test/responses/response_audience_self_closed_tag.xml.base64 +0 -1
  109. data/test/responses/response_double_status_code.xml.base64 +0 -1
  110. data/test/responses/response_encrypted_attrs.xml.base64 +0 -1
  111. data/test/responses/response_encrypted_nameid.xml.base64 +0 -1
  112. data/test/responses/response_eval.xml +0 -7
  113. data/test/responses/response_no_cert_and_encrypted_attrs.xml +0 -29
  114. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  115. data/test/responses/response_unsigned_xml_base64 +0 -1
  116. data/test/responses/response_with_ampersands.xml +0 -139
  117. data/test/responses/response_with_ampersands.xml.base64 +0 -93
  118. data/test/responses/response_with_ds_namespace_at_the_root.xml.base64 +0 -1
  119. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  120. data/test/responses/response_with_multiple_attribute_values.xml +0 -67
  121. data/test/responses/response_with_retrieval_method.xml +0 -26
  122. data/test/responses/response_with_saml2_namespace.xml.base64 +0 -102
  123. data/test/responses/response_with_signed_assertion.xml.base64 +0 -66
  124. data/test/responses/response_with_signed_assertion_2.xml.base64 +0 -1
  125. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  126. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  127. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  128. data/test/responses/response_without_attributes.xml.base64 +0 -79
  129. data/test/responses/response_without_reference_uri.xml.base64 +0 -1
  130. data/test/responses/response_wrapped.xml.base64 +0 -150
  131. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +0 -1
  132. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  133. data/test/responses/signed_nameid_in_atts.xml +0 -47
  134. data/test/responses/signed_unqual_nameid_in_atts.xml +0 -47
  135. data/test/responses/simple_saml_php.xml +0 -71
  136. data/test/responses/starfield_response.xml.base64 +0 -1
  137. data/test/responses/test_sign.xml +0 -43
  138. data/test/responses/unsigned_encrypted_adfs.xml +0 -23
  139. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +0 -1
  140. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +0 -1
  141. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +0 -1
  142. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +0 -1
  143. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +0 -1
  144. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +0 -1
  145. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +0 -1
  146. data/test/responses/valid_response.xml.base64 +0 -1
  147. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  148. data/test/saml_message_test.rb +0 -56
  149. data/test/settings_test.rb +0 -301
  150. data/test/slo_logoutrequest_test.rb +0 -448
  151. data/test/slo_logoutresponse_test.rb +0 -185
  152. data/test/test_helper.rb +0 -323
  153. data/test/utils_test.rb +0 -254
  154. data/test/xml_security_test.rb +0 -421
@@ -1,6 +1,4 @@
1
1
  require "base64"
2
- require "zlib"
3
- require "cgi"
4
2
  require "net/http"
5
3
  require "net/https"
6
4
  require "rexml/document"
@@ -15,15 +13,37 @@ module OneLogin
15
13
  #
16
14
  class IdpMetadataParser
17
15
 
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"
16
+ module SamlMetadata
17
+ module Vocabulary
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
+ end
23
+
24
+ NAMESPACE = {
25
+ "md" => Vocabulary::METADATA,
26
+ "NameFormat" => Vocabulary::NAME_FORMAT,
27
+ "saml" => Vocabulary::SAML_ASSERTION,
28
+ "ds" => Vocabulary::DSIG
29
+ }.freeze
30
+ end
22
31
 
32
+ include SamlMetadata::Vocabulary
23
33
  attr_reader :document
24
34
  attr_reader :response
25
35
  attr_reader :options
26
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
+
27
47
  # Parse the Identity Provider metadata and update the settings with the
28
48
  # IdP values
29
49
  #
@@ -58,8 +78,25 @@ module OneLogin
58
78
  #
59
79
  # @raise [HttpError] Failure to fetch remote IdP metadata
60
80
  def parse_remote_to_hash(url, validate_cert = true, options = {})
81
+ parse_remote_to_array(url, validate_cert, options)[0]
82
+ end
83
+
84
+ # Parse all Identity Provider metadata and return the results as Array
85
+ #
86
+ # @param url [String] Url where the XML of the Identity Provider Metadata is published.
87
+ # @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
88
+ #
89
+ # @param options [Hash] options used for parsing the metadata
90
+ # @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
91
+ # @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
92
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, all found IdPs are returned.
93
+ #
94
+ # @return [Array<Hash>]
95
+ #
96
+ # @raise [HttpError] Failure to fetch remote IdP metadata
97
+ def parse_remote_to_array(url, validate_cert = true, options = {})
61
98
  idp_metadata = get_idp_metadata(url, validate_cert)
62
- parse_to_hash(idp_metadata, options)
99
+ parse_to_array(idp_metadata, options)
63
100
  end
64
101
 
65
102
  # Parse the Identity Provider metadata and update the settings with the IdP values
@@ -76,6 +113,16 @@ module OneLogin
76
113
  def parse(idp_metadata, options = {})
77
114
  parsed_metadata = parse_to_hash(idp_metadata, options)
78
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
+
79
126
  settings = options[:settings]
80
127
 
81
128
  if settings.nil?
@@ -98,28 +145,35 @@ module OneLogin
98
145
  #
99
146
  # @return [Hash]
100
147
  def parse_to_hash(idp_metadata, options = {})
148
+ parse_to_array(idp_metadata, options)[0]
149
+ end
150
+
151
+ # Parse all Identity Provider metadata and return the results as Array
152
+ #
153
+ # @param idp_metadata [String]
154
+ #
155
+ # @param options [Hash] options used for parsing the metadata and the returned Settings instance
156
+ # @option options [Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
157
+ # @option options [Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
158
+ # @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When ommitted, all found IdPs are returned.
159
+ #
160
+ # @return [Array<Hash>]
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 = {})
101
166
  @document = REXML::Document.new(idp_metadata)
102
167
  @options = options
103
- @entity_descriptor = nil
104
168
 
105
- if idpsso_descriptor.nil?
169
+ idpsso_descriptors = self.class.get_idps(@document, options[:entity_id])
170
+ if !idpsso_descriptors.any?
106
171
  raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
107
172
  end
108
173
 
109
- {
110
- :idp_entity_id => idp_entity_id,
111
- :name_identifier_format => idp_name_id_format,
112
- :idp_sso_target_url => single_signon_service_url(options),
113
- :idp_slo_target_url => single_logout_service_url(options),
114
- :idp_attribute_names => attribute_names,
115
- :idp_cert => nil,
116
- :idp_cert_fingerprint => nil,
117
- :idp_cert_multi => nil
118
- }.tap do |response_hash|
119
- merge_certificates_into(response_hash) unless certificates.nil?
120
- end
174
+ return idpsso_descriptors.map{|id| IdpMetadata.new(id, id.parent.attributes["entityID"])}
121
175
  end
122
-
176
+
123
177
  private
124
178
 
125
179
  # Retrieve the remote IdP metadata from the URL or a cached copy.
@@ -145,7 +199,8 @@ module OneLogin
145
199
  end
146
200
 
147
201
  get = Net::HTTP::Get.new(uri.request_uri)
148
- response = http.request(get)
202
+ get.basic_auth uri.user, uri.password if uri.user
203
+ @response = http.request(get)
149
204
  return response.body if response.is_a? Net::HTTPSuccess
150
205
 
151
206
  raise OneLogin::RubySaml::HttpError.new(
@@ -153,130 +208,154 @@ module OneLogin
153
208
  )
154
209
  end
155
210
 
156
- def entity_descriptor
157
- @entity_descriptor ||= REXML::XPath.first(
158
- document,
159
- entity_descriptor_path,
160
- namespace
161
- )
162
- end
211
+ class IdpMetadata
212
+ attr_reader :idpsso_descriptor, :entity_id
213
+
214
+ def initialize(idpsso_descriptor, entity_id)
215
+ @idpsso_descriptor = idpsso_descriptor
216
+ @entity_id = entity_id
217
+ end
163
218
 
164
- def entity_descriptor_path
165
- path = "//md:EntityDescriptor"
166
- entity_id = options[:entity_id]
167
- return path unless entity_id
168
- path << "[@entityID=\"#{entity_id}\"]"
169
- end
219
+ def to_hash(options = {})
220
+ {
221
+ :idp_entity_id => @entity_id,
222
+ :name_identifier_format => idp_name_id_format,
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),
226
+ :idp_attribute_names => attribute_names,
227
+ :idp_cert => nil,
228
+ :idp_cert_fingerprint => nil,
229
+ :idp_cert_multi => nil,
230
+ :valid_until => valid_until,
231
+ :cache_duration => cache_duration,
232
+ }.tap do |response_hash|
233
+ merge_certificates_into(response_hash) unless certificates.nil?
234
+ end
235
+ end
170
236
 
171
- def idpsso_descriptor
172
- unless entity_descriptor.nil?
173
- return REXML::XPath.first(
174
- entity_descriptor,
175
- "md:IDPSSODescriptor",
176
- namespace
237
+ # @return [String|nil] IdP Name ID Format value if exists
238
+ #
239
+ def idp_name_id_format
240
+ node = REXML::XPath.first(
241
+ @idpsso_descriptor,
242
+ "md:NameIDFormat",
243
+ SamlMetadata::NAMESPACE
177
244
  )
245
+ Utils.element_text(node)
178
246
  end
179
- end
180
247
 
181
- # @return [String|nil] IdP Entity ID value if exists
182
- #
183
- def idp_entity_id
184
- entity_descriptor.attributes["entityID"]
185
- end
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
186
254
 
187
- # @return [String|nil] IdP Name ID Format value if exists
188
- #
189
- def idp_name_id_format
190
- node = REXML::XPath.first(
191
- entity_descriptor,
192
- "md:IDPSSODescriptor/md:NameIDFormat",
193
- namespace
194
- )
195
- Utils.element_text(node)
196
- end
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
197
261
 
198
- # @param binding_priority [Array]
199
- # @return [String|nil] SingleSignOnService binding if exists
200
- #
201
- def single_signon_service_binding(binding_priority = nil)
202
- nodes = REXML::XPath.match(
203
- entity_descriptor,
204
- "md:IDPSSODescriptor/md:SingleSignOnService/@Binding",
205
- namespace
206
- )
207
- if binding_priority
208
- values = nodes.map(&:value)
209
- binding_priority.detect{ |binding| values.include? binding }
210
- else
211
- nodes.first.value if nodes.any?
262
+ # @param binding_priority [Array]
263
+ # @return [String|nil] SingleSignOnService binding if exists
264
+ #
265
+ def single_signon_service_binding(binding_priority = nil)
266
+ nodes = REXML::XPath.match(
267
+ @idpsso_descriptor,
268
+ "md:SingleSignOnService/@Binding",
269
+ SamlMetadata::NAMESPACE
270
+ )
271
+ if binding_priority
272
+ values = nodes.map(&:value)
273
+ binding_priority.detect{ |binding| values.include? binding }
274
+ else
275
+ nodes.first.value if nodes.any?
276
+ end
212
277
  end
213
- end
214
278
 
215
- # @param options [Hash]
216
- # @return [String|nil] SingleSignOnService endpoint if exists
217
- #
218
- def single_signon_service_url(options = {})
219
- binding = single_signon_service_binding(options[:sso_binding])
220
- unless binding.nil?
279
+ # @param options [Hash]
280
+ # @return [String|nil] SingleSignOnService endpoint if exists
281
+ #
282
+ def single_signon_service_url(options = {})
283
+ binding = single_signon_service_binding(options[:sso_binding])
284
+ return if binding.nil?
285
+
221
286
  node = REXML::XPath.first(
222
- entity_descriptor,
223
- "md:IDPSSODescriptor/md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
224
- namespace
287
+ @idpsso_descriptor,
288
+ "md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
289
+ SamlMetadata::NAMESPACE
225
290
  )
226
291
  return node.value if node
227
292
  end
228
- end
229
293
 
230
- # @param binding_priority [Array]
231
- # @return [String|nil] SingleLogoutService binding if exists
232
- #
233
- def single_logout_service_binding(binding_priority = nil)
234
- nodes = REXML::XPath.match(
235
- entity_descriptor,
236
- "md:IDPSSODescriptor/md:SingleLogoutService/@Binding",
237
- namespace
238
- )
239
- if binding_priority
240
- values = nodes.map(&:value)
241
- binding_priority.detect{ |binding| values.include? binding }
242
- else
243
- nodes.first.value if nodes.any?
294
+ # @param binding_priority [Array]
295
+ # @return [String|nil] SingleLogoutService binding if exists
296
+ #
297
+ def single_logout_service_binding(binding_priority = nil)
298
+ nodes = REXML::XPath.match(
299
+ @idpsso_descriptor,
300
+ "md:SingleLogoutService/@Binding",
301
+ SamlMetadata::NAMESPACE
302
+ )
303
+ if binding_priority
304
+ values = nodes.map(&:value)
305
+ binding_priority.detect{ |binding| values.include? binding }
306
+ else
307
+ nodes.first.value if nodes.any?
308
+ end
244
309
  end
245
- end
246
310
 
247
- # @param options [Hash]
248
- # @return [String|nil] SingleLogoutService endpoint if exists
249
- #
250
- def single_logout_service_url(options = {})
251
- binding = single_logout_service_binding(options[:slo_binding])
252
- unless binding.nil?
311
+ # @param options [Hash]
312
+ # @return [String|nil] SingleLogoutService endpoint if exists
313
+ #
314
+ def single_logout_service_url(options = {})
315
+ binding = single_logout_service_binding(options[:slo_binding])
316
+ return if binding.nil?
317
+
253
318
  node = REXML::XPath.first(
254
- entity_descriptor,
255
- "md:IDPSSODescriptor/md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
256
- namespace
319
+ @idpsso_descriptor,
320
+ "md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
321
+ SamlMetadata::NAMESPACE
257
322
  )
258
323
  return node.value if node
259
324
  end
260
- end
261
325
 
262
- # @return [String|nil] Unformatted Certificate if exists
263
- #
264
- def certificates
265
- @certificates ||= begin
266
- signing_nodes = REXML::XPath.match(
267
- entity_descriptor,
268
- "md:IDPSSODescriptor/md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
269
- namespace
270
- )
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?
271
332
 
272
- encryption_nodes = REXML::XPath.match(
273
- entity_descriptor,
274
- "md:IDPSSODescriptor/md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
275
- namespace
333
+ node = REXML::XPath.first(
334
+ @idpsso_descriptor,
335
+ "md:SingleLogoutService[@Binding=\"#{binding}\"]/@ResponseLocation",
336
+ SamlMetadata::NAMESPACE
276
337
  )
338
+ return node.value if node
339
+ end
340
+
341
+ # @return [String|nil] Unformatted Certificate if exists
342
+ #
343
+ def certificates
344
+ @certificates ||= begin
345
+ signing_nodes = REXML::XPath.match(
346
+ @idpsso_descriptor,
347
+ "md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
348
+ SamlMetadata::NAMESPACE
349
+ )
350
+
351
+ encryption_nodes = REXML::XPath.match(
352
+ @idpsso_descriptor,
353
+ "md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
354
+ SamlMetadata::NAMESPACE
355
+ )
356
+
357
+ return nil if signing_nodes.empty? && encryption_nodes.empty?
277
358
 
278
- certs = nil
279
- unless signing_nodes.empty? && encryption_nodes.empty?
280
359
  certs = {}
281
360
  unless signing_nodes.empty?
282
361
  certs['signing'] = []
@@ -291,71 +370,62 @@ module OneLogin
291
370
  certs['encryption'] << Utils.element_text(cert_node)
292
371
  end
293
372
  end
373
+ certs
294
374
  end
295
- certs
296
375
  end
297
- end
298
376
 
299
- # @return [String|nil] the fingerpint of the X509Certificate if it exists
300
- #
301
- def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1)
302
- @fingerprint ||= begin
303
- if certificate
377
+ # @return [String|nil] the fingerpint of the X509Certificate if it exists
378
+ #
379
+ def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1)
380
+ @fingerprint ||= begin
381
+ return unless certificate
382
+
304
383
  cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))
305
384
 
306
385
  fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
307
386
  fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
308
387
  end
309
388
  end
310
- end
311
389
 
312
- # @return [Array] the names of all SAML attributes if any exist
313
- #
314
- def attribute_names
315
- nodes = REXML::XPath.match(
316
- entity_descriptor,
317
- "md:IDPSSODescriptor/saml:Attribute/@Name",
318
- namespace
319
- )
320
- nodes.map(&:value)
321
- end
322
-
323
- def namespace
324
- {
325
- "md" => METADATA,
326
- "NameFormat" => NAME_FORMAT,
327
- "saml" => SAML_ASSERTION,
328
- "ds" => DSIG
329
- }
330
- end
390
+ # @return [Array] the names of all SAML attributes if any exist
391
+ #
392
+ def attribute_names
393
+ nodes = REXML::XPath.match(
394
+ @idpsso_descriptor ,
395
+ "saml:Attribute/@Name",
396
+ SamlMetadata::NAMESPACE
397
+ )
398
+ nodes.map(&:value)
399
+ end
331
400
 
332
- def merge_certificates_into(parsed_metadata)
333
- if (certificates.size == 1 &&
401
+ def merge_certificates_into(parsed_metadata)
402
+ if (certificates.size == 1 &&
334
403
  (certificates_has_one('signing') || certificates_has_one('encryption'))) ||
335
404
  (certificates_has_one('signing') && certificates_has_one('encryption') &&
336
405
  certificates["signing"][0] == certificates["encryption"][0])
337
406
 
338
- if certificates.key?("signing")
339
- parsed_metadata[:idp_cert] = certificates["signing"][0]
340
- parsed_metadata[:idp_cert_fingerprint] = fingerprint(
341
- parsed_metadata[:idp_cert],
342
- parsed_metadata[:idp_cert_fingerprint_algorithm]
343
- )
407
+ if certificates.key?("signing")
408
+ parsed_metadata[:idp_cert] = certificates["signing"][0]
409
+ parsed_metadata[:idp_cert_fingerprint] = fingerprint(
410
+ parsed_metadata[:idp_cert],
411
+ parsed_metadata[:idp_cert_fingerprint_algorithm]
412
+ )
413
+ else
414
+ parsed_metadata[:idp_cert] = certificates["encryption"][0]
415
+ parsed_metadata[:idp_cert_fingerprint] = fingerprint(
416
+ parsed_metadata[:idp_cert],
417
+ parsed_metadata[:idp_cert_fingerprint_algorithm]
418
+ )
419
+ end
344
420
  else
345
- parsed_metadata[:idp_cert] = certificates["encryption"][0]
346
- parsed_metadata[:idp_cert_fingerprint] = fingerprint(
347
- parsed_metadata[:idp_cert],
348
- parsed_metadata[:idp_cert_fingerprint_algorithm]
349
- )
421
+ # symbolize keys of certificates and pass it on
422
+ parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
350
423
  end
351
- else
352
- # symbolize keys of certificates and pass it on
353
- parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
354
424
  end
355
- end
356
425
 
357
- def certificates_has_one(key)
358
- certificates.key?(key) && certificates[key].size == 1
426
+ def certificates_has_one(key)
427
+ certificates.key?(key) && certificates[key].size == 1
428
+ end
359
429
  end
360
430
 
361
431
  def merge_parsed_metadata_into(settings, parsed_metadata)
@@ -7,7 +7,10 @@ module OneLogin
7
7
  DEFAULT_LOGGER = ::Logger.new(STDOUT)
8
8
 
9
9
  def self.logger
10
- @logger || (defined?(::Rails) && Rails.respond_to?(:logger) && Rails.logger) || DEFAULT_LOGGER
10
+ @logger ||= begin
11
+ (defined?(::Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
12
+ DEFAULT_LOGGER
13
+ end
11
14
  end
12
15
 
13
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.
@@ -47,6 +53,11 @@ module OneLogin
47
53
  # conflicts so this line will solve them.
48
54
  relay_state = params[:RelayState] || params['RelayState']
49
55
 
56
+ if relay_state.nil?
57
+ params.delete(:RelayState)
58
+ params.delete('RelayState')
59
+ end
60
+
50
61
  request_doc = create_logout_request_xml_doc(settings)
51
62
  request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
52
63
 
@@ -84,6 +95,11 @@ module OneLogin
84
95
  # @return [String] The SAMLRequest String.
85
96
  #
86
97
  def create_logout_request_xml_doc(settings)
98
+ document = create_xml_document(settings)
99
+ sign_document(document, settings)
100
+ end
101
+
102
+ def create_xml_document(settings)
87
103
  time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
88
104
 
89
105
  request_doc = XMLSecurity::Document.new
@@ -93,11 +109,11 @@ module OneLogin
93
109
  root.attributes['ID'] = uuid
94
110
  root.attributes['IssueInstant'] = time
95
111
  root.attributes['Version'] = "2.0"
96
- 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?
97
113
 
98
- if settings.issuer
114
+ if settings.sp_entity_id
99
115
  issuer = root.add_element "saml:Issuer"
100
- issuer.text = settings.issuer
116
+ issuer.text = settings.sp_entity_id
101
117
  end
102
118
 
103
119
  nameid = root.add_element "saml:NameID"
@@ -117,14 +133,18 @@ module OneLogin
117
133
  sessionindex.text = settings.sessionindex
118
134
  end
119
135
 
136
+ request_doc
137
+ end
138
+
139
+ def sign_document(document, settings)
120
140
  # embed signature
121
141
  if settings.security[:logout_requests_signed] && settings.private_key && settings.certificate && settings.security[:embed_sign]
122
142
  private_key = settings.get_sp_key
123
143
  cert = settings.get_sp_cert
124
- request_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
144
+ document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
125
145
  end
126
146
 
127
- request_doc
147
+ document
128
148
  end
129
149
  end
130
150
  end