ruby-saml 1.13.0 → 1.17.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|