ruby-saml 1.1.2 → 1.2.0

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.

checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 162de30b9475ee4bc219f26c544304d051ab5c34
4
+ data.tar.gz: 685afabca84ac713ab43a2c848cfdb20dd92f579
5
+ SHA512:
6
+ metadata.gz: e5a3f645b2cf71452e7f22a2bbc853f393978516e1abc185b3cbbfe4aa19403b3b3760c839d72f81fd36128241e09c58dff8c84c5b1b2dc1b0177a85b235cf81
7
+ data.tar.gz: 7ef80e8936bda82946041ebc220002b7d7a745819a2a419f190c6a0980449cd00516338df8879dcb67b13dd882ba155a665ed31154113fc2826112629fbbedcc
data/README.md CHANGED
@@ -1,12 +1,17 @@
1
1
  # Ruby SAML [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.png)](http://travis-ci.org/onelogin/ruby-saml) [![Coverage Status](https://coveralls.io/repos/onelogin/ruby-saml/badge.svg?branch=master%0A)](https://coveralls.io/r/onelogin/ruby-saml?branch=master%0A) [![Gem Version](https://badge.fury.io/rb/ruby-saml.svg)](http://badge.fury.io/rb/ruby-saml)
2
2
 
3
+ ## Updating from 1.1.x to 1.2.X
3
4
 
4
- ## Updating from 1.0.x to 1.1.X
5
+ Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom, refactor error handling and some minor improvements
5
6
 
6
- Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
7
+ There is no compatibility issue detected.
7
8
 
8
9
  For more details, please review [the changelog](changelog.md).
9
10
 
11
+ ## Updating from 1.0.x to 1.1.X
12
+
13
+ Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
14
+
10
15
  ## Updating from 0.9.x to 1.0.X
11
16
 
12
17
  Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
@@ -33,6 +38,7 @@ We created a demo project for Rails4 that uses the latest version of this librar
33
38
  ### Supported versions of Ruby
34
39
  * 1.8.7
35
40
  * 1.9.x
41
+ * 2.0.x
36
42
  * 2.1.x
37
43
  * 2.2.x
38
44
  * JRuby 1.7.19
@@ -254,7 +260,7 @@ end
254
260
  The following attributes are set:
255
261
  * idp_sso_target_url
256
262
  * idp_slo_target_url
257
- * idp_cert_fingerpint
263
+ * idp_cert_fingerprint
258
264
 
259
265
  If you are using saml:AttributeStatement to transfer metadata, like the user name, you can access all the attributes through response.attributes. It contains all the saml:AttributeStatement with its 'Name' as a indifferent key the one/more saml:AttributeValue as value. The value returned depends on the value of the
260
266
  `single_value_compatibility` (when activate, only one value returned, the first one)
@@ -386,7 +392,10 @@ The settings related to sign are stored in the `security` attribute of the setti
386
392
  ```ruby
387
393
  settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
388
394
  settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
389
- settings.security[:logout_responses_signed] = true # Enable or not signature on Logout Response
395
+ settings.security[:logout_responses_signed] = true # Enable or not
396
+ signature on Logout Response
397
+ settings.security[:want_assertions_signed] = true # Enable or not
398
+ the requirement of signed assertion
390
399
  settings.security[:metadata_signed] = true # Enable or not signature on Metadata
391
400
 
392
401
  settings.security[:digest_method] = XMLSecurity::Document::SHA1
@@ -466,8 +475,8 @@ and this method process the SAML Logout Response sent by the IdP as reply of the
466
475
  def process_logout_response
467
476
  settings = Account.get_saml_settings
468
477
 
469
- if session.has_key? :transation_id
470
- logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transation_id])
478
+ if session.has_key? :transaction_id
479
+ logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transaction_id])
471
480
  else
472
481
  logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings)
473
482
  end
data/changelog.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # RubySaml Changelog
2
2
 
3
- ### 1.1.2 (February 15, 2015)
3
+ ### 1.2.0 (April 29, 2016)
4
+ * [#269](https://github.com/onelogin/ruby-saml/pull/269) Refactor error handling; allow collect error messages when soft=true (normal validation stop after find first error)
5
+ * [#289](https://github.com/onelogin/ruby-saml/pull/289) Remove uuid gem in favor of SecureRandom
6
+ * [#297](https://github.com/onelogin/ruby-saml/pull/297) Implement EncryptedKey RetrievalMethod support
7
+ * [#298](https://github.com/onelogin/ruby-saml/pull/298) IDP metadata parsing improved: binding parsing, fingerprint_algorithm support)
8
+ * [#299](https://github.com/onelogin/ruby-saml/pull/299) Make 'signing' at KeyDescriptor optional
9
+ * [#308](https://github.com/onelogin/ruby-saml/pull/308) Support name_id_format on SAMLResponse
10
+ * [#315](https://github.com/onelogin/ruby-saml/pull/315) Support for canonicalization with comments
11
+ * [#316](https://github.com/onelogin/ruby-saml/pull/316) Fix Misspelling of transation_id to transaction_id
12
+ * [#321](https://github.com/onelogin/ruby-saml/pull/321) Support Attribute Names on IDPSSODescriptor parser
13
+ * Changes on empty URI of Signature reference management
14
+ * [#320](https://github.com/onelogin/ruby-saml/pull/320) Dont mutate document to fix lack of reference URI
15
+ * [#306](https://github.com/onelogin/ruby-saml/pull/306) Support WantAssertionsSigned
16
+
17
+ ### 1.1.2 (February 15, 2016)
4
18
  * Improve signature validation. Add tests.
5
19
  [#302](https://github.com/onelogin/ruby-saml/pull/302) Add Destination validation.
6
20
  * [#292](https://github.com/onelogin/ruby-saml/pull/292) Improve the error message when validating the audience.
@@ -1,8 +1,8 @@
1
- require "uuid"
2
1
  require "rexml/document"
3
2
 
4
3
  require "onelogin/ruby-saml/logging"
5
4
  require "onelogin/ruby-saml/saml_message"
5
+ require "onelogin/ruby-saml/utils"
6
6
 
7
7
  # Only supports SAML 2.0
8
8
  module OneLogin
@@ -20,7 +20,7 @@ module OneLogin
20
20
  # Asigns an ID, a random uuid.
21
21
  #
22
22
  def initialize
23
- @uuid = "_" + UUID.new.generate
23
+ @uuid = OneLogin::RubySaml::Utils.uuid
24
24
  end
25
25
 
26
26
  # Creates the AuthNRequest string.
@@ -0,0 +1,27 @@
1
+ require "onelogin/ruby-saml/validation_error"
2
+
3
+ module OneLogin
4
+ module RubySaml
5
+ module ErrorHandling
6
+ attr_accessor :errors
7
+
8
+ # Append the cause to the errors array, and based on the value of soft, return false or raise
9
+ # an exception. soft_override is provided as a means of overriding the object's notion of
10
+ # soft for just this invocation.
11
+ def append_error(error_msg, soft_override = nil)
12
+ @errors << error_msg
13
+
14
+ unless soft_override.nil? ? soft : soft_override
15
+ raise ValidationError.new(error_msg)
16
+ end
17
+
18
+ false
19
+ end
20
+
21
+ # Reset the errors array
22
+ def reset_errors!
23
+ @errors = []
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,5 +1,4 @@
1
1
  require "base64"
2
- require "uuid"
3
2
  require "zlib"
4
3
  require "cgi"
5
4
  require "net/http"
@@ -16,8 +15,10 @@ module OneLogin
16
15
  #
17
16
  class IdpMetadataParser
18
17
 
19
- METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
20
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
18
+ METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
19
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
20
+ NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*"
21
+ SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
21
22
 
22
23
  attr_reader :document
23
24
  attr_reader :response
@@ -26,26 +27,30 @@ module OneLogin
26
27
  # IdP values
27
28
  #
28
29
  # @param (see IdpMetadataParser#get_idp_metadata)
30
+ # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
29
31
  # @return (see IdpMetadataParser#get_idp_metadata)
30
32
  # @raise (see IdpMetadataParser#get_idp_metadata)
31
- def parse_remote(url, validate_cert = true)
33
+ def parse_remote(url, validate_cert = true, options = {})
32
34
  idp_metadata = get_idp_metadata(url, validate_cert)
33
- parse(idp_metadata)
35
+ parse(idp_metadata, options)
34
36
  end
35
37
 
36
38
  # Parse the Identity Provider metadata and update the settings with the IdP values
37
39
  # @param idp_metadata [String]
40
+ # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
38
41
  #
39
- def parse(idp_metadata)
42
+ def parse(idp_metadata, options = {})
40
43
  @document = REXML::Document.new(idp_metadata)
41
44
 
42
- OneLogin::RubySaml::Settings.new.tap do |settings|
45
+ (options[:settings] || OneLogin::RubySaml::Settings.new).tap do |settings|
43
46
  settings.idp_entity_id = idp_entity_id
44
47
  settings.name_identifier_format = idp_name_id_format
45
- settings.idp_sso_target_url = single_signon_service_url
46
- settings.idp_slo_target_url = single_logout_service_url
48
+ settings.idp_sso_target_url = single_signon_service_url(options)
49
+ settings.idp_slo_target_url = single_logout_service_url(options)
47
50
  settings.idp_cert = certificate_base64
48
- settings.idp_cert_fingerprint = fingerprint
51
+ settings.idp_cert_fingerprint = fingerprint(settings.idp_cert_fingerprint_algorithm)
52
+ settings.idp_attribute_names = attribute_names
53
+ settings.idp_cert_fingerprint = fingerprint(settings.idp_cert_fingerprint_algorithm)
49
54
  end
50
55
  end
51
56
 
@@ -112,23 +117,61 @@ module OneLogin
112
117
  node.text if node
113
118
  end
114
119
 
120
+ # @param binding_priority [Array]
121
+ # @return [String|nil] SingleSignOnService binding if exists
122
+ #
123
+ def single_signon_service_binding(binding_priority = nil)
124
+ nodes = REXML::XPath.match(
125
+ document,
126
+ "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Binding",
127
+ { "md" => METADATA }
128
+ )
129
+ if binding_priority
130
+ values = nodes.map(&:value)
131
+ binding_priority.detect{ |binding| values.include? binding }
132
+ else
133
+ nodes.first.value if nodes.any?
134
+ end
135
+ end
136
+
137
+ # @param options [Hash]
115
138
  # @return [String|nil] SingleSignOnService endpoint if exists
116
139
  #
117
- def single_signon_service_url
140
+ def single_signon_service_url(options = {})
141
+ binding = options[:sso_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
118
142
  node = REXML::XPath.first(
119
143
  document,
120
- "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Location",
144
+ "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
121
145
  { "md" => METADATA }
122
146
  )
123
147
  node.value if node
124
148
  end
125
149
 
150
+ # @param binding_priority [Array]
151
+ # @return [String|nil] SingleLogoutService binding if exists
152
+ #
153
+ def single_logout_service_binding(binding_priority = nil)
154
+ nodes = REXML::XPath.match(
155
+ document,
156
+ "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Binding",
157
+ { "md" => METADATA }
158
+ )
159
+ if binding_priority
160
+ values = nodes.map(&:value)
161
+ binding_priority.detect{ |binding| values.include? binding }
162
+ else
163
+ nodes.first.value if nodes.any?
164
+ end
165
+ end
166
+
167
+ # @param options [Hash]
126
168
  # @return [String|nil] SingleLogoutService endpoint if exists
127
169
  #
128
- def single_logout_service_url
170
+ def single_logout_service_url(options = {})
171
+ binding = options[:slo_binding] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
129
172
  node = REXML::XPath.first(
130
173
  document,
131
- "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService/@Location",
174
+ "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
132
175
  { "md" => METADATA }
133
176
  )
134
177
  node.value if node
@@ -143,6 +186,14 @@ module OneLogin
143
186
  "/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
144
187
  { "md" => METADATA, "ds" => DSIG }
145
188
  )
189
+
190
+ unless node
191
+ node = REXML::XPath.first(
192
+ document,
193
+ "/md:EntityDescriptor/md:IDPSSODescriptor/md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
194
+ { "md" => METADATA, "ds" => DSIG }
195
+ )
196
+ end
146
197
  node.text if node
147
198
  end
148
199
  end
@@ -158,14 +209,27 @@ module OneLogin
158
209
 
159
210
  # @return [String|nil] the SHA-1 fingerpint of the X509Certificate if it exists
160
211
  #
161
- def fingerprint
212
+ def fingerprint(fingerprint_algorithm)
162
213
  @fingerprint ||= begin
163
214
  if certificate
164
215
  cert = OpenSSL::X509::Certificate.new(certificate)
165
- Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":")
216
+
217
+ fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
218
+ fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
166
219
  end
167
220
  end
168
221
  end
222
+
223
+ # @return [Array] the names of all SAML attributes if any exist
224
+ #
225
+ def attribute_names
226
+ nodes = REXML::XPath.match(
227
+ document,
228
+ "/md:EntityDescriptor/md:IDPSSODescriptor/saml:Attribute/@Name",
229
+ { "md" => METADATA, "NameFormat" => NAME_FORMAT, "saml" => SAML_ASSERTION }
230
+ )
231
+ nodes.map(&:value)
232
+ end
169
233
  end
170
234
  end
171
235
  end
@@ -1,7 +1,6 @@
1
- require "uuid"
2
-
3
1
  require "onelogin/ruby-saml/logging"
4
2
  require "onelogin/ruby-saml/saml_message"
3
+ require "onelogin/ruby-saml/utils"
5
4
 
6
5
  # Only supports SAML 2.0
7
6
  module OneLogin
@@ -18,7 +17,7 @@ module OneLogin
18
17
  # Asigns an ID, a random uuid.
19
18
  #
20
19
  def initialize
21
- @uuid = "_" + UUID.new.generate
20
+ @uuid = OneLogin::RubySaml::Utils.uuid
22
21
  end
23
22
 
24
23
  # Creates the Logout Request string.
@@ -108,7 +107,7 @@ module OneLogin
108
107
  nameid.text = settings.name_identifier_value
109
108
  else
110
109
  # If no NameID is present in the settings we generate one
111
- nameid.text = "_" + UUID.new.generate
110
+ nameid.text = OneLogin::RubySaml::Utils.uuid
112
111
  nameid.attributes['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
113
112
  end
114
113
 
@@ -10,13 +10,11 @@ module OneLogin
10
10
  # SAML2 Logout Response (SLO IdP initiated, Parser)
11
11
  #
12
12
  class Logoutresponse < SamlMessage
13
+ include ErrorHandling
13
14
 
14
15
  # OneLogin::RubySaml::Settings Toolkit settings
15
16
  attr_accessor :settings
16
17
 
17
- # Array with the causes
18
- attr_accessor :errors
19
-
20
18
  attr_reader :document
21
19
  attr_reader :response
22
20
  attr_reader :options
@@ -47,18 +45,6 @@ module OneLogin
47
45
  @document = XMLSecurity::SignedDocument.new(@response)
48
46
  end
49
47
 
50
- # Append the cause to the errors array, and based on the value of soft, return false or raise
51
- # an exception
52
- def append_error(error_msg)
53
- @errors << error_msg
54
- return soft ? false : validation_error(error_msg)
55
- end
56
-
57
- # Reset the errors array
58
- def reset_errors!
59
- @errors = []
60
- end
61
-
62
48
  # Checks if the Status has the "Success" code
63
49
  # @return [Boolean] True if the StatusCode is Sucess
64
50
  # @raise [ValidationError] if soft == false and validation fails
@@ -117,18 +103,30 @@ module OneLogin
117
103
  end
118
104
 
119
105
  # Aux function to validate the Logout Response
106
+ # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
120
107
  # @return [Boolean] TRUE if the SAML Response is valid
121
108
  # @raise [ValidationError] if soft == false and validation fails
122
109
  #
123
- def validate
110
+ def validate(collect_errors = false)
124
111
  reset_errors!
125
112
 
126
- valid_state? &&
127
- validate_success_status &&
128
- validate_structure &&
129
- valid_in_response_to? &&
130
- valid_issuer? &&
131
- validate_signature
113
+ if collect_errors
114
+ valid_state?
115
+ validate_success_status
116
+ validate_structure
117
+ valid_in_response_to?
118
+ valid_issuer?
119
+ validate_signature
120
+
121
+ @errors.empty?
122
+ else
123
+ valid_state? &&
124
+ validate_success_status &&
125
+ validate_structure &&
126
+ valid_in_response_to? &&
127
+ valid_issuer? &&
128
+ validate_signature
129
+ end
132
130
  end
133
131
 
134
132
  private
@@ -1,7 +1,7 @@
1
1
  require "uri"
2
- require "uuid"
3
2
 
4
3
  require "onelogin/ruby-saml/logging"
4
+ require "onelogin/ruby-saml/utils"
5
5
 
6
6
  # Only supports SAML 2.0
7
7
  module OneLogin
@@ -29,8 +29,7 @@ module OneLogin
29
29
  sp_sso = root.add_element "md:SPSSODescriptor", {
30
30
  "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
31
31
  "AuthnRequestsSigned" => settings.security[:authn_requests_signed],
32
- # However we would like assertions signed if idp_cert_fingerprint or idp_cert is set
33
- "WantAssertionsSigned" => !!(settings.idp_cert_fingerprint || settings.idp_cert)
32
+ "WantAssertionsSigned" => settings.security[:want_assertions_signed],
34
33
  }
35
34
 
36
35
  # Add KeyDescriptor if messages will be signed / encrypted
@@ -50,7 +49,7 @@ module OneLogin
50
49
  xc2.text = cert_text
51
50
  end
52
51
 
53
- root.attributes["ID"] = "_" + UUID.new.generate
52
+ root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
54
53
  if settings.issuer
55
54
  root.attributes["entityID"] = settings.issuer
56
55
  end
@@ -11,6 +11,8 @@ module OneLogin
11
11
  # SAML2 Authentication Response. SAML Response
12
12
  #
13
13
  class Response < SamlMessage
14
+ include ErrorHandling
15
+
14
16
  ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
15
17
  PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
16
18
  DSIG = "http://www.w3.org/2000/09/xmldsig#"
@@ -21,9 +23,6 @@ module OneLogin
21
23
  # OneLogin::RubySaml::Settings Toolkit settings
22
24
  attr_accessor :settings
23
25
 
24
- # Array with the causes [Array of strings]
25
- attr_accessor :errors
26
-
27
26
  attr_reader :document
28
27
  attr_reader :decrypted_document
29
28
  attr_reader :response
@@ -39,16 +38,15 @@ module OneLogin
39
38
  # or :matches_request_id that will validate that the response matches the ID of the request,
40
39
  # or skip the subject confirmation validation with the :skip_subject_confirmation option
41
40
  def initialize(response, options = {})
42
- @errors = []
43
-
44
41
  raise ArgumentError.new("Response cannot be nil") if response.nil?
45
- @options = options
46
42
 
43
+ @errors = []
44
+ @options = options
47
45
  @soft = true
48
- if !options.empty? && !options[:settings].nil?
46
+ unless options[:settings].nil?
49
47
  @settings = options[:settings]
50
- if !options[:settings].soft.nil?
51
- @soft = options[:settings].soft
48
+ unless @settings.soft.nil?
49
+ @soft = @settings.soft
52
50
  end
53
51
  end
54
52
 
@@ -60,41 +58,36 @@ module OneLogin
60
58
  end
61
59
  end
62
60
 
63
- # Append the cause to the errors array, and based on the value of soft, return false or raise
64
- # an exception
65
- def append_error(error_msg)
66
- @errors << error_msg
67
- return soft ? false : validation_error(error_msg)
68
- end
69
-
70
- # Reset the errors array
71
- def reset_errors!
72
- @errors = []
73
- end
74
-
75
61
  # Validates the SAML Response with the default values (soft = true)
62
+ # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
76
63
  # @return [Boolean] TRUE if the SAML Response is valid
77
64
  #
78
- def is_valid?
79
- validate
65
+ def is_valid?(collect_errors = false)
66
+ validate(collect_errors)
80
67
  end
81
68
 
82
69
  # @return [String] the NameID provided by the SAML response from the IdP.
83
70
  #
84
71
  def name_id
85
- @name_id ||= begin
86
- encrypted_node = xpath_first_from_signed_assertion('/a:Subject/a:EncryptedID')
87
- if encrypted_node
88
- node = decrypt_nameid(encrypted_node)
89
- else
90
- node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
72
+ @name_id ||=
73
+ if name_id_node
74
+ name_id_node.text
91
75
  end
92
- node.nil? ? nil : node.text
93
- end
94
76
  end
95
77
 
96
78
  alias_method :nameid, :name_id
97
79
 
80
+ # @return [String] the NameID Format provided by the SAML response from the IdP.
81
+ #
82
+ def name_id_format
83
+ @name_id_format ||=
84
+ if name_id_node && name_id_node.attribute("Format")
85
+ name_id_node.attribute("Format").value
86
+ end
87
+ end
88
+
89
+ alias_method :nameid_format, :name_id_format
90
+
98
91
 
99
92
  # Gets the SessionIndex from the AuthnStatement.
100
93
  # Could be used to be stored in the local session in order
@@ -291,28 +284,48 @@ module OneLogin
291
284
  private
292
285
 
293
286
  # Validates the SAML Response (calls several validation methods)
287
+ # @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
294
288
  # @return [Boolean] True if the SAML Response is valid, otherwise False if soft=True
295
289
  # @raise [ValidationError] if soft == false and validation fails
296
290
  #
297
- def validate
291
+ def validate(collect_errors = false)
298
292
  reset_errors!
299
293
 
300
- validate_response_state &&
301
- validate_version &&
302
- validate_id &&
303
- validate_success_status &&
304
- validate_num_assertion &&
305
- validate_no_encrypted_attributes &&
306
- validate_signed_elements &&
307
- validate_structure &&
308
- validate_in_response_to &&
309
- validate_conditions &&
310
- validate_audience &&
311
- validate_destination &&
312
- validate_issuer &&
313
- validate_session_expiration &&
314
- validate_subject_confirmation &&
315
- validate_signature
294
+ if collect_errors
295
+ return false unless validate_response_state
296
+ validate_version
297
+ validate_id
298
+ validate_success_status
299
+ validate_num_assertion
300
+ validate_no_encrypted_attributes
301
+ validate_signed_elements
302
+ validate_structure
303
+ validate_in_response_to
304
+ validate_conditions
305
+ validate_audience
306
+ validate_issuer
307
+ validate_session_expiration
308
+ validate_subject_confirmation
309
+ validate_signature
310
+ @errors.empty?
311
+ else
312
+ validate_response_state &&
313
+ validate_version &&
314
+ validate_id &&
315
+ validate_success_status &&
316
+ validate_num_assertion &&
317
+ validate_no_encrypted_attributes &&
318
+ validate_signed_elements &&
319
+ validate_structure &&
320
+ validate_in_response_to &&
321
+ validate_conditions &&
322
+ validate_audience &&
323
+ validate_destination &&
324
+ validate_issuer &&
325
+ validate_session_expiration &&
326
+ validate_subject_confirmation &&
327
+ validate_signature
328
+ end
316
329
  end
317
330
 
318
331
 
@@ -442,6 +455,10 @@ module OneLogin
442
455
  return append_error("Found an unexpected number of Signature Element. SAML Response rejected")
443
456
  end
444
457
 
458
+ if settings.security[:want_assertions_signed] && !(signed_elements.include? "Assertion")
459
+ return append_error("The Assertion of the Response is not signed and the SP requires it")
460
+ end
461
+
445
462
  true
446
463
  end
447
464
 
@@ -638,6 +655,18 @@ module OneLogin
638
655
  true
639
656
  end
640
657
 
658
+ def name_id_node
659
+ @name_id_node ||=
660
+ begin
661
+ encrypted_node = xpath_first_from_signed_assertion('/a:Subject/a:EncryptedID')
662
+ if encrypted_node
663
+ node = decrypt_nameid(encrypted_node)
664
+ else
665
+ node = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
666
+ end
667
+ end
668
+ end
669
+
641
670
  # Extracts the first appearance that matchs the subelt (pattern)
642
671
  # Search on any Assertion that is signed, or has a Response parent signed
643
672
  # @param subelt [String] The XPath pattern
@@ -686,7 +715,7 @@ module OneLogin
686
715
  #
687
716
  def generate_decrypted_document
688
717
  if settings.nil? || !settings.get_sp_key
689
- validation_error('An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method')
718
+ raise ValidationError.new('An EncryptedAssertion found and no SP private key found on the settings to decrypt it. Be sure you provided the :settings parameter at the initialize method')
690
719
  end
691
720
 
692
721
  # Marshal at Ruby 1.8.7 throw an Exception
@@ -752,7 +781,7 @@ module OneLogin
752
781
  #
753
782
  def decrypt_element(encrypt_node, rgrex)
754
783
  if settings.nil? || !settings.get_sp_key
755
- return validation_error('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
784
+ raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
756
785
  end
757
786
 
758
787
  elem_plaintext = OneLogin::RubySaml::Utils.decrypt_data(encrypt_node, settings.get_sp_key)