ruby-saml 0.8.18 → 0.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-saml might be problematic. Click here for more details.

Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +1 -6
  4. data/Gemfile +2 -12
  5. data/README.md +363 -35
  6. data/Rakefile +14 -0
  7. data/changelog.md +22 -9
  8. data/lib/onelogin/ruby-saml/attribute_service.rb +34 -0
  9. data/lib/onelogin/ruby-saml/attributes.rb +26 -64
  10. data/lib/onelogin/ruby-saml/authrequest.rb +47 -93
  11. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
  12. data/lib/onelogin/ruby-saml/logoutrequest.rb +36 -100
  13. data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -35
  14. data/lib/onelogin/ruby-saml/metadata.rb +46 -16
  15. data/lib/onelogin/ruby-saml/response.rb +63 -373
  16. data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
  17. data/lib/onelogin/ruby-saml/settings.rb +54 -122
  18. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +25 -71
  19. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +37 -102
  20. data/lib/onelogin/ruby-saml/utils.rb +32 -199
  21. data/lib/onelogin/ruby-saml/version.rb +1 -1
  22. data/lib/ruby-saml.rb +5 -2
  23. data/lib/schemas/{saml20assertion_schema.xsd → saml-schema-assertion-2.0.xsd} +283 -283
  24. data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
  25. data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
  26. data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
  27. data/lib/schemas/{saml20protocol_schema.xsd → saml-schema-protocol-2.0.xsd} +302 -302
  28. data/lib/schemas/sstc-metadata-attr.xsd +35 -0
  29. data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
  30. data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  31. data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  32. data/lib/schemas/{xenc_schema.xsd → xenc-schema.xsd} +1 -11
  33. data/lib/schemas/xml.xsd +287 -0
  34. data/lib/schemas/{xmldsig_schema.xsd → xmldsig-core-schema.xsd} +0 -9
  35. data/lib/xml_security.rb +83 -235
  36. data/ruby-saml.gemspec +1 -0
  37. data/test/idp_metadata_parser_test.rb +54 -0
  38. data/test/logoutrequest_test.rb +68 -155
  39. data/test/logoutresponse_test.rb +43 -32
  40. data/test/metadata_test.rb +87 -0
  41. data/test/request_test.rb +102 -99
  42. data/test/response_test.rb +181 -495
  43. data/test/responses/idp_descriptor.xml +3 -0
  44. data/test/responses/logoutresponse_fixtures.rb +7 -8
  45. data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
  46. data/test/responses/response_with_multiple_attribute_values.xml +1 -1
  47. data/test/responses/slo_request.xml +4 -0
  48. data/test/settings_test.rb +25 -112
  49. data/test/slo_logoutrequest_test.rb +40 -50
  50. data/test/slo_logoutresponse_test.rb +86 -185
  51. data/test/test_helper.rb +27 -102
  52. data/test/xml_security_test.rb +114 -337
  53. metadata +30 -81
  54. data/lib/onelogin/ruby-saml/setting_error.rb +0 -6
  55. data/test/certificates/certificate.der +0 -0
  56. data/test/certificates/formatted_certificate +0 -14
  57. data/test/certificates/formatted_chained_certificate +0 -42
  58. data/test/certificates/formatted_private_key +0 -12
  59. data/test/certificates/formatted_rsa_private_key +0 -12
  60. data/test/certificates/invalid_certificate1 +0 -1
  61. data/test/certificates/invalid_certificate2 +0 -1
  62. data/test/certificates/invalid_certificate3 +0 -12
  63. data/test/certificates/invalid_chained_certificate1 +0 -1
  64. data/test/certificates/invalid_private_key1 +0 -1
  65. data/test/certificates/invalid_private_key2 +0 -1
  66. data/test/certificates/invalid_private_key3 +0 -10
  67. data/test/certificates/invalid_rsa_private_key1 +0 -1
  68. data/test/certificates/invalid_rsa_private_key2 +0 -1
  69. data/test/certificates/invalid_rsa_private_key3 +0 -10
  70. data/test/certificates/ruby-saml-2.crt +0 -15
  71. data/test/requests/logoutrequest_fixtures.rb +0 -47
  72. data/test/responses/encrypted_new_attack.xml.base64 +0 -1
  73. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
  74. data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
  75. data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
  76. data/test/responses/invalids/no_signature.xml.base64 +0 -1
  77. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
  78. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
  79. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
  80. data/test/responses/response_node_text_attack.xml.base64 +0 -1
  81. data/test/responses/response_with_concealed_signed_assertion.xml +0 -51
  82. data/test/responses/response_with_doubled_signed_assertion.xml +0 -49
  83. data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
  84. data/test/responses/response_with_signed_assertion_3.xml +0 -30
  85. data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
  86. data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
  87. data/test/responses/response_wrapped.xml.base64 +0 -150
  88. data/test/responses/valid_response.xml.base64 +0 -1
  89. data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
  90. data/test/utils_test.rb +0 -231
@@ -1,48 +1,30 @@
1
1
  require "xml_security"
2
2
  require "time"
3
3
  require "nokogiri"
4
- require "onelogin/ruby-saml/utils"
5
- require 'onelogin/ruby-saml/attributes'
6
4
 
7
5
  # Only supports SAML 2.0
8
6
  module OneLogin
9
7
  module RubySaml
10
8
 
11
- class Response
9
+ class Response < SamlMessage
12
10
  ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
13
11
  PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
14
12
  DSIG = "http://www.w3.org/2000/09/xmldsig#"
15
13
 
16
14
  # TODO: This should probably be ctor initialized too... WDYT?
17
15
  attr_accessor :settings
16
+ attr_accessor :errors
18
17
 
19
18
  attr_reader :options
20
19
  attr_reader :response
21
20
  attr_reader :document
22
21
 
23
22
  def initialize(response, options = {})
23
+ @errors = []
24
24
  raise ArgumentError.new("Response cannot be nil") if response.nil?
25
25
  @options = options
26
- @response = OneLogin::RubySaml::Utils.decode_raw_saml(response)
27
- @document = XMLSecurity::SignedDocument.new(@response)
28
- end
29
-
30
- def response_id
31
- @response_id ||= begin
32
- node = REXML::XPath.first(
33
- document,
34
- "/p:Response",
35
- { "p" => PROTOCOL }
36
- )
37
- node.nil? ? nil : node.attributes['ID']
38
- end
39
- end
40
-
41
- def assertion_id
42
- @assertion_id ||= begin
43
- node = xpath_first_from_signed_assertion("")
44
- node.nil? ? nil : node.attributes['ID']
45
- end
26
+ @response = decode_raw_saml(response)
27
+ @document = XMLSecurity::SignedDocument.new(@response, @errors)
46
28
  end
47
29
 
48
30
  def is_valid?
@@ -53,46 +35,16 @@ module OneLogin
53
35
  validate(false)
54
36
  end
55
37
 
56
- def name_id_node
57
- @name_id ||= begin
58
- xpath_first_from_signed_assertion('/a:Subject/a:NameID')
59
- end
38
+ def errors
39
+ @errors
60
40
  end
61
41
 
62
42
  # The value of the user identifier as designated by the initialization request response
63
43
  def name_id
64
- @name_id ||= Utils.element_text(name_id_node)
65
- end
66
-
67
- alias nameid name_id
68
-
69
- # @return [String] the NameID Format provided by the SAML response from the IdP.
70
- #
71
- def name_id_format
72
- @name_id_format ||=
73
- if name_id_node && name_id_node.attribute("Format")
74
- name_id_node.attribute("Format").value
75
- end
76
- end
77
-
78
- alias_method :nameid_format, :name_id_format
79
-
80
- # @return [String] the NameID SPNameQualifier provided by the SAML response from the IdP.
81
- #
82
- def name_id_spnamequalifier
83
- @name_id_spnamequalifier ||=
84
- if name_id_node && name_id_node.attribute("SPNameQualifier")
85
- name_id_node.attribute("SPNameQualifier").value
86
- end
87
- end
88
-
89
- # @return [String] the NameID NameQualifier provided by the SAML response from the IdP.
90
- #
91
- def name_id_namequalifier
92
- @name_id_namequalifier ||=
93
- if name_id_node && name_id_node.attribute("NameQualifier")
94
- name_id_node.attribute("NameQualifier").value
95
- end
44
+ @name_id ||= begin
45
+ node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
46
+ node.nil? ? nil : node.text
47
+ end
96
48
  end
97
49
 
98
50
  def sessionindex
@@ -102,9 +54,9 @@ module OneLogin
102
54
  end
103
55
  end
104
56
 
105
- # Gets the Attributes from the AttributeStatement element.
106
- #
57
+ # Returns OneLogin::RubySaml::Attributes enumerable collection.
107
58
  # All attributes can be iterated over +attributes.each+ or returned as array by +attributes.all+
59
+ #
108
60
  # For backwards compatibility ruby-saml returns by default only the first value for a given attribute with
109
61
  # attributes['name']
110
62
  # To get all of the attributes, use:
@@ -113,36 +65,24 @@ module OneLogin
113
65
  # OneLogin::RubySaml::Attributes.single_value_compatibility = false
114
66
  # Now this will return an array:
115
67
  # attributes['name']
116
- #
117
- # @return [Attributes] OneLogin::RubySaml::Attributes enumerable collection.
118
- #
119
68
  def attributes
120
69
  @attr_statements ||= begin
121
70
  attributes = Attributes.new
122
71
 
123
- stmt_elements = xpath_from_signed_assertion('/a:AttributeStatement')
124
- stmt_elements.each do |stmt_element|
125
- stmt_element.elements.each do |attr_element|
126
- name = attr_element.attributes["Name"]
127
- values = attr_element.elements.collect{|e|
128
- if (e.elements.nil? || e.elements.size == 0)
129
- # SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
130
- # otherwise the value is to be regarded as empty.
131
- ["true", "1"].include?(e.attributes['xsi:nil']) ? nil : Utils.element_text(e)
132
- # explicitly support saml2:NameID with saml2:NameQualifier if supplied in attributes
133
- # this is useful for allowing eduPersonTargetedId to be passed as an opaque identifier to use to
134
- # identify the subject in an SP rather than email or other less opaque attributes
135
- # NameQualifier, if present is prefixed with a "/" to the value
136
- else
137
- REXML::XPath.match(e,'a:NameID', { "a" => ASSERTION }).collect{|n|
138
- (n.attributes['NameQualifier'] ? n.attributes['NameQualifier'] +"/" : '') + Utils.element_text(n)
139
- }
140
- end
141
- }
142
-
143
- attributes.add(name, values.flatten)
144
- end
72
+ stmt_element = xpath_first_from_signed_assertion('/a:AttributeStatement')
73
+ return attributes if stmt_element.nil?
74
+
75
+ stmt_element.elements.each do |attr_element|
76
+ name = attr_element.attributes["Name"]
77
+ values = attr_element.elements.collect{|e|
78
+ # SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
79
+ # otherwise the value is to be regarded as empty.
80
+ ["true", "1"].include?(e.attributes['xsi:nil']) ? nil : e.text.to_s
81
+ }
82
+
83
+ attributes.add(name, values)
145
84
  end
85
+
146
86
  attributes
147
87
  end
148
88
  end
@@ -163,6 +103,13 @@ module OneLogin
163
103
  end
164
104
  end
165
105
 
106
+ def status_message
107
+ @status_message ||= begin
108
+ node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusMessage", { "p" => PROTOCOL, "a" => ASSERTION })
109
+ node.text if node
110
+ end
111
+ end
112
+
166
113
  # Conditions (if any) for the assertion to run
167
114
  def conditions
168
115
  @conditions ||= xpath_first_from_signed_assertion('/a:Conditions')
@@ -180,223 +127,43 @@ module OneLogin
180
127
  @issuer ||= begin
181
128
  node = REXML::XPath.first(document, "/p:Response/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
182
129
  node ||= xpath_first_from_signed_assertion('/a:Issuer')
183
- Utils.element_text(node)
184
- end
185
- end
186
-
187
- # Gets the Issuers (from Response and Assertion).
188
- # (returns the first node that matches the supplied xpath from the Response and from the Assertion)
189
- # @return [Array] Array with the Issuers (REXML::Element)
190
- #
191
- def issuers
192
- @issuers ||= begin
193
- issuer_response_nodes = REXML::XPath.match(
194
- document,
195
- "/p:Response/a:Issuer",
196
- { "p" => PROTOCOL, "a" => ASSERTION }
197
- )
198
-
199
- unless issuer_response_nodes.size == 1
200
- error_msg = "Issuer of the Response not found or multiple."
201
- raise ValidationError.new(error_msg)
202
- end
203
-
204
- issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
205
- unless issuer_assertion_nodes.size == 1
206
- error_msg = "Issuer of the Assertion not found or multiple."
207
- raise ValidationError.new(error_msg)
208
- end
209
-
210
- nodes = issuer_response_nodes + issuer_assertion_nodes
211
- nodes.map { |node| Utils.element_text(node) }.compact.uniq
212
- end
213
- end
214
-
215
- # @return [Array] The Audience elements from the Contitions of the SAML Response.
216
- #
217
- def audiences
218
- @audiences ||= begin
219
- nodes = xpath_from_signed_assertion('/a:Conditions/a:AudienceRestriction/a:Audience')
220
- nodes.map { |node| Utils.element_text(node) }.reject(&:empty?)
130
+ node.nil? ? nil : node.text
221
131
  end
222
132
  end
223
133
 
224
134
  private
225
135
 
226
- def validation_error(message)
227
- raise ValidationError.new(message)
228
- end
229
-
230
136
  def validate(soft = true)
231
- validate_structure(soft) &&
232
- validate_success_status(soft) &&
233
- validate_num_assertion &&
234
- validate_signed_elements(soft) &&
235
- validate_response_state(soft) &&
236
- validate_conditions(soft) &&
237
- validate_audience(soft) &&
238
- validate_issuer(soft) &&
239
- validate_signature(soft) &&
240
- success?
241
- end
242
-
243
- # Validates that the SAML Response only contains a single Assertion (encrypted or not).
244
- # @return [Boolean] True if the SAML Response contains one unique Assertion, otherwise False
245
- #
246
- def validate_num_assertion(soft = true)
247
- assertions = REXML::XPath.match(
248
- document,
249
- "//a:Assertion",
250
- { "a" => ASSERTION }
251
- )
252
- encrypted_assertions = REXML::XPath.match(
253
- document,
254
- "//a:EncryptedAssertion",
255
- { "a" => ASSERTION }
256
- )
257
-
258
- unless assertions.size + encrypted_assertions.size == 1
259
- return soft ? false : validation_error("SAML Response must contain 1 assertion")
260
- end
261
-
262
- true
263
- end
264
-
265
- # Validates the Signed elements
266
- # @return [Boolean] True if there is 1 or 2 Elements signed in the SAML Response
267
- # an are a Response or an Assertion Element, otherwise False if soft=True
268
- #
269
- def validate_signed_elements(soft)
270
- signature_nodes = REXML::XPath.match(
271
- document,
272
- "//ds:Signature",
273
- {"ds"=>DSIG}
274
- )
275
- signed_elements = []
276
- verified_seis = []
277
- verified_ids = []
278
- signature_nodes.each do |signature_node|
279
- signed_element = signature_node.parent.name
280
- if signed_element != 'Response' && signed_element != 'Assertion'
281
- return soft ? false : validation_error("Invalid Signature Element '#{signed_element}'. SAML Response rejected")
282
- end
283
-
284
- if signature_node.parent.attributes['ID'].nil?
285
- return soft ? false : validation_error("Signed Element must contain an ID. SAML Response rejected")
286
- end
287
-
288
- id = signature_node.parent.attributes.get_attribute("ID").value
289
- if verified_ids.include?(id)
290
- return soft ? false : validation_error("Duplicated ID. SAML Response rejected")
291
- end
292
- verified_ids.push(id)
293
-
294
- # Check that reference URI matches the parent ID and no duplicate References or IDs
295
- ref = REXML::XPath.first(signature_node, ".//ds:Reference", {"ds"=>DSIG})
296
- if ref
297
- uri = ref.attributes.get_attribute("URI")
298
- if uri && !uri.value.empty?
299
- sei = uri.value[1..-1]
300
-
301
- unless sei == id
302
- return soft ? false : validation_error("Found an invalid Signed Element. SAML Response rejected")
303
- end
304
-
305
- if verified_seis.include?(sei)
306
- return soft ? false : validation_error("Duplicated Reference URI. SAML Response rejected")
307
- end
308
-
309
- verified_seis.push(sei)
310
- end
311
- end
312
-
313
- signed_elements << signed_element
314
- end
315
-
316
- unless signature_nodes.length < 3 && !signed_elements.empty?
317
- return soft ? false : validation_error("Found an unexpected number of Signature Element. SAML Response rejected")
318
- end
319
-
320
- true
137
+ valid_saml?(document, soft) &&
138
+ validate_response_state(soft) &&
139
+ validate_conditions(soft) &&
140
+ validate_issuer(soft) &&
141
+ document.validate_document(get_fingerprint, soft) &&
142
+ validate_success_status(soft)
321
143
  end
322
144
 
323
- # Validates the Status of the SAML Response
324
- # @return [Boolean] True if the SAML Response contains a Success code, otherwise False if soft == false
325
- # @raise [ValidationError] if soft == false and validation fails
326
- #
327
145
  def validate_success_status(soft = true)
328
- return true if success?
329
-
330
- return false unless soft
331
-
332
- error_msg = 'The status code of the Response was not Success'
333
- status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code, status_message)
334
- return validation_error(status_error_msg)
335
- end
336
-
337
- # Checks if the Status has the "Success" code
338
- # @return [Boolean] True if the StatusCode is Sucess
339
- #
340
- def success?
341
- status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
342
- end
343
-
344
- # @return [String] StatusCode value from a SAML Response.
345
- #
346
- def status_code
347
- @status_code ||= begin
348
- nodes = REXML::XPath.match(
349
- document,
350
- "/p:Response/p:Status/p:StatusCode",
351
- { "p" => PROTOCOL }
352
- )
353
- if nodes.size == 1
354
- node = nodes[0]
355
- code = node.attributes["Value"] if node && node.attributes
356
-
357
- unless code == "urn:oasis:names:tc:SAML:2.0:status:Success"
358
- nodes = REXML::XPath.match(
359
- document,
360
- "/p:Response/p:Status/p:StatusCode/p:StatusCode",
361
- { "p" => PROTOCOL }
362
- )
363
- statuses = nodes.collect do |inner_node|
364
- inner_node.attributes["Value"]
365
- end
366
- extra_code = statuses.join(" | ")
367
- if extra_code
368
- code = "#{code} | #{extra_code}"
369
- end
370
- end
371
- code
372
- end
373
- end
374
- end
375
-
376
- # @return [String] the StatusMessage value from a SAML Response.
377
- #
378
- def status_message
379
- @status_message ||= begin
380
- nodes = REXML::XPath.match(
381
- document,
382
- "/p:Response/p:Status/p:StatusMessage",
383
- { "p" => PROTOCOL }
384
- )
385
- if nodes.size == 1
386
- Utils.element_text(nodes.first)
387
- end
146
+ if success?
147
+ true
148
+ else
149
+ soft ? false : validation_error(status_message)
388
150
  end
389
151
  end
390
152
 
391
153
  def validate_structure(soft = true)
392
154
  Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
393
- @schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
155
+ @schema = Nokogiri::XML::Schema(IO.read('saml-schema-protocol-2.0.xsd'))
394
156
  @xml = Nokogiri::XML(self.document.to_s)
395
157
  end
396
158
  if soft
397
- @schema.validate(@xml).map{ return false }
159
+ @schema.validate(@xml).map{
160
+ @errors << "Schema validation failed";
161
+ return false
162
+ }
398
163
  else
399
- @schema.validate(@xml).map{ |error| validation_error("#{error.message}\n\n#{@xml.to_s}") }
164
+ @schema.validate(@xml).map{ |error| @errors << "#{error.message}\n\n#{@xml.to_s}";
165
+ validation_error("#{error.message}\n\n#{@xml.to_s}")
166
+ }
400
167
  end
401
168
  end
402
169
 
@@ -432,24 +199,13 @@ module OneLogin
432
199
  node
433
200
  end
434
201
 
435
- # Extracts all the appearances that matchs the subelt (pattern)
436
- # Search on any Assertion that is signed, or has a Response parent signed
437
- # @param subelt [String] The XPath pattern
438
- # @return [Array of REXML::Element] Return all matches
439
- #
440
- def xpath_from_signed_assertion(subelt=nil)
441
- node = REXML::XPath.match(
442
- document,
443
- "/p:Response/a:Assertion[@ID=$id]#{subelt}",
444
- { "p" => PROTOCOL, "a" => ASSERTION },
445
- { 'id' => document.signed_element_id }
446
- )
447
- node.concat( REXML::XPath.match(
448
- document,
449
- "/p:Response[@ID=$id]/a:Assertion#{subelt}",
450
- { "p" => PROTOCOL, "a" => ASSERTION },
451
- { 'id' => document.signed_element_id }
452
- ))
202
+ def get_fingerprint
203
+ if settings.idp_cert
204
+ cert = OpenSSL::X509::Certificate.new(settings.idp_cert)
205
+ Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
206
+ else
207
+ settings.idp_cert_fingerprint
208
+ end
453
209
  end
454
210
 
455
211
  def validate_conditions(soft = true)
@@ -459,10 +215,12 @@ module OneLogin
459
215
  now = Time.now.utc
460
216
 
461
217
  if not_before && (now + (options[:allowed_clock_drift] || 0)) < not_before
218
+ @errors << "Current time is earlier than NotBefore condition #{(now + (options[:allowed_clock_drift] || 0))} < #{not_before})"
462
219
  return soft ? false : validation_error("Current time is earlier than NotBefore condition")
463
220
  end
464
221
 
465
222
  if not_on_or_after && now >= not_on_or_after
223
+ @errors << "Current time is on or after NotOnOrAfter condition (#{now} >= #{not_on_or_after})"
466
224
  return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
467
225
  end
468
226
 
@@ -472,64 +230,9 @@ module OneLogin
472
230
  def validate_issuer(soft = true)
473
231
  return true if settings.idp_entity_id.nil?
474
232
 
475
- begin
476
- obtained_issuers = issuers
477
- rescue ValidationError => e
478
- return soft ? false : validation_error("Error while extracting issuers")
479
- end
480
-
481
- obtained_issuers.each do |issuer|
482
- unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
483
- error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>"
484
- return soft ? false : validation_error(error_msg)
485
- end
486
- end
487
-
488
- true
489
- end
490
-
491
- def validate_signature(soft = true)
492
- error_msg = "Invalid Signature on SAML Response"
493
-
494
- sig_elements = REXML::XPath.match(
495
- document,
496
- "/p:Response[@ID=$id]/ds:Signature]",
497
- { "p" => PROTOCOL, "ds" => DSIG },
498
- { 'id' => document.signed_element_id }
499
- )
500
-
501
- # Check signature nodes
502
- if sig_elements.nil? || sig_elements.size == 0
503
- sig_elements = REXML::XPath.match(
504
- document,
505
- "/p:Response/a:Assertion[@ID=$id]/ds:Signature",
506
- {"p" => PROTOCOL, "a" => ASSERTION, "ds"=>DSIG},
507
- { 'id' => document.signed_element_id }
508
- )
509
- end
510
-
511
- if sig_elements.size != 1
512
- if sig_elements.size == 0
513
- error_msg += ". Signed element id ##{doc.signed_element_id} is not found"
514
- else
515
- error_msg += ". Signed element id ##{doc.signed_element_id} is found more than once"
516
- end
517
- return soft ? false : validation_error(error_msg)
518
- end
519
-
520
- opts = {}
521
- opts[:fingerprint_alg] = OpenSSL::Digest::SHA1.new
522
- opts[:cert] = settings.get_idp_cert
523
- fingerprint = settings.get_fingerprint
524
-
525
- unless fingerprint
526
- return soft ? false : validation_error("No fingerprint or certificate on settings")
527
- end
528
-
529
- unless document.validate_document(fingerprint, soft, opts)
530
- return soft ? false : validation_error(error_msg)
233
+ unless URI.parse(issuer) == URI.parse(settings.idp_entity_id)
234
+ return soft ? false : validation_error("Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>")
531
235
  end
532
-
533
236
  true
534
237
  end
535
238
 
@@ -538,19 +241,6 @@ module OneLogin
538
241
  Time.parse(node.attributes[attribute])
539
242
  end
540
243
  end
541
-
542
- def validate_audience(soft = true)
543
- return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
544
-
545
- unless audiences.include? settings.sp_entity_id
546
- s = audiences.count > 1 ? 's' : '';
547
- error_msg = "Invalid Audience#{s}. The audience#{s} #{audiences.join(',')}, did not match the expected audience #{settings.sp_entity_id}"
548
- return soft ? false : validation_error(error_msg)
549
- end
550
-
551
- true
552
- end
553
-
554
244
  end
555
245
  end
556
246
  end
@@ -0,0 +1,78 @@
1
+ require 'cgi'
2
+ require 'zlib'
3
+ require 'base64'
4
+ require "rexml/document"
5
+ require "rexml/xpath"
6
+
7
+ module OneLogin
8
+ module RubySaml
9
+ class SamlMessage
10
+ include REXML
11
+
12
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
13
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
14
+
15
+ def valid_saml?(document, soft = true)
16
+ Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
17
+ @schema = Nokogiri::XML::Schema(IO.read('saml-schema-protocol-2.0.xsd'))
18
+ @xml = Nokogiri::XML(document.to_s)
19
+ end
20
+ if soft
21
+ @schema.validate(@xml).map{ return false }
22
+ else
23
+ @schema.validate(@xml).map{ |error| validation_error("#{error.message}\n\n#{@xml.to_s}") }
24
+ end
25
+ end
26
+
27
+ def validation_error(message)
28
+ raise ValidationError.new(message)
29
+ end
30
+
31
+ private
32
+
33
+ def decode_raw_saml(saml)
34
+ if saml =~ /^</
35
+ return saml
36
+ elsif (decoded = decode(saml)) =~ /^</
37
+ return decoded
38
+ elsif (inflated = inflate(decoded)) =~ /^</
39
+ return inflated
40
+ end
41
+
42
+ return nil
43
+ end
44
+
45
+ def encode_raw_saml(saml, settings)
46
+ saml = Zlib::Deflate.deflate(saml, 9)[2..-5] if settings.compress_request
47
+ base64_saml = Base64.encode64(saml)
48
+ return CGI.escape(base64_saml)
49
+ end
50
+
51
+ def decode(encoded)
52
+ Base64.decode64(encoded)
53
+ end
54
+
55
+ def encode(encoded)
56
+ Base64.encode64(encoded).gsub(/\n/, "")
57
+ end
58
+
59
+ def escape(unescaped)
60
+ CGI.escape(unescaped)
61
+ end
62
+
63
+ def unescape(escaped)
64
+ CGI.unescape(escaped)
65
+ end
66
+
67
+ def inflate(deflated)
68
+ zlib = Zlib::Inflate.new(-Zlib::MAX_WBITS)
69
+ zlib.inflate(deflated)
70
+ end
71
+
72
+ def deflate(inflated)
73
+ Zlib::Deflate.deflate(inflated, 9)[2..-5]
74
+ end
75
+
76
+ end
77
+ end
78
+ end