2022-05-14 03:53:04 +00:00
require File . expand_path ( File . join ( File . dirname ( __FILE__ ) , " test_helper " ) )
require 'onelogin/ruby-saml/response'
class RubySamlTest < Minitest :: Test
describe " Response " do
let ( :settings ) { OneLogin :: RubySaml :: Settings . new }
let ( :response ) { OneLogin :: RubySaml :: Response . new ( response_document_without_recipient ) }
let ( :response_without_attributes ) { OneLogin :: RubySaml :: Response . new ( response_document_without_attributes ) }
let ( :response_with_multiple_attribute_statements ) { OneLogin :: RubySaml :: Response . new ( fixture ( :response_with_multiple_attribute_statements ) ) }
let ( :response_without_reference_uri ) { OneLogin :: RubySaml :: Response . new ( response_document_without_reference_uri ) }
let ( :response_with_signed_assertion ) { OneLogin :: RubySaml :: Response . new ( response_document_with_signed_assertion ) }
let ( :response_with_ds_namespace_at_the_root ) { OneLogin :: RubySaml :: Response . new ( response_document_with_ds_namespace_at_the_root ) }
let ( :response_unsigned ) { OneLogin :: RubySaml :: Response . new ( response_document_unsigned ) }
let ( :response_wrapped ) { OneLogin :: RubySaml :: Response . new ( response_document_wrapped ) }
let ( :response_multiple_attr_values ) { OneLogin :: RubySaml :: Response . new ( fixture ( :response_with_multiple_attribute_values ) ) }
let ( :response_valid_signed ) { OneLogin :: RubySaml :: Response . new ( response_document_valid_signed ) }
let ( :response_valid_signed_without_recipient ) { OneLogin :: RubySaml :: Response . new ( response_document_valid_signed , { :skip_recipient_check = > true } ) }
let ( :response_valid_signed_without_x509certificate ) { OneLogin :: RubySaml :: Response . new ( response_document_valid_signed_without_x509certificate ) }
let ( :response_no_id ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_id.xml.base64 " ) ) }
let ( :response_no_version ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_saml2.xml.base64 " ) ) }
let ( :response_multi_assertion ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " multiple_assertions.xml.base64 " ) ) }
let ( :response_no_conditions ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_conditions.xml.base64 " ) ) }
let ( :response_no_conditions_with_skip ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_conditions.xml.base64 " ) , { :skip_conditions = > true } ) }
let ( :response_no_authnstatement ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_authnstatement.xml.base64 " ) ) }
let ( :response_no_authnstatement_with_skip ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_authnstatement.xml.base64 " ) , { :skip_authnstatement = > true } ) }
let ( :response_empty_destination ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " empty_destination.xml.base64 " ) ) }
let ( :response_empty_destination_with_skip ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " empty_destination.xml.base64 " ) , { :skip_destination = > true } ) }
let ( :response_no_status ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_status.xml.base64 " ) ) }
let ( :response_no_statuscode ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_status_code.xml.base64 " ) ) }
let ( :response_statuscode_responder ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " status_code_responder.xml.base64 " ) ) }
let ( :response_statuscode_responder_and_msg ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " status_code_responer_and_msg.xml.base64 " ) ) }
let ( :response_double_statuscode ) { OneLogin :: RubySaml :: Response . new ( response_document_double_status_code ) }
let ( :response_encrypted_attrs ) { OneLogin :: RubySaml :: Response . new ( response_document_encrypted_attrs ) }
let ( :response_no_signed_elements ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_signature.xml.base64 " ) ) }
let ( :response_multiple_signed ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " multiple_signed.xml.base64 " ) ) }
let ( :response_audience_self_closed ) { OneLogin :: RubySaml :: Response . new ( read_response ( " response_audience_self_closed_tag.xml.base64 " ) ) }
let ( :response_invalid_audience ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " invalid_audience.xml.base64 " ) ) }
let ( :response_invalid_audience_with_skip ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " invalid_audience.xml.base64 " ) , { :skip_audience = > true } ) }
let ( :response_invalid_signed_element ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " response_invalid_signed_element.xml.base64 " ) ) }
let ( :response_invalid_issuer_assertion ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " invalid_issuer_assertion.xml.base64 " ) ) }
let ( :response_invalid_issuer_message ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " invalid_issuer_message.xml.base64 " ) ) }
let ( :response_no_issuer_response ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_issuer_response.xml.base64 " ) ) }
let ( :response_no_issuer_assertion ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_issuer_assertion.xml.base64 " ) ) }
let ( :response_no_nameid ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_nameid.xml.base64 " ) ) }
let ( :response_empty_nameid ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " empty_nameid.xml.base64 " ) ) }
let ( :response_wrong_spnamequalifier ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " wrong_spnamequalifier.xml.base64 " ) ) }
let ( :response_duplicated_attributes ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " duplicated_attributes.xml.base64 " ) ) }
let ( :response_no_subjectconfirmation_data ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_subjectconfirmation_data.xml.base64 " ) ) }
let ( :response_no_subjectconfirmation_method ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " no_subjectconfirmation_method.xml.base64 " ) ) }
let ( :response_invalid_subjectconfirmation_inresponse ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " invalid_subjectconfirmation_inresponse.xml.base64 " ) ) }
let ( :response_invalid_subjectconfirmation_recipient ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " invalid_subjectconfirmation_recipient.xml.base64 " ) ) }
let ( :response_invalid_subjectconfirmation_nb ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " invalid_subjectconfirmation_nb.xml.base64 " ) ) }
let ( :response_invalid_subjectconfirmation_noa ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " invalid_subjectconfirmation_noa.xml.base64 " ) ) }
let ( :response_invalid_signature_position ) { OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " invalid_signature_position.xml.base64 " ) ) }
let ( :response_encrypted_nameid ) { OneLogin :: RubySaml :: Response . new ( response_document_encrypted_nameid ) }
def generate_audience_error ( expected , actual )
s = actual . count > 1 ? 's' : '' ;
return " Invalid Audience #{ s } . The audience #{ s } #{ actual . join ( ',' ) } , did not match the expected audience #{ expected } "
end
it " raise an exception when response is initialized with nil " do
assert_raises ( ArgumentError ) { OneLogin :: RubySaml :: Response . new ( nil ) }
end
it " not filter available options only " do
options = { :skip_destination = > true , :foo = > :bar }
response = OneLogin :: RubySaml :: Response . new ( response_document_valid_signed , options )
assert_includes response . options . keys , :skip_destination
assert_includes response . options . keys , :foo
end
it " be able to parse a document which contains ampersands " do
XMLSecurity :: SignedDocument . any_instance . stubs ( :digests_match? ) . returns ( true )
OneLogin :: RubySaml :: Response . any_instance . stubs ( :validate_conditions ) . returns ( true )
ampersands_response = OneLogin :: RubySaml :: Response . new ( ampersands_document )
ampersands_response . settings = settings
ampersands_response . settings . idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783'
assert ! ampersands_response . is_valid?
assert_includes ampersands_response . errors , " SAML Response must contain 1 assertion "
end
describe " Prevent node text with comment attack (VU # 475445) " do
before do
@response = OneLogin :: RubySaml :: Response . new ( read_response ( 'response_node_text_attack.xml.base64' ) )
end
it " receives the full NameID when there is an injected comment " do
assert_equal " support@onelogin.com " , @response . name_id
end
it " receives the full AttributeValue when there is an injected comment " do
assert_equal " smith " , @response . attributes [ " surname " ]
end
end
describe " Another test to prevent with comment attack (VU # 475445) " do
before do
@response = OneLogin :: RubySaml :: Response . new ( read_response ( 'response_node_text_attack2.xml.base64' ) , { :skip_recipient_check = > true } )
@response . settings = settings
@response . settings . idp_cert_fingerprint = ruby_saml_cert_fingerprint
end
it " receives the full NameID when there is an injected comment, validates the response " do
assert_equal " test@onelogin.com " , @response . name_id
end
end
describe " Another test with CDATA injected " do
before do
@response = OneLogin :: RubySaml :: Response . new ( read_response ( 'response_node_text_attack3.xml.base64' ) , { :skip_recipient_check = > true } )
@response . settings = settings
@response . settings . idp_cert_fingerprint = ruby_saml_cert_fingerprint
end
it " it normalizes CDATA but reject SAMLResponse due signature invalidation " do
assert_equal " test@onelogin.com.evil.com " , @response . name_id
assert ! @response . is_valid?
assert_includes @response . errors , " Invalid Signature on SAML Response "
end
end
describe " Prevent XEE attack " do
before do
@response = OneLogin :: RubySaml :: Response . new ( fixture ( :attackxee ) )
end
it " false when evil attack vector is present, soft = true " do
@response . soft = true
assert ! @response . send ( :validate_structure )
assert_includes @response . errors , " Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd "
end
it " raise when evil attack vector is present, soft = false " do
@response . soft = false
assert_raises ( OneLogin :: RubySaml :: ValidationError ) do
@response . send ( :validate_structure )
end
end
end
it " adapt namespace " do
refute_nil response . nameid
refute_nil response_without_attributes . nameid
refute_nil response_with_signed_assertion . nameid
end
it " default to raw input when a response is not Base64 encoded " do
decoded = Base64 . decode64 ( response_document_without_attributes )
response_from_raw = OneLogin :: RubySaml :: Response . new ( decoded )
assert response_from_raw . document
end
describe " Assertion " do
it " only retreive an assertion with an ID that matches the signature's reference URI " do
response_wrapped . stubs ( :conditions ) . returns ( nil )
settings . idp_cert_fingerprint = signature_fingerprint_1
response_wrapped . settings = settings
assert_nil response_wrapped . nameid
end
end
describe " # is_valid? " do
describe " soft = false " do
before do
response . soft = false
response_valid_signed . soft = false
end
it " raise when response is initialized with blank data " do
blank_response = OneLogin :: RubySaml :: Response . new ( '' )
blank_response . soft = false
error_msg = " Blank response "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
blank_response . is_valid?
end
assert_includes blank_response . errors , error_msg
end
it " raise when settings have not been set " do
error_msg = " No settings on response "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
response . is_valid?
end
assert_includes response . errors , error_msg
end
it " raise when No fingerprint or certificate on settings " do
settings . idp_cert_fingerprint = nil
settings . idp_cert = nil
settings . idp_cert_multi = nil
response . settings = settings
error_msg = " No fingerprint or certificate on settings "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
response . is_valid?
end
assert_includes response . errors , error_msg
end
it " raise when signature wrapping attack " do
response_wrapped . stubs ( :conditions ) . returns ( nil )
response_wrapped . stubs ( :validate_subject_confirmation ) . returns ( true )
settings . idp_cert_fingerprint = signature_fingerprint_1
response_wrapped . settings = settings
assert ! response_wrapped . is_valid?
end
it " raise when no signature " do
settings . idp_cert_fingerprint = signature_fingerprint_1
response_no_signed_elements . settings = settings
response_no_signed_elements . soft = false
error_msg = " Found an unexpected number of Signature Element. SAML Response rejected "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
response_no_signed_elements . is_valid?
end
end
it " raise when multiple signatures " do
settings . idp_cert_fingerprint = signature_fingerprint_1
response_multiple_signed . settings = settings
response_multiple_signed . soft = false
error_msg = " Duplicated ID. SAML Response rejected "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
response_multiple_signed . is_valid?
end
end
it " validate SAML 2.0 XML structure " do
resp_xml = Base64 . decode64 ( response_document_unsigned ) . gsub ( / emailAddress / , 'test' )
response_unsigned_mod = OneLogin :: RubySaml :: Response . new ( Base64 . encode64 ( resp_xml ) )
response_unsigned_mod . stubs ( :conditions ) . returns ( nil )
settings . idp_cert_fingerprint = signature_fingerprint_1
response_unsigned_mod . settings = settings
response_unsigned_mod . soft = false
assert_raises ( OneLogin :: RubySaml :: ValidationError , 'Digest mismatch' ) do
response_unsigned_mod . is_valid?
end
end
it " raise when encountering a condition that prevents the document from being valid " do
settings . idp_cert_fingerprint = ruby_saml_cert_fingerprint
response . settings = settings
response . soft = false
error_msg = " Current time is on or after NotOnOrAfter condition "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
response . is_valid?
end
assert_includes response . errors [ 0 ] , error_msg
end
it " raise when encountering a SAML Response with bad formatted " do
settings . idp_cert_fingerprint = signature_fingerprint_1
response_without_attributes . settings = settings
response_without_attributes . soft = false
assert_raises ( OneLogin :: RubySaml :: ValidationError ) do
response_without_attributes . is_valid?
end
end
it " raise when the inResponseTo value does not match the Request ID " do
settings . soft = false
settings . idp_cert_fingerprint = signature_fingerprint_1
opts = { }
opts [ :settings ] = settings
opts [ :matches_request_id ] = " invalid_request_id "
response_valid_signed = OneLogin :: RubySaml :: Response . new ( response_document_valid_signed , opts )
error_msg = " The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
response_valid_signed . is_valid?
end
assert_includes response_valid_signed . errors , error_msg
end
it " raise when there is no valid audience " do
settings . idp_cert_fingerprint = signature_fingerprint_1
settings . sp_entity_id = 'invalid'
response_valid_signed . settings = settings
response_valid_signed . soft = false
error_msg = generate_audience_error ( response_valid_signed . settings . sp_entity_id , [ 'https://someone.example.com/audience' ] )
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
response_valid_signed . is_valid?
end
assert_includes response_valid_signed . errors , error_msg
end
it " raise when no ID present in the SAML Response " do
settings . idp_cert_fingerprint = signature_fingerprint_1
response_no_id . settings = settings
response_no_id . soft = false
error_msg = " Missing ID attribute on SAML Response "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
response_no_id . is_valid?
end
assert_includes response_no_id . errors , error_msg
end
it " raise when no 2.0 Version present in the SAML Response " do
settings . idp_cert_fingerprint = signature_fingerprint_1
response_no_version . settings = settings
response_no_version . soft = false
error_msg = " Unsupported SAML version "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
response_no_version . is_valid?
end
assert_includes response_no_version . errors , error_msg
end
end
describe " soft = true " do
before do
response . soft = true
response_valid_signed . soft = true
end
it " return true when the response is initialized with valid data " do
response_valid_signed_without_recipient . stubs ( :conditions ) . returns ( nil )
response_valid_signed_without_recipient . settings = settings
response_valid_signed_without_recipient . settings . idp_cert_fingerprint = ruby_saml_cert_fingerprint
assert response_valid_signed_without_recipient . is_valid?
assert_empty response_valid_signed_without_recipient . errors
end
it " return true when the response is initialized with valid data and using certificate instead of fingerprint " do
response_valid_signed_without_recipient . stubs ( :conditions ) . returns ( nil )
response_valid_signed_without_recipient . settings = settings
response_valid_signed_without_recipient . settings . idp_cert = ruby_saml_cert_text
assert response_valid_signed_without_recipient . is_valid?
assert_empty response_valid_signed_without_recipient . errors
end
it " return false when response is initialized with blank data " do
blank_response = OneLogin :: RubySaml :: Response . new ( '' )
blank_response . soft = true
assert ! blank_response . is_valid?
assert_includes blank_response . errors , " Blank response "
end
it " return false if settings have not been set " do
assert ! response . is_valid?
assert_includes response . errors , " No settings on response "
end
it " return false if fingerprint or certificate not been set on settings " do
response . settings = settings
assert ! response . is_valid?
assert_includes response . errors , " No fingerprint or certificate on settings "
end
it " should be idempotent when the response is initialized with invalid data " do
response_unsigned . stubs ( :conditions ) . returns ( nil )
response_unsigned . settings = settings
assert ! response_unsigned . is_valid?
assert ! response_unsigned . is_valid?
end
it " should be idempotent when the response is initialized with valid data " do
response_valid_signed_without_recipient . stubs ( :conditions ) . returns ( nil )
response_valid_signed_without_recipient . settings = settings
response_valid_signed_without_recipient . settings . idp_cert_fingerprint = ruby_saml_cert_fingerprint
assert response_valid_signed_without_recipient . is_valid?
assert response_valid_signed_without_recipient . is_valid?
end
it " not allow signature wrapping attack " do
response_wrapped . stubs ( :conditions ) . returns ( nil )
response_wrapped . stubs ( :validate_subject_confirmation ) . returns ( true )
settings . idp_cert_fingerprint = signature_fingerprint_1
response_wrapped . settings = settings
assert ! response_wrapped . is_valid?
end
it " support dynamic namespace resolution on signature elements " do
no_signature_response = OneLogin :: RubySaml :: Response . new ( fixture ( " no_signature_ns.xml " ) )
no_signature_response . stubs ( :conditions ) . returns ( nil )
no_signature_response . stubs ( :validate_subject_confirmation ) . returns ( true )
no_signature_response . settings = settings
no_signature_response . settings . idp_cert_fingerprint = " 28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA "
XMLSecurity :: SignedDocument . any_instance . expects ( :validate_signature ) . returns ( true )
assert no_signature_response . is_valid?
end
it " validate ADFS assertions " do
adfs_response = OneLogin :: RubySaml :: Response . new ( fixture ( :adfs_response_sha256 ) )
adfs_response . stubs ( :conditions ) . returns ( nil )
adfs_response . stubs ( :validate_subject_confirmation ) . returns ( true )
settings . idp_cert_fingerprint = " 28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA "
adfs_response . settings = settings
adfs_response . soft = true
assert adfs_response . is_valid?
end
it " validate SAML 2.0 XML structure " do
resp_xml = Base64 . decode64 ( response_document_unsigned ) . gsub ( / emailAddress / , 'test' )
response_unsigned_mod = OneLogin :: RubySaml :: Response . new ( Base64 . encode64 ( resp_xml ) )
response_unsigned_mod . stubs ( :conditions ) . returns ( nil )
settings . idp_cert_fingerprint = signature_fingerprint_1
response_unsigned_mod . settings = settings
response_unsigned_mod . soft = true
assert ! response_unsigned_mod . is_valid?
end
it " return false when encountering a condition that prevents the document from being valid " do
settings . idp_cert_fingerprint = ruby_saml_cert_fingerprint
response . settings = settings
error_msg = " Current time is on or after NotOnOrAfter condition "
assert ! response . is_valid?
2023-02-13 15:21:45 +00:00
assert_includes response . errors [ 0 ] , error_msg
2022-05-14 03:53:04 +00:00
end
it " return false when encountering a SAML Response with bad formatted " do
settings . idp_cert_fingerprint = signature_fingerprint_1
response_without_attributes . settings = settings
response_without_attributes . soft = true
error_msg = " Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd "
response_without_attributes . is_valid?
assert_includes response_without_attributes . errors , error_msg
end
it " return false when the inResponseTo value does not match the Request ID " do
settings . soft = true
settings . idp_cert_fingerprint = signature_fingerprint_1
opts = { }
opts [ :settings ] = settings
opts [ :matches_request_id ] = " invalid_request_id "
response_valid_signed = OneLogin :: RubySaml :: Response . new ( response_document_valid_signed , opts )
response_valid_signed . is_valid?
assert_includes response_valid_signed . errors , " The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id "
end
it " return false when there is no valid audience " do
settings . idp_cert_fingerprint = signature_fingerprint_1
settings . sp_entity_id = 'invalid'
response_valid_signed . settings = settings
response_valid_signed . is_valid?
assert_includes response_valid_signed . errors , generate_audience_error ( response_valid_signed . settings . sp_entity_id , [ 'https://someone.example.com/audience' ] )
end
it " return false when no ID present in the SAML Response " do
settings . idp_cert_fingerprint = signature_fingerprint_1
response_no_id . settings = settings
response_no_id . soft = true
response_no_id . is_valid?
assert_includes response_no_id . errors , " Missing ID attribute on SAML Response "
end
it " return false when no 2.0 Version present in the SAML Response " do
settings . idp_cert_fingerprint = signature_fingerprint_1
response_no_version . settings = settings
response_no_version . soft = true
error_msg = " Unsupported SAML version "
response_no_version . is_valid?
2023-02-13 15:21:45 +00:00
assert_includes response_no_version . errors , error_msg
2022-05-14 03:53:04 +00:00
end
it " return true when a nil URI is given in the ds:Reference " do
settings . idp_cert = ruby_saml_cert_text
settings . assertion_consumer_service_url = " http://localhost:9001/v1/users/authorize/saml "
response_without_reference_uri . settings = settings
response_without_reference_uri . stubs ( :conditions ) . returns ( nil )
response_without_reference_uri . is_valid?
assert_empty response_without_reference_uri . errors
assert 'saml@user.com' , response_without_reference_uri . attributes [ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress' ]
end
it " collect errors when collect_errors=true " do
settings . idp_cert = ruby_saml_cert_text
settings . sp_entity_id = 'invalid'
response_invalid_subjectconfirmation_recipient . settings = settings
collect_errors = true
response_invalid_subjectconfirmation_recipient . is_valid? ( collect_errors )
assert_includes response_invalid_subjectconfirmation_recipient . errors , generate_audience_error ( 'invalid' , [ 'http://stuff.com/endpoints/metadata.php' ] )
assert_includes response_invalid_subjectconfirmation_recipient . errors , " Invalid Signature on SAML Response "
end
end
end
describe " # validate_audience " do
it " return true when the audience is valid " do
response . settings = settings
response . settings . sp_entity_id = '{audience}'
assert response . send ( :validate_audience )
assert_empty response . errors
end
it " return true when the audience is self closing and strict audience validation is not enabled " do
response_audience_self_closed . settings = settings
response_audience_self_closed . settings . sp_entity_id = '{audience}'
assert response_audience_self_closed . send ( :validate_audience )
assert_empty response_audience_self_closed . errors
end
it " return false when the audience is self closing and strict audience validation is enabled " do
response_audience_self_closed . settings = settings
response_audience_self_closed . settings . security [ :strict_audience_validation ] = true
response_audience_self_closed . settings . sp_entity_id = '{audience}'
refute response_audience_self_closed . send ( :validate_audience )
assert_includes response_audience_self_closed . errors , " Invalid Audiences. The <AudienceRestriction> element contained only empty <Audience> elements. Expected audience {audience}. "
end
it " return false when the audience is invalid " do
response . settings = settings
response . settings . sp_entity_id = 'invalid_audience'
assert ! response . send ( :validate_audience )
assert_includes response . errors , generate_audience_error ( response . settings . sp_entity_id , [ '{audience}' ] )
end
end
describe " # validate_destination " do
it " return true when the destination of the SAML Response matches the assertion consumer service url " do
response . settings = settings
assert response . send ( :validate_destination )
assert_empty response . errors
end
it " return false when the destination of the SAML Response does not match the assertion consumer service url " do
response . settings = settings
response . settings . assertion_consumer_service_url = 'invalid_acs'
assert ! response . send ( :validate_destination )
assert_includes response . errors , " The response was received at #{ response . destination } instead of #{ response . settings . assertion_consumer_service_url } "
end
it " return false when the destination of the SAML Response is empty " do
response_empty_destination . settings = settings
assert ! response_empty_destination . send ( :validate_destination )
assert_includes response_empty_destination . errors , " The response has an empty Destination value "
end
it " return true when the destination of the SAML Response is empty but skip_destination option is used " do
response_empty_destination_with_skip . settings = settings
assert response_empty_destination_with_skip . send ( :validate_destination )
assert_empty response_empty_destination . errors
end
it " returns true on a case insensitive match on the domain " do
response_valid_signed_without_x509certificate . settings = settings
response_valid_signed_without_x509certificate . settings . assertion_consumer_service_url = 'http://APP.muDa.no/sso/consume'
assert response_valid_signed_without_x509certificate . send ( :validate_destination )
assert_empty response_valid_signed_without_x509certificate . errors
end
it " returns true on a case insensitive match on the scheme " do
response_valid_signed_without_x509certificate . settings = settings
response_valid_signed_without_x509certificate . settings . assertion_consumer_service_url = 'HTTP://app.muda.no/sso/consume'
assert response_valid_signed_without_x509certificate . send ( :validate_destination )
assert_empty response_valid_signed_without_x509certificate . errors
end
it " returns false on a case insenstive match on the path " do
response_valid_signed_without_x509certificate . settings = settings
response_valid_signed_without_x509certificate . settings . assertion_consumer_service_url = 'http://app.muda.no/SSO/consume'
assert ! response_valid_signed_without_x509certificate . send ( :validate_destination )
assert_includes response_valid_signed_without_x509certificate . errors , " The response was received at #{ response_valid_signed_without_x509certificate . destination } instead of #{ response_valid_signed_without_x509certificate . settings . assertion_consumer_service_url } "
end
it " returns true if it can't parse out a full URI. " do
response_valid_signed_without_x509certificate . settings = settings
response_valid_signed_without_x509certificate . settings . assertion_consumer_service_url = 'presenter'
assert ! response_valid_signed_without_x509certificate . send ( :validate_destination )
assert_includes response_valid_signed_without_x509certificate . errors , " The response was received at #{ response_valid_signed_without_x509certificate . destination } instead of #{ response_valid_signed_without_x509certificate . settings . assertion_consumer_service_url } "
end
end
describe " # validate_issuer " do
it " return true when the issuer of the Message/Assertion matches the IdP entityId " do
response_valid_signed . settings = settings
assert response_valid_signed . send ( :validate_issuer )
response_valid_signed . settings . idp_entity_id = 'https://app.onelogin.com/saml2'
assert response_valid_signed . send ( :validate_issuer )
end
it " return false when the issuer of the Message does not match the IdP entityId " do
response_invalid_issuer_message . settings = settings
response_invalid_issuer_message . settings . idp_entity_id = 'http://idp.example.com/'
assert ! response_invalid_issuer_message . send ( :validate_issuer )
assert_includes response_invalid_issuer_message . errors , " Doesn't match the issuer, expected: < #{ response_invalid_issuer_message . settings . idp_entity_id } >, but was: <http://invalid.issuer.example.com/> "
end
it " return false when the issuer of the Assertion does not match the IdP entityId " do
response_invalid_issuer_assertion . settings = settings
response_invalid_issuer_assertion . settings . idp_entity_id = 'http://idp.example.com/'
assert ! response_invalid_issuer_assertion . send ( :validate_issuer )
assert_includes response_invalid_issuer_assertion . errors , " Doesn't match the issuer, expected: < #{ response_invalid_issuer_assertion . settings . idp_entity_id } >, but was: <http://invalid.issuer.example.com/> "
end
end
describe " # validate_num_assertion " do
it " return true when SAML Response contains 1 assertion " do
assert response . send ( :validate_num_assertion )
assert_empty response . errors
end
it " return false when no 2.0 Version present in the SAML Response " do
assert ! response_multi_assertion . send ( :validate_num_assertion )
assert_includes response_multi_assertion . errors , " SAML Response must contain 1 assertion "
end
end
describe " validate_success_status " do
it " return true when the status is 'Success' " do
assert response . send ( :validate_success_status )
assert_empty response . errors
end
it " return false when no Status provided " do
assert ! response_no_status . send ( :validate_success_status )
assert_includes response_no_status . errors , " The status code of the Response was not Success "
end
it " return false when no StatusCode provided " do
assert ! response_no_statuscode . send ( :validate_success_status )
assert_includes response_no_statuscode . errors , " The status code of the Response was not Success "
end
it " return false when the status is not 'Success' " do
assert ! response_statuscode_responder . send ( :validate_success_status )
assert_includes response_statuscode_responder . errors , " The status code of the Response was not Success, was Responder "
end
it " return false when the status is not 'Success', and shows the StatusMessage " do
assert ! response_statuscode_responder_and_msg . send ( :validate_success_status )
assert_includes response_statuscode_responder_and_msg . errors , " The status code of the Response was not Success, was Responder -> something_is_wrong "
end
it " return false when the status is not 'Success' " do
assert ! response_double_statuscode . send ( :validate_success_status )
assert_includes response_double_statuscode . errors , " The status code of the Response was not Success, was Requester => UnsupportedBinding "
end
end
describe " # validate_structure " do
it " return true when encountering a wellformed SAML Response " do
assert response . send ( :validate_structure )
assert_empty response . errors
end
it " return false when encountering a mailformed element that prevents the document from being valid " do
response_without_attributes . soft = true
response_without_attributes . send ( :validate_structure )
assert response_without_attributes . errors . include? " Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd "
end
it " raise when encountering a mailformed element that prevents the document from being valid " do
response_without_attributes . soft = false
assert_raises ( OneLogin :: RubySaml :: ValidationError ) {
response_without_attributes . send ( :validate_structure )
}
end
end
describe " validate_formatted_x509_certificate " do
let ( :response_with_formatted_x509certificate ) {
OneLogin :: RubySaml :: Response . new ( read_response ( " valid_response_with_formatted_x509certificate.xml.base64 " ) , {
:skip_conditions = > true ,
:skip_subject_confirmation = > true } )
}
it " be able to parse the response wihout errors " do
response_with_formatted_x509certificate . settings = settings
response_with_formatted_x509certificate . settings . idp_cert = ruby_saml_cert_text
assert response_with_formatted_x509certificate . is_valid?
assert_empty response_with_formatted_x509certificate . errors
end
end
describe " # validate_in_response_to " do
it " return true when the inResponseTo value matches the Request ID " do
response = OneLogin :: RubySaml :: Response . new ( response_document_valid_signed , :settings = > settings , :matches_request_id = > " _fc4a34b0-7efb-012e-caae-782bcb13bb38 " )
assert response . send ( :validate_in_response_to )
assert_empty response . errors
end
it " return true when no Request ID is provided for checking " do
response = OneLogin :: RubySaml :: Response . new ( response_document_valid_signed , :settings = > settings )
assert response . send ( :validate_in_response_to )
assert_empty response . errors
end
it " return false when the inResponseTo value does not match the Request ID " do
response = OneLogin :: RubySaml :: Response . new ( response_document_valid_signed , :settings = > settings , :matches_request_id = > " invalid_request_id " )
assert ! response . send ( :validate_in_response_to )
assert_includes response . errors , " The InResponseTo of the Response: _fc4a34b0-7efb-012e-caae-782bcb13bb38, does not match the ID of the AuthNRequest sent by the SP: invalid_request_id "
end
end
describe " # validate_audience " do
it " return true when the audience is valid " do
response_valid_signed . settings = settings
response_valid_signed . settings . sp_entity_id = " https://someone.example.com/audience "
assert response_valid_signed . send ( :validate_audience )
assert_empty response_valid_signed . errors
end
it " return true when there is not sp_entity_id defined " do
response_valid_signed . settings = settings
response_valid_signed . settings . sp_entity_id = nil
assert response_valid_signed . send ( :validate_audience )
assert_empty response_valid_signed . errors
end
it " return false when there is no valid audience " do
response_invalid_audience . settings = settings
response_invalid_audience . settings . sp_entity_id = " https://invalid.example.com/audience "
assert ! response_invalid_audience . send ( :validate_audience )
assert_includes response_invalid_audience . errors , generate_audience_error ( response_invalid_audience . settings . sp_entity_id , [ 'http://invalid.audience.com' ] )
end
it " return true when there is no valid audience but skip_destination option is used " do
response_invalid_audience_with_skip . settings = settings
response_invalid_audience_with_skip . settings . sp_entity_id = " https://invalid.example.com/audience "
assert response_invalid_audience_with_skip . send ( :validate_audience )
assert_empty response_invalid_audience_with_skip . errors
end
end
describe " # validate_issuer " do
it " return true when the issuer of the Message/Assertion matches the IdP entityId or it was empty " do
response_valid_signed . settings = settings
assert response_valid_signed . send ( :validate_issuer )
assert_empty response_valid_signed . errors
response_valid_signed . settings . idp_entity_id = 'https://app.onelogin.com/saml2'
assert response_valid_signed . send ( :validate_issuer )
assert_empty response_valid_signed . errors
end
it " return false when the issuer of the Message does not match the IdP entityId " do
response_invalid_issuer_message . settings = settings
response_invalid_issuer_message . settings . idp_entity_id = 'http://idp.example.com/'
assert ! response_invalid_issuer_message . send ( :validate_issuer )
assert_includes response_invalid_issuer_message . errors , " Doesn't match the issuer, expected: < #{ response_invalid_issuer_message . settings . idp_entity_id } >, but was: <http://invalid.issuer.example.com/> "
end
it " return false when the issuer of the Assertion does not match the IdP entityId " do
response_invalid_issuer_assertion . settings = settings
response_invalid_issuer_assertion . settings . idp_entity_id = 'http://idp.example.com/'
assert ! response_invalid_issuer_assertion . send ( :validate_issuer )
assert_includes response_invalid_issuer_assertion . errors , " Doesn't match the issuer, expected: < #{ response_invalid_issuer_assertion . settings . idp_entity_id } >, but was: <http://invalid.issuer.example.com/> "
end
it " return false when the no issuer at the Response " do
response_no_issuer_response . settings = settings
response_no_issuer_response . settings . idp_entity_id = 'http://idp.example.com/'
assert ! response_no_issuer_response . send ( :validate_issuer )
assert_includes response_no_issuer_response . errors , " Issuer of the Response not found or multiple. "
end
it " return false when the no issuer at the Assertion " do
response_no_issuer_assertion . settings = settings
response_no_issuer_assertion . settings . idp_entity_id = 'http://idp.example.com/'
assert ! response_no_issuer_assertion . send ( :validate_issuer )
assert_includes response_no_issuer_assertion . errors , " Issuer of the Assertion not found or multiple. "
end
end
describe " # validate_subject_confirmation " do
it " return true when valid subject confirmation " do
response_valid_signed . settings = settings
response_valid_signed . settings . assertion_consumer_service_url = 'recipient'
assert response_valid_signed . send ( :validate_subject_confirmation )
assert_empty response_valid_signed . errors
end
it " return false when no subject confirmation data " do
response_no_subjectconfirmation_data . settings = settings
assert ! response_no_subjectconfirmation_data . send ( :validate_subject_confirmation )
assert_includes response_no_subjectconfirmation_data . errors , " A valid SubjectConfirmation was not found on this Response "
end
it " return false when no valid subject confirmation method " do
response_no_subjectconfirmation_method . settings = settings
assert ! response_no_subjectconfirmation_method . send ( :validate_subject_confirmation )
assert_includes response_no_subjectconfirmation_method . errors , " A valid SubjectConfirmation was not found on this Response "
end
it " return false when invalid inresponse " do
response_invalid_subjectconfirmation_inresponse . settings = settings
assert ! response_invalid_subjectconfirmation_inresponse . send ( :validate_subject_confirmation )
assert_includes response_invalid_subjectconfirmation_inresponse . errors , " A valid SubjectConfirmation was not found on this Response "
end
it " return false when invalid NotBefore " do
response_invalid_subjectconfirmation_nb . settings = settings
assert ! response_invalid_subjectconfirmation_nb . send ( :validate_subject_confirmation )
assert_includes response_invalid_subjectconfirmation_nb . errors , " A valid SubjectConfirmation was not found on this Response "
end
it " return false when invalid NotOnOrAfter " do
response_invalid_subjectconfirmation_noa . settings = settings
assert ! response_invalid_subjectconfirmation_noa . send ( :validate_subject_confirmation )
assert_includes response_invalid_subjectconfirmation_noa . errors , " A valid SubjectConfirmation was not found on this Response "
end
it " return true when valid subject confirmation recipient " do
response_valid_signed . settings = settings
response_valid_signed . settings . assertion_consumer_service_url = 'recipient'
assert response_valid_signed . send ( :validate_subject_confirmation )
assert_empty response_valid_signed . errors
assert_empty response_valid_signed . errors
end
it " return false when invalid subject confirmation recipient " do
response_valid_signed . settings = settings
response_valid_signed . settings . assertion_consumer_service_url = 'not-the-recipient'
assert ! response_valid_signed . send ( :validate_subject_confirmation )
assert_includes response_valid_signed . errors , " A valid SubjectConfirmation was not found on this Response "
end
it " return false when invalid subject confirmation recipient, but skipping the check(default) " do
response_valid_signed_without_recipient . settings = settings
response_valid_signed_without_recipient . settings . assertion_consumer_service_url = 'not-the-recipient'
assert response_valid_signed_without_recipient . send ( :validate_subject_confirmation )
assert_empty response_valid_signed_without_recipient . errors
end
it " return true when the skip_subject_confirmation option is passed and the subject confirmation is valid " do
opts = { }
opts [ :skip_subject_confirmation ] = true
response_with_skip = OneLogin :: RubySaml :: Response . new ( response_document_valid_signed , opts )
response_with_skip . settings = settings
response_with_skip . settings . assertion_consumer_service_url = 'recipient'
Time . expects ( :now ) . times ( 0 ) # ensures the test isn't run and thus Time.now.utc is never called within the test
assert response_with_skip . send ( :validate_subject_confirmation )
assert_empty response_with_skip . errors
end
it " return true when the skip_subject_confirmation option is passed and the response has an invalid subject confirmation " do
opts = { }
opts [ :skip_subject_confirmation ] = true
response_with_skip = OneLogin :: RubySaml :: Response . new ( read_invalid_response ( " invalid_subjectconfirmation_noa.xml.base64 " ) , opts )
response_with_skip . settings = settings
Time . expects ( :now ) . times ( 0 ) # ensures the test isn't run and thus Time.now.utc is never called within the test
assert response_with_skip . send ( :validate_subject_confirmation )
assert_empty response_with_skip . errors
end
end
describe " # validate_session_expiration " do
it " return true when the session has not expired " do
response_valid_signed . settings = settings
assert response_valid_signed . send ( :validate_session_expiration )
assert_empty response_valid_signed . errors
end
it " return false when the session has expired " do
response . settings = settings
assert ! response . send ( :validate_session_expiration )
assert_includes response . errors , " The attributes have expired, based on the SessionNotOnOrAfter of the AuthnStatement of this Response "
end
it " returns true when the session has expired, but is still within the allowed_clock_drift " do
drift = ( Time . now - Time . parse ( " 2010-11-19T21:57:37Z " ) ) * 60 # seconds ago that this assertion expired
drift += 10 # add a buffer of 10 seconds to make sure the test passes
opts = { }
opts [ :allowed_clock_drift ] = drift
response_with_drift = OneLogin :: RubySaml :: Response . new ( response_document_without_recipient , opts )
response_with_drift . settings = settings
assert response_with_drift . send ( :validate_session_expiration )
assert_empty response_with_drift . errors
end
end
describe " # validate_signature " do
it " return true when the signature is valid " do
settings . idp_cert_fingerprint = ruby_saml_cert_fingerprint
response_valid_signed . settings = settings
assert response_valid_signed . send ( :validate_signature )
assert_empty response_valid_signed . errors
end
it " return true when the signature is valid and ds namespace is at the root " do
settings . idp_cert_fingerprint = '5614657ab692b960480389723a36446a5fe1f7ec'
response_with_ds_namespace_at_the_root . settings = settings
assert response_with_ds_namespace_at_the_root . send ( :validate_signature )
assert_empty response_with_ds_namespace_at_the_root . errors
end
it " return true when the signature is valid and fingerprint provided " do
settings . idp_cert_fingerprint = '49:EC:3F:A4:71:8A:1E:C9:DB:70:A7:CC:33:36:96:F0:48:8C:4E:DA'
xml = ' PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIERlc3RpbmF0aW9uPSJodHRwczovL2NvZGVycGFkLmlvL3NhbWwvYWNzIiBJRD0iXzEwOGE1ZTg0MDllYzRjZjlhY2QxYzQ2OWU5ZDcxNGFkIiBJblJlc3BvbnNlVG89Il80ZmZmYWE2MC02OTZiLTAxMzMtMzg4Ni0wMjQxZjY1YzA2OTMiIElzc3VlSW5zdGFudD0iMjAxNS0xMS0wOVQyMzo1NTo0M1oiIFZlcnNpb249IjIuMCI + PHNhbWw6SXNzdWVyIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iPmh0dHBzOi8vbG9naW4uaHVsdS5jb208L3NhbWw6SXNzdWVyPjxkczpTaWduYXR1cmUgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiPjxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIj48L2RzOkNhbm9uaWNhbGl6YXRpb25NZXRob2Q + PGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSI + PC9kczpTaWduYXR1cmVNZXRob2Q + PGRzOlJlZmVyZW5jZSBVUkk9IiNfMTA4YTVlODQwOWVjNGNmOWFjZDFjNDY5ZTlkNzE0YWQiPjxkczpUcmFuc2Zvcm1zPjxkczpUcmFuc2Zvcm0gQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjZW52ZWxvcGVkLXNpZ25hdHVyZSI + PC9kczpUcmFuc2Zvcm0 + PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyI + PC9kczpUcmFuc2Zvcm0 + PC9kczpUcmFuc2Zvcm1zPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSI + PC9kczpEaWdlc3RNZXRob2Q + PGRzOkRpZ2VzdFZhbHVlPm9sQllXbTQyRi9oZm0xdHJYTHk2a3V6MXlMUT08L2RzOkRpZ2VzdFZhbHVlPjwvZHM6UmVmZXJlbmNlPjwvZHM6U2lnbmVkSW5mbz48ZHM6U2lnbmF0dXJlVmFsdWU + dXNRTmY5WGpKTDRlOXVucnVCdWViSnQ3R0tXM2hJUk9teWVqTm1NMHM4WFhlWHN3WHc4U3ZCZi8zeDNNWEpkWnpNV0pOM3ExN2tGWHN2bTVna1JzbkE9PTwvZHM6U2lnbmF0dXJlVmFsdWU + PGRzOktleUluZm8 + PGRzOlg1MDlEYXRhPjxkczpYNTA5Q2VydGlmaWNhdGU + TUlJQ1FEQ0NBZXFnQXdJQkFnSUpBSVZOdzVLRzR1aTFNQTBHQ1NxR1NJYjNEUUVCQlFVQU1Fd3hDekFKQmdOVkJBWVRBa2RDTVJJd0VBWURWUVFJRXdsQ1pYSnJjMmhwY21VeEVEQU9CZ05WQkFjVEIwNWxkMkoxY25reEZ6QVZCZ05WQkFvVERrMTVJRU52YlhCaGJua2dUSFJrTUI0WERURXlNVEF5TlRBMk1qY3pORm9YRFRJeU1UQXlNekEyTWpjek5Gb3dUREVMTUFrR0ExVUVCaE1DUjBJeEVqQVFCZ05WQkFnVENVSmxjbXR6YUdseVpURVFNQTRHQTFVRUJ4TUhUbVYzWW5WeWVURVhNQlVHQTFVRUNoTU9UWGtnUTI5dGNHRnVlU0JNZEdRd1hEQU5CZ2txaGtpRzl3MEJBUUVGQUFOTEFEQklBa0VBd1NOL2dpMzNSbXBBUW9MUWo3UDZ6QW5OVDBSbjdiakMzMjNuM3ExT25mdm52UjBmUWp2TnQ3ckRrQTVBdjVRbk02VjRZVU5Vbk1mYk9RcTBXTGJMU3dJREFRQUJvNEd1TUlHck1CMEdBMVVkRGdRV0JCUWZJSDFvZkJWcHNSQWNJTUsyaGJsN25nTVRZREI4QmdOVkhTTUVkVEJ6Z0JRZklIMW9mQlZwc1JBY0lNSzJoYmw3bmdNVFlLRlFwRTR3VERFTE1Ba0dBMVVFQmhNQ1IwSXhFakFRQmdOVkJBZ1RDVUpsY210emFHbHlaVEVRTUE0R0ExVUVCeE1IVG1WM1luVnllVEVYTUJVR0ExVUVDaE1PVFhrZ1EyOXRjR0Z1ZVNCTWRHU0NDUUNGVGNPU2h1TG90VEFNQmdOVkhSTUVCVEFEQVFIL01BMEdDU3FHU0liM0RRRUJCUVVBQTBFQXFvZ1YzdVBjbEtYRG1EWk1UN3ZsUFl4TEFxQ0dIWnRsQ3h6NGhNNEtTdGxEMi9HTmMxWGlMYjFoL0swQ0pMRG9zckVJYm0zd2lPMk12VEVSclZZU01RPT08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48L2RzOlNpZ25hdHVyZT48c2FtbHA6U3RhdHVzPjxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiPjwvc2FtbHA6U3RhdHVzQ29kZT48L3NhbWxwOlN0YXR1cz48c2FtbDpBc3NlcnRpb24geG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9Il8wMTg4MmRhOTM2OTQ0ZDFlYTZlZmY0NDA2NTc2MzFiNSIgSXNzdWVJbnN0YW50PSIyMDE1LTExLTA5VDIzOjU1OjQzWiIgVmVyc2lvbj0iMi4wIj48c2FtbDpJc3N1ZXI + aHR0cHM6Ly9sb2dpbi5odWx1LmNvbTwvc2FtbDpJc3N1ZXI + PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI + PGRzOlNpZ25lZEluZm8 + PGRzOkNhbm9uaWNhbGl6YXRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzEwL3htbC1leGMtYzE0biMiPjwvZHM6Q2Fub25pY2FsaXphdGlvbk1ldGhvZD48ZHM6U2lnbmF0dXJlTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3JzYS1zaGExIj48L2RzOlNpZ25hdHVyZU1ldGhvZD48ZHM6UmVmZXJlbmNlIFVSST0iI18wMTg4MmRhOTM2OTQ0ZDFlYTZlZmY0NDA2NTc2MzFiNSI + PGRzOlRyYW5zZm9ybXM + PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIj48L2RzOlRyYW5zZm9ybT48ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIj48L2RzOlRyYW5zZm9ybT48L2RzOlRyYW5zZm9ybXM + PGRzOkRpZ2VzdE1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNzaGExIj48L2RzOkRpZ2VzdE1ldGhvZD48ZHM6RGlnZXN0VmFsdWU + cmo2YzhucC9BUmV0ZkJ1
response_x = OneLogin :: RubySaml :: Response . new ( xml )
response_x . settings = settings
assert response_x . send ( :validate_signature )
assert_empty response_x . errors
end
it " return false when no fingerprint " do
settings . idp_cert_fingerprint = nil
settings . idp_cert = nil
response . settings = settings
assert ! response . send ( :validate_signature )
assert_includes response . errors , " Invalid Signature on SAML Response "
end
it " return false when the signature is invalid " do
settings . idp_cert_fingerprint = signature_fingerprint_1
response . settings = settings
assert ! response . send ( :validate_signature )
assert_includes response . errors , " Fingerprint mismatch "
assert_includes response . errors , " Invalid Signature on SAML Response "
end
it " return false when no X509Certificate and not cert provided at settings " do
settings . idp_cert_fingerprint = ruby_saml_cert_fingerprint
settings . idp_cert = nil
response_valid_signed_without_x509certificate . settings = settings
assert ! response_valid_signed_without_x509certificate . send ( :validate_signature )
assert_includes response_valid_signed_without_x509certificate . errors , " Invalid Signature on SAML Response "
end
it " return false when cert expired and check_idp_cert_expiration enabled " do
settings . idp_cert_fingerprint = nil
settings . idp_cert = ruby_saml_cert_text
settings . security [ :check_idp_cert_expiration ] = true
response_valid_signed . settings = settings
assert ! response_valid_signed . send ( :validate_signature )
assert_includes response_valid_signed . errors , " IdP x509 certificate expired "
end
it " return false when X509Certificate and the cert provided at settings mismatches " do
settings . idp_cert_fingerprint = nil
settings . idp_cert = signature_1
response_valid_signed_without_x509certificate . settings = settings
assert ! response_valid_signed_without_x509certificate . send ( :validate_signature )
assert_includes response_valid_signed_without_x509certificate . errors , " Key validation error "
assert_includes response_valid_signed_without_x509certificate . errors , " Invalid Signature on SAML Response "
end
it " return false when X509Certificate has invalid content " do
settings . idp_cert_fingerprint = nil
settings . idp_cert = ruby_saml_cert_text
content = read_response ( 'response_with_signed_message_and_assertion.xml' )
content = content . sub ( / <ds:X509Certificate>.*< \/ ds:X509Certificate> / ,
" <ds:X509Certificate>an-invalid-certificate</ds:X509Certificate> " )
response_invalid_x509certificate = OneLogin :: RubySaml :: Response . new ( content )
response_invalid_x509certificate . settings = settings
assert ! response_invalid_x509certificate . send ( :validate_signature )
assert_includes response_invalid_x509certificate . errors , " Document Certificate Error "
assert_includes response_invalid_x509certificate . errors , " Invalid Signature on SAML Response "
end
it " return true when X509Certificate and the cert provided at settings matches " do
settings . idp_cert_fingerprint = nil
settings . idp_cert = ruby_saml_cert_text
response_valid_signed_without_x509certificate . settings = settings
assert response_valid_signed_without_x509certificate . send ( :validate_signature )
assert_empty response_valid_signed_without_x509certificate . errors
end
it " return false when signature wrapping attack " do
signature_wrapping_attack = read_invalid_response ( " signature_wrapping_attack.xml.base64 " )
response_wrapped = OneLogin :: RubySaml :: Response . new ( signature_wrapping_attack )
response_wrapped . stubs ( :conditions ) . returns ( nil )
response_wrapped . stubs ( :validate_subject_confirmation ) . returns ( true )
settings . idp_cert_fingerprint = " afe71c28ef740bc87425be13a2263d37971da1f9 "
response_wrapped . settings = settings
assert ! response_wrapped . send ( :validate_signature )
assert_includes response_wrapped . errors , " Invalid Signature on SAML Response "
assert_includes response_wrapped . errors , " Signed element id # pfxc3d2b542-0f7e-8767-8e87-5b0dc6913375 is not found "
end
end
describe " # validate_signature with multiple idp certs " do
it " return true when at least a cert on idp_cert_multi is valid " do
settings . idp_cert_multi = {
:signing = > [ ruby_saml_cert_text2 , ruby_saml_cert_text ] ,
:encryption = > [ ]
}
response_valid_signed . settings = settings
2023-02-13 15:21:45 +00:00
response_valid_signed . send ( :validate_signature )
assert_empty response_valid_signed . errors
end
it " return true when at least a cert on idp_cert_multi is valid and keys are strings " do
settings . idp_cert_multi = {
" signing " = > [ ruby_saml_cert_text2 , ruby_saml_cert_text ] ,
" encryption " = > [ ]
}
response_valid_signed . settings = settings
assert response_valid_signed . send ( :validate_signature )
2022-05-14 03:53:04 +00:00
assert_empty response_valid_signed . errors
end
it " return false when none cert on idp_cert_multi is valid " do
settings . idp_cert_fingerprint = ruby_saml_cert_fingerprint
settings . idp_cert_multi = {
:signing = > [ ruby_saml_cert_text2 , ruby_saml_cert_text2 ] ,
:encryption = > [ ]
}
response_valid_signed . settings = settings
assert ! response_valid_signed . send ( :validate_signature )
assert_includes response_valid_signed . errors , " Certificate of the Signature element does not match provided certificate "
assert_includes response_valid_signed . errors , " Invalid Signature on SAML Response "
end
end
describe " # validate nameid " do
it " return false when no nameid element and required by settings " do
settings . security [ :want_name_id ] = true
response_no_nameid . settings = settings
assert ! response_no_nameid . send ( :validate_name_id )
assert_includes response_no_nameid . errors , " No NameID element found in the assertion of the Response "
end
it " return false when no nameid element and required by settings " do
response_empty_nameid . settings = settings
assert ! response_empty_nameid . send ( :validate_name_id )
assert_includes response_empty_nameid . errors , " An empty NameID value found "
end
it " return false when no nameid value " do
response_empty_nameid . settings = settings
assert ! response_empty_nameid . send ( :validate_name_id )
assert_includes response_empty_nameid . errors , " An empty NameID value found "
end
it " return false when wrong_spnamequalifier " do
settings . sp_entity_id = 'sp_entity_id'
response_wrong_spnamequalifier . settings = settings
assert ! response_wrong_spnamequalifier . send ( :validate_name_id )
assert_includes response_wrong_spnamequalifier . errors , " The SPNameQualifier value mistmatch the SP entityID value. "
end
it " return true when no nameid element but not required by settings " do
settings . security [ :want_name_id ] = false
response_no_nameid . settings = settings
assert response_no_nameid . send ( :validate_name_id )
end
it " return true when nameid is valid and response_wrong_spnamequalifier matches the SP issuer " do
settings . sp_entity_id = 'wrong-sp-entityid'
response_wrong_spnamequalifier . settings = settings
assert response_wrong_spnamequalifier . send ( :validate_name_id )
end
end
describe " # nameid " do
it " extract the value of the name id element " do
assert_equal " support@onelogin.com " , response . nameid
assert_equal " someone@example.com " , response_with_signed_assertion . nameid
end
it " be extractable from an OpenSAML response " do
response_open_saml = OneLogin :: RubySaml :: Response . new ( fixture ( :open_saml ) )
assert_equal " someone@example.org " , response_open_saml . nameid
end
it " be extractable from a Simple SAML PHP response " do
response_ssp = OneLogin :: RubySaml :: Response . new ( fixture ( :simple_saml_php ) )
assert_equal " someone@example.com " , response_ssp . nameid
end
end
describe " # name_id_format " do
it " extract the value of the name id element " do
assert_equal " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress " , response . name_id_format
assert_equal " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress " , response_with_signed_assertion . name_id_format
end
end
describe " # sessionindex " do
it " extract the value of the sessionindex element " do
response = OneLogin :: RubySaml :: Response . new ( fixture ( :simple_saml_php ) )
assert_equal " _51be37965feb5579d803141076936dc2e9d1d98ebf " , response . sessionindex
end
end
describe " # check_one_conditions " do
it " return false when none or more than one conditions element " do
response_no_conditions . soft = true
assert ! response_no_conditions . send ( :validate_one_conditions )
assert_includes response_no_conditions . errors , " The Assertion must include one Conditions element "
end
it " return true when one conditions element " do
response . soft = true
assert response . send ( :validate_one_conditions )
end
it " return true when no conditions are present and skip_conditions is true " do
response_no_conditions_with_skip . soft = true
assert response_no_conditions_with_skip . send ( :validate_one_conditions )
end
end
describe " # check_one_authnstatement " do
it " return false when none or more than one authnstatement element " do
response_no_authnstatement . soft = true
assert ! response_no_authnstatement . send ( :validate_one_authnstatement )
assert_includes response_no_authnstatement . errors , " The Assertion must include one AuthnStatement element "
end
it " return true when one authnstatement element " do
response . soft = true
assert response . send ( :validate_one_authnstatement )
end
it " return true when SAML Response is empty but skip_authstatement option is used " do
response_no_authnstatement_with_skip . soft = true
assert response_no_authnstatement_with_skip . send ( :validate_one_authnstatement )
assert_empty response_empty_destination_with_skip . errors
end
end
describe " # check_conditions " do
it " check time conditions " do
response . soft = true
assert ! response . send ( :validate_conditions )
response_time_updated = OneLogin :: RubySaml :: Response . new ( response_document_without_recipient_with_time_updated )
response_time_updated . soft = true
assert response_time_updated . send ( :validate_conditions )
Timecop . freeze ( Time . parse ( " 2011-06-14T18:25:01.516Z " ) ) do
response_with_saml2_namespace = OneLogin :: RubySaml :: Response . new ( response_document_with_saml2_namespace )
response_with_saml2_namespace . soft = true
assert response_with_saml2_namespace . send ( :validate_conditions )
end
end
it " optionally allows for clock drift on NotBefore " do
settings . soft = true
# The NotBefore condition in the document is 2011-06-14T18:21:01.516Z
Timecop . freeze ( Time . parse ( " 2011-06-14T18:21:01Z " ) ) do
special_response_with_saml2_namespace = OneLogin :: RubySaml :: Response . new (
response_document_with_saml2_namespace ,
:allowed_clock_drift = > 0 . 515 ,
:settings = > settings
)
assert ! special_response_with_saml2_namespace . send ( :validate_conditions )
special_response_with_saml2_namespace = OneLogin :: RubySaml :: Response . new (
response_document_with_saml2_namespace ,
:allowed_clock_drift = > 0 . 516
)
assert special_response_with_saml2_namespace . send ( :validate_conditions )
special_response_with_saml2_namespace = OneLogin :: RubySaml :: Response . new (
response_document_with_saml2_namespace ,
:allowed_clock_drift = > '0.515' ,
:settings = > settings
)
assert ! special_response_with_saml2_namespace . send ( :validate_conditions )
special_response_with_saml2_namespace = OneLogin :: RubySaml :: Response . new (
response_document_with_saml2_namespace ,
:allowed_clock_drift = > '0.516'
)
assert special_response_with_saml2_namespace . send ( :validate_conditions )
end
end
it " optionally allows for clock drift on NotOnOrAfter " do
# Java Floats behave differently than MRI
java = defined? ( RUBY_ENGINE ) && %w[ jruby truffleruby ] . include? ( RUBY_ENGINE )
settings . soft = true
# The NotBefore condition in the document is 2011-06-1418:31:01.516Z
Timecop . freeze ( Time . parse ( " 2011-06-14T18:31:02Z " ) ) do
special_response_with_saml2_namespace = OneLogin :: RubySaml :: Response . new (
response_document_with_saml2_namespace ,
:allowed_clock_drift = > 0 . 483 ,
:settings = > settings
)
assert ! special_response_with_saml2_namespace . send ( :validate_conditions )
special_response_with_saml2_namespace = OneLogin :: RubySaml :: Response . new (
response_document_with_saml2_namespace ,
:allowed_clock_drift = > java ? 0 . 485 : 0 . 484
)
assert special_response_with_saml2_namespace . send ( :validate_conditions )
special_response_with_saml2_namespace = OneLogin :: RubySaml :: Response . new (
response_document_with_saml2_namespace ,
:allowed_clock_drift = > '0.483' ,
:settings = > settings
)
assert ! special_response_with_saml2_namespace . send ( :validate_conditions )
special_response_with_saml2_namespace = OneLogin :: RubySaml :: Response . new (
response_document_with_saml2_namespace ,
:allowed_clock_drift = > java ? '0.485' : '0.484'
)
assert special_response_with_saml2_namespace . send ( :validate_conditions )
end
end
end
describe " # attributes " do
it " extract the first attribute in a hash accessed via its symbol " do
assert_equal " demo " , response . attributes [ :uid ]
end
it " extract the first attribute in a hash accessed via its name " do
assert_equal " demo " , response . attributes [ " uid " ]
end
it " extract all attributes " do
assert_equal " demo " , response . attributes [ :uid ]
assert_equal " value " , response . attributes [ :another_value ]
end
it " work for implicit namespaces " do
assert_equal " someone@example.com " , response_with_signed_assertion . attributes [ " http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress " ]
end
it " extract attributes from all AttributeStatement tags " do
assert_equal " smith " , response_with_multiple_attribute_statements . attributes [ :surname ]
assert_equal " bob " , response_with_multiple_attribute_statements . attributes [ :firstname ]
end
it " not raise on responses without attributes " do
assert_equal OneLogin :: RubySaml :: Attributes . new , response_unsigned . attributes
end
describe " # encrypted attributes " do
it " raise error when the assertion contains encrypted attributes but no private key to decrypt " do
settings . private_key = nil
response_encrypted_attrs . settings = settings
assert_raises ( OneLogin :: RubySaml :: ValidationError , " An EncryptedAttribute found and no SP private key found on the settings to decrypt it " ) do
2023-02-13 15:21:45 +00:00
response_encrypted_attrs . attributes
2022-05-14 03:53:04 +00:00
end
end
it " extract attributes when the assertion contains encrypted attributes and the private key is provided " do
settings . certificate = ruby_saml_cert_text
settings . private_key = ruby_saml_key_text
response_encrypted_attrs . settings = settings
attributes = response_encrypted_attrs . attributes
assert_equal " test " , attributes [ :uid ]
assert_equal " test@example.com " , attributes [ :mail ]
end
end
it " return false when validating a response with duplicate attributes " do
response_duplicated_attributes . settings = settings
response_duplicated_attributes . options [ :check_duplicated_attributes ] = true
assert ! response_duplicated_attributes . send ( :validate_no_duplicated_attributes )
assert_includes response_duplicated_attributes . errors , " Found an Attribute element with duplicated Name "
end
it " return true when validating a response with duplicate attributes but skip check " do
response_duplicated_attributes . settings = settings
assert response_duplicated_attributes . send ( :validate_no_duplicated_attributes )
end
describe " # multiple values " do
it " extract single value as string " do
assert_equal " demo " , response_multiple_attr_values . attributes [ :uid ]
end
it " extract single value as string in compatibility mode off " do
OneLogin :: RubySaml :: Attributes . single_value_compatibility = false
assert_equal [ " demo " ] , response_multiple_attr_values . attributes [ :uid ]
# classes are not reloaded between tests so restore default
OneLogin :: RubySaml :: Attributes . single_value_compatibility = true
end
it " extract first of multiple values as string for b/w compatibility " do
assert_equal 'value1' , response_multiple_attr_values . attributes [ :another_value ]
end
it " extract first of multiple values as string for b/w compatibility in compatibility mode off " do
OneLogin :: RubySaml :: Attributes . single_value_compatibility = false
assert_equal [ 'value1' , 'value2' ] , response_multiple_attr_values . attributes [ :another_value ]
OneLogin :: RubySaml :: Attributes . single_value_compatibility = true
end
it " return array with all attributes when asked in XML order " do
assert_equal [ 'value1' , 'value2' ] , response_multiple_attr_values . attributes . multi ( :another_value )
end
it " return array with all attributes when asked in XML order in compatibility mode off " do
OneLogin :: RubySaml :: Attributes . single_value_compatibility = false
assert_equal [ 'value1' , 'value2' ] , response_multiple_attr_values . attributes . multi ( :another_value )
OneLogin :: RubySaml :: Attributes . single_value_compatibility = true
end
it " return first of multiple values when multiple Attribute tags in XML " do
assert_equal 'role1' , response_multiple_attr_values . attributes [ :role ]
end
it " return first of multiple values when multiple Attribute tags in XML in compatibility mode off " do
OneLogin :: RubySaml :: Attributes . single_value_compatibility = false
assert_equal [ 'role1' , 'role2' , 'role3' ] , response_multiple_attr_values . attributes [ :role ]
OneLogin :: RubySaml :: Attributes . single_value_compatibility = true
end
it " return all of multiple values in reverse order when multiple Attribute tags in XML " do
assert_equal [ 'role1' , 'role2' , 'role3' ] , response_multiple_attr_values . attributes . multi ( :role )
end
it " return all of multiple values in reverse order when multiple Attribute tags in XML in compatibility mode off " do
OneLogin :: RubySaml :: Attributes . single_value_compatibility = false
assert_equal [ 'role1' , 'role2' , 'role3' ] , response_multiple_attr_values . attributes . multi ( :role )
OneLogin :: RubySaml :: Attributes . single_value_compatibility = true
end
it " return all of multiple values when multiple Attribute tags in multiple AttributeStatement tags " do
OneLogin :: RubySaml :: Attributes . single_value_compatibility = false
assert_equal [ 'role1' , 'role2' , 'role3' ] , response_with_multiple_attribute_statements . attributes . multi ( :role )
OneLogin :: RubySaml :: Attributes . single_value_compatibility = true
end
it " return nil value correctly " do
assert_nil response_multiple_attr_values . attributes [ :attribute_with_nil_value ]
end
it " return nil value correctly when not in compatibility mode off " do
OneLogin :: RubySaml :: Attributes . single_value_compatibility = false
assert_equal [ nil ] , response_multiple_attr_values . attributes [ :attribute_with_nil_value ]
OneLogin :: RubySaml :: Attributes . single_value_compatibility = true
end
it " return multiple values including nil and empty string " do
response = OneLogin :: RubySaml :: Response . new ( fixture ( :response_with_multiple_attribute_values ) )
assert_equal [ " " , " valuePresent " , nil , nil ] , response . attributes . multi ( :attribute_with_nils_and_empty_strings )
end
it " return multiple values from [] when not in compatibility mode off " do
OneLogin :: RubySaml :: Attributes . single_value_compatibility = false
assert_equal [ " " , " valuePresent " , nil , nil ] , response_multiple_attr_values . attributes [ :attribute_with_nils_and_empty_strings ]
OneLogin :: RubySaml :: Attributes . single_value_compatibility = true
end
it " check what happens when trying retrieve attribute that does not exists " do
assert_nil response_multiple_attr_values . attributes [ :attribute_not_exists ]
assert_nil response_multiple_attr_values . attributes . single ( :attribute_not_exists )
assert_nil response_multiple_attr_values . attributes . multi ( :attribute_not_exists )
OneLogin :: RubySaml :: Attributes . single_value_compatibility = false
assert_nil response_multiple_attr_values . attributes [ :attribute_not_exists ]
assert_nil response_multiple_attr_values . attributes . single ( :attribute_not_exists )
assert_nil response_multiple_attr_values . attributes . multi ( :attribute_not_exists )
OneLogin :: RubySaml :: Attributes . single_value_compatibility = true
end
end
end
describe " # session_expires_at " do
it " extract the value of the SessionNotOnOrAfter attribute " do
assert response . session_expires_at . is_a? ( Time )
end
it " return nil when the value of the SessionNotOnOrAfter is not set " do
assert_nil response_without_attributes . session_expires_at
end
end
describe " # success " do
it " find a status code that says success " do
response . success?
end
end
describe '#xpath_first_from_signed_assertion' do
it 'not allow arbitrary code execution' do
2023-02-13 15:21:45 +00:00
$evalled = nil
2022-05-14 03:53:04 +00:00
malicious_response_document = fixture ( 'response_eval' , false )
malicious_response = OneLogin :: RubySaml :: Response . new ( malicious_response_document )
malicious_response . send ( :xpath_first_from_signed_assertion )
assert_nil $evalled
end
end
describe '#sign_document' do
it 'Sign an unsigned SAML Response XML and initiate the SAML object with it' do
xml = Base64 . decode64 ( fixture ( " test_sign.xml " ) )
document = XMLSecurity :: Document . new ( xml )
formatted_cert = OneLogin :: RubySaml :: Utils . format_cert ( ruby_saml_cert_text )
cert = OpenSSL :: X509 :: Certificate . new ( formatted_cert )
formatted_private_key = OneLogin :: RubySaml :: Utils . format_private_key ( ruby_saml_key_text )
private_key = OpenSSL :: PKey :: RSA . new ( formatted_private_key )
document . sign_document ( private_key , cert )
signed_response = OneLogin :: RubySaml :: Response . new ( document . to_s )
settings . assertion_consumer_service_url = " http://recipient "
settings . idp_cert = ruby_saml_cert_text
signed_response . settings = settings
Timecop . freeze ( Time . parse ( " 2015-03-18T04:50:24Z " ) ) do
assert signed_response . is_valid?
end
assert_empty signed_response . errors
end
end
describe '#want_assertion_signed' do
before do
settings . security [ :want_assertions_signed ] = true
@signed_assertion = OneLogin :: RubySaml :: Response . new ( response_document_with_signed_assertion , :settings = > settings )
@no_signed_assertion = OneLogin :: RubySaml :: Response . new ( response_document_valid_signed , :settings = > settings )
end
it 'returns false if :want_assertion_signed enabled and Assertion not signed' do
assert ! @no_signed_assertion . send ( :validate_signed_elements )
assert_includes @no_signed_assertion . errors , " The Assertion of the Response is not signed and the SP requires it "
end
it 'returns true if :want_assertion_signed enabled and Assertion is signed' do
assert @signed_assertion . send ( :validate_signed_elements )
assert_empty @signed_assertion . errors
end
end
describe " retrieve nameID " do
it 'is possible when nameID inside the assertion' do
response_valid_signed . settings = settings
assert_equal " test@onelogin.com " , response_valid_signed . nameid
end
it 'is not possible when encryptID inside the assertion but no private key' do
response_encrypted_nameid . settings = settings
assert_raises ( OneLogin :: RubySaml :: ValidationError , " An EncryptedID found and no SP private key found on the settings to decrypt it " ) do
assert_equal " test@onelogin.com " , response_encrypted_nameid . nameid
end
assert_raises ( OneLogin :: RubySaml :: ValidationError , " An EncryptedID found and no SP private key found on the settings to decrypt it " ) do
assert_equal " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress " , response_encrypted_nameid . name_id_format
end
end
it 'is possible when encryptID inside the assertion and settings has the private key' do
settings . private_key = ruby_saml_key_text
response_encrypted_nameid . settings = settings
assert_equal " test@onelogin.com " , response_encrypted_nameid . nameid
assert_equal " urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress " , response_encrypted_nameid . name_id_format
end
end
describe 'try to initialize an encrypted response' do
it 'raise if an encrypted assertion is found and no sp private key to decrypt it' do
error_msg = " 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 "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
2023-02-13 15:21:45 +00:00
OneLogin :: RubySaml :: Response . new ( signed_message_encrypted_unsigned_assertion )
2022-05-14 03:53:04 +00:00
end
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
2023-02-13 15:21:45 +00:00
OneLogin :: RubySaml :: Response . new ( signed_message_encrypted_unsigned_assertion , :settings = > settings )
2022-05-14 03:53:04 +00:00
end
settings . certificate = ruby_saml_cert_text
settings . private_key = ruby_saml_key_text
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
response3 = OneLogin :: RubySaml :: Response . new ( signed_message_encrypted_unsigned_assertion )
response3 . settings
end
end
it 'raise if an encrypted assertion is found and the sp private key is wrong' do
settings . certificate = ruby_saml_cert_text
wrong_private_key = ruby_saml_key_text . sub! ( 'A' , 'B' )
settings . private_key = wrong_private_key
error_msg = " Neither PUB key nor PRIV key: nested asn1 error "
assert_raises ( OpenSSL :: PKey :: RSAError , error_msg ) do
2023-02-13 15:21:45 +00:00
OneLogin :: RubySaml :: Response . new ( signed_message_encrypted_unsigned_assertion , :settings = > settings )
2022-05-14 03:53:04 +00:00
end
end
it 'return true if an encrypted assertion is found and settings initialized with private_key' do
settings . certificate = ruby_saml_cert_text
settings . private_key = ruby_saml_key_text
response = OneLogin :: RubySaml :: Response . new ( signed_message_encrypted_unsigned_assertion , :settings = > settings )
assert response . decrypted_document
response2 = OneLogin :: RubySaml :: Response . new ( signed_message_encrypted_signed_assertion , :settings = > settings )
assert response2 . decrypted_document
response3 = OneLogin :: RubySaml :: Response . new ( unsigned_message_encrypted_signed_assertion , :settings = > settings )
assert response3 . decrypted_document
response4 = OneLogin :: RubySaml :: Response . new ( unsigned_message_encrypted_unsigned_assertion , :settings = > settings )
assert response4 . decrypted_document
assert OneLogin :: RubySaml :: Response . new (
Base64 . encode64 ( File . read ( 'test/responses/unsigned_encrypted_adfs.xml' ) ) ,
:settings = > settings
) . decrypted_document
end
end
describe " retrieve nameID and attributes from encrypted assertion " do
before do
settings . idp_cert_fingerprint = 'EE:17:4E:FB:A8:81:71:12:0D:2A:78:43:BC:E7:0C:07:58:79:F4:F4'
settings . sp_entity_id = 'http://rubysaml.com:3000/saml/metadata'
settings . assertion_consumer_service_url = 'http://rubysaml.com:3000/saml/acs'
settings . certificate = ruby_saml_cert_text
settings . private_key = ruby_saml_key_text
end
it 'is possible when signed_message_encrypted_unsigned_assertion' do
response = OneLogin :: RubySaml :: Response . new ( signed_message_encrypted_unsigned_assertion , :settings = > settings )
Timecop . freeze ( Time . parse ( " 2015-03-19T14:30:31Z " ) ) do
assert response . is_valid?
assert_empty response . errors
assert_equal " test " , response . attributes [ :uid ]
assert_equal " 98e2bb61075e951b37d6b3be6954a54b340d86c7 " , response . nameid
end
end
it 'is possible when signed_message_encrypted_signed_assertion' do
response = OneLogin :: RubySaml :: Response . new ( signed_message_encrypted_signed_assertion , :settings = > settings )
Timecop . freeze ( Time . parse ( " 2015-03-19T14:30:31Z " ) ) do
assert response . is_valid?
assert_empty response . errors
assert_equal " test " , response . attributes [ :uid ]
assert_equal " 98e2bb61075e951b37d6b3be6954a54b340d86c7 " , response . nameid
end
end
it 'is possible when unsigned_message_encrypted_signed_assertion' do
response = OneLogin :: RubySaml :: Response . new ( unsigned_message_encrypted_signed_assertion , :settings = > settings )
Timecop . freeze ( Time . parse ( " 2015-03-19T14:30:31Z " ) ) do
assert response . is_valid?
assert_empty response . errors
assert_equal " test " , response . attributes [ :uid ]
assert_equal " 98e2bb61075e951b37d6b3be6954a54b340d86c7 " , response . nameid
end
end
it 'is not possible when unsigned_message_encrypted_unsigned_assertion' do
response = OneLogin :: RubySaml :: Response . new ( unsigned_message_encrypted_unsigned_assertion , :settings = > settings )
Timecop . freeze ( Time . parse ( " 2015-03-19T14:30:31Z " ) ) do
assert ! response . is_valid?
assert_includes response . errors , " Found an unexpected number of Signature Element. SAML Response rejected "
end
end
end
describe " # decrypt_assertion " do
before do
settings . private_key = ruby_saml_key_text
end
describe " check right settings " do
it " is not possible to decrypt the assertion if no private key " do
response = OneLogin :: RubySaml :: Response . new ( signed_message_encrypted_unsigned_assertion , :settings = > settings )
encrypted_assertion_node = REXML :: XPath . first (
response . document ,
" (/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/) " ,
{ " p " = > " urn:oasis:names:tc:SAML:2.0:protocol " , " a " = > " urn:oasis:names:tc:SAML:2.0:assertion " }
)
response . settings . private_key = nil
error_msg = " An EncryptedAssertion found and no SP private key found on the settings to decrypt it "
assert_raises ( OneLogin :: RubySaml :: ValidationError , error_msg ) do
2023-02-13 15:21:45 +00:00
response . send ( :decrypt_assertion , encrypted_assertion_node )
2022-05-14 03:53:04 +00:00
end
end
it " is possible to decrypt the assertion if private key " do
response = OneLogin :: RubySaml :: Response . new ( signed_message_encrypted_unsigned_assertion , :settings = > settings )
encrypted_assertion_node = REXML :: XPath . first (
response . document ,
" (/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/) " ,
{ " p " = > " urn:oasis:names:tc:SAML:2.0:protocol " , " a " = > " urn:oasis:names:tc:SAML:2.0:assertion " }
)
decrypted = response . send ( :decrypt_assertion , encrypted_assertion_node )
encrypted_assertion_node2 = REXML :: XPath . first (
decrypted ,
" (/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/) " ,
{ " p " = > " urn:oasis:names:tc:SAML:2.0:protocol " , " a " = > " urn:oasis:names:tc:SAML:2.0:assertion " }
)
assert_nil encrypted_assertion_node2
assert decrypted . name , " Assertion "
end
it " is possible to decrypt the assertion if private key provided and EncryptedKey RetrievalMethod presents in response " do
settings . private_key = ruby_saml_key_text
resp = read_response ( 'response_with_retrieval_method.xml' )
response = OneLogin :: RubySaml :: Response . new ( resp , :settings = > settings )
encrypted_assertion_node = REXML :: XPath . first (
response . document ,
" (/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/) " ,
{ " p " = > " urn:oasis:names:tc:SAML:2.0:protocol " , " a " = > " urn:oasis:names:tc:SAML:2.0:assertion " }
)
decrypted = response . send ( :decrypt_assertion , encrypted_assertion_node )
encrypted_assertion_node2 = REXML :: XPath . first (
decrypted ,
" (/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/) " ,
{ " p " = > " urn:oasis:names:tc:SAML:2.0:protocol " , " a " = > " urn:oasis:names:tc:SAML:2.0:assertion " }
)
assert_nil encrypted_assertion_node2
assert decrypted . name , " Assertion "
end
it " is possible to decrypt the assertion if private key but no saml namespace on the Assertion Element that is inside the EncryptedAssertion " do
unsigned_message_encrypted_assertion_without_saml_namespace = read_response ( 'unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64' )
response = OneLogin :: RubySaml :: Response . new ( unsigned_message_encrypted_assertion_without_saml_namespace , :settings = > settings )
encrypted_assertion_node = REXML :: XPath . first (
response . document ,
" (/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/) " ,
{ " p " = > " urn:oasis:names:tc:SAML:2.0:protocol " , " a " = > " urn:oasis:names:tc:SAML:2.0:assertion " }
)
decrypted = response . send ( :decrypt_assertion , encrypted_assertion_node )
encrypted_assertion_node2 = REXML :: XPath . first (
decrypted ,
" (/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/) " ,
{ " p " = > " urn:oasis:names:tc:SAML:2.0:protocol " , " a " = > " urn:oasis:names:tc:SAML:2.0:assertion " }
)
assert_nil encrypted_assertion_node2
assert decrypted . name , " Assertion "
end
end
describe " check different encrypt methods supported " do
it " EncryptionMethod DES-192 && Key Encryption Algorithm RSA-1_5 " do
unsigned_message_des192_encrypted_signed_assertion = read_response ( 'unsigned_message_des192_encrypted_signed_assertion.xml.base64' )
response = OneLogin :: RubySaml :: Response . new ( unsigned_message_des192_encrypted_signed_assertion , :settings = > settings )
assert_equal " test " , response . attributes [ :uid ]
assert_equal " _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 " , response . nameid
end
it " EncryptionMethod AES-128 && Key Encryption Algorithm RSA-OAEP-MGF1P " do
unsigned_message_aes128_encrypted_signed_assertion = read_response ( 'unsigned_message_aes128_encrypted_signed_assertion.xml.base64' )
response = OneLogin :: RubySaml :: Response . new ( unsigned_message_aes128_encrypted_signed_assertion , :settings = > settings )
assert_equal " test " , response . attributes [ :uid ]
assert_equal " _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 " , response . nameid
end
it " EncryptionMethod AES-192 && Key Encryption Algorithm RSA-OAEP-MGF1P " do
unsigned_message_aes192_encrypted_signed_assertion = read_response ( 'unsigned_message_aes192_encrypted_signed_assertion.xml.base64' )
response = OneLogin :: RubySaml :: Response . new ( unsigned_message_aes192_encrypted_signed_assertion , :settings = > settings )
assert_equal " test " , response . attributes [ :uid ]
assert_equal " _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 " , response . nameid
end
it " EncryptionMethod AES-256 && Key Encryption Algorithm RSA-OAEP-MGF1P " do
unsigned_message_aes256_encrypted_signed_assertion = read_response ( 'unsigned_message_aes256_encrypted_signed_assertion.xml.base64' )
response = OneLogin :: RubySaml :: Response . new ( unsigned_message_aes256_encrypted_signed_assertion , :settings = > settings )
assert_equal " test " , response . attributes [ :uid ]
assert_equal " _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 " , response . nameid
end
it " EncryptionMethod AES-128-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P " do
return unless OpenSSL :: Cipher . ciphers . include? 'AES-128-GCM'
unsigned_message_aes128gcm_encrypted_signed_assertion = read_response ( 'unsigned_message_aes128gcm_encrypted_signed_assertion.xml.base64' )
response = OneLogin :: RubySaml :: Response . new ( unsigned_message_aes128gcm_encrypted_signed_assertion , :settings = > settings )
assert_equal " test " , response . attributes [ :uid ]
assert_equal " _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 " , response . nameid
end
it " EncryptionMethod AES-192-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P " do
return unless OpenSSL :: Cipher . ciphers . include? 'AES-192-GCM'
unsigned_message_aes192gcm_encrypted_signed_assertion = read_response ( 'unsigned_message_aes192gcm_encrypted_signed_assertion.xml.base64' )
response = OneLogin :: RubySaml :: Response . new ( unsigned_message_aes192gcm_encrypted_signed_assertion , :settings = > settings )
assert_equal " test " , response . attributes [ :uid ]
assert_equal " _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 " , response . nameid
end
it " EncryptionMethod AES-256-GCM && Key Encryption Algorithm RSA-OAEP-MGF1P " do
return unless OpenSSL :: Cipher . ciphers . include? 'AES-256-GCM'
unsigned_message_aes256gcm_encrypted_signed_assertion = read_response ( 'unsigned_message_aes256gcm_encrypted_signed_assertion.xml.base64' )
response = OneLogin :: RubySaml :: Response . new ( unsigned_message_aes256gcm_encrypted_signed_assertion , :settings = > settings )
assert_equal " test " , response . attributes [ :uid ]
assert_equal " _ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7 " , response . nameid
end
end
end
describe " # status_code " do
it 'urn:oasis:names:tc:SAML:2.0:status:Responder' do
assert_equal response_statuscode_responder . status_code , 'urn:oasis:names:tc:SAML:2.0:status:Responder'
end
it 'urn:oasis:names:tc:SAML:2.0:status:Requester and urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding' do
assert_equal response_double_statuscode . status_code , 'urn:oasis:names:tc:SAML:2.0:status:Requester | urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding'
end
end
describe " test qualified name id in attributes " do
it " parsed the nameid " do
response = OneLogin :: RubySaml :: Response . new ( read_response ( " signed_nameid_in_atts.xml " ) , :settings = > settings )
response . settings . idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783'
assert_empty response . errors
assert_equal " test " , response . attributes [ :uid ]
assert_equal " http://idp.example.com/metadata.php/ZdrjpwEdw22vKoxWAbZB78/gQ7s= " , response . attributes . single ( 'urn:oid:1.3.6.1.4.1.5923.1.1.1.10' )
end
end
describe " test unqualified name id in attributes " do
it " parsed the nameid " do
response = OneLogin :: RubySaml :: Response . new ( read_response ( " signed_unqual_nameid_in_atts.xml " ) , :settings = > settings )
response . settings . idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783'
assert_empty response . errors
assert_equal " test " , response . attributes [ :uid ]
assert_equal " ZdrjpwEdw22vKoxWAbZB78/gQ7s= " , response . attributes . single ( 'urn:oid:1.3.6.1.4.1.5923.1.1.1.10' )
end
end
describe " signature wrapping attack with encrypted assertion " do
it " should not be valid " do
settings . private_key = ruby_saml_key_text
signature_wrapping_attack = read_invalid_response ( " encrypted_new_attack.xml.base64 " )
response_wrapped = OneLogin :: RubySaml :: Response . new ( signature_wrapping_attack , :settings = > settings )
response_wrapped . stubs ( :conditions ) . returns ( nil )
response_wrapped . stubs ( :validate_subject_confirmation ) . returns ( true )
settings . idp_cert_fingerprint = " 385b1eec71143f00db6af936e2ea12a28771d72c "
assert ! response_wrapped . is_valid?
assert_includes response_wrapped . errors , " Found an invalid Signed Element. SAML Response rejected "
end
end
describe " signature wrapping attack - concealed SAML response body " do
it " should not be valid " do
signature_wrapping_attack = read_invalid_response ( " response_with_concealed_signed_assertion.xml " )
response_wrapped = OneLogin :: RubySaml :: Response . new ( signature_wrapping_attack , :settings = > settings )
settings . idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d'
response_wrapped . stubs ( :conditions ) . returns ( nil )
response_wrapped . stubs ( :validate_subject_confirmation ) . returns ( true )
assert ! response_wrapped . is_valid?
assert_includes response_wrapped . errors , " SAML Response must contain 1 assertion "
end
end
describe " signature wrapping attack - doubled signed assertion SAML response " do
it " should not be valid " do
signature_wrapping_attack = read_invalid_response ( " response_with_doubled_signed_assertion.xml " )
response_wrapped = OneLogin :: RubySaml :: Response . new ( signature_wrapping_attack , :settings = > settings )
settings . idp_cert_fingerprint = '4b68c453c7d994aad9025c99d5efcf566287fe8d'
response_wrapped . stubs ( :conditions ) . returns ( nil )
response_wrapped . stubs ( :validate_subject_confirmation ) . returns ( true )
assert ! response_wrapped . is_valid?
assert_includes response_wrapped . errors , " SAML Response must contain 1 assertion "
end
end
end
end