ruby-saml-mod 0.1.22 → 0.1.23

Sign up to get free protection for your applications and to get access to all the features.
@@ -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