diff --git a/README.md b/README.md index 1f6537c9..cab6dc81 100644 --- a/README.md +++ b/README.md @@ -101,11 +101,11 @@ and the following optional features: * `page.status_code` * `page.response_headers` * `page.save_screenshot` -* `page.render_base64` -* `page.scroll_to` +* `page.driver.render_base64(format, options)` +* `page.driver.scroll_to(left, top)` +* `page.driver.basic_authorize(user, password)` * cookie handling * drag-and-drop -* basic http authentication There are some additional features: @@ -235,17 +235,6 @@ page.within_window fb_popup do end ``` -### Basic HTTP authentication ### - -This method can be used for basic authentication: - -``` ruby -page.driver.basic_authorize('username', 'password') -``` - -It actually appends `Authorize` header to bunch of your headers, so since you've -set it don't try to use `headers=` which will overwrite it. - ## Customization ## diff --git a/lib/capybara/poltergeist/browser.rb b/lib/capybara/poltergeist/browser.rb index 014c20e7..9402e204 100644 --- a/lib/capybara/poltergeist/browser.rb +++ b/lib/capybara/poltergeist/browser.rb @@ -232,6 +232,10 @@ def cookies_enabled=(flag) command 'cookies_enabled', !!flag end + def set_http_auth(user, password) + command 'set_http_auth', user, password + end + def js_errors=(val) command 'set_js_errors', !!val end diff --git a/lib/capybara/poltergeist/client/browser.coffee b/lib/capybara/poltergeist/client/browser.coffee index 38e6fec3..1bff8101 100644 --- a/lib/capybara/poltergeist/client/browser.coffee +++ b/lib/capybara/poltergeist/client/browser.coffee @@ -327,6 +327,10 @@ class Poltergeist.Browser phantom.cookiesEnabled = flag this.sendResponse(true) + set_http_auth: (user, password) -> + @page.setHttpAuth(user, password) + this.sendResponse(true) + set_js_errors: (value) -> @js_errors = value this.sendResponse(true) diff --git a/lib/capybara/poltergeist/client/compiled/browser.js b/lib/capybara/poltergeist/client/compiled/browser.js index 92d73a35..f7d62005 100644 --- a/lib/capybara/poltergeist/client/compiled/browser.js +++ b/lib/capybara/poltergeist/client/compiled/browser.js @@ -440,6 +440,11 @@ Poltergeist.Browser = (function() { return this.sendResponse(true); }; + Browser.prototype.set_http_auth = function(user, password) { + this.page.setHttpAuth(user, password); + return this.sendResponse(true); + }; + Browser.prototype.set_js_errors = function(value) { this.js_errors = value; return this.sendResponse(true); diff --git a/lib/capybara/poltergeist/client/compiled/web_page.js b/lib/capybara/poltergeist/client/compiled/web_page.js index 0cf81b7c..32221cda 100644 --- a/lib/capybara/poltergeist/client/compiled/web_page.js +++ b/lib/capybara/poltergeist/client/compiled/web_page.js @@ -145,6 +145,11 @@ Poltergeist.WebPage = (function() { } }; + WebPage.prototype.setHttpAuth = function(user, password) { + this["native"].settings.userName = user; + return this["native"].settings.password = password; + }; + WebPage.prototype.networkTraffic = function() { return this._networkTraffic; }; diff --git a/lib/capybara/poltergeist/client/web_page.coffee b/lib/capybara/poltergeist/client/web_page.coffee index 80b7b762..dc5bef61 100644 --- a/lib/capybara/poltergeist/client/web_page.coffee +++ b/lib/capybara/poltergeist/client/web_page.coffee @@ -93,6 +93,10 @@ class Poltergeist.WebPage @_statusCode = response.status @_responseHeaders = response.headers + setHttpAuth: (user, password) -> + @native.settings.userName = user + @native.settings.password = password + networkTraffic: -> @_networkTraffic diff --git a/lib/capybara/poltergeist/driver.rb b/lib/capybara/poltergeist/driver.rb index 21b999de..f820d2a9 100644 --- a/lib/capybara/poltergeist/driver.rb +++ b/lib/capybara/poltergeist/driver.rb @@ -225,10 +225,14 @@ def cookies_enabled=(flag) browser.cookies_enabled = flag end - # Since PhantomJS doesn't send `Authorize` header with POST - # request at all, it's better to set header manually. + # * PhantomJS with set settings doesn't send `Authorize` on POST request + # * With manually set header PhantomJS makes next request with + # `Authorization: Basic Og==` header when settings are empty and the + # response was `401 Unauthorized` (which means Base64.encode64(':')). + # Combining both methods to reach proper behavior. def basic_authorize(user, password) - credentials = ["#{user}:#{password}"].pack('m*') + browser.set_http_auth(user, password) + credentials = ["#{user}:#{password}"].pack('m*').strip add_header('Authorization', "Basic #{credentials}") end diff --git a/spec/integration/driver_spec.rb b/spec/integration/driver_spec.rb index e9a3bb03..20000ed5 100644 --- a/spec/integration/driver_spec.rb +++ b/spec/integration/driver_spec.rb @@ -622,14 +622,14 @@ def create_screenshot(file, *args) end context 'basic http authentication' do - it 'does not set header' do + it 'denies without credentials' do @session.visit '/poltergeist/basic_auth' expect(@session.status_code).to eq(401) expect(@session).not_to have_content('Welcome, authenticated client') end - it 'sets header' do + it 'allows with given credentials' do @driver.basic_authorize('login', 'pass') @session.visit '/poltergeist/basic_auth' @@ -637,6 +637,35 @@ def create_screenshot(file, *args) expect(@session.status_code).to eq(200) expect(@session).to have_content('Welcome, authenticated client') end + + it 'allows even overwriting headers' do + @driver.basic_authorize('login', 'pass') + @driver.headers = [{ 'Poltergeist' => 'true' }] + + @session.visit '/poltergeist/basic_auth' + + expect(@session.status_code).to eq(200) + expect(@session).to have_content('Welcome, authenticated client') + end + + it 'denies with wrong credentials' do + @driver.basic_authorize('user', 'pass!') + + @session.visit '/poltergeist/basic_auth' + + expect(@session.status_code).to eq(401) + expect(@session).not_to have_content('Welcome, authenticated client') + end + + it 'allows on POST request' do + @driver.basic_authorize('login', 'pass') + + @session.visit '/poltergeist/basic_auth' + @session.click_button('Submit') + + expect(@session.status_code).to eq(200) + expect(@session).to have_content('Authorized POST request') + end end end end diff --git a/spec/support/test_app.rb b/spec/support/test_app.rb index 8b265b79..26531a8b 100644 --- a/spec/support/test_app.rb +++ b/spec/support/test_app.rb @@ -7,6 +7,19 @@ class TestApp POLTERGEIST_VIEWS = File.dirname(__FILE__) + "/views" POLTERGEIST_PUBLIC = File.dirname(__FILE__) + "/public" + helpers do + def requires_credentials(login, password) + return if authorized?(login, password) + headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"' + halt 401, "Not authorized\n" + end + + def authorized?(login, password) + @auth ||= Rack::Auth::Basic::Request.new(request.env) + @auth.provided? and @auth.basic? and @auth.credentials and @auth.credentials == [login, password] + end + end + get '/poltergeist/test.js' do File.read("#{POLTERGEIST_PUBLIC}/test.js") end @@ -42,13 +55,13 @@ class TestApp end get '/poltergeist/basic_auth' do - auth = Rack::Auth::Basic::Request.new(request.env) - if auth.provided? and auth.basic? and auth.credentials and auth.credentials == ['login', 'pass'] - 'Welcome, authenticated client' - else - headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"' - halt 401, "Not authorized\n" - end + requires_credentials('login', 'pass') + render_view :basic_auth + end + + post '/poltergeist/post_basic_auth' do + requires_credentials('login', 'pass') + 'Authorized POST request' end get '/poltergeist/:view' do |view| diff --git a/spec/support/views/basic_auth.erb b/spec/support/views/basic_auth.erb new file mode 100644 index 00000000..a5789e20 --- /dev/null +++ b/spec/support/views/basic_auth.erb @@ -0,0 +1,5 @@ +Welcome, authenticated client + +
+ +