Skip to content

Commit 1194615

Browse files
committed
Support collect_errors, add tests
2 parents 9f52a28 + 20f9706 commit 1194615

File tree

9 files changed

+183
-109
lines changed

9 files changed

+183
-109
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

lib/onelogin/ruby-saml/logoutresponse.rb

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ module RubySaml
1010
# SAML2 Logout Response (SLO IdP initiated, Parser)
1111
#
1212
class Logoutresponse < SamlMessage
13+
include ErrorHandling
1314

1415
# OneLogin::RubySaml::Settings Toolkit settings
1516
attr_accessor :settings
1617

17-
# Array with the causes
18-
attr_accessor :errors
19-
2018
attr_reader :document
2119
attr_reader :response
2220
attr_reader :options
@@ -47,18 +45,6 @@ def initialize(response, settings = nil, options = {})
4745
@document = XMLSecurity::SignedDocument.new(@response)
4846
end
4947

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-
6248
# Checks if the Status has the "Success" code
6349
# @return [Boolean] True if the StatusCode is Sucess
6450
# @raise [ValidationError] if soft == false and validation fails
@@ -117,18 +103,30 @@ def status_message
117103
end
118104

119105
# Aux function to validate the Logout Response
106+
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
120107
# @return [Boolean] TRUE if the SAML Response is valid
121108
# @raise [ValidationError] if soft == false and validation fails
122109
#
123-
def validate
110+
def validate(collect_errors = false)
124111
reset_errors!
125112

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
132130
end
133131

134132
private

lib/onelogin/ruby-saml/response.rb

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module RubySaml
1111
# SAML2 Authentication Response. SAML Response
1212
#
1313
class Response < SamlMessage
14+
include ErrorHandling
15+
1416
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
1517
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
1618
DSIG = "http://www.w3.org/2000/09/xmldsig#"
@@ -21,9 +23,6 @@ class Response < SamlMessage
2123
# OneLogin::RubySaml::Settings Toolkit settings
2224
attr_accessor :settings
2325

24-
# Array with the causes [Array of strings]
25-
attr_accessor :errors
26-
2726
attr_reader :document
2827
attr_reader :decrypted_document
2928
attr_reader :response
@@ -39,11 +38,10 @@ class Response < SamlMessage
3938
# or :matches_request_id that will validate that the response matches the ID of the request,
4039
# or skip the subject confirmation validation with the :skip_subject_confirmation option
4140
def initialize(response, options = {})
42-
@errors = []
43-
4441
raise ArgumentError.new("Response cannot be nil") if response.nil?
45-
@options = options
4642

43+
@errors = []
44+
@options = options
4745
@soft = true
4846
unless options[:settings].nil?
4947
@settings = options[:settings]
@@ -60,23 +58,12 @@ def initialize(response, options = {})
6058
end
6159
end
6260

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-
7561
# 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)
7663
# @return [Boolean] TRUE if the SAML Response is valid
7764
#
78-
def is_valid?
79-
validate
65+
def is_valid?(collect_errors = false)
66+
validate(collect_errors)
8067
end
8168

8269
# @return [String] the NameID provided by the SAML response from the IdP.
@@ -297,28 +284,48 @@ def allowed_clock_drift
297284
private
298285

299286
# 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)
300288
# @return [Boolean] True if the SAML Response is valid, otherwise False if soft=True
301289
# @raise [ValidationError] if soft == false and validation fails
302290
#
303-
def validate
291+
def validate(collect_errors = false)
304292
reset_errors!
305293

306-
validate_response_state &&
307-
validate_version &&
308-
validate_id &&
309-
validate_success_status &&
310-
validate_num_assertion &&
311-
validate_no_encrypted_attributes &&
312-
validate_signed_elements &&
313-
validate_structure &&
314-
validate_in_response_to &&
315-
validate_conditions &&
316-
validate_audience &&
317-
validate_destination &&
318-
validate_issuer &&
319-
validate_session_expiration &&
320-
validate_subject_confirmation &&
321-
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
322329
end
323330

324331

@@ -708,7 +715,7 @@ def xpath_from_signed_assertion(subelt=nil)
708715
#
709716
def generate_decrypted_document
710717
if settings.nil? || !settings.get_sp_key
711-
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')
712719
end
713720

714721
# Marshal at Ruby 1.8.7 throw an Exception
@@ -774,7 +781,7 @@ def decrypt_nameid(encryptedid_node)
774781
#
775782
def decrypt_element(encrypt_node, rgrex)
776783
if settings.nil? || !settings.get_sp_key
777-
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')
778785
end
779786

780787
elem_plaintext = OneLogin::RubySaml::Utils.decrypt_data(encrypt_node, settings.get_sp_key)

lib/onelogin/ruby-saml/saml_message.rb

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require 'rexml/document'
66
require 'rexml/xpath'
77
require 'thread'
8+
require "onelogin/ruby-saml/error_handling"
89

910
# Only supports SAML 2.0
1011
module OneLogin
@@ -69,23 +70,15 @@ def valid_saml?(document, soft = true)
6970
end
7071
rescue Exception => error
7172
return false if soft
72-
validation_error("XML load failed: #{error.message}")
73+
raise ValidationError.new("XML load failed: #{error.message}")
7374
end
7475

7576
SamlMessage.schema.validate(xml).map do |error|
7677
return false if soft
77-
validation_error("#{error.message}\n\n#{xml.to_s}")
78+
raise ValidationError.new("#{error.message}\n\n#{xml.to_s}")
7879
end
7980
end
8081

81-
# Raise a ValidationError with the provided message
82-
# @param message [String] Message of the exception
83-
# @raise [ValidationError]
84-
#
85-
def validation_error(message)
86-
raise ValidationError.new(message)
87-
end
88-
8982
private
9083

9184
# Base64 decode and try also to inflate a SAML Message

lib/onelogin/ruby-saml/slo_logoutrequest.rb

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@ module RubySaml
1111
# SAML2 Logout Request (SLO IdP initiated, Parser)
1212
#
1313
class SloLogoutrequest < SamlMessage
14+
include ErrorHandling
1415

1516
# OneLogin::RubySaml::Settings Toolkit settings
1617
attr_accessor :settings
1718

18-
# Array with the causes [Array of strings]
19-
attr_accessor :errors
20-
2119
attr_reader :document
2220
attr_reader :request
2321
attr_reader :options
@@ -32,10 +30,10 @@ class SloLogoutrequest < SamlMessage
3230
# @raise [ArgumentError] If Request is nil
3331
#
3432
def initialize(request, options = {})
35-
@errors = []
3633
raise ArgumentError.new("Request cannot be nil") if request.nil?
37-
@options = options
3834

35+
@errors = []
36+
@options = options
3937
@soft = true
4038
unless options[:settings].nil?
4139
@settings = options[:settings]
@@ -48,23 +46,12 @@ def initialize(request, options = {})
4846
@document = REXML::Document.new(@request)
4947
end
5048

51-
# Append the cause to the errors array, and based on the value of soft, return false or raise
52-
# an exception
53-
def append_error(error_msg)
54-
@errors << error_msg
55-
return soft ? false : validation_error(error_msg)
56-
end
57-
58-
# Reset the errors array
59-
def reset_errors!
60-
@errors = []
61-
end
62-
6349
# Validates the Logout Request with the default values (soft = true)
50+
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating.
6451
# @return [Boolean] TRUE if the Logout Request is valid
6552
#
66-
def is_valid?
67-
validate
53+
def is_valid?(collect_errors = false)
54+
validate(collect_errors)
6855
end
6956

7057
# @return [String] Gets the NameID of the Logout Request.
@@ -132,19 +119,32 @@ def session_indexes
132119
private
133120

134121
# Hard aux function to validate the Logout Request
122+
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
135123
# @return [Boolean] TRUE if the Logout Request is valid
136124
# @raise [ValidationError] if soft == false and validation fails
137125
#
138-
def validate
126+
def validate(collect_errors = false)
139127
reset_errors!
140128

141-
validate_request_state &&
142-
validate_id &&
143-
validate_version &&
144-
validate_structure &&
145-
validate_not_on_or_after &&
146-
validate_issuer &&
147-
validate_signature
129+
if collect_errors
130+
validate_request_state
131+
validate_id
132+
validate_version
133+
validate_structure
134+
validate_not_on_or_after
135+
validate_issuer
136+
validate_signature
137+
138+
@errors.empty?
139+
else
140+
validate_request_state &&
141+
validate_id &&
142+
validate_version &&
143+
validate_structure &&
144+
validate_not_on_or_after &&
145+
validate_issuer &&
146+
validate_signature
147+
end
148148
end
149149

150150
# Validates that the Logout Request contains an ID
@@ -230,7 +230,7 @@ def validate_signature
230230
return true if options.nil?
231231
return true unless options.has_key? :get_params
232232
return true unless options[:get_params].has_key? 'Signature'
233-
return true if settings.nil? || settings.get_idp_cert.nil?
233+
return true if settings.get_idp_cert.nil?
234234

235235
query_string = OneLogin::RubySaml::Utils.build_query(
236236
:type => 'SAMLRequest',

0 commit comments

Comments
 (0)