ruby-saml-mod 0.1.22 → 0.1.23

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.
@@ -2,7 +2,7 @@ module Onelogin::Saml
2
2
  class Response
3
3
 
4
4
  attr_accessor :settings
5
- attr_reader :document, :decrypted_document, :xml, :response
5
+ attr_reader :document, :xml, :response
6
6
  attr_reader :name_id, :name_qualifier, :session_index, :saml_attributes
7
7
  attr_reader :status_code, :status_message
8
8
  attr_reader :in_response_to, :destination, :issuer
@@ -14,14 +14,14 @@ module Onelogin::Saml
14
14
  @xml = Base64.decode64(@response)
15
15
  @document = LibXML::XML::Document.string(@xml)
16
16
  @document.extend(XMLSecurity::SignedDocument)
17
- rescue => e
17
+ rescue
18
18
  # could not parse document, everything is invalid
19
19
  @response = nil
20
20
  return
21
21
  end
22
22
 
23
- @issuer = @document.find_first("/samlp:Response/saml:Issuer", Onelogin::NAMESPACES).content rescue nil
24
- @status_code = @document.find_first("/samlp:Response/samlp:Status/samlp:StatusCode", Onelogin::NAMESPACES)["Value"] rescue nil
23
+ @issuer = document.find_first("/samlp:Response/saml:Issuer", Onelogin::NAMESPACES).content rescue nil
24
+ @status_code = document.find_first("/samlp:Response/samlp:Status/samlp:StatusCode", Onelogin::NAMESPACES)["Value"] rescue nil
25
25
 
26
26
  process(settings) if settings
27
27
  end
@@ -30,79 +30,119 @@ module Onelogin::Saml
30
30
  @settings = settings
31
31
  return unless @response
32
32
 
33
- @decrypted_document = LibXML::XML::Document.document(@document)
34
- @decrypted_document.extend(XMLSecurity::SignedDocument)
35
- @decrypted_document.decrypt!(@settings)
33
+ @in_response_to = untrusted_find_first("/samlp:Response")['InResponseTo'] rescue nil
34
+ @destination = untrusted_find_first("/samlp:Response")['Destination'] rescue nil
35
+ @status_message = untrusted_find_first("/samlp:Response/samlp:Status/samlp:StatusCode").content rescue nil
36
+
37
+ @name_id = trusted_find_first("saml:Assertion/saml:Subject/saml:NameID").content rescue nil
38
+ @name_qualifier = trusted_find_first("saml:Assertion/saml:Subject/saml:NameID")["NameQualifier"] rescue nil
39
+ @session_index = trusted_find_first("saml:Assertion/saml:AuthnStatement")["SessionIndex"] rescue nil
36
40
 
37
- @in_response_to = @decrypted_document.find_first("/samlp:Response", Onelogin::NAMESPACES)['InResponseTo'] rescue nil
38
- @destination = @decrypted_document.find_first("/samlp:Response", Onelogin::NAMESPACES)['Destination'] rescue nil
39
- @name_id = @decrypted_document.find_first("/samlp:Response/saml:Assertion/saml:Subject/saml:NameID", Onelogin::NAMESPACES).content rescue nil
40
41
  @saml_attributes = {}
41
- @decrypted_document.find("//saml:Attribute", Onelogin::NAMESPACES).each do |attr|
42
+ trusted_find("saml:Attribute").each do |attr|
42
43
  attrname = attr['FriendlyName'] || Onelogin::ATTRIBUTES[attr['Name']] || attr['Name']
43
44
  @saml_attributes[attrname] = attr.content.strip rescue nil
44
45
  end
45
- @name_qualifier = @decrypted_document.find_first("/samlp:Response/saml:Assertion/saml:Subject/saml:NameID", Onelogin::NAMESPACES)["NameQualifier"] rescue nil
46
- @session_index = @decrypted_document.find_first("/samlp:Response/saml:Assertion/saml:AuthnStatement", Onelogin::NAMESPACES)["SessionIndex"] rescue nil
47
- @status_message = @decrypted_document.find_first("/samlp:Response/samlp:Status/samlp:StatusCode", Onelogin::NAMESPACES).content rescue nil
46
+ end
47
+
48
+ def disable_signature_validation!(settings)
49
+ @settings = settings
50
+ @is_valid = true
51
+ @trusted_roots = [decrypted_document.root]
52
+ end
53
+
54
+ def decrypted_document
55
+ @decrypted_document ||= LibXML::XML::Document.document(document).tap do |doc|
56
+ doc.extend(XMLSecurity::SignedDocument)
57
+ doc.decrypt!(settings)
58
+ end
59
+ end
60
+
61
+ def untrusted_find_first(xpath)
62
+ decrypted_document.find(xpath, Onelogin::NAMESPACES).first
63
+ end
64
+
65
+ def trusted_find_first(xpath)
66
+ trusted_find(xpath).first
67
+ end
68
+
69
+ def trusted_find(xpath)
70
+ trusted_roots.map do |trusted_root|
71
+ trusted_root.find("descendant-or-self::#{xpath}", Onelogin::NAMESPACES).to_a
72
+ end.flatten.compact
48
73
  end
49
74
 
50
75
  def logger=(val)
51
76
  @logger = val
52
77
  end
53
-
78
+
54
79
  def is_valid?
55
- if @response.nil? || @response == ""
80
+ @is_valid ||= validate
81
+ end
82
+
83
+ def validate
84
+ if response.nil? || response == ""
56
85
  @validation_error = "No response to validate"
57
86
  return false
58
87
  end
59
-
60
- if !@settings.idp_cert_fingerprint
88
+
89
+ if !settings.idp_cert_fingerprint
61
90
  @validation_error = "No fingerprint configured in SAML settings"
62
91
  return false
63
92
  end
64
-
93
+
65
94
  # Verify the original document if it has a signature, otherwise verify the signature
66
95
  # in the encrypted portion. If there is no signature, then we can't verify.
67
96
  verified = false
68
- if @document.find_first("//ds:Signature", Onelogin::NAMESPACES)
69
- verified = @document.validate(@settings.idp_cert_fingerprint, @logger)
97
+
98
+ if document.has_signature?
99
+ verified = document.validate(settings.idp_cert_fingerprint, @logger)
70
100
  if !verified
71
- @validation_error = @document.validation_error
101
+ @validation_error = document.validation_error
72
102
  return false
73
103
  end
74
104
  end
75
105
 
76
- if !verified && @decrypted_document.find_first("//ds:Signature", Onelogin::NAMESPACES)
77
- verified = @decrypted_document.validate(@settings.idp_cert_fingerprint, @logger)
106
+ if !verified && decrypted_document.has_signature?
107
+ verified = decrypted_document.validate(settings.idp_cert_fingerprint, @logger)
78
108
  if !verified
79
- @validation_error = @document.validation_error
109
+ @validation_error = decrypted_document.validation_error
80
110
  return false
81
111
  end
82
112
  end
83
-
113
+
84
114
  if !verified
85
115
  @validation_error = "No signature found in the response"
86
116
  return false
87
117
  end
88
-
118
+
119
+ # If we get here, validation has succeeded, and we can trust all
120
+ # <ds:Signature> elements. Each of those has a <ds:Reference> which
121
+ # points to the root of the root of the NodeSet it signs.
122
+ @trusted_roots = decrypted_document.signed_roots
123
+
89
124
  true
90
125
  end
91
-
126
+
127
+ # triggers validation
128
+ def trusted_roots
129
+ is_valid? ? @trusted_roots : []
130
+ end
131
+
92
132
  def success_status?
93
133
  @status_code == Onelogin::Saml::StatusCodes::SUCCESS_URI
94
134
  end
95
-
135
+
96
136
  def auth_failure?
97
137
  @status_code == Onelogin::Saml::StatusCodes::AUTHN_FAILED_URI
98
138
  end
99
-
139
+
100
140
  def no_authn_context?
101
141
  @status_code == Onelogin::Saml::StatusCodes::NO_AUTHN_CONTEXT_URI
102
142
  end
103
-
143
+
104
144
  def fingerprint_from_idp
105
- if base64_cert = @decrypted_document.find_first("//ds:X509Certificate", Onelogin::NAMESPACES)
145
+ if base64_cert = decrypted_document.find_first("//ds:X509Certificate", Onelogin::NAMESPACES)
106
146
  cert_text = Base64.decode64(base64_cert.content)
107
147
  cert = OpenSSL::X509::Certificate.new(cert_text)
108
148
  Digest::SHA1.hexdigest(cert.to_der)
data/lib/xml_sec.rb CHANGED
@@ -267,6 +267,29 @@ module XMLSecurity
267
267
  "-----BEGIN PUBLIC KEY-----\n#{base64}-----END PUBLIC KEY-----"
268
268
  end
269
269
 
270
+ def has_signature?
271
+ signatures.any?
272
+ end
273
+
274
+ def signed_roots
275
+ signatures.map do |sig|
276
+ ref = sig.find('.//ds:Reference', Onelogin::NAMESPACES).first
277
+ signed_element_id = ref['URI'].sub(/^#/, '')
278
+
279
+ if signed_element_id.empty?
280
+ self.root
281
+ else
282
+ xpath_id_query = %Q(ancestor::*[@ID = "#{signed_element_id}"])
283
+
284
+ ref.find(xpath_id_query, Onelogin::NAMESPACES).first
285
+ end
286
+ end.compact
287
+ end
288
+
289
+ def signatures
290
+ @signatures ||= self.find("//ds:Signature", Onelogin::NAMESPACES)
291
+ end
292
+
270
293
  def validate(idp_cert_fingerprint, logger = nil)
271
294
  # get cert from response
272
295
  base64_cert = self.find_first("//ds:X509Certificate", Onelogin::NAMESPACES).content
@@ -1,9 +1,9 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{ruby-saml-mod}
3
- s.version = "0.1.22"
3
+ s.version = "0.1.23"
4
4
 
5
5
  s.authors = ["OneLogin LLC", "Bracken", "Zach", "Cody", "Jeremy", "Paul"]
6
- s.date = %q{2013-05-07}
6
+ s.date = %q{2014-01-25}
7
7
  s.extra_rdoc_files = [
8
8
  "LICENSE"
9
9
  ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-saml-mod
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.22
4
+ version: 0.1.23
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2013-05-07 00:00:00.000000000 Z
17
+ date: 2014-01-25 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: libxml-ruby