spec/omniauth/strategies/cas_spec.rb in omniauth-cas-1.1.0.beta.1 vs spec/omniauth/strategies/cas_spec.rb in omniauth-cas-1.1.0.pre.rc.1

- old
+ new

@@ -1,15 +1,18 @@ require 'spec_helper' describe OmniAuth::Strategies::CAS, type: :strategy do include Rack::Test::Methods - class MyCasProvider < OmniAuth::Strategies::CAS; end # TODO: Not really needed. just an alias but it requires the :name option which might confuse users... - def app + 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_key: :employeeid + use MyCasProvider, name: :cas, host: 'cas.example.org', ssl: false, port: 8080, uid_field: :employeeid 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 @@ -20,17 +23,60 @@ subject { last_response } it { should be_redirect } - it 'should redirect to the CAS server' do - subject.headers['Location'].should == 'http://cas.example.org:8080/login?' + redirect_params + 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' } @@ -50,104 +96,155 @@ it_behaves_like 'a CAS redirect response' end end - describe 'GET /auth/cas/callback without a ticket' do - before { get '/auth/cas/callback' } + describe 'GET /auth/cas/callback' do + context 'without a ticket' do + before { get '/auth/cas/callback' } - subject { last_response } + subject { last_response } - it { should be_redirect } + it { should be_redirect } - it 'should have a failure message' do - subject.headers['Location'].should == '/auth/failure?message=no_ticket&strategy=cas' + it 'redirects with a failure message' do + expect(subject.headers).to include 'Location' => '/auth/failure?message=no_ticket&strategy=cas' + end end - end - describe 'GET /auth/cas/callback 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 + 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 } + subject { last_response } - it { should be_redirect } + it { should be_redirect } - it 'should have a failure message' do - subject.headers['Location'].should == '/auth/failure?message=invalid_ticket&strategy=cas' + it 'redirects with a failure message' do + expect(subject.headers).to include 'Location' => '/auth/failure?message=invalid_ticket&strategy=cas' + end end - end - describe 'GET /auth/cas/callback with a valid ticket' do - let(:return_url) { 'http://127.0.0.10/?some=parameter' } + 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}") ) - 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/cas_success.xml') ) + get "/auth/cas/callback?ticket=593af&url=#{return_url}" + end - 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 'should strip the ticket parameter from the callback URL' do - @request_uri.scan('ticket=').length.should == 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 - it 'should properly encode the service URL' do - WebMock.should 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'] } - context "request.env['omniauth.auth']" do - subject { last_request.env['omniauth.auth'] } + it { should be_kind_of Hash } - it { should be_kind_of Hash } + it 'identifes the provider' do + expect(subject.provider).to eq :cas + end - its(:provider) { should == :cas } + it 'returns the UID of the user' do + expect(subject.uid).to eq '54' + end - its(:uid) { should == '54'} + context 'the info hash' do + subject { last_request.env['omniauth.auth']['info'] } - 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 - it { should have(6).items } + context 'the extra hash' do + subject { last_request.env['omniauth.auth']['extra'] } - its(:name) { should == 'Peter Segel' } - its(:first_name) { should == 'Peter' } - its(:last_name) { should == 'Segel' } - its(:email) { should == 'psegel@intridea.com' } - its(:location) { should == 'Washington, D.C.' } - its(:image) { should == '/images/user.jpg' } - its(:phone) { should == '555-555-5555' } - end + 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' + end + end - context 'the extra hash' do - subject { last_request.env['omniauth.auth']['extra'] } + context 'the credentials hash' do + subject { last_request.env['omniauth.auth']['credentials'] } - it { should have(3).items } + it 'has a ticket value' do + expect(subject.ticket).to eq '593af' + end + end + end - its(:user) { should == 'psegel' } - its(:employeeid) { should == '54' } - its(:hire_date) { should == '2004-07-13' } + it 'calls through to the master app' do + expect(last_response.body).to eq 'true' + end end - context 'the credentials hash' do - subject { last_request.env['omniauth.auth']['credentials'] } + let(:return_url) { 'http://127.0.0.10/?some=parameter' } - it { should have(1).items } + context 'with JASIG flavored XML' do + let(:xml_file_name) { 'cas_success_jasig.xml' } - its(:ticket) { should == '593af' } + it_behaves_like :successful_validation end - end - it 'should call through to the master app' do - last_response.body.should == 'true' + 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[ + <samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion\" ID="123abc-1234-ab12-cd34-1234abcd" Version="2.0" IssueInstant="#{Time.now.to_s}"> + <saml:NameID>@NOT_USED@</saml:NameID> + <samlp:SessionIndex>ST-123456-123abc456def</samlp:SessionIndex> + </samlp:LogoutRequest> + ] + 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