ruby-saml 0.8.12 → 0.8.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

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