require 'spec_helper' describe OmniAuth::Strategies::CAS, type: :strategy do include Rack::Test::Methods let(:my_cas_provider) { Class.new(OmniAuth::Strategies::CAS) } before do stub_const 'MyCasProvider', my_cas_provider end let(:app) do Rack::Builder.new { use OmniAuth::Test::PhonySession use MyCasProvider, name: :cas, host: 'cas.example.org', ssl: false, port: 8080, uid_field: :employeeid, fetch_raw_info: Proc.new { |v, opts, ticket, info, node| info.empty? ? {} : { "roles" => node.xpath('//cas:roles').map(&:text), } } run lambda { |env| [404, {'Content-Type' => 'text/plain'}, [env.key?('omniauth.auth').to_s]] } }.to_app end # TODO: Verify that these are even useful tests shared_examples_for 'a CAS redirect response' do let(:redirect_params) { 'service=' + Rack::Utils.escape("http://example.org/auth/cas/callback?url=#{Rack::Utils.escape(return_url)}") } before { get url, nil, request_env } subject { last_response } it { should be_redirect } it 'redirects to the CAS server' do expect(subject.headers).to include 'Location' => "http://cas.example.org:8080/login?#{redirect_params}" end end describe '#cas_url' do let(:params) { Hash.new } let(:provider) { MyCasProvider.new(nil, params) } subject { provider.cas_url } it 'raises an ArgumentError' do expect{subject}.to raise_error ArgumentError, %r{:host and :login_url MUST be provided} end context 'with an explicit :url option' do let(:url) { 'https://example.org:8080/my_cas' } let(:params) { super().merge url:url } before { subject } it { should eq url } it 'parses the URL into it the appropriate strategy options' do expect(provider.options).to include ssl:true expect(provider.options).to include host:'example.org' expect(provider.options).to include port:8080 expect(provider.options).to include path:'/my_cas' end end context 'with explicit URL component' do let(:params) { super().merge host:'example.org', port:1234, ssl:true, path:'/a/path' } before { subject } it { should eq 'https://example.org:1234/a/path' } it 'parses the URL into it the appropriate strategy options' do expect(provider.options).to include ssl:true expect(provider.options).to include host:'example.org' expect(provider.options).to include port:1234 expect(provider.options).to include path:'/a/path' end end end describe 'defaults' do subject { MyCasProvider.default_options.to_hash } it { should include('ssl' => true) } end describe 'GET /auth/cas' do let(:return_url) { 'http://myapp.com/admin/foo' } context 'with a referer' do let(:url) { '/auth/cas' } let(:request_env) { { 'HTTP_REFERER' => return_url } } it_behaves_like 'a CAS redirect response' end context 'with an explicit return URL' do let(:url) { "/auth/cas?url=#{return_url}" } let(:request_env) { {} } it_behaves_like 'a CAS redirect response' end end describe 'GET /auth/cas/callback' do context 'without a ticket' do before { get '/auth/cas/callback' } subject { last_response } it { should be_redirect } it 'redirects with a failure message' do expect(subject.headers).to include 'Location' => '/auth/failure?message=no_ticket&strategy=cas' end end context 'with an invalid ticket' do before do stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=9391d/). to_return( body: File.read('spec/fixtures/cas_failure.xml') ) get '/auth/cas/callback?ticket=9391d' end subject { last_response } it { should be_redirect } it 'redirects with a failure message' do expect(subject.headers).to include 'Location' => '/auth/failure?message=invalid_ticket&strategy=cas' end end describe 'with a valid ticket' do shared_examples :successful_validation do before do stub_request(:get, /^http:\/\/cas.example.org:8080?\/serviceValidate\?([^&]+&)?ticket=593af/) .with { |request| @request_uri = request.uri.to_s } .to_return( body: File.read("spec/fixtures/#{xml_file_name}") ) get "/auth/cas/callback?ticket=593af&url=#{return_url}" end it 'strips the ticket parameter from the callback URL' do expect(@request_uri.scan('ticket=').size).to eq 1 end it 'properly encodes the service URL' do expect(WebMock).to have_requested(:get, 'http://cas.example.org:8080/serviceValidate') .with(query: { ticket: '593af', service: 'http://example.org/auth/cas/callback?url=' + Rack::Utils.escape('http://127.0.0.10/?some=parameter') }) end context "request.env['omniauth.auth']" do subject { last_request.env['omniauth.auth'] } it { should be_kind_of Hash } it 'identifes the provider' do expect(subject.provider).to eq :cas end it 'returns the UID of the user' do expect(subject.uid).to eq '54' end context 'the info hash' do subject { last_request.env['omniauth.auth']['info'] } it 'includes user info attributes' do expect(subject.name).to eq 'Peter Segel' expect(subject.first_name).to eq 'Peter' expect(subject.last_name).to eq 'Segel' expect(subject.nickname).to eq 'psegel' expect(subject.email).to eq 'psegel@intridea.com' expect(subject.location).to eq 'Washington, D.C.' expect(subject.image).to eq '/images/user.jpg' expect(subject.phone).to eq '555-555-5555' end end context 'the extra hash' do subject { last_request.env['omniauth.auth']['extra'] } it 'includes additional user attributes' do expect(subject.user).to eq 'psegel' expect(subject.employeeid).to eq '54' expect(subject.hire_date).to eq '2004-07-13' expect(subject.roles).to eq %w(senator lobbyist financier) end end context 'the credentials hash' do subject { last_request.env['omniauth.auth']['credentials'] } it 'has a ticket value' do expect(subject.ticket).to eq '593af' end end end it 'calls through to the master app' do expect(last_response.body).to eq 'true' end end let(:return_url) { 'http://127.0.0.10/?some=parameter' } context 'with JASIG flavored XML' do let(:xml_file_name) { 'cas_success_jasig.xml' } it_behaves_like :successful_validation end context 'with classic XML' do let(:xml_file_name) { 'cas_success.xml' } it_behaves_like :successful_validation end end end describe 'POST /auth/cas/callback' do describe 'with a Single Sign-Out logoutRequest' do let(:logoutRequest) do %Q[ @NOT_USED@ ST-123456-123abc456def ] end let(:logout_request) { double('logout_request', call:[200,{},'OK']) } subject do post 'auth/cas/callback', logoutRequest:logoutRequest end before do allow_any_instance_of(MyCasProvider) .to receive(:logout_request_service) .and_return double('LogoutRequest', new:logout_request) subject end it 'initializes a LogoutRequest' do expect(logout_request).to have_received :call end end end end