ruby-saml 0.8.12 → 0.8.17

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 (50) hide show
  1. checksums.yaml +7 -7
  2. data/lib/onelogin/ruby-saml/logoutrequest.rb +2 -1
  3. data/lib/onelogin/ruby-saml/logoutresponse.rb +9 -51
  4. data/lib/onelogin/ruby-saml/response.rb +133 -21
  5. data/lib/onelogin/ruby-saml/settings.rb +28 -10
  6. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +101 -0
  7. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +12 -9
  8. data/lib/onelogin/ruby-saml/utils.rb +92 -0
  9. data/lib/onelogin/ruby-saml/version.rb +1 -1
  10. data/lib/ruby-saml.rb +1 -0
  11. data/lib/xml_security.rb +222 -86
  12. data/test/certificates/certificate.der +0 -0
  13. data/test/certificates/formatted_certificate +14 -0
  14. data/test/certificates/formatted_chained_certificate +42 -0
  15. data/test/certificates/formatted_private_key +12 -0
  16. data/test/certificates/formatted_rsa_private_key +12 -0
  17. data/test/certificates/invalid_certificate1 +1 -0
  18. data/test/certificates/invalid_certificate2 +1 -0
  19. data/test/certificates/invalid_certificate3 +12 -0
  20. data/test/certificates/invalid_chained_certificate1 +1 -0
  21. data/test/certificates/invalid_private_key1 +1 -0
  22. data/test/certificates/invalid_private_key2 +1 -0
  23. data/test/certificates/invalid_private_key3 +10 -0
  24. data/test/certificates/invalid_rsa_private_key1 +1 -0
  25. data/test/certificates/invalid_rsa_private_key2 +1 -0
  26. data/test/certificates/invalid_rsa_private_key3 +10 -0
  27. data/test/certificates/ruby-saml-2.crt +15 -0
  28. data/test/logoutresponse_test.rb +2 -16
  29. data/test/requests/logoutrequest_fixtures.rb +47 -0
  30. data/test/response_test.rb +227 -15
  31. data/test/responses/adfs_response_xmlns.xml +45 -0
  32. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
  33. data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
  34. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  35. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  36. data/test/responses/invalids/response_with_concealed_signed_assertion.xml +51 -0
  37. data/test/responses/invalids/response_with_doubled_signed_assertion.xml +49 -0
  38. data/test/responses/invalids/signature_wrapping_attack.xml.base64 +1 -0
  39. data/test/responses/logoutresponse_fixtures.rb +4 -4
  40. data/test/responses/response_with_signed_assertion_3.xml +30 -0
  41. data/test/responses/response_with_signed_message_and_assertion.xml +34 -0
  42. data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
  43. data/test/responses/valid_response_without_x509certificate.xml.base64 +1 -0
  44. data/test/settings_test.rb +106 -0
  45. data/test/slo_logoutrequest_test.rb +66 -0
  46. data/test/slo_logoutresponse_test.rb +8 -0
  47. data/test/test_helper.rb +62 -30
  48. data/test/utils_test.rb +191 -1
  49. data/test/xml_security_test.rb +329 -36
  50. metadata +109 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
- ---
2
- SHA512:
3
- metadata.gz: 8a2479b6725a5a9e7fdc76a4bec612e2f0c66cf53cbb79ff7c1dc0343d1cc56c09e9fd3b2d3490bdacbb09b16b473702d42fa42fdb6fedff6e7fa5a44fa421a2
4
- data.tar.gz: 43b1cfb12dc3fc14a2cbc139430e3ad15b975dca0d95d0d8c14363f362833de181a52ca10b8b607215c4b5aa23ffa62f2d8f3a3b2c9b5541e9387c635ca77150
5
- SHA256:
6
- metadata.gz: 694ade703ed05cc38aa2ca98cbfee57cc16223991ae6539422c136164cf29608
7
- data.tar.gz: ee07b69a9391b26c9af95d0cfdbaa57c8991fa187b869660e15c549fcbbe47e3
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1ec15a6a64795cd0b10b796d6aef230a7d7d439c
4
+ data.tar.gz: a07ddee9fb7bfe9ca2f20cde2c9cadfd5bbac121
5
+ SHA512:
6
+ metadata.gz: 013b1a3b9b2eb015253dcc4992a1d8be73a3dce996271d701375914e3e7f3e64f6eff3094af1ade43fc807ce452093d05c071cf79b5b2cc8b63d3041688f9344
7
+ data.tar.gz: ed258b192c0cbd0b5c589183ecacd9591dad637783430130a9dfd97b6c68c1062520cf91255ed997adf693f1c7f344d84cf0184a821202c8c8ea85509c069864
@@ -114,7 +114,8 @@ module OneLogin
114
114
 
115
115
  if settings.name_identifier_value
116
116
  name_id = root.add_element "saml:NameID", { "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
117
- name_id.attributes['NameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
117
+ nameid.attributes['NameQualifier'] = settings.idp_name_qualifier if settings.idp_name_qualifier
118
+ nameid.attributes['SPNameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
118
119
  name_id.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
119
120
  name_id.text = settings.name_identifier_value
120
121
  end
@@ -1,7 +1,5 @@
1
1
  require "xml_security"
2
2
  require "time"
3
- require "base64"
4
- require "zlib"
5
3
 
6
4
  module OneLogin
7
5
  module RubySaml
@@ -30,8 +28,8 @@ module OneLogin
30
28
  self.settings = settings
31
29
 
32
30
  @options = options
33
- @response = decode_raw_response(response)
34
- @document = XMLSecurity::SignedDocument.new(response)
31
+ @response = OneLogin::RubySaml::Utils.decode_raw_saml(response)
32
+ @document = XMLSecurity::SignedDocument.new(@response)
35
33
  end
36
34
 
37
35
  def validate!
@@ -39,7 +37,7 @@ module OneLogin
39
37
  end
40
38
 
41
39
  def validate(soft = true)
42
- return false unless valid_saml?(soft) && valid_state?(soft)
40
+ return false unless validate_structure(soft)
43
41
 
44
42
  valid_in_response_to?(soft) && valid_issuer?(soft) && success?(soft)
45
43
  end
@@ -53,7 +51,7 @@ module OneLogin
53
51
 
54
52
  def in_response_to
55
53
  @in_response_to ||= begin
56
- node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL, "a" => ASSERTION })
54
+ node = REXML::XPath.first(document, "/p:LogoutResponse", { "p" => PROTOCOL })
57
55
  node.nil? ? nil : node.attributes['InResponseTo']
58
56
  end
59
57
  end
@@ -61,7 +59,6 @@ module OneLogin
61
59
  def issuer
62
60
  @issuer ||= begin
63
61
  node = REXML::XPath.first(document, "/p:LogoutResponse/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
64
- node ||= REXML::XPath.first(document, "/p:LogoutResponse/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
65
62
  Utils.element_text(node)
66
63
  end
67
64
  end
@@ -75,28 +72,7 @@ module OneLogin
75
72
 
76
73
  private
77
74
 
78
- def decode(encoded)
79
- Base64.decode64(encoded)
80
- end
81
-
82
- def inflate(deflated)
83
- zlib = Zlib::Inflate.new(-Zlib::MAX_WBITS)
84
- zlib.inflate(deflated)
85
- end
86
-
87
- def decode_raw_response(response)
88
- if response =~ /^</
89
- return response
90
- elsif (decoded = decode(response)) =~ /^</
91
- return decoded
92
- elsif (inflated = inflate(decoded)) =~ /^</
93
- return inflated
94
- end
95
-
96
- raise "Couldn't decode SAMLResponse"
97
- end
98
-
99
- def valid_saml?(soft = true)
75
+ def validate_structure(soft = true)
100
76
  Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
101
77
  @schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
102
78
  @xml = Nokogiri::XML(self.document.to_s)
@@ -108,26 +84,6 @@ module OneLogin
108
84
  end
109
85
  end
110
86
 
111
- def valid_state?(soft = true)
112
- if response.empty?
113
- return soft ? false : validation_error("Blank response")
114
- end
115
-
116
- if settings.nil?
117
- return soft ? false : validation_error("No settings on response")
118
- end
119
-
120
- if settings.sp_entity_id.nil?
121
- return soft ? false : validation_error("No sp_entity_id in settings")
122
- end
123
-
124
- if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
125
- return soft ? false : validation_error("No fingerprint or certificate on settings")
126
- end
127
-
128
- true
129
- end
130
-
131
87
  def valid_in_response_to?(soft = true)
132
88
  return true unless self.options.has_key? :matches_request_id
133
89
 
@@ -139,8 +95,10 @@ module OneLogin
139
95
  end
140
96
 
141
97
  def valid_issuer?(soft = true)
142
- unless URI.parse(issuer) == URI.parse(self.settings.sp_entity_id)
143
- return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.sp_entity_id}>, but was: <#{issuer}>")
98
+ return true if settings.nil? || settings.idp_entity_id.nil? || issuer.nil?
99
+
100
+ unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
101
+ return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.idp_entity_id}>, but was: <#{issuer}>")
144
102
  end
145
103
  true
146
104
  end
@@ -1,6 +1,7 @@
1
1
  require "xml_security"
2
2
  require "time"
3
3
  require "nokogiri"
4
+ require "onelogin/ruby-saml/utils"
4
5
  require 'onelogin/ruby-saml/attributes'
5
6
 
6
7
  # Only supports SAML 2.0
@@ -22,7 +23,7 @@ module OneLogin
22
23
  def initialize(response, options = {})
23
24
  raise ArgumentError.new("Response cannot be nil") if response.nil?
24
25
  @options = options
25
- @response = (response =~ /^</) ? response : Base64.decode64(response)
26
+ @response = OneLogin::RubySaml::Utils.decode_raw_saml(response)
26
27
  @document = XMLSecurity::SignedDocument.new(@response)
27
28
  end
28
29
 
@@ -34,16 +35,48 @@ module OneLogin
34
35
  validate(false)
35
36
  end
36
37
 
37
- # The value of the user identifier as designated by the initialization request response
38
- def name_id
38
+ def name_id_node
39
39
  @name_id ||= begin
40
- node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
41
- Utils.element_text(node)
40
+ xpath_first_from_signed_assertion('/a:Subject/a:NameID')
42
41
  end
43
42
  end
44
43
 
44
+ # The value of the user identifier as designated by the initialization request response
45
+ def name_id
46
+ @name_id ||= Utils.element_text(name_id_node)
47
+ end
48
+
45
49
  alias nameid name_id
46
50
 
51
+ # @return [String] the NameID Format provided by the SAML response from the IdP.
52
+ #
53
+ def name_id_format
54
+ @name_id_format ||=
55
+ if name_id_node && name_id_node.attribute("Format")
56
+ name_id_node.attribute("Format").value
57
+ end
58
+ end
59
+
60
+ alias_method :nameid_format, :name_id_format
61
+
62
+ # @return [String] the NameID SPNameQualifier provided by the SAML response from the IdP.
63
+ #
64
+ def name_id_spnamequalifier
65
+ @name_id_spnamequalifier ||=
66
+ if name_id_node && name_id_node.attribute("SPNameQualifier")
67
+ name_id_node.attribute("SPNameQualifier").value
68
+ end
69
+ end
70
+
71
+ # @return [String] the NameID NameQualifier provided by the SAML response from the IdP.
72
+ #
73
+ def name_id_namequalifier
74
+ @name_id_namequalifier ||=
75
+ if name_id_node && name_id_node.attribute("NameQualifier")
76
+ name_id_node.attribute("NameQualifier").value
77
+ end
78
+ end
79
+
47
80
  def sessionindex
48
81
  @sessionindex ||= begin
49
82
  node = xpath_first_from_signed_assertion('/a:AuthnStatement')
@@ -133,6 +166,34 @@ module OneLogin
133
166
  end
134
167
  end
135
168
 
169
+ # Gets the Issuers (from Response and Assertion).
170
+ # (returns the first node that matches the supplied xpath from the Response and from the Assertion)
171
+ # @return [Array] Array with the Issuers (REXML::Element)
172
+ #
173
+ def issuers
174
+ @issuers ||= begin
175
+ issuer_response_nodes = REXML::XPath.match(
176
+ document,
177
+ "/p:Response/a:Issuer",
178
+ { "p" => PROTOCOL, "a" => ASSERTION }
179
+ )
180
+
181
+ unless issuer_response_nodes.size == 1
182
+ error_msg = "Issuer of the Response not found or multiple."
183
+ raise ValidationError.new(error_msg)
184
+ end
185
+
186
+ issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
187
+ unless issuer_assertion_nodes.size == 1
188
+ error_msg = "Issuer of the Assertion not found or multiple."
189
+ raise ValidationError.new(error_msg)
190
+ end
191
+
192
+ nodes = issuer_response_nodes + issuer_assertion_nodes
193
+ nodes.map { |node| Utils.element_text(node) }.compact.uniq
194
+ end
195
+ end
196
+
136
197
  # @return [Array] The Audience elements from the Contitions of the SAML Response.
137
198
  #
138
199
  def audiences
@@ -156,7 +217,8 @@ module OneLogin
156
217
  validate_response_state(soft) &&
157
218
  validate_conditions(soft) &&
158
219
  validate_audience(soft) &&
159
- document.validate_document(get_fingerprint, soft) &&
220
+ validate_issuer(soft) &&
221
+ validate_signature(soft) &&
160
222
  success?
161
223
  end
162
224
 
@@ -224,7 +286,6 @@ module OneLogin
224
286
 
225
287
  if verified_seis.include?(sei)
226
288
  return soft ? false : validation_error("Duplicated Reference URI. SAML Response rejected")
227
- return append_error("Duplicated Reference URI. SAML Response rejected")
228
289
  end
229
290
 
230
291
  verified_seis.push(sei)
@@ -373,15 +434,6 @@ module OneLogin
373
434
  ))
374
435
  end
375
436
 
376
- def get_fingerprint
377
- if settings.idp_cert
378
- cert = OpenSSL::X509::Certificate.new(settings.idp_cert)
379
- Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
380
- else
381
- settings.idp_cert_fingerprint
382
- end
383
- end
384
-
385
437
  def validate_conditions(soft = true)
386
438
  return true if conditions.nil?
387
439
  return true if options[:skip_conditions]
@@ -399,17 +451,76 @@ module OneLogin
399
451
  true
400
452
  end
401
453
 
454
+ def validate_issuer(soft = true)
455
+ return true if settings.idp_entity_id.nil?
456
+
457
+ begin
458
+ obtained_issuers = issuers
459
+ rescue ValidationError => e
460
+ return soft ? false : validation_error("Error while extracting issuers")
461
+ end
462
+
463
+ obtained_issuers.each do |issuer|
464
+ unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
465
+ error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>"
466
+ return soft ? false : validation_error(error_msg)
467
+ end
468
+ end
469
+
470
+ true
471
+ end
472
+
473
+ def validate_signature(soft = true)
474
+ error_msg = "Invalid Signature on SAML Response"
475
+
476
+ sig_elements = REXML::XPath.match(
477
+ document,
478
+ "/p:Response[@ID=$id]/ds:Signature]",
479
+ { "p" => PROTOCOL, "ds" => DSIG },
480
+ { 'id' => document.signed_element_id }
481
+ )
482
+
483
+ # Check signature nodes
484
+ if sig_elements.nil? || sig_elements.size == 0
485
+ sig_elements = REXML::XPath.match(
486
+ document,
487
+ "/p:Response/a:Assertion[@ID=$id]/ds:Signature",
488
+ {"p" => PROTOCOL, "a" => ASSERTION, "ds"=>DSIG},
489
+ { 'id' => document.signed_element_id }
490
+ )
491
+ end
492
+
493
+ if sig_elements.size != 1
494
+ if sig_elements.size == 0
495
+ error_msg += ". Signed element id ##{doc.signed_element_id} is not found"
496
+ else
497
+ error_msg += ". Signed element id ##{doc.signed_element_id} is found more than once"
498
+ end
499
+ return soft ? false : validation_error(error_msg)
500
+ end
501
+
502
+ opts = {}
503
+ opts[:fingerprint_alg] = OpenSSL::Digest::SHA1.new
504
+ opts[:cert] = settings.get_idp_cert
505
+ fingerprint = settings.get_fingerprint
506
+
507
+ unless fingerprint
508
+ return soft ? false : validation_error("No fingerprint or certificate on settings")
509
+ end
510
+
511
+ unless document.validate_document(fingerprint, soft, opts)
512
+ return soft ? false : validation_error(error_msg)
513
+ end
514
+
515
+ true
516
+ end
517
+
402
518
  def parse_time(node, attribute)
403
519
  if node && node.attributes[attribute]
404
520
  Time.parse(node.attributes[attribute])
405
521
  end
406
522
  end
407
523
 
408
- # Validates the Audience, (If the Audience match the Service Provider EntityID)
409
- # If fails, the error is added to the errors array
410
- # @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True
411
- # @raise [ValidationError] if soft == false and validation fails
412
- #
413
524
  def validate_audience(soft = true)
414
525
  return true if audiences.empty? || settings.sp_entity_id.nil? || settings.sp_entity_id.empty?
415
526
 
@@ -421,6 +532,7 @@ module OneLogin
421
532
 
422
533
  true
423
534
  end
535
+
424
536
  end
425
537
  end
426
538
  end
@@ -27,16 +27,17 @@ module OneLogin
27
27
  attr_accessor :idp_cert_fingerprint
28
28
  attr_accessor :idp_cert
29
29
  attr_accessor :idp_slo_target_url
30
+ attr_accessor :idp_entity_id
30
31
  #sp data
31
32
  attr_accessor :sp_entity_id
32
33
  attr_accessor :assertion_consumer_service_url
33
34
  attr_accessor :authn_context
34
35
  attr_accessor :sp_name_qualifier
36
+ attr_accessor :idp_name_qualifier
35
37
  attr_accessor :name_identifier_format
36
38
  attr_accessor :name_identifier_value
37
39
  attr_accessor :name_identifier_value_requested
38
40
  attr_accessor :sessionindex
39
- attr_accessor :assertion_consumer_logout_service_url
40
41
  attr_accessor :compress_request
41
42
  attr_accessor :compress_response
42
43
  attr_accessor :double_quote_xml_attribute_values
@@ -117,21 +118,38 @@ module OneLogin
117
118
  @single_logout_service_binding = url
118
119
  end
119
120
 
120
- # @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
121
+ # Calculates the fingerprint of the IdP x509 certificate.
122
+ # @return [String] The fingerprint
121
123
  #
122
- def get_sp_cert
123
- return nil if certificate.nil? || certificate.empty?
124
+ def get_fingerprint
125
+ idp_cert_fingerprint || begin
126
+ idp_cert = get_idp_cert
127
+ if idp_cert
128
+ Digest::SHA1.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":")
129
+ end
130
+ end
131
+ end
124
132
 
125
- formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
126
- OpenSSL::X509::Certificate.new(formatted_cert)
133
+ # @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
134
+ #
135
+ def get_idp_cert
136
+ return nil if idp_cert.nil?
137
+
138
+ if idp_cert.respond_to?(:to_pem)
139
+ idp_cert
140
+ else
141
+ return nil if idp_cert.empty?
142
+ formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
143
+ OpenSSL::X509::Certificate.new(formatted_cert)
144
+ end
127
145
  end
128
146
 
129
- # @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
147
+ # @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
130
148
  #
131
- def get_sp_cert_new
132
- return nil if certificate_new.nil? || certificate_new.empty?
149
+ def get_sp_cert
150
+ return nil if certificate.nil? || certificate.empty?
133
151
 
134
- formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate_new)
152
+ formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
135
153
  OpenSSL::X509::Certificate.new(formatted_cert)
136
154
  end
137
155
 
@@ -0,0 +1,101 @@
1
+ require "xml_security"
2
+ require "time"
3
+
4
+ # Only supports SAML 2.0
5
+ # SAML2 Logout Request (SLO IdP initiated, Parser)
6
+ module OneLogin
7
+ module RubySaml
8
+ class SloLogoutrequest
9
+
10
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
11
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
12
+
13
+ # OneLogin::RubySaml::Settings Toolkit settings
14
+ attr_accessor :settings
15
+
16
+ attr_reader :document
17
+ attr_reader :request
18
+ attr_reader :options
19
+
20
+ def initialize(request, settings = nil, options = {})
21
+ raise ArgumentError.new("Request cannot be nil") if request.nil?
22
+ self.settings = settings
23
+
24
+ @options = options
25
+ @request = OneLogin::RubySaml::Utils.decode_raw_saml(request)
26
+ @document = XMLSecurity::SignedDocument.new(@request)
27
+ end
28
+
29
+ def validate!
30
+ validate(false)
31
+ end
32
+
33
+ def validate(soft = true)
34
+ return false unless validate_structure(soft)
35
+
36
+ valid_issuer?(soft)
37
+ end
38
+
39
+ def name_id
40
+ @name_id ||= begin
41
+ node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
42
+ Utils.element_text(node)
43
+ end
44
+ end
45
+
46
+ alias_method :nameid, :name_id
47
+
48
+ def name_id_format
49
+ @name_id_node ||= REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
50
+ @name_id_format ||=
51
+ if @name_id_node && @name_id_node.attribute("Format")
52
+ @name_id_node.attribute("Format").value
53
+ end
54
+ end
55
+
56
+ alias_method :nameid_format, :name_id_format
57
+
58
+ def id
59
+ @id ||= begin
60
+ node = REXML::XPath.first(document, "/p:LogoutRequest", { "p" => PROTOCOL } )
61
+ node.nil? ? nil : node.attributes['ID']
62
+ end
63
+ end
64
+
65
+ def issuer
66
+ @issuer ||= begin
67
+ node = REXML::XPath.first(document, "/p:LogoutRequest/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
68
+ Utils.element_text(node)
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def validate_structure(soft = true)
75
+ Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
76
+ @schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
77
+ @xml = Nokogiri::XML(self.document.to_s)
78
+ end
79
+ if soft
80
+ @schema.validate(@xml).map{ return false }
81
+ else
82
+ @schema.validate(@xml).map{ |error| validation_error("#{error.message}\n\n#{@xml.to_s}") }
83
+ end
84
+ end
85
+
86
+ def valid_issuer?(soft = true)
87
+ return true if settings.nil? || settings.idp_entity_id.nil? || issuer.nil?
88
+
89
+ unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
90
+ return soft ? false : validation_error("Doesn't match the issuer, expected: <#{self.settings.idp_entity_id}>, but was: <#{issuer}>")
91
+ end
92
+ true
93
+ end
94
+
95
+ def validation_error(message)
96
+ raise ValidationError.new(message)
97
+ end
98
+
99
+ end
100
+ end
101
+ end