ruby-saml 1.13.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.github/FUNDING.yml +3 -0
- data/.github/workflows/test.yml +67 -3
- data/CHANGELOG.md +31 -1
- data/LICENSE +2 -1
- data/README.md +141 -40
- data/UPGRADING.md +1 -1
- data/lib/onelogin/ruby-saml/authrequest.rb +8 -9
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +3 -3
- data/lib/onelogin/ruby-saml/logoutrequest.rb +7 -7
- data/lib/onelogin/ruby-saml/logoutresponse.rb +1 -1
- data/lib/onelogin/ruby-saml/metadata.rb +21 -25
- data/lib/onelogin/ruby-saml/response.rb +25 -20
- data/lib/onelogin/ruby-saml/saml_message.rb +2 -3
- data/lib/onelogin/ruby-saml/settings.rb +137 -42
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +39 -33
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +8 -8
- data/lib/onelogin/ruby-saml/utils.rb +96 -26
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/xml_security.rb +22 -9
- data/ruby-saml.gemspec +41 -13
- metadata +50 -40
@@ -613,7 +613,12 @@ module OneLogin
|
|
613
613
|
#
|
614
614
|
def validate_audience
|
615
615
|
return true if options[:skip_audience]
|
616
|
-
return true if
|
616
|
+
return true if settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
|
617
|
+
|
618
|
+
if audiences.empty?
|
619
|
+
return true unless settings.security[:strict_audience_validation]
|
620
|
+
return append_error("Invalid Audiences. The <AudienceRestriction> element contained only empty <Audience> elements. Expected audience #{settings.sp_entity_id}.")
|
621
|
+
end
|
617
622
|
|
618
623
|
unless audiences.include? settings.sp_entity_id
|
619
624
|
s = audiences.count > 1 ? 's' : '';
|
@@ -736,7 +741,7 @@ module OneLogin
|
|
736
741
|
# @return [Boolean] True if the SessionNotOnOrAfter of the AuthnStatement is valid, otherwise (when expired) False if soft=True
|
737
742
|
# @raise [ValidationError] if soft == false and validation fails
|
738
743
|
#
|
739
|
-
def validate_session_expiration
|
744
|
+
def validate_session_expiration
|
740
745
|
return true if session_expires_at.nil?
|
741
746
|
|
742
747
|
now = Time.now.utc
|
@@ -910,9 +915,9 @@ module OneLogin
|
|
910
915
|
begin
|
911
916
|
encrypted_node = xpath_first_from_signed_assertion('/a:Subject/a:EncryptedID')
|
912
917
|
if encrypted_node
|
913
|
-
|
918
|
+
decrypt_nameid(encrypted_node)
|
914
919
|
else
|
915
|
-
|
920
|
+
xpath_first_from_signed_assertion('/a:Subject/a:NameID')
|
916
921
|
end
|
917
922
|
end
|
918
923
|
end
|
@@ -964,7 +969,7 @@ module OneLogin
|
|
964
969
|
# @return [XMLSecurity::SignedDocument] The SAML Response with the assertion decrypted
|
965
970
|
#
|
966
971
|
def generate_decrypted_document
|
967
|
-
if settings.nil? ||
|
972
|
+
if settings.nil? || settings.get_sp_decryption_keys.empty?
|
968
973
|
raise ValidationError.new('An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method')
|
969
974
|
end
|
970
975
|
|
@@ -1007,42 +1012,42 @@ module OneLogin
|
|
1007
1012
|
end
|
1008
1013
|
|
1009
1014
|
# Decrypts an EncryptedID element
|
1010
|
-
# @param
|
1015
|
+
# @param encrypted_id_node [REXML::Element] The EncryptedID element
|
1011
1016
|
# @return [REXML::Document] The decrypted EncrypedtID element
|
1012
1017
|
#
|
1013
|
-
def decrypt_nameid(
|
1014
|
-
decrypt_element(
|
1018
|
+
def decrypt_nameid(encrypted_id_node)
|
1019
|
+
decrypt_element(encrypted_id_node, /(.*<\/(\w+:)?NameID>)/m)
|
1015
1020
|
end
|
1016
1021
|
|
1017
|
-
# Decrypts an
|
1018
|
-
# @param
|
1019
|
-
# @return [REXML::Document] The decrypted
|
1022
|
+
# Decrypts an EncryptedAttribute element
|
1023
|
+
# @param encrypted_attribute_node [REXML::Element] The EncryptedAttribute element
|
1024
|
+
# @return [REXML::Document] The decrypted EncryptedAttribute element
|
1020
1025
|
#
|
1021
|
-
def decrypt_attribute(
|
1022
|
-
decrypt_element(
|
1026
|
+
def decrypt_attribute(encrypted_attribute_node)
|
1027
|
+
decrypt_element(encrypted_attribute_node, /(.*<\/(\w+:)?Attribute>)/m)
|
1023
1028
|
end
|
1024
1029
|
|
1025
1030
|
# Decrypt an element
|
1026
|
-
# @param
|
1027
|
-
# @param
|
1031
|
+
# @param encrypt_node [REXML::Element] The encrypted element
|
1032
|
+
# @param regexp [Regexp] The regular expression to extract the decrypted data
|
1028
1033
|
# @return [REXML::Document] The decrypted element
|
1029
1034
|
#
|
1030
|
-
def decrypt_element(encrypt_node,
|
1031
|
-
if settings.nil? ||
|
1035
|
+
def decrypt_element(encrypt_node, regexp)
|
1036
|
+
if settings.nil? || settings.get_sp_decryption_keys.empty?
|
1032
1037
|
raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
|
1033
1038
|
end
|
1034
1039
|
|
1035
|
-
|
1036
1040
|
if encrypt_node.name == 'EncryptedAttribute'
|
1037
1041
|
node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
|
1038
1042
|
else
|
1039
1043
|
node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'
|
1040
1044
|
end
|
1041
1045
|
|
1042
|
-
elem_plaintext = OneLogin::RubySaml::Utils.
|
1046
|
+
elem_plaintext = OneLogin::RubySaml::Utils.decrypt_multi(encrypt_node, settings.get_sp_decryption_keys)
|
1047
|
+
|
1043
1048
|
# If we get some problematic noise in the plaintext after decrypting.
|
1044
1049
|
# This quick regexp parse will grab only the Element and discard the noise.
|
1045
|
-
elem_plaintext = elem_plaintext.match(
|
1050
|
+
elem_plaintext = elem_plaintext.match(regexp)[0]
|
1046
1051
|
|
1047
1052
|
# To avoid namespace errors if saml namespace is not defined
|
1048
1053
|
# create a parent node first with the namespace defined
|
@@ -4,7 +4,6 @@ require 'base64'
|
|
4
4
|
require 'nokogiri'
|
5
5
|
require 'rexml/document'
|
6
6
|
require 'rexml/xpath'
|
7
|
-
require 'thread'
|
8
7
|
require "onelogin/ruby-saml/error_handling"
|
9
8
|
|
10
9
|
# Only supports SAML 2.0
|
@@ -69,14 +68,14 @@ module OneLogin
|
|
69
68
|
xml = Nokogiri::XML(document.to_s) do |config|
|
70
69
|
config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
|
71
70
|
end
|
72
|
-
rescue
|
71
|
+
rescue StandardError => error
|
73
72
|
return false if soft
|
74
73
|
raise ValidationError.new("XML load failed: #{error.message}")
|
75
74
|
end
|
76
75
|
|
77
76
|
SamlMessage.schema.validate(xml).map do |schema_error|
|
78
77
|
return false if soft
|
79
|
-
raise ValidationError.new("#{schema_error.message}\n\n#{xml
|
78
|
+
raise ValidationError.new("#{schema_error.message}\n\n#{xml}")
|
80
79
|
end
|
81
80
|
end
|
82
81
|
|
@@ -20,7 +20,7 @@ module OneLogin
|
|
20
20
|
end
|
21
21
|
|
22
22
|
config.each do |k,v|
|
23
|
-
acc = "#{k
|
23
|
+
acc = "#{k}=".to_sym
|
24
24
|
if respond_to? acc
|
25
25
|
value = v.is_a?(Hash) ? v.dup : v
|
26
26
|
send(acc, value)
|
@@ -60,8 +60,8 @@ module OneLogin
|
|
60
60
|
attr_accessor :attributes_index
|
61
61
|
attr_accessor :force_authn
|
62
62
|
attr_accessor :certificate
|
63
|
-
attr_accessor :certificate_new
|
64
63
|
attr_accessor :private_key
|
64
|
+
attr_accessor :sp_cert_multi
|
65
65
|
attr_accessor :authn_context
|
66
66
|
attr_accessor :authn_context_comparison
|
67
67
|
attr_accessor :authn_context_decl_ref
|
@@ -70,6 +70,7 @@ module OneLogin
|
|
70
70
|
attr_accessor :security
|
71
71
|
attr_accessor :soft
|
72
72
|
# Deprecated
|
73
|
+
attr_accessor :certificate_new
|
73
74
|
attr_accessor :assertion_consumer_logout_service_url
|
74
75
|
attr_reader :assertion_consumer_logout_service_binding
|
75
76
|
attr_accessor :issuer
|
@@ -180,10 +181,7 @@ module OneLogin
|
|
180
181
|
# @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
|
181
182
|
#
|
182
183
|
def get_idp_cert
|
183
|
-
|
184
|
-
|
185
|
-
formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
|
186
|
-
OpenSSL::X509::Certificate.new(formatted_cert)
|
184
|
+
OneLogin::RubySaml::Utils.build_cert_object(idp_cert)
|
187
185
|
end
|
188
186
|
|
189
187
|
# @return [Hash with 2 arrays of OpenSSL::X509::Certificate] Build multiple IdP certificates from the settings.
|
@@ -191,63 +189,79 @@ module OneLogin
|
|
191
189
|
def get_idp_cert_multi
|
192
190
|
return nil if idp_cert_multi.nil? || idp_cert_multi.empty?
|
193
191
|
|
194
|
-
raise ArgumentError.new("Invalid value for idp_cert_multi")
|
192
|
+
raise ArgumentError.new("Invalid value for idp_cert_multi") unless idp_cert_multi.is_a?(Hash)
|
195
193
|
|
196
194
|
certs = {:signing => [], :encryption => [] }
|
197
195
|
|
198
|
-
|
199
|
-
idp_cert_multi[
|
200
|
-
|
201
|
-
certs[:signing].push(OpenSSL::X509::Certificate.new(formatted_cert))
|
202
|
-
end
|
203
|
-
end
|
196
|
+
[:signing, :encryption].each do |type|
|
197
|
+
certs_for_type = idp_cert_multi[type] || idp_cert_multi[type.to_s]
|
198
|
+
next if !certs_for_type || certs_for_type.empty?
|
204
199
|
|
205
|
-
|
206
|
-
|
207
|
-
formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
|
208
|
-
certs[:encryption].push(OpenSSL::X509::Certificate.new(formatted_cert))
|
200
|
+
certs_for_type.each do |idp_cert|
|
201
|
+
certs[type].push(OneLogin::RubySaml::Utils.build_cert_object(idp_cert))
|
209
202
|
end
|
210
203
|
end
|
211
204
|
|
212
205
|
certs
|
213
206
|
end
|
214
207
|
|
215
|
-
# @return [OpenSSL::X509::Certificate
|
216
|
-
#
|
217
|
-
|
218
|
-
|
208
|
+
# @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>>>]
|
209
|
+
# Build the SP certificates and private keys from the settings. If
|
210
|
+
# check_sp_cert_expiration is true, only returns certificates and private keys
|
211
|
+
# that are not expired.
|
212
|
+
def get_sp_certs
|
213
|
+
certs = get_all_sp_certs
|
214
|
+
return certs unless security[:check_sp_cert_expiration]
|
219
215
|
|
220
|
-
|
221
|
-
|
216
|
+
active_certs = { signing: [], encryption: [] }
|
217
|
+
certs.each do |use, pairs|
|
218
|
+
next if pairs.empty?
|
222
219
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
220
|
+
pairs = pairs.select { |cert, _| !cert || OneLogin::RubySaml::Utils.is_cert_active(cert) }
|
221
|
+
raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.") if pairs.empty?
|
222
|
+
|
223
|
+
active_certs[use] = pairs.freeze
|
227
224
|
end
|
225
|
+
active_certs.freeze
|
226
|
+
end
|
228
227
|
|
229
|
-
|
228
|
+
# @return [Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>]
|
229
|
+
# The SP signing certificate and private key.
|
230
|
+
def get_sp_signing_pair
|
231
|
+
get_sp_certs[:signing].first
|
230
232
|
end
|
231
233
|
|
232
|
-
# @return [OpenSSL::X509::Certificate
|
233
|
-
#
|
234
|
-
def
|
235
|
-
|
234
|
+
# @return [OpenSSL::X509::Certificate] The SP signing certificate.
|
235
|
+
# @deprecated Use get_sp_signing_pair or get_sp_certs instead.
|
236
|
+
def get_sp_cert
|
237
|
+
node = get_sp_signing_pair
|
238
|
+
node[0] if node
|
239
|
+
end
|
236
240
|
|
237
|
-
|
238
|
-
|
241
|
+
# @return [OpenSSL::PKey::RSA] The SP signing key.
|
242
|
+
def get_sp_signing_key
|
243
|
+
node = get_sp_signing_pair
|
244
|
+
node[1] if node
|
239
245
|
end
|
240
246
|
|
241
|
-
# @
|
242
|
-
|
243
|
-
def get_sp_key
|
244
|
-
return nil if private_key.nil? || private_key.empty?
|
247
|
+
# @deprecated Use get_sp_signing_key or get_sp_certs instead.
|
248
|
+
alias_method :get_sp_key, :get_sp_signing_key
|
245
249
|
|
246
|
-
|
247
|
-
|
250
|
+
# @return [Array<OpenSSL::PKey::RSA>] The SP decryption keys.
|
251
|
+
def get_sp_decryption_keys
|
252
|
+
ary = get_sp_certs[:encryption].map { |pair| pair[1] }
|
253
|
+
ary.compact!
|
254
|
+
ary.uniq!(&:to_pem)
|
255
|
+
ary.freeze
|
248
256
|
end
|
249
257
|
|
250
|
-
|
258
|
+
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings.
|
259
|
+
#
|
260
|
+
# @deprecated Use get_sp_certs instead
|
261
|
+
def get_sp_cert_new
|
262
|
+
node = get_sp_certs[:signing].last
|
263
|
+
node[0] if node
|
264
|
+
end
|
251
265
|
|
252
266
|
def idp_binding_from_embed_sign
|
253
267
|
security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect]
|
@@ -280,9 +294,90 @@ module OneLogin
|
|
280
294
|
:digest_method => XMLSecurity::Document::SHA1,
|
281
295
|
:signature_method => XMLSecurity::Document::RSA_SHA1,
|
282
296
|
:check_idp_cert_expiration => false,
|
283
|
-
:check_sp_cert_expiration => false
|
297
|
+
:check_sp_cert_expiration => false,
|
298
|
+
:strict_audience_validation => false,
|
299
|
+
:lowercase_url_encoding => false
|
284
300
|
}.freeze
|
285
301
|
}.freeze
|
302
|
+
|
303
|
+
private
|
304
|
+
|
305
|
+
# @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>>>]
|
306
|
+
# Build the SP certificates and private keys from the settings. Returns all
|
307
|
+
# certificates and private keys, even if they are expired.
|
308
|
+
def get_all_sp_certs
|
309
|
+
validate_sp_certs_params!
|
310
|
+
get_sp_certs_multi || get_sp_certs_single
|
311
|
+
end
|
312
|
+
|
313
|
+
# Validate certificate, certificate_new, private_key, and sp_cert_multi params.
|
314
|
+
def validate_sp_certs_params!
|
315
|
+
multi = sp_cert_multi && !sp_cert_multi.empty?
|
316
|
+
cert = certificate && !certificate.empty?
|
317
|
+
cert_new = certificate_new && !certificate_new.empty?
|
318
|
+
pk = private_key && !private_key.empty?
|
319
|
+
if multi && (cert || cert_new || pk)
|
320
|
+
raise ArgumentError.new("Cannot specify both sp_cert_multi and certificate, certificate_new, private_key parameters")
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Get certs from certificate, certificate_new, and private_key parameters.
|
325
|
+
def get_sp_certs_single
|
326
|
+
certs = { :signing => [], :encryption => [] }
|
327
|
+
|
328
|
+
sp_key = OneLogin::RubySaml::Utils.build_private_key_object(private_key)
|
329
|
+
cert = OneLogin::RubySaml::Utils.build_cert_object(certificate)
|
330
|
+
if cert || sp_key
|
331
|
+
ary = [cert, sp_key].freeze
|
332
|
+
certs[:signing] << ary
|
333
|
+
certs[:encryption] << ary
|
334
|
+
end
|
335
|
+
|
336
|
+
cert_new = OneLogin::RubySaml::Utils.build_cert_object(certificate_new)
|
337
|
+
if cert_new
|
338
|
+
ary = [cert_new, sp_key].freeze
|
339
|
+
certs[:signing] << ary
|
340
|
+
certs[:encryption] << ary
|
341
|
+
end
|
342
|
+
|
343
|
+
certs
|
344
|
+
end
|
345
|
+
|
346
|
+
# Get certs from get_sp_cert_multi parameter.
|
347
|
+
def get_sp_certs_multi
|
348
|
+
return if sp_cert_multi.nil? || sp_cert_multi.empty?
|
349
|
+
|
350
|
+
raise ArgumentError.new("sp_cert_multi must be a Hash") unless sp_cert_multi.is_a?(Hash)
|
351
|
+
|
352
|
+
certs = { :signing => [], :encryption => [] }.freeze
|
353
|
+
|
354
|
+
[:signing, :encryption].each do |type|
|
355
|
+
certs_for_type = sp_cert_multi[type] || sp_cert_multi[type.to_s]
|
356
|
+
next if !certs_for_type || certs_for_type.empty?
|
357
|
+
|
358
|
+
unless certs_for_type.is_a?(Array) && certs_for_type.all? { |cert| cert.is_a?(Hash) }
|
359
|
+
raise ArgumentError.new("sp_cert_multi :#{type} node must be an Array of Hashes")
|
360
|
+
end
|
361
|
+
|
362
|
+
certs_for_type.each do |pair|
|
363
|
+
cert = pair[:certificate] || pair['certificate'] || pair[:cert] || pair['cert']
|
364
|
+
key = pair[:private_key] || pair['private_key'] || pair[:key] || pair['key']
|
365
|
+
|
366
|
+
unless cert && key
|
367
|
+
raise ArgumentError.new("sp_cert_multi :#{type} node Hashes must specify keys :certificate and :private_key")
|
368
|
+
end
|
369
|
+
|
370
|
+
certs[type] << [
|
371
|
+
OneLogin::RubySaml::Utils.build_cert_object(cert),
|
372
|
+
OneLogin::RubySaml::Utils.build_private_key_object(key)
|
373
|
+
].freeze
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
certs.each { |_, ary| ary.freeze }
|
378
|
+
certs
|
379
|
+
end
|
286
380
|
end
|
287
381
|
end
|
288
382
|
end
|
383
|
+
|
@@ -62,10 +62,7 @@ module OneLogin
|
|
62
62
|
# @return [String] Gets the NameID of the Logout Request.
|
63
63
|
#
|
64
64
|
def name_id
|
65
|
-
@name_id ||=
|
66
|
-
node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
67
|
-
Utils.element_text(node)
|
68
|
-
end
|
65
|
+
@name_id ||= Utils.element_text(name_id_node)
|
69
66
|
end
|
70
67
|
|
71
68
|
alias_method :nameid, :name_id
|
@@ -73,15 +70,49 @@ module OneLogin
|
|
73
70
|
# @return [String] Gets the NameID Format of the Logout Request.
|
74
71
|
#
|
75
72
|
def name_id_format
|
76
|
-
@name_id_node ||= REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
77
73
|
@name_id_format ||=
|
78
|
-
if
|
79
|
-
|
74
|
+
if name_id_node && name_id_node.attribute("Format")
|
75
|
+
name_id_node.attribute("Format").value
|
80
76
|
end
|
81
77
|
end
|
82
78
|
|
83
79
|
alias_method :nameid_format, :name_id_format
|
84
80
|
|
81
|
+
def name_id_node
|
82
|
+
@name_id_node ||=
|
83
|
+
begin
|
84
|
+
encrypted_node = REXML::XPath.first(document, "/p:LogoutRequest/a:EncryptedID", { "p" => PROTOCOL, "a" => ASSERTION })
|
85
|
+
if encrypted_node
|
86
|
+
node = decrypt_nameid(encrypted_node)
|
87
|
+
else
|
88
|
+
node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Decrypts an EncryptedID element
|
94
|
+
# @param encrypted_id_node [REXML::Element] The EncryptedID element
|
95
|
+
# @return [REXML::Document] The decrypted EncrypedtID element
|
96
|
+
#
|
97
|
+
def decrypt_nameid(encrypted_id_node)
|
98
|
+
|
99
|
+
if settings.nil? || settings.get_sp_decryption_keys.empty?
|
100
|
+
raise ValidationError.new('An ' + encrypted_id_node.name + ' found and no SP private key found on the settings to decrypt it')
|
101
|
+
end
|
102
|
+
|
103
|
+
elem_plaintext = OneLogin::RubySaml::Utils.decrypt_multi(encrypted_id_node, settings.get_sp_decryption_keys)
|
104
|
+
# If we get some problematic noise in the plaintext after decrypting.
|
105
|
+
# This quick regexp parse will grab only the Element and discard the noise.
|
106
|
+
elem_plaintext = elem_plaintext.match(/(.*<\/(\w+:)?NameID>)/m)[0]
|
107
|
+
|
108
|
+
# To avoid namespace errors if saml namespace is not defined
|
109
|
+
# create a parent node first with the namespace defined
|
110
|
+
node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'
|
111
|
+
elem_plaintext = node_header + elem_plaintext + '</node>'
|
112
|
+
doc = REXML::Document.new(elem_plaintext)
|
113
|
+
doc.root[0]
|
114
|
+
end
|
115
|
+
|
85
116
|
# @return [String|nil] Gets the ID attribute from the Logout Request. if exists.
|
86
117
|
#
|
87
118
|
def id
|
@@ -248,32 +279,8 @@ module OneLogin
|
|
248
279
|
return true unless options.has_key? :get_params
|
249
280
|
return true unless options[:get_params].has_key? 'Signature'
|
250
281
|
|
251
|
-
|
252
|
-
# of URI-encoded values _as sent by the IDP_:
|
253
|
-
#
|
254
|
-
# > Further, note that URL-encoding is not canonical; that is, there are multiple legal encodings for a given
|
255
|
-
# > value. The relying party MUST therefore perform the verification step using the original URL-encoded
|
256
|
-
# > values it received on the query string. It is not sufficient to re-encode the parameters after they have been
|
257
|
-
# > processed by software because the resulting encoding may not match the signer's encoding.
|
258
|
-
#
|
259
|
-
# <http://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf>
|
260
|
-
#
|
261
|
-
# If we don't have the original parts (for backward compatibility) required to correctly verify the signature,
|
262
|
-
# then fabricate them by re-encoding the parsed URI parameters, and hope that we're lucky enough to use
|
263
|
-
# the exact same URI-encoding as the IDP. (This is not the case if the IDP is ADFS!)
|
264
|
-
options[:raw_get_params] ||= {}
|
265
|
-
if options[:raw_get_params]['SAMLRequest'].nil? && !options[:get_params]['SAMLRequest'].nil?
|
266
|
-
options[:raw_get_params]['SAMLRequest'] = CGI.escape(options[:get_params]['SAMLRequest'])
|
267
|
-
end
|
268
|
-
if options[:raw_get_params]['RelayState'].nil? && !options[:get_params]['RelayState'].nil?
|
269
|
-
options[:raw_get_params]['RelayState'] = CGI.escape(options[:get_params]['RelayState'])
|
270
|
-
end
|
271
|
-
if options[:raw_get_params]['SigAlg'].nil? && !options[:get_params]['SigAlg'].nil?
|
272
|
-
options[:raw_get_params]['SigAlg'] = CGI.escape(options[:get_params]['SigAlg'])
|
273
|
-
end
|
282
|
+
options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding])
|
274
283
|
|
275
|
-
# If we only received the raw version of SigAlg,
|
276
|
-
# then parse it back into the decoded params hash for convenience.
|
277
284
|
if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
|
278
285
|
options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
|
279
286
|
end
|
@@ -335,7 +342,6 @@ module OneLogin
|
|
335
342
|
|
336
343
|
true
|
337
344
|
end
|
338
|
-
|
339
345
|
end
|
340
346
|
end
|
341
347
|
end
|
@@ -13,7 +13,7 @@ module OneLogin
|
|
13
13
|
class SloLogoutresponse < SamlMessage
|
14
14
|
|
15
15
|
# Logout Response ID
|
16
|
-
|
16
|
+
attr_accessor :uuid
|
17
17
|
|
18
18
|
# Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class.
|
19
19
|
# Asigns an ID, a random uuid.
|
@@ -41,7 +41,7 @@ module OneLogin
|
|
41
41
|
saml_response = CGI.escape(params.delete("SAMLResponse"))
|
42
42
|
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
|
43
43
|
params.each_pair do |key, value|
|
44
|
-
response_params << "&#{key
|
44
|
+
response_params << "&#{key}=#{CGI.escape(value.to_s)}"
|
45
45
|
end
|
46
46
|
|
47
47
|
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
|
@@ -78,9 +78,10 @@ module OneLogin
|
|
78
78
|
response = deflate(response) if settings.compress_response
|
79
79
|
base64_response = encode(response)
|
80
80
|
response_params = {"SAMLResponse" => base64_response}
|
81
|
+
sp_signing_key = settings.get_sp_signing_key
|
81
82
|
|
82
|
-
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] &&
|
83
|
-
params['SigAlg']
|
83
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && sp_signing_key
|
84
|
+
params['SigAlg'] = settings.security[:signature_method]
|
84
85
|
url_string = OneLogin::RubySaml::Utils.build_query(
|
85
86
|
:type => 'SAMLResponse',
|
86
87
|
:data => base64_response,
|
@@ -88,7 +89,7 @@ module OneLogin
|
|
88
89
|
:sig_alg => params['SigAlg']
|
89
90
|
)
|
90
91
|
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
|
91
|
-
signature =
|
92
|
+
signature = sp_signing_key.sign(sign_algorithm.new, url_string)
|
92
93
|
params['Signature'] = encode(signature)
|
93
94
|
end
|
94
95
|
|
@@ -150,9 +151,8 @@ module OneLogin
|
|
150
151
|
|
151
152
|
def sign_document(document, settings)
|
152
153
|
# embed signature
|
153
|
-
|
154
|
-
|
155
|
-
cert = settings.get_sp_cert
|
154
|
+
cert, private_key = settings.get_sp_signing_pair
|
155
|
+
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && private_key && cert
|
156
156
|
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
|
157
157
|
end
|
158
158
|
|