Skip to content

Commit

Permalink
Add Sign validation test (http-redirect binding). Refactor test. vali…
Browse files Browse the repository at this point in the history
…d_saml? now register at @errors the error validation
  • Loading branch information
pitbulk committed Mar 29, 2015
1 parent 0efb8b7 commit 42a7bbd
Show file tree
Hide file tree
Showing 7 changed files with 414 additions and 243 deletions.
25 changes: 19 additions & 6 deletions lib/onelogin/ruby-saml/logoutresponse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Logoutresponse < SamlMessage
# @raise [ArgumentError]
#
def initialize(response, settings = nil)
@errors = []
raise ArgumentError.new("Logoutresponse cannot be nil") if response.nil?
@settings = settings

Expand Down Expand Up @@ -188,11 +189,16 @@ def valid_state?(soft = true)
# @raise [ValidationError] if soft == false and validation fails
#
def validate_structure(soft = true)
valid = valid_saml?(document, soft)
unless valid
@errors << "Invalid Logout Response. Not match the saml-schema-protocol-2.0.xsd"
begin
valid = valid_saml?(document, soft)
unless valid
@errors << "Invalid Logout Response. Not match the saml-schema-protocol-2.0.xsd"
end
valid
rescue OneLogin::RubySaml::ValidationError => e
@errors << e.message
raise e
end
valid
end

# Validates the Status of the Logout Response
Expand Down Expand Up @@ -289,12 +295,19 @@ def validate_signature(soft = true, get_params = nil)
:sig_alg => get_params['SigAlg']
)

return OneLogin::RubySaml::Utils.verify_signature(
valid = OneLogin::RubySaml::Utils.verify_signature(
:cert => settings.get_idp_cert,
:sig_alg => get_params['SigAlg'],
:signature => Base64.encode64(get_params['Signature']),
:signature => get_params['Signature'],
:query_string => query_string
)

unless valid
error_msg = "Invalid Signature on Logout Response"
@errors << error_msg
return soft ? false : validation_error(error_msg)
end
true
end
end
end
Expand Down
15 changes: 10 additions & 5 deletions lib/onelogin/ruby-saml/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -384,11 +384,16 @@ def validate_signed_elements()
# @raise [ValidationError] if soft == false and validation fails
#
def validate_structure(soft = true)
valid = valid_saml?(document, soft)
unless valid
@errors << "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"
end
valid
begin
valid = valid_saml?(document, soft)
unless valid
@errors << "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"
end
valid
rescue OneLogin::RubySaml::ValidationError => e
@errors << e.message
raise e
end
end

# Validates if the provided request_id match the inResponseTo value.
Expand Down
24 changes: 18 additions & 6 deletions lib/onelogin/ruby-saml/slo_logoutrequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,16 @@ def validate_request_state(soft = true)
# @raise [ValidationError] if soft == false and validation fails
#
def validate_structure(soft = true)
valid = valid_saml?(document, soft)
unless valid
@errors << "Invalid Logout Request. Not match the saml-schema-protocol-2.0.xsd"
begin
valid = valid_saml?(document, soft)
unless valid
@errors << "Invalid Logout Request. Not match the saml-schema-protocol-2.0.xsd"
end
valid
rescue OneLogin::RubySaml::ValidationError => e
@errors << e.message
raise e
end
valid
end

# Validates the Destination, (if the Logout Request is received where expected)
Expand Down Expand Up @@ -267,12 +272,19 @@ def validate_signature(soft = true, get_params = nil)
:sig_alg => get_params['SigAlg']
)

return OneLogin::RubySaml::Utils.verify_signature(
valid = OneLogin::RubySaml::Utils.verify_signature(
:cert => settings.get_idp_cert,
:sig_alg => get_params['SigAlg'],
:signature => Base64.encode64(get_params['Signature']),
:signature => get_params['Signature'],
:query_string => query_string
)

unless valid
error_msg = "Invalid Signature on Logout Request"
@errors << error_msg
return soft ? false : validation_error(error_msg)
end
true
end

end
Expand Down
6 changes: 3 additions & 3 deletions lib/onelogin/ruby-saml/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def self.format_private_key(key, heads=true)
# to generate the Signature
# @param params [Hash] Parameters to build the Query String
# @option params [String] :type 'SAMLRequest' or 'SAMLResponse'
# @option params [String] :data The plain text request or response
# @option params [String] :data Base64 encoded SAMLRequest or SAMLResponse
# @option params [String] :relay_state The RelayState parameter
# @option params [String] :sig_alg The SigAlg parameter
# @return [String] The Query String
Expand All @@ -74,15 +74,15 @@ def self.build_query(params)
# @param params [Hash] Parameters to be used in the validation process
# @option params [OpenSSL::X509::Certificate] cert The Identity provider public certtificate
# @option params [String] sig_alg The SigAlg parameter
# @option params [String] signature The Base64 decoded Signature
# @option params [String] signature The Signature parameter (base64 encoded)
# @option params [String] query_string The SigAlg parameter
# @return [Boolean] True if the Signature is valid, False otherwise
#
def self.verify_signature(params)
cert, sig_alg, signature, query_string = [:cert, :sig_alg, :signature, :query_string].map { |k| params[k]}

signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(sig_alg)
return cert.public_key.verify(signature_algorithm.new, signature, query_string)
return cert.public_key.verify(signature_algorithm.new, Base64.decode64(signature), query_string)
end
end
end
Expand Down
93 changes: 85 additions & 8 deletions test/logoutresponse_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,45 +161,68 @@ class RubySamlTest < Minitest::Test
it "raises validation error when response initiated with blank" do
logoutresponse = OneLogin::RubySaml::Logoutresponse.new("", settings)

assert_raises(OneLogin::RubySaml::ValidationError, "Blank Logout Response") do
expected_error_msg = "Blank Logout Response"
assert_raises(OneLogin::RubySaml::ValidationError, expected_error_msg) do
logoutresponse.validate!
end
assert logoutresponse.errors.include? expected_error_msg
end

it "raises validation error when matching for wrong request id" do
expected_request_id = "_some_other_expected_id"

logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response, settings)

assert_raises(OneLogin::RubySaml::ValidationError, "Logout Response does not match the request ID, expected: <{expected_request_id}>, but was: <{logoutresponse.in_response_to}>") do
expected_error_msg = "Logout Response does not match the request ID, expected: <#{expected_request_id}>, but was: <#{logoutresponse.in_response_to}>"
assert_raises(OneLogin::RubySaml::ValidationError, expected_error_msg) do
logoutresponse.validate!(false, expected_request_id)
end
assert logoutresponse.errors.include? expected_error_msg
end

it "raise validation error for wrong request status" do
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response, settings)

assert_raises(OneLogin::RubySaml::ValidationError, "The status code of the Logout Response was not Success, was Requester") do
expected_error_msg = "The status code of the Logout Response was not Success, was Requester"
assert_raises(OneLogin::RubySaml::ValidationError, expected_error_msg) do
logoutresponse.validate!
end
assert logoutresponse.errors.include? expected_error_msg
end

it "raise validation error for wrong request status and status_message" do
unsuccessful_response_with_status_message = unsuccessful_response
unsuccessful_response_with_status_message = unsuccessful_response_with_status_message.gsub('</samlp:Status>', '<samlp:StatusMessage>It was requester</samlp:StatusMessage></samlp:Status>')

logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response_with_status_message, settings)

expected_error_msg = "The status code of the Logout Response was not Success, was Requester -> It was requester"
assert_raises(OneLogin::RubySaml::ValidationError, expected_error_msg) do
logoutresponse.validate!
end
assert logoutresponse.errors.include? expected_error_msg
end

it "raise validation error when in bad state" do
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response)

assert_raises(OneLogin::RubySaml::ValidationError, "No settings on Logout Response") do
expected_error_msg = "No settings on Logout Response"
assert_raises(OneLogin::RubySaml::ValidationError, expected_error_msg) do
logoutresponse.validate!
end
assert logoutresponse.errors.include? expected_error_msg
end

it "raise validation error when in lack of issuer setting" do
bad_settings = settings
bad_settings.issuer = nil
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_response, bad_settings)

assert_raises(OneLogin::RubySaml::ValidationError, "No issuer in settings") do
expected_error_msg = "No issuer in settings"
assert_raises(OneLogin::RubySaml::ValidationError, expected_error_msg) do
logoutresponse.validate!
end
assert logoutresponse.errors.include? expected_error_msg
end

it "raise validation error when responses with wrong issuer" do
Expand All @@ -208,17 +231,21 @@ class RubySamlTest < Minitest::Test
bad_settings.idp_entity_id = 'http://invalid.issuer.example.com/'
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response({:uuid => in_relation_to_request_id}), bad_settings)

assert_raises(OneLogin::RubySaml::ValidationError, "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: <http://app.muda.no>") do
expected_error_msg = "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: <http://app.muda.no>"
assert_raises(OneLogin::RubySaml::ValidationError, expected_error_msg) do
logoutresponse.validate!
end
assert logoutresponse.errors.include? expected_error_msg
end

it "raise error for invalid xml" do
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(invalid_xml_response, settings)

assert_raises(OneLogin::RubySaml::ValidationError, "OneLogin::RubySaml::ValidationError: Element '{urn:oasis:names:tc:SAML:2.0:protocol}LogoutResponse': The attribute 'IssueInstant' is required but missing.") do
expected_error_msg = "Element '{urn:oasis:names:tc:SAML:2.0:protocol}LogoutResponse': The attribute 'IssueInstant' is required but missing."
assert_raises(OneLogin::RubySaml::ValidationError, expected_error_msg) do
logoutresponse.validate!
end
assert logoutresponse.errors[0].include? expected_error_msg
end

it "raise when the destination of the Logout Response not match the service logout url" do
Expand All @@ -227,11 +254,61 @@ class RubySamlTest < Minitest::Test
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_response, bad_settings)
logoutresponse.document.root.attributes['Destination'] = 'http://sp.example.com/sls'

assert_raises(OneLogin::RubySaml::ValidationError, "The Logout Response was received at #{logoutresponse.destination} instead of #{logoutresponse.settings.single_logout_service_url}") do
expected_error_msg = "The Logout Response was received at #{logoutresponse.destination} instead of #{logoutresponse.settings.single_logout_service_url}"
assert_raises(OneLogin::RubySaml::ValidationError, expected_error_msg) do
logoutresponse.validate!
end
assert logoutresponse.errors.include? expected_error_msg
end
end

describe "#validate_signature" do
before do
settings.idp_slo_target_url = "http://example.com?field=value"
settings.security[:logout_responses_signed] = true
settings.security[:embed_sign] = false
settings.certificate = ruby_saml_cert_text
settings.private_key = ruby_saml_key_text
settings.idp_cert = ruby_saml_cert_text
end

it "return true when valid RSA_SHA1 Signature" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, random_id, "Custom Logout Message", :RelayState => 'http://example.com')
params['RelayState'] = params[:RelayState]
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings)
assert logoutresponse_sign_test.send(:validate_signature, true, params)
end

it "return true when valid RSA_SHA256 Signature" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, random_id, "Custom Logout Message", :RelayState => 'http://example.com')
params['RelayState'] = params[:RelayState]
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings)
assert logoutresponse_sign_test.send(:validate_signature, true, params)
end

it "return false when invalid RSA_SHA1 Signature" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, random_id, "Custom Logout Message", :RelayState => 'http://example.com')
params['RelayState'] = 'http://invalid.example.com'
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings)
assert !logoutresponse_sign_test.send(:validate_signature, true, params)
end

it "raise when invalid RSA_SHA1 Signature" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
params = OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, random_id, "Custom Logout Message", :RelayState => 'http://example.com')
params['RelayState'] = 'http://invalid.example.com'
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings)

expected_error_msg = "Invalid Signature on Logout Response"
assert_raises(OneLogin::RubySaml::ValidationError, expected_error_msg) do
logoutresponse_sign_test.send(:validate_signature, false, params)
end
assert logoutresponse_sign_test.errors.include? expected_error_msg
end
end

end
end
Loading

0 comments on commit 42a7bbd

Please sign in to comment.