spec/arachni/session_spec.rb in arachni-0.4.7 vs spec/arachni/session_spec.rb in arachni-1.0

- old
+ new

@@ -1,375 +1,403 @@ require 'spec_helper' describe Arachni::Session do before( :all ) do - @url = web_server_url_for( :session ) + @url = web_server_url_for( :session ) @opts = Arachni::Options.instance end + before(:each) do + @opts.url = @url + end after( :each ) do - Arachni::Options.reset - Arachni::HTTP.reset + @session.clean_up if @session + @opts.reset + Arachni::HTTP::Client.reset + Arachni::Data.session.clear end - def new_session - Arachni::Session.new + subject { @session = Arachni::Session.new } + let(:configured) do + subject.configure( + url: "#{@url}/login", + inputs: { + username: 'john', + password: 'doe' + } + ) + + @opts.session.check_url = @url + @opts.session.check_pattern = 'logged-in user' + + subject end - describe '#opts' do - describe '#login_check_url and #login_check_pattern' do - it 'sets a login check' do - s = new_session - s.opts.url = @url + describe "#{Arachni::OptionGroups::Session}" do + describe '#has_login_check?' do + context 'when #check_url and #check_pattern have not been configured' do + it 'returns false' do + subject.has_login_check?.should be_false + end + end - s.has_login_sequence?.should be_false - s.login_sequence = proc do - res = s.http.get( @url, async: false, follow_location: true ).response - return false if !res + context 'when #check_url and #check_pattern have been configured' do + it 'returns true' do + @opts.session.check_url = @url + @opts.session.check_pattern = 'logged-in user' - login_form = s.forms_from_response( res ).first - next false if !login_form + subject.has_login_check?.should be_true + end + end + end + end - login_form['username'] = 'john' - login_form['password'] = 'doe' - res = login_form.submit( async: false, update_cookies: true, follow_location: false ).response - return false if !res + describe '#configuration' do + it "returns #{Arachni::Data::Session}#configuration" do + subject.configuration.object_id.should == + Arachni::Data.session.configuration.object_id + end + end - true + describe '#clean_up' do + it 'shuts down the #browser' do + configured.login + configured.should be_logged_in + + browser = configured.browser + configured.clean_up + browser.pid.should be_nil + end + + it 'clears the #configuration' do + configured.should be_configured + configured.clean_up + configured.should_not be_configured + end + end + + describe '#browser' do + context 'before calling #login' do + it 'returns nil' do + configured.browser.should be_nil + end + end + + context 'after #login' do + it "returns an #{Arachni::Browser}" do + configured.login + configured.browser.should be_kind_of Arachni::Browser + end + end + end + + describe '#login' do + it 'finds and submits the login form with the given credentials' do + configured.login + configured.should be_logged_in + end + + it 'returns the resulting page' do + configured.login.should be_kind_of Arachni::Page + + transition = configured.login.dom.transitions.first + transition.event.should == :load + transition.element.should == :page + transition.options[:url].should == configured.configuration[:url] + + transition = configured.login.dom.transitions.last + transition.event.should == :submit + transition.element.tag_name.should == :form + + transition.options[:inputs]['username'].should == + configured.configuration[:inputs][:username] + + transition.options[:inputs]['password'].should == + configured.configuration[:inputs][:password] + end + + it 'can handle Javascript forms' do + subject.configure( + url: "#{@url}/javascript_login", + inputs: { + username: 'john', + password: 'doe' + } + ) + + @opts.session.check_url = @url + @opts.session.check_pattern = 'logged-in user' + + subject.login + + subject.should be_logged_in + end + + context 'when no configuration has been provided' do + it "raises #{described_class::Error::NotConfigured}" do + expect { subject.login }.to raise_error described_class::Error::NotConfigured + end + end + + context 'each time' do + it 'uses a fresh #browser' do + configured.login + browser = configured.browser + + configured.login + configured.browser.object_id.should_not == browser.object_id + configured.browser.should be_kind_of Arachni::Browser + end + end + end + + describe '#logged_in?' do + context 'when no login check is available' do + it "raises #{described_class::Error::NoLoginCheck}" do + expect { subject.logged_in? }.to raise_error described_class::Error::NoLoginCheck + end + end + + context 'when a login check is available' do + context 'and a valid session is available' do + it 'returns true' do + configured.login + configured.should be_logged_in end - s.has_login_sequence?.should be_true + end - s.has_login_check?.should be_false - s.opts.login_check_url = @url - s.opts.login_check_pattern = 'logged-in user' - s.has_login_check?.should be_true + context 'and a valid session is not available' do + it 'returns true' do + @opts.session.check_url = @url + @opts.session.check_pattern = 'logged-in user' - s.logged_in?.should be_false - s.login.should be_true - s.logged_in?.should be_true + subject.should_not be_logged_in + end + end - bool = false - s.logged_in? { |b| bool = b } - s.http.run - bool.should be_true + context 'when a block is given' do + it 'performs the check asynchronously' do + configured.login - not_bool = true - s.logged_in?( no_cookiejar: true ) { |b| not_bool = b } - s.http.run - not_bool.should be_false + bool = false + configured.logged_in? { |b| bool = b } + configured.http.run + bool.should be_true + + not_bool = true + configured.logged_in?( no_cookie_jar: true ) { |b| not_bool = b } + configured.http.run + not_bool.should be_false + end end end end + describe '#configured?' do + context 'when login instructions have been provided' do + it 'returns true' do + configured.configured?.should be_true + end + end + + context 'when login instructions have not been provided' do + it 'returns false' do + subject.configured?.should be_false + end + end + end + describe '#cookies' do it 'returns session cookies' do - s = new_session - s.http.get @url + '/cookies', async: false, update_cookies: true + subject.http.get @url + '/with_nonce', mode: :sync, update_cookies: true - s.cookies.select { |c| c.name == 'rack.session' }.size == 1 - s.cookies.select { |c| c.name == 'session_cookie' }.size == 1 + subject.cookies.map(&:name).sort.should == %w(rack.session session_cookie).sort + end + end - s.can_login?.should be_false - s.has_login_sequence?.should be_false + describe '#cookie' do + it 'returns the cookie that determines the login status' do + subject.configure( + url: "#{@url}/nonce_login", + inputs: { + username: 'nonce_john', + password: 'nonce_doe' + } + ) - s.login_form = s.find_login_form( url: @url + '/nonce_login' ). - update( username: 'nonce_john', password: 'nonce_doe' ) - # lets invalidate the form nonce now # (to make sure that it will be refreshed before logging in) - s.http.get @url + '/nonce_login', async: false + subject.http.get @url + '/nonce_login', mode: :sync - s.has_login_sequence?.should be_true + subject.configured?.should be_true - s.set_login_check @url + '/with_nonce', 'logged-in user' + @opts.session.check_url = @url + '/with_nonce' + @opts.session.check_pattern = 'logged-in user' + subject.login + cookie = nil - s.cookie { |c| cookie = c } - s.http.run + subject.cookie { |c| cookie = c } + subject.http.run cookie.name.should == 'rack.session' - s.can_login?.should be_true - s.logged_in?.should be_false + subject.can_login?.should be_true end + context 'when called without having configured a login check' do it 'should raise an exception' do - trigger = proc { new_session.cookie } - - raised = false - begin - trigger.call - rescue Arachni::Error - raised = true - end - raised.should be_true - - raised = false - begin - trigger.call - rescue Arachni::Session::Error - raised = true - end - raised.should be_true - - raised = false - begin - trigger.call - rescue Arachni::Session::Error::NoLoginCheck - raised = true - end - raised.should be_true + expect { subject.cookie }.to raise_error described_class::Error::NoLoginCheck end end end describe '#find_login_form' do - before { @id = "#{@url}/login::post::[\"password\", \"token\", \"username\"]" } + before { @id = "#{@url}/login:form:[\"password\", \"token\", \"username\"]" } context 'when passed an array of :pages' do it 'should go through its forms and locate the login one' do p = Arachni::Page.from_url( @url + '/login' ) - s = new_session - - s.find_login_form( pages: [ p, p ] ).id.should == @id + subject.find_login_form( pages: [ p, p ] ).coverage_id.should == @id end end context 'when passed an array of :forms' do it 'should go through its forms and locate the login one' do p = Arachni::Page.from_url( @url + '/login' ) - s = new_session - - s.find_login_form( forms: p.forms ).id.should == @id + subject.find_login_form( forms: p.forms ).coverage_id.should == @id end end context 'when passed a url' do it 'store the cookies set by that url' do - Arachni::HTTP.cookies.should be_empty + Arachni::HTTP::Client.cookies.should be_empty - new_session.find_login_form( url: @url + '/login' ).id.should == @id + subject.find_login_form( url: @url + '/login' ).coverage_id.should == @id - Arachni::HTTP.cookies.find do |c| + Arachni::HTTP::Client.cookies.find do |c| c.name == 'you_need_to' && c.value == 'preserve this' end.should be_kind_of Arachni::Cookie end context 'and called without a block' do it 'should operate in blocking mode, go through its forms and locate the login one' do - s = new_session - s.find_login_form( url: @url + '/login' ).id.should == @id + subject.find_login_form( url: @url + '/login' ).coverage_id.should == @id end end context 'and called with a block' do it 'should operate in async mode, go through its forms, locate the login one and pass it to the block' do - s = new_session form = nil - s.find_login_form( url: @url + '/login' ) { |f| form = f } - s.http.run + subject.find_login_form( url: @url + '/login' ) { |f| form = f } + subject.http.run - form.id.should == @id + form.coverage_id.should == @id end end end context 'when passed an array of :inputs' do it 'should use them to narrow down the list' do - new_session.find_login_form( url: @url + '/multiple', - inputs: :token ).id.should == @id + subject.find_login_form( + url: @url + '/multiple', + inputs: :token + ).coverage_id.should == @id end end context 'when passed an :action' do context Regexp do it 'should use it to match against form actions' do - new_session.find_login_form( url: @url + '/multiple', - action: /login/ ).id.should == @id + subject.find_login_form( + url: @url + '/multiple', + action: /login/ + ).coverage_id.should == @id end end context String do it 'should use it to match against form actions' do - new_session.find_login_form( url: @url + '/multiple', - action: "#{@url}/login" ). - id.should == @id + subject.find_login_form( + url: @url + '/multiple', + action: "#{@url}/login" + ).coverage_id.should == @id end end end - end - describe '#login_form=' do - it 'sets a login form' do - s = new_session - - s.can_login?.should be_false - s.has_login_sequence?.should be_false - - s.login_form = s.find_login_form( url: @url + '/nonce_login' ). - update( username: 'nonce_john', password: 'nonce_doe' ) - - # lets invalidate the form nonce now - # (to make sure that it will be refreshed before logging in) - s.http.get @url + '/nonce_login', async: false - - s.has_login_sequence?.should be_true - - s.set_login_check @url + '/with_nonce', 'logged-in user' - - s.can_login?.should be_true - s.logged_in?.should be_false - - s.login - s.logged_in?.should be_true - end - end - describe '#can_login?' do context 'when there are no login sequences' do it 'returns false' do - new_session.can_login?.should be_false + subject.can_login?.should be_false end end + context 'when there are login sequences' do it 'returns true' do - s = new_session - s.login_sequence = proc {} - s.login_check = proc {} - s.can_login?.should be_true + configured.can_login?.should be_true end end end - describe '#login' do - context 'when there is no login capability' do - it 'returns nil' do - s = new_session - s.can_login?.should be_false - s.has_login_sequence?.should be_false - s.login.should be_nil - end - end - end - - describe '#logged_in?' do - context 'when there is no login check' do - it 'returns nil' do - s = new_session - s.can_login?.should be_false - s.has_login_check?.should be_false - s.logged_in?.should be_nil - end - end - end - describe '#ensure_logged_in' do context 'when the login is successful' do it 'returns true' do - s = new_session - s.set_login_check @url + '/with_nonce', 'logged-in user' - s.login_form = s.find_login_form( url: @url + '/nonce_login' ). - update( username: 'nonce_john', password: 'nonce_doe' ) + @opts.session.check_url = @url + '/with_nonce' + @opts.session.check_pattern = 'logged-in user' - s.logged_in?.should be_false - s.ensure_logged_in - s.logged_in?.should be_true + subject.configure( + url: "#{@url}/nonce_login", + inputs: { + username: 'nonce_john', + password: 'nonce_doe' + } + ) + + subject.logged_in?.should be_false + subject.ensure_logged_in + subject.logged_in?.should be_true end end context 'when the login fails' do it 'returns false' do - s = new_session - s.set_login_check @url + '/with_nonce', 'logged-in user' - s.login_form = s.find_login_form( url: @url + '/nonce_login' ). - update( username: '1', password: '2' ) + @opts.session.check_url = @url + '/with_nonce' + @opts.session.check_pattern = 'logged-in user' + subject.configure( + url: "#{@url}/nonce_login", + inputs: { + username: '1', + password: '2' + } + ) - s.logged_in?.should be_false - s.ensure_logged_in - s.logged_in?.should be_false + subject.logged_in?.should be_false + subject.ensure_logged_in + subject.logged_in?.should be_false end end context 'when the login attempt fails' do it 'retries 5 times' do - s = new_session - s.set_login_check @url, 'logged-in user' - s.login_form = s.find_login_form( url: @url + '/disappearing_login' ). - update( username: 'john', password: 'doe' ) + @opts.session.check_url = @url + @opts.session.check_pattern = 'logged-in user' - s.logged_in?.should be_false - s.ensure_logged_in - s.logged_in?.should be_true + subject.configure( + url: "#{@url}/disappearing_login", + inputs: { + username: 'john', + password: 'doe' + } + ) + + subject.logged_in?.should be_false + subject.ensure_logged_in + subject.logged_in?.should be_true end end context 'when there is no login capability' do it 'returns nil' do - s = new_session - s.can_login?.should be_false - s.ensure_logged_in.should be_nil + subject.can_login?.should be_false + subject.ensure_logged_in.should be_nil end - end - end - - describe '#login_sequence' do - context 'when a block is given' do - it 'sets it as a login sequence' do - s = new_session - s.login_sequence { :yeah! } - s.login_sequence.call.should == :yeah! - s.login.should == :yeah! - end - end - end - - describe '#login_check' do - context 'when a block is given' do - it 'sets it as a login sequence' do - s = new_session - s.login_check { :yeah! } - s.login_check.call.should == :yeah! - s.logged_in?.should == :yeah! - end - end - end - - describe '#set_login_check' do - it 'sets a login check using a URL and regular expression' do - s = new_session - url = web_server_url_for( :session ) + '/' - s.opts.url = "#{url}/congrats" - - s.has_login_sequence?.should be_false - s.login_sequence = proc do - res = s.http.get( url, async: false, follow_location: true ).response - return false if !res - - login_form = s.forms_from_response( res ).first - next false if !login_form - - login_form['username'] = 'john' - login_form['password'] = 'doe' - res = login_form.submit( async: false, update_cookies: true, follow_location: false ).response - return false if !res - - true - end - s.has_login_sequence?.should be_true - - s.has_login_check?.should be_false - s.set_login_check( url, 'logged-in user' ) - s.has_login_check?.should be_true - - s.logged_in?.should be_false - s.login.should be_true - s.logged_in?.should be_true - - bool = false - s.logged_in? { |b| bool = b } - s.http.run - bool.should be_true - - not_bool = true - s.logged_in?( no_cookiejar: true ) { |b| not_bool = b } - s.http.run - not_bool.should be_false end end end