|
| 1 | +require_relative 'test_helper' |
| 2 | + |
| 3 | +require 'ruby_saml/metadata' |
| 4 | + |
| 5 | +class OneloginAliasTest < Minitest::Test |
| 6 | + |
| 7 | + describe 'legacy OneLogin namespace alias' do |
| 8 | + |
| 9 | + describe 'equality with Object' do |
| 10 | + it "should be equal" do |
| 11 | + assert_equal OneLogin, Object |
| 12 | + assert_equal ::OneLogin, Object |
| 13 | + assert_equal OneLogin::RubySaml, OneLogin::RubySaml |
| 14 | + assert_equal ::OneLogin::RubySaml, ::RubySaml |
| 15 | + end |
| 16 | + end |
| 17 | + |
| 18 | + describe 'Metadata' do |
| 19 | + let(:settings) { OneLogin::RubySaml::Settings.new } |
| 20 | + let(:xml_text) { OneLogin::RubySaml::Metadata.new.generate(settings, false) } |
| 21 | + let(:xml_doc) { REXML::Document.new(xml_text) } |
| 22 | + let(:spsso_descriptor) { REXML::XPath.first(xml_doc, "//md:SPSSODescriptor") } |
| 23 | + let(:acs) { REXML::XPath.first(xml_doc, "//md:AssertionConsumerService") } |
| 24 | + |
| 25 | + before do |
| 26 | + settings.sp_entity_id = "https://example.com" |
| 27 | + settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" |
| 28 | + settings.assertion_consumer_service_url = "https://foo.example/saml/consume" |
| 29 | + end |
| 30 | + |
| 31 | + it "generates Pretty Print Service Provider Metadata" do |
| 32 | + xml_text = OneLogin::RubySaml::Metadata.new.generate(settings, true) |
| 33 | + # assert correct xml declaration |
| 34 | + start = "<?xml version='1.0' encoding='UTF-8'?>\n<md:EntityDescriptor" |
| 35 | + assert_equal xml_text[0..start.length-1],start |
| 36 | + assert_equal "https://example.com", REXML::XPath.first(xml_doc, "//md:EntityDescriptor").attribute("entityID").value |
| 37 | + assert_equal "urn:oasis:names:tc:SAML:2.0:protocol", spsso_descriptor.attribute("protocolSupportEnumeration").value |
| 38 | + assert_equal "false", spsso_descriptor.attribute("AuthnRequestsSigned").value |
| 39 | + assert_equal "false", spsso_descriptor.attribute("WantAssertionsSigned").value |
| 40 | + assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", REXML::XPath.first(xml_doc, "//md:NameIDFormat").text.strip |
| 41 | + assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", acs.attribute("Binding").value |
| 42 | + assert_equal "https://foo.example/saml/consume", acs.attribute("Location").value |
| 43 | + assert validate_xml!(xml_text, "saml-schema-metadata-2.0.xsd") |
| 44 | + end |
| 45 | + end |
| 46 | + |
| 47 | + describe 'Attributes' do |
| 48 | + let(:attributes) do |
| 49 | + OneLogin::RubySaml::Attributes.new({ |
| 50 | + 'email' => ['tom@hanks.com'], |
| 51 | + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname' => ['Tom'], |
| 52 | + 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname' => ['Hanks'] |
| 53 | + }) |
| 54 | + end |
| 55 | + |
| 56 | + it 'fetches attributes' do |
| 57 | + assert_equal('tom@hanks.com', attributes.fetch('email')) |
| 58 | + assert_equal('tom@hanks.com', attributes.fetch(:email)) |
| 59 | + assert_equal('Tom', attributes.fetch(/givenname/)) |
| 60 | + assert_equal('Tom', attributes.fetch(/gi(.*)/)) |
| 61 | + assert_nil(attributes.fetch(/^z.*/)) |
| 62 | + assert_equal('Hanks', attributes.fetch(/surname/)) |
| 63 | + end |
| 64 | + end |
| 65 | + |
| 66 | + describe "Response" do |
| 67 | + let(:settings) { OneLogin::RubySaml::Settings.new } |
| 68 | + let(:response) { OneLogin::RubySaml::Response.new(response_document_without_recipient) } |
| 69 | + let(:response_without_attributes) { OneLogin::RubySaml::Response.new(response_document_without_attributes) } |
| 70 | + let(:response_with_multiple_attribute_statements) { OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_statements)) } |
| 71 | + let(:response_without_reference_uri) { OneLogin::RubySaml::Response.new(response_document_without_reference_uri) } |
| 72 | + let(:response_with_signed_assertion) { OneLogin::RubySaml::Response.new(response_document_with_signed_assertion) } |
| 73 | + let(:response_with_ds_namespace_at_the_root) { OneLogin::RubySaml::Response.new(response_document_with_ds_namespace_at_the_root)} |
| 74 | + let(:response_unsigned) { OneLogin::RubySaml::Response.new(response_document_unsigned) } |
| 75 | + let(:response_wrapped) { OneLogin::RubySaml::Response.new(response_document_wrapped) } |
| 76 | + let(:response_multiple_attr_values) { OneLogin::RubySaml::Response.new(fixture(:response_with_multiple_attribute_values)) } |
| 77 | + let(:response_valid_signed) { OneLogin::RubySaml::Response.new(response_document_valid_signed) } |
| 78 | + let(:response_valid_signed_without_recipient) { OneLogin::RubySaml::Response.new(response_document_valid_signed, {:skip_recipient_check => true })} |
| 79 | + let(:response_valid_signed_without_x509certificate) { OneLogin::RubySaml::Response.new(response_document_valid_signed_without_x509certificate) } |
| 80 | + let(:response_no_id) { OneLogin::RubySaml::Response.new(read_invalid_response("no_id.xml.base64")) } |
| 81 | + let(:response_no_version) { OneLogin::RubySaml::Response.new(read_invalid_response("no_saml2.xml.base64")) } |
| 82 | + let(:response_multi_assertion) { OneLogin::RubySaml::Response.new(read_invalid_response("multiple_assertions.xml.base64")) } |
| 83 | + let(:response_no_conditions) { OneLogin::RubySaml::Response.new(read_invalid_response("no_conditions.xml.base64")) } |
| 84 | + let(:response_no_conditions_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("no_conditions.xml.base64"), { :skip_conditions => true }) } |
| 85 | + let(:response_no_authnstatement) { OneLogin::RubySaml::Response.new(read_invalid_response("no_authnstatement.xml.base64")) } |
| 86 | + let(:response_no_authnstatement_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("no_authnstatement.xml.base64"), {:skip_authnstatement => true}) } |
| 87 | + let(:response_empty_destination) { OneLogin::RubySaml::Response.new(read_invalid_response("empty_destination.xml.base64")) } |
| 88 | + let(:response_empty_destination_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("empty_destination.xml.base64"), {:skip_destination => true}) } |
| 89 | + let(:response_no_status) { OneLogin::RubySaml::Response.new(read_invalid_response("no_status.xml.base64")) } |
| 90 | + let(:response_no_statuscode) { OneLogin::RubySaml::Response.new(read_invalid_response("no_status_code.xml.base64")) } |
| 91 | + let(:response_statuscode_responder) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responder.xml.base64")) } |
| 92 | + let(:response_statuscode_responder_and_msg) { OneLogin::RubySaml::Response.new(read_invalid_response("status_code_responer_and_msg.xml.base64")) } |
| 93 | + let(:response_double_statuscode) { OneLogin::RubySaml::Response.new(response_document_double_status_code) } |
| 94 | + let(:response_encrypted_attrs) { OneLogin::RubySaml::Response.new(response_document_encrypted_attrs) } |
| 95 | + let(:response_no_signed_elements) { OneLogin::RubySaml::Response.new(read_invalid_response("no_signature.xml.base64")) } |
| 96 | + let(:response_multiple_signed) { OneLogin::RubySaml::Response.new(read_invalid_response("multiple_signed.xml.base64")) } |
| 97 | + let(:response_audience_self_closed) { OneLogin::RubySaml::Response.new(read_response("response_audience_self_closed_tag.xml.base64")) } |
| 98 | + let(:response_invalid_audience) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_audience.xml.base64")) } |
| 99 | + let(:response_invalid_audience_with_skip) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_audience.xml.base64"), {:skip_audience => true}) } |
| 100 | + let(:response_invalid_signed_element) { OneLogin::RubySaml::Response.new(read_invalid_response("response_invalid_signed_element.xml.base64")) } |
| 101 | + let(:response_invalid_issuer_assertion) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_issuer_assertion.xml.base64")) } |
| 102 | + let(:response_invalid_issuer_message) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_issuer_message.xml.base64")) } |
| 103 | + let(:response_no_issuer_response) { OneLogin::RubySaml::Response.new(read_invalid_response("no_issuer_response.xml.base64")) } |
| 104 | + let(:response_no_issuer_assertion) { OneLogin::RubySaml::Response.new(read_invalid_response("no_issuer_assertion.xml.base64")) } |
| 105 | + let(:response_no_nameid) { OneLogin::RubySaml::Response.new(read_invalid_response("no_nameid.xml.base64")) } |
| 106 | + let(:response_empty_nameid) { OneLogin::RubySaml::Response.new(read_invalid_response("empty_nameid.xml.base64")) } |
| 107 | + let(:response_wrong_spnamequalifier) { OneLogin::RubySaml::Response.new(read_invalid_response("wrong_spnamequalifier.xml.base64")) } |
| 108 | + let(:response_duplicated_attributes) { OneLogin::RubySaml::Response.new(read_invalid_response("duplicated_attributes.xml.base64")) } |
| 109 | + let(:response_no_subjectconfirmation_data) { OneLogin::RubySaml::Response.new(read_invalid_response("no_subjectconfirmation_data.xml.base64")) } |
| 110 | + let(:response_no_subjectconfirmation_method) { OneLogin::RubySaml::Response.new(read_invalid_response("no_subjectconfirmation_method.xml.base64")) } |
| 111 | + let(:response_invalid_subjectconfirmation_inresponse) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_inresponse.xml.base64")) } |
| 112 | + let(:response_invalid_subjectconfirmation_recipient) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_recipient.xml.base64")) } |
| 113 | + let(:response_invalid_subjectconfirmation_nb) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_nb.xml.base64")) } |
| 114 | + let(:response_invalid_subjectconfirmation_noa) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_subjectconfirmation_noa.xml.base64")) } |
| 115 | + let(:response_invalid_signature_position) { OneLogin::RubySaml::Response.new(read_invalid_response("invalid_signature_position.xml.base64")) } |
| 116 | + let(:response_encrypted_nameid) { OneLogin::RubySaml::Response.new(response_document_encrypted_nameid) } |
| 117 | + |
| 118 | + def generate_audience_error(expected, actual) |
| 119 | + s = actual.count > 1 ? 's' : ''; |
| 120 | + return "Invalid Audience#{s}. The audience#{s} #{actual.join(',')}, did not match the expected audience #{expected}" |
| 121 | + end |
| 122 | + |
| 123 | + it "raise an exception when response is initialized with nil" do |
| 124 | + assert_raises(ArgumentError) { OneLogin::RubySaml::Response.new(nil) } |
| 125 | + end |
| 126 | + |
| 127 | + it "not filter available options only" do |
| 128 | + options = { :skip_destination => true, :foo => :bar } |
| 129 | + response = OneLogin::RubySaml::Response.new(response_document_valid_signed, options) |
| 130 | + assert_includes response.options.keys, :skip_destination |
| 131 | + assert_includes response.options.keys, :foo |
| 132 | + end |
| 133 | + |
| 134 | + it "be able to parse a document which contains ampersands" do |
| 135 | + XMLSecurity::SignedDocument.any_instance.stubs(:digests_match?).returns(true) |
| 136 | + OneLogin::RubySaml::Response.any_instance.stubs(:validate_conditions).returns(true) |
| 137 | + |
| 138 | + ampersands_response = OneLogin::RubySaml::Response.new(ampersands_document) |
| 139 | + ampersands_response.settings = settings |
| 140 | + ampersands_response.settings.idp_cert_fingerprint = 'c51985d947f1be57082025050846eb27f6cab783' |
| 141 | + |
| 142 | + assert !ampersands_response.is_valid? |
| 143 | + assert_includes ampersands_response.errors, "SAML Response must contain 1 assertion" |
| 144 | + end |
| 145 | + |
| 146 | + describe "Prevent node text with comment attack (VU#475445)" do |
| 147 | + before do |
| 148 | + @response = OneLogin::RubySaml::Response.new(read_response('response_node_text_attack.xml.base64')) |
| 149 | + end |
| 150 | + |
| 151 | + it "receives the full NameID when there is an injected comment" do |
| 152 | + assert_equal "support@onelogin.com", @response.name_id |
| 153 | + end |
| 154 | + |
| 155 | + it "receives the full AttributeValue when there is an injected comment" do |
| 156 | + assert_equal "smith", @response.attributes["surname"] |
| 157 | + end |
| 158 | + end |
| 159 | + |
| 160 | + describe "Another test to prevent with comment attack (VU#475445)" do |
| 161 | + before do |
| 162 | + @response = OneLogin::RubySaml::Response.new(read_response('response_node_text_attack2.xml.base64'), {:skip_recipient_check => true }) |
| 163 | + @response.settings = settings |
| 164 | + @response.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint |
| 165 | + end |
| 166 | + |
| 167 | + it "receives the full NameID when there is an injected comment, validates the response" do |
| 168 | + assert_equal "test@onelogin.com", @response.name_id |
| 169 | + end |
| 170 | + end |
| 171 | + |
| 172 | + describe "Another test with CDATA injected" do |
| 173 | + before do |
| 174 | + @response = OneLogin::RubySaml::Response.new(read_response('response_node_text_attack3.xml.base64'), {:skip_recipient_check => true }) |
| 175 | + @response.settings = settings |
| 176 | + @response.settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint |
| 177 | + end |
| 178 | + |
| 179 | + it "it normalizes CDATA but reject SAMLResponse due signature invalidation" do |
| 180 | + assert_equal "test@onelogin.com.evil.com", @response.name_id |
| 181 | + assert !@response.is_valid? |
| 182 | + assert_includes @response.errors, "Invalid Signature on SAML Response" |
| 183 | + end |
| 184 | + end |
| 185 | + |
| 186 | + describe "Prevent XEE attack" do |
| 187 | + before do |
| 188 | + @response = OneLogin::RubySaml::Response.new(fixture(:attackxee)) |
| 189 | + end |
| 190 | + |
| 191 | + it "false when evil attack vector is present, soft = true" do |
| 192 | + @response.soft = true |
| 193 | + assert !@response.send(:validate_structure) |
| 194 | + assert_includes @response.errors, "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd" |
| 195 | + end |
| 196 | + |
| 197 | + it "raise when evil attack vector is present, soft = false " do |
| 198 | + @response.soft = false |
| 199 | + |
| 200 | + assert_raises(OneLogin::RubySaml::ValidationError) do |
| 201 | + @response.send(:validate_structure) |
| 202 | + end |
| 203 | + end |
| 204 | + end |
| 205 | + end |
| 206 | + end |
| 207 | +end |
0 commit comments