ruby-saml 1.12.4 → 1.18.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.
@@ -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
@@ -16,21 +15,17 @@ module OneLogin
16
15
  class SamlMessage
17
16
  include REXML
18
17
 
19
- ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
20
- PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
18
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
19
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol".freeze
21
20
 
22
21
  BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
23
- @@mutex = Mutex.new
24
-
25
- MAX_BYTE_SIZE = 250000
26
22
 
27
23
  # @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
28
24
  #
29
25
  def self.schema
30
- @@mutex.synchronize do
31
- Dir.chdir(File.expand_path("../../../schemas", __FILE__)) do
32
- ::Nokogiri::XML::Schema(File.read("saml-schema-protocol-2.0.xsd"))
33
- end
26
+ path = File.expand_path("../../../schemas/saml-schema-protocol-2.0.xsd", __FILE__)
27
+ File.open(path) do |file|
28
+ ::Nokogiri::XML::Schema(file)
34
29
  end
35
30
  end
36
31
 
@@ -70,14 +65,14 @@ module OneLogin
70
65
  def valid_saml?(document, soft = true, check_malformed_doc = true)
71
66
  begin
72
67
  xml = XMLSecurity::BaseDocument.safe_load_xml(document, check_malformed_doc)
73
- rescue Exception => error
68
+ rescue StandardError => error
74
69
  return false if soft
75
70
  raise ValidationError.new("XML load failed: #{error.message}")
76
71
  end
77
72
 
78
73
  SamlMessage.schema.validate(xml).map do |schema_error|
79
74
  return false if soft
80
- raise ValidationError.new("#{schema_error.message}\n\n#{xml.to_s}")
75
+ raise ValidationError.new("#{schema_error.message}\n\n#{xml}")
81
76
  end
82
77
  end
83
78
 
@@ -85,15 +80,17 @@ module OneLogin
85
80
 
86
81
  # Base64 decode and try also to inflate a SAML Message
87
82
  # @param saml [String] The deflated and encoded SAML Message
83
+ # @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
88
84
  # @return [String] The plain SAML Message
89
85
  #
90
- def decode_raw_saml(saml)
91
- return saml unless base64_encoded?(saml)
92
-
93
- if saml.bytesize > MAX_BYTE_SIZE
94
- raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
86
+ def decode_raw_saml(saml, settings = nil)
87
+ settings = OneLogin::RubySaml::Settings.new if settings.nil?
88
+ if saml.bytesize > settings.message_max_bytesize
89
+ raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
95
90
  end
96
91
 
92
+ return saml unless base64_encoded?(saml)
93
+
97
94
  decoded = decode(saml)
98
95
  begin
99
96
  message = inflate(decoded)
@@ -101,8 +98,8 @@ module OneLogin
101
98
  message = decoded
102
99
  end
103
100
 
104
- if message.bytesize > MAX_BYTE_SIZE
105
- raise ValidationError.new("Encoded SAML Message exceeds " + MAX_BYTE_SIZE.to_s + " bytes, so was rejected")
101
+ if message.bytesize > settings.message_max_bytesize
102
+ raise ValidationError.new("SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
106
103
  end
107
104
 
108
105
  message
@@ -20,7 +20,7 @@ module OneLogin
20
20
  end
21
21
 
22
22
  config.each do |k,v|
23
- acc = "#{k.to_s}=".to_sym
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)
@@ -31,9 +31,8 @@ module OneLogin
31
31
 
32
32
  # IdP Data
33
33
  attr_accessor :idp_entity_id
34
-
35
- attr_accessor :idp_sso_service_url
36
- attr_accessor :idp_slo_service_url
34
+ attr_writer :idp_sso_service_url
35
+ attr_writer :idp_slo_service_url
37
36
  attr_accessor :idp_slo_response_service_url
38
37
  attr_accessor :idp_cert
39
38
  attr_accessor :idp_cert_fingerprint
@@ -43,8 +42,10 @@ module OneLogin
43
42
  attr_accessor :idp_name_qualifier
44
43
  attr_accessor :valid_until
45
44
  # SP Data
45
+ attr_writer :sp_entity_id
46
46
  attr_accessor :assertion_consumer_service_url
47
- attr_accessor :assertion_consumer_service_binding
47
+ attr_reader :assertion_consumer_service_binding
48
+ attr_writer :single_logout_service_url
48
49
  attr_accessor :sp_name_qualifier
49
50
  attr_accessor :name_identifier_format
50
51
  attr_accessor :name_identifier_value
@@ -53,14 +54,15 @@ module OneLogin
53
54
  attr_accessor :compress_request
54
55
  attr_accessor :compress_response
55
56
  attr_accessor :double_quote_xml_attribute_values
57
+ attr_accessor :message_max_bytesize
56
58
  attr_accessor :check_malformed_doc
57
59
  attr_accessor :passive
58
- attr_accessor :protocol_binding
60
+ attr_reader :protocol_binding
59
61
  attr_accessor :attributes_index
60
62
  attr_accessor :force_authn
61
63
  attr_accessor :certificate
62
- attr_accessor :certificate_new
63
64
  attr_accessor :private_key
65
+ attr_accessor :sp_cert_multi
64
66
  attr_accessor :authn_context
65
67
  attr_accessor :authn_context_comparison
66
68
  attr_accessor :authn_context_decl_ref
@@ -68,9 +70,10 @@ module OneLogin
68
70
  # Work-flow
69
71
  attr_accessor :security
70
72
  attr_accessor :soft
71
- # Compability
73
+ # Deprecated
74
+ attr_accessor :certificate_new
72
75
  attr_accessor :assertion_consumer_logout_service_url
73
- attr_accessor :assertion_consumer_logout_service_binding
76
+ attr_reader :assertion_consumer_logout_service_binding
74
77
  attr_accessor :issuer
75
78
  attr_accessor :idp_sso_target_url
76
79
  attr_accessor :idp_slo_target_url
@@ -78,94 +81,89 @@ module OneLogin
78
81
  # @return [String] IdP Single Sign On Service URL
79
82
  #
80
83
  def idp_sso_service_url
81
- val = nil
82
- if @idp_sso_service_url.nil?
83
- if @idp_sso_target_url
84
- val = @idp_sso_target_url
85
- end
86
- else
87
- val = @idp_sso_service_url
88
- end
89
- val
84
+ @idp_sso_service_url || @idp_sso_target_url
90
85
  end
91
86
 
92
87
  # @return [String] IdP Single Logout Service URL
93
88
  #
94
89
  def idp_slo_service_url
95
- val = nil
96
- if @idp_slo_service_url.nil?
97
- if @idp_slo_target_url
98
- val = @idp_slo_target_url
99
- end
100
- else
101
- val = @idp_slo_service_url
102
- end
103
- val
90
+ @idp_slo_service_url || @idp_slo_target_url
91
+ end
92
+
93
+ # @return [String] IdP Single Sign On Service Binding
94
+ #
95
+ def idp_sso_service_binding
96
+ @idp_sso_service_binding || idp_binding_from_embed_sign
97
+ end
98
+
99
+ # Setter for IdP Single Sign On Service Binding
100
+ # @param value [String, Symbol].
101
+ #
102
+ def idp_sso_service_binding=(value)
103
+ @idp_sso_service_binding = get_binding(value)
104
+ end
105
+
106
+ # @return [String] IdP Single Logout Service Binding
107
+ #
108
+ def idp_slo_service_binding
109
+ @idp_slo_service_binding || idp_binding_from_embed_sign
110
+ end
111
+
112
+ # Setter for IdP Single Logout Service Binding
113
+ # @param value [String, Symbol].
114
+ #
115
+ def idp_slo_service_binding=(value)
116
+ @idp_slo_service_binding = get_binding(value)
104
117
  end
105
118
 
106
119
  # @return [String] SP Entity ID
107
120
  #
108
121
  def sp_entity_id
109
- val = nil
110
- if @sp_entity_id.nil?
111
- if @issuer
112
- val = @issuer
113
- end
114
- else
115
- val = @sp_entity_id
116
- end
117
- val
122
+ @sp_entity_id || @issuer
118
123
  end
119
124
 
120
- # Setter for SP Entity ID.
121
- # @param val [String].
125
+ # Setter for SP Protocol Binding
126
+ # @param value [String, Symbol].
122
127
  #
123
- def sp_entity_id=(val)
124
- @sp_entity_id = val
128
+ def protocol_binding=(value)
129
+ @protocol_binding = get_binding(value)
125
130
  end
126
131
 
127
- # @return [String] Single Logout Service URL.
132
+ # Setter for SP Assertion Consumer Service Binding
133
+ # @param value [String, Symbol].
128
134
  #
129
- def single_logout_service_url
130
- val = nil
131
- if @single_logout_service_url.nil?
132
- if @assertion_consumer_logout_service_url
133
- val = @assertion_consumer_logout_service_url
134
- end
135
- else
136
- val = @single_logout_service_url
137
- end
138
- val
135
+ def assertion_consumer_service_binding=(value)
136
+ @assertion_consumer_service_binding = get_binding(value)
139
137
  end
140
138
 
141
- # Setter for the Single Logout Service URL.
142
- # @param url [String].
139
+ # @return [String] Single Logout Service URL.
143
140
  #
144
- def single_logout_service_url=(url)
145
- @single_logout_service_url = url
141
+ def single_logout_service_url
142
+ @single_logout_service_url || @assertion_consumer_logout_service_url
146
143
  end
147
144
 
148
145
  # @return [String] Single Logout Service Binding.
149
146
  #
150
147
  def single_logout_service_binding
151
- val = nil
152
- if @single_logout_service_binding.nil?
153
- if @assertion_consumer_logout_service_binding
154
- val = @assertion_consumer_logout_service_binding
155
- end
156
- else
157
- val = @single_logout_service_binding
158
- end
159
- val
148
+ @single_logout_service_binding || @assertion_consumer_logout_service_binding
160
149
  end
161
150
 
162
151
  # Setter for Single Logout Service Binding.
163
152
  #
164
153
  # (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
165
- # @param url [String]
154
+ # @param value [String, Symbol]
166
155
  #
167
- def single_logout_service_binding=(url)
168
- @single_logout_service_binding = url
156
+ def single_logout_service_binding=(value)
157
+ @single_logout_service_binding = get_binding(value)
158
+ end
159
+
160
+ # @deprecated Setter for legacy Single Logout Service Binding parameter.
161
+ #
162
+ # (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
163
+ # @param value [String, Symbol]
164
+ #
165
+ def assertion_consumer_logout_service_binding=(value)
166
+ @assertion_consumer_logout_service_binding = get_binding(value)
169
167
  end
170
168
 
171
169
  # Calculates the fingerprint of the IdP x509 certificate.
@@ -184,10 +182,7 @@ module OneLogin
184
182
  # @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
185
183
  #
186
184
  def get_idp_cert
187
- return nil if idp_cert.nil? || idp_cert.empty?
188
-
189
- formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
190
- OpenSSL::X509::Certificate.new(formatted_cert)
185
+ OneLogin::RubySaml::Utils.build_cert_object(idp_cert)
191
186
  end
192
187
 
193
188
  # @return [Hash with 2 arrays of OpenSSL::X509::Certificate] Build multiple IdP certificates from the settings.
@@ -195,73 +190,101 @@ module OneLogin
195
190
  def get_idp_cert_multi
196
191
  return nil if idp_cert_multi.nil? || idp_cert_multi.empty?
197
192
 
198
- raise ArgumentError.new("Invalid value for idp_cert_multi") if not idp_cert_multi.is_a?(Hash)
193
+ raise ArgumentError.new("Invalid value for idp_cert_multi") unless idp_cert_multi.is_a?(Hash)
199
194
 
200
195
  certs = {:signing => [], :encryption => [] }
201
196
 
202
- if idp_cert_multi.key?(:signing) and not idp_cert_multi[:signing].empty?
203
- idp_cert_multi[:signing].each do |idp_cert|
204
- formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
205
- certs[:signing].push(OpenSSL::X509::Certificate.new(formatted_cert))
206
- end
207
- end
197
+ [:signing, :encryption].each do |type|
198
+ certs_for_type = idp_cert_multi[type] || idp_cert_multi[type.to_s]
199
+ next if !certs_for_type || certs_for_type.empty?
208
200
 
209
- if idp_cert_multi.key?(:encryption) and not idp_cert_multi[:encryption].empty?
210
- idp_cert_multi[:encryption].each do |idp_cert|
211
- formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
212
- certs[:encryption].push(OpenSSL::X509::Certificate.new(formatted_cert))
201
+ certs_for_type.each do |idp_cert|
202
+ certs[type].push(OneLogin::RubySaml::Utils.build_cert_object(idp_cert))
213
203
  end
214
204
  end
215
205
 
216
206
  certs
217
207
  end
218
208
 
219
- # @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
220
- #
221
- def get_sp_cert
222
- return nil if certificate.nil? || certificate.empty?
209
+ # @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>>>]
210
+ # Build the SP certificates and private keys from the settings. If
211
+ # check_sp_cert_expiration is true, only returns certificates and private keys
212
+ # that are not expired.
213
+ def get_sp_certs
214
+ certs = get_all_sp_certs
215
+ return certs unless security[:check_sp_cert_expiration]
223
216
 
224
- formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
225
- cert = OpenSSL::X509::Certificate.new(formatted_cert)
217
+ active_certs = { signing: [], encryption: [] }
218
+ certs.each do |use, pairs|
219
+ next if pairs.empty?
226
220
 
227
- if security[:check_sp_cert_expiration]
228
- if OneLogin::RubySaml::Utils.is_cert_expired(cert)
229
- raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
230
- end
221
+ pairs = pairs.select { |cert, _| !cert || OneLogin::RubySaml::Utils.is_cert_active(cert) }
222
+ raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.") if pairs.empty?
223
+
224
+ active_certs[use] = pairs.freeze
231
225
  end
226
+ active_certs.freeze
227
+ end
232
228
 
233
- cert
229
+ # @return [Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>]
230
+ # The SP signing certificate and private key.
231
+ def get_sp_signing_pair
232
+ get_sp_certs[:signing].first
234
233
  end
235
234
 
236
- # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
237
- #
238
- def get_sp_cert_new
239
- return nil if certificate_new.nil? || certificate_new.empty?
235
+ # @return [OpenSSL::X509::Certificate] The SP signing certificate.
236
+ # @deprecated Use get_sp_signing_pair or get_sp_certs instead.
237
+ def get_sp_cert
238
+ node = get_sp_signing_pair
239
+ node[0] if node
240
+ end
240
241
 
241
- formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate_new)
242
- OpenSSL::X509::Certificate.new(formatted_cert)
242
+ # @return [OpenSSL::PKey::RSA] The SP signing key.
243
+ def get_sp_signing_key
244
+ node = get_sp_signing_pair
245
+ node[1] if node
243
246
  end
244
247
 
245
- # @return [OpenSSL::PKey::RSA] Build the SP private from the settings (previously format it)
248
+ # @deprecated Use get_sp_signing_key or get_sp_certs instead.
249
+ alias_method :get_sp_key, :get_sp_signing_key
250
+
251
+ # @return [Array<OpenSSL::PKey::RSA>] The SP decryption keys.
252
+ def get_sp_decryption_keys
253
+ ary = get_sp_certs[:encryption].map { |pair| pair[1] }
254
+ ary.compact!
255
+ ary.uniq!(&:to_pem)
256
+ ary.freeze
257
+ end
258
+
259
+ # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings.
246
260
  #
247
- def get_sp_key
248
- return nil if private_key.nil? || private_key.empty?
261
+ # @deprecated Use get_sp_certs instead
262
+ def get_sp_cert_new
263
+ node = get_sp_certs[:signing].last
264
+ node[0] if node
265
+ end
249
266
 
250
- formatted_private_key = OneLogin::RubySaml::Utils.format_private_key(private_key)
251
- OpenSSL::PKey::RSA.new(formatted_private_key)
267
+ def idp_binding_from_embed_sign
268
+ security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect]
252
269
  end
253
270
 
254
- private
271
+ def get_binding(value)
272
+ return unless value
273
+
274
+ Utils::BINDINGS[value.to_sym] || value
275
+ end
255
276
 
256
277
  DEFAULTS = {
257
- :assertion_consumer_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
258
- :single_logout_service_binding => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze,
278
+ :assertion_consumer_service_binding => Utils::BINDINGS[:post],
279
+ :single_logout_service_binding => Utils::BINDINGS[:redirect],
259
280
  :idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
260
281
  :compress_request => true,
261
282
  :compress_response => true,
283
+ :message_max_bytesize => 250000,
262
284
  :soft => true,
263
- :check_malformed_doc => true,
285
+ :check_malformed_doc => true,
264
286
  :double_quote_xml_attribute_values => false,
287
+
265
288
  :security => {
266
289
  :authn_requests_signed => false,
267
290
  :logout_requests_signed => false,
@@ -270,13 +293,94 @@ module OneLogin
270
293
  :want_assertions_encrypted => false,
271
294
  :want_name_id => false,
272
295
  :metadata_signed => false,
273
- :embed_sign => false,
296
+ :embed_sign => false, # Deprecated
274
297
  :digest_method => XMLSecurity::Document::SHA1,
275
298
  :signature_method => XMLSecurity::Document::RSA_SHA1,
276
299
  :check_idp_cert_expiration => false,
277
- :check_sp_cert_expiration => false
300
+ :check_sp_cert_expiration => false,
301
+ :strict_audience_validation => false,
302
+ :lowercase_url_encoding => false
278
303
  }.freeze
279
304
  }.freeze
305
+
306
+ private
307
+
308
+ # @return [Hash<Symbol, Array<Array<OpenSSL::X509::Certificate, OpenSSL::PKey::RSA>>>]
309
+ # Build the SP certificates and private keys from the settings. Returns all
310
+ # certificates and private keys, even if they are expired.
311
+ def get_all_sp_certs
312
+ validate_sp_certs_params!
313
+ get_sp_certs_multi || get_sp_certs_single
314
+ end
315
+
316
+ # Validate certificate, certificate_new, private_key, and sp_cert_multi params.
317
+ def validate_sp_certs_params!
318
+ multi = sp_cert_multi && !sp_cert_multi.empty?
319
+ cert = certificate && !certificate.empty?
320
+ cert_new = certificate_new && !certificate_new.empty?
321
+ pk = private_key && !private_key.empty?
322
+ if multi && (cert || cert_new || pk)
323
+ raise ArgumentError.new("Cannot specify both sp_cert_multi and certificate, certificate_new, private_key parameters")
324
+ end
325
+ end
326
+
327
+ # Get certs from certificate, certificate_new, and private_key parameters.
328
+ def get_sp_certs_single
329
+ certs = { :signing => [], :encryption => [] }
330
+
331
+ sp_key = OneLogin::RubySaml::Utils.build_private_key_object(private_key)
332
+ cert = OneLogin::RubySaml::Utils.build_cert_object(certificate)
333
+ if cert || sp_key
334
+ ary = [cert, sp_key].freeze
335
+ certs[:signing] << ary
336
+ certs[:encryption] << ary
337
+ end
338
+
339
+ cert_new = OneLogin::RubySaml::Utils.build_cert_object(certificate_new)
340
+ if cert_new
341
+ ary = [cert_new, sp_key].freeze
342
+ certs[:signing] << ary
343
+ certs[:encryption] << ary
344
+ end
345
+
346
+ certs
347
+ end
348
+
349
+ # Get certs from get_sp_cert_multi parameter.
350
+ def get_sp_certs_multi
351
+ return if sp_cert_multi.nil? || sp_cert_multi.empty?
352
+
353
+ raise ArgumentError.new("sp_cert_multi must be a Hash") unless sp_cert_multi.is_a?(Hash)
354
+
355
+ certs = { :signing => [], :encryption => [] }.freeze
356
+
357
+ [:signing, :encryption].each do |type|
358
+ certs_for_type = sp_cert_multi[type] || sp_cert_multi[type.to_s]
359
+ next if !certs_for_type || certs_for_type.empty?
360
+
361
+ unless certs_for_type.is_a?(Array) && certs_for_type.all? { |cert| cert.is_a?(Hash) }
362
+ raise ArgumentError.new("sp_cert_multi :#{type} node must be an Array of Hashes")
363
+ end
364
+
365
+ certs_for_type.each do |pair|
366
+ cert = pair[:certificate] || pair['certificate'] || pair[:cert] || pair['cert']
367
+ key = pair[:private_key] || pair['private_key'] || pair[:key] || pair['key']
368
+
369
+ unless cert && key
370
+ raise ArgumentError.new("sp_cert_multi :#{type} node Hashes must specify keys :certificate and :private_key")
371
+ end
372
+
373
+ certs[type] << [
374
+ OneLogin::RubySaml::Utils.build_cert_object(cert),
375
+ OneLogin::RubySaml::Utils.build_private_key_object(key)
376
+ ].freeze
377
+ end
378
+ end
379
+
380
+ certs.each { |_, ary| ary.freeze }
381
+ certs
382
+ end
280
383
  end
281
384
  end
282
385
  end
386
+