From be56dcbfacb0e32cd24516573316c7aed31e6f66 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Thu, 16 Nov 2023 13:45:14 -0500 Subject: [PATCH] Resolves merge conflict in OmniAuth::Strategies::CAS::SamlTicketValidator. Adds #success_body loading in OmniAuth::Strategies::CAS::SamlTicketValidator --- .../strategies/cas/saml_ticket_validator.rb | 51 ++++---- spec/fixtures/berkeley_cas_success.xml | 78 ++++++++++++ .../cas/saml_ticket_validator_spec.rb | 113 ++++++++++++++++++ 3 files changed, 222 insertions(+), 20 deletions(-) create mode 100644 spec/fixtures/berkeley_cas_success.xml create mode 100644 spec/omniauth/strategies/cas/saml_ticket_validator_spec.rb diff --git a/lib/omniauth/strategies/cas/saml_ticket_validator.rb b/lib/omniauth/strategies/cas/saml_ticket_validator.rb index f6f867f..b1f5a55 100644 --- a/lib/omniauth/strategies/cas/saml_ticket_validator.rb +++ b/lib/omniauth/strategies/cas/saml_ticket_validator.rb @@ -8,6 +8,8 @@ class CAS class SamlTicketValidator VALIDATION_REQUEST_HEADERS = { 'Accept' => '*/*' } + attr_reader :success_body + # Build a validator from a +configuration+, a # +return_to+ URL, and a +ticket+. # @@ -23,6 +25,7 @@ def initialize(strategy, options, return_to_url, ticket) # Executes a network request to process the CAS Service Response def call @response_body = get_saml_response_body + @success_body = find_authentication_success(@response_body) self end @@ -40,7 +43,6 @@ def user_info doc.remove_namespaces! if success?(doc) attrs = extract_attributes(doc) -<<<<<<< HEAD attrs["nameIdentifier"] = extract_name_identifier(doc) { "user" => attrs["uid"] }.merge(attrs) else @@ -49,17 +51,29 @@ def user_info end rescue Nokogiri::XML::XPath::SyntaxError OmniAuth.logger.warn "Could not parse SAML response, will return nil user_info:\n#{@response_body}" -======= - { "user" => attrs["uid"] }.merge(attrs) - end - rescue Nokogiri::XML::XPath::SyntaxError ->>>>>>> f87f036 (create SamlTicketValidator for validating tickets against samlValidate and asserting attributes) nil end end private + # finds an `` node in + # a `` body if present; returns nil + # if the passed body is nil or if there is no such node. + def find_authentication_success(body) + return nil if body.nil? || body == '' + begin + doc = Nokogiri::XML(body) + begin + doc.xpath('/Envelope/Body/Response/Status/StatusCode') + rescue Nokogiri::XML::XPath::SyntaxError + nil + end + rescue Nokogiri::XML::XPath::SyntaxError + nil + end + end + def success?(doc) doc.css("StatusCode").attr("Value").text == "saml1p:Success" end @@ -71,26 +85,23 @@ def extract_attributes(doc) end end -<<<<<<< HEAD def extract_name_identifier(doc) doc.css("AuthenticationStatement Subject NameIdentifier").text end -======= ->>>>>>> f87f036 (create SamlTicketValidator for validating tickets against samlValidate and asserting attributes) def saml_payload <<-SAML - - - - - - #{@ticket} - - - - + + + + + + #{@ticket} + + + + SAML end diff --git a/spec/fixtures/berkeley_cas_success.xml b/spec/fixtures/berkeley_cas_success.xml new file mode 100644 index 0000000..42d8138 --- /dev/null +++ b/spec/fixtures/berkeley_cas_success.xml @@ -0,0 +1,78 @@ + + + + + + + + + + https://clc.example.com/auth/cas/callback?url=https%3A%2F%2Fclc.example.com%2F + + + + + 1044957 + + urn:oasis:names:tc:SAML:1.0:cm:artifact + + + + + + 1044957 + + urn:oasis:names:tc:SAML:1.0:cm:artifact + + + + UsernamePasswordCredential + + + 192.168.0.5 + + + urn:oasis:names:tc:SAML:1.0:am:password + + + true + + + 2023-11-17T21:56:19.066445Z + + + Static Credentials + + + Static Credentials + + + 192.168.0.45 + + + Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 + + + false + + + + + + diff --git a/spec/omniauth/strategies/cas/saml_ticket_validator_spec.rb b/spec/omniauth/strategies/cas/saml_ticket_validator_spec.rb new file mode 100644 index 0000000..4ff5919 --- /dev/null +++ b/spec/omniauth/strategies/cas/saml_ticket_validator_spec.rb @@ -0,0 +1,113 @@ +require 'spec_helper' + +describe OmniAuth::Strategies::CAS::SamlTicketValidator do + let(:strategy) do + double('strategy', + service_validate_url: 'https://example.org/serviceValidate' + ) + end + let(:provider_options) do + double('provider_options', + disable_ssl_verification?: false, + merge_multivalued_attributes: false, + ca_path: '/etc/ssl/certsZOMG' + ) + end + let(:validator) do + OmniAuth::Strategies::CAS::SamlTicketValidator.new( strategy, provider_options, '/foo', nil ) + end + + describe '#call' do + before do + stub_request(:post, 'https://example.org/serviceValidate?') + .to_return(status: 200, body: '') + end + + subject { validator.call } + + it 'returns itself' do + expect(subject).to eq validator + end + + it 'uses the configured CA path' do + subject + expect(provider_options).to have_received :ca_path + end + end + + describe 'called instances' do + let(:ok_fixture) do + File.expand_path(File.join(File.dirname(__FILE__), '../../../fixtures/berkeley_cas_success.xml')) + end + let(:service_response) { File.read(ok_fixture) } + + describe '#success_body' do + before do + stub_request(:post, 'https://example.org/serviceValidate?') + .to_return(status: 200, body: service_response) + validator.call + end + + subject { validator.success_body } + + it 'provides status code' do + expect(subject).to be_an_instance_of Nokogiri::XML::NodeSet + expect(subject.first).to be_an_instance_of Nokogiri::XML::Element + expect(subject.first['Value']).to eq 'saml1p:Success' + end + end + + describe '#user_info' do + before do + stub_request(:post, 'https://example.org/serviceValidate?') + .to_return(status: 200, body: service_response) + validator.call + end + + subject { validator.user_info } + + context 'with default settings' do + it 'parses user info from the response' do + expect(subject).to include 'authenticationDate' => '2023-11-17T21:56:19.066445Z' + expect(subject).to include 'authenticationMethod' => 'Static Credentials' + expect(subject).to include 'clientIpAddress' => '192.168.0.5' + expect(subject).to include 'credentialType' => 'UsernamePasswordCredential' + expect(subject).to include 'isFromNewLogin' => 'true' + expect(subject).to include 'longTermAuthenticationRequestTokenUsed' => 'false' + expect(subject).to include 'nameIdentifier' => '1044957' + expect(subject).to include 'samlAuthenticationStatementAuthMethod' => 'urn:oasis:names:tc:SAML:1.0:am:password' + expect(subject).to include 'serverIpAddress' => '192.168.0.45' + expect(subject).to include 'successfulAuthenticationHandlers' => 'Static Credentials' + expect(subject).to include 'user' => nil + expect(subject).to include 'userAgent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' + end + end + + context 'when merging multivalued attributes' do + let(:provider_options) do + double('provider_options', + disable_ssl_verification?: false, + merge_multivalued_attributes: true, + ca_path: '/etc/ssl/certsZOMG' + ) + end + + it 'parses multivalued user info from the response' do + expect(subject).to include 'authenticationDate' => '2023-11-17T21:56:19.066445Z' + expect(subject).to include 'authenticationMethod' => 'Static Credentials' + expect(subject).to include 'clientIpAddress' => '192.168.0.5' + expect(subject).to include 'credentialType' => 'UsernamePasswordCredential' + expect(subject).to include 'isFromNewLogin' => 'true' + expect(subject).to include 'longTermAuthenticationRequestTokenUsed' => 'false' + expect(subject).to include 'nameIdentifier' => '1044957' + expect(subject).to include 'samlAuthenticationStatementAuthMethod' => 'urn:oasis:names:tc:SAML:1.0:am:password' + expect(subject).to include 'serverIpAddress' => '192.168.0.45' + expect(subject).to include 'successfulAuthenticationHandlers' => 'Static Credentials' + expect(subject).to include 'user' => nil + expect(subject).to include 'userAgent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36' + end + end + end + end + +end