ruby-saml 1.1.2 → 1.2.0

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.

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)