Skip to content

Commit

Permalink
Implement basic authentication using DevTools
Browse files Browse the repository at this point in the history
driver.register(username: 'admin', password: '123456')
driver.register(username: 'admin', password: '123456', uri: /mysite/)

Currently supported only in Chrome and Edge
  • Loading branch information
p0deje committed Sep 25, 2020
1 parent 4719030 commit 4b9458d
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 1 deletion.
1 change: 1 addition & 0 deletions rb/lib/selenium/webdriver/chrome/driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Driver < WebDriver::Driver
include DriverExtensions::TakesScreenshot
include DriverExtensions::DownloadsFiles
include DriverExtensions::HasDevTools
include DriverExtensions::HasAuthentication

def browser
:chrome
Expand Down
1 change: 1 addition & 0 deletions rb/lib/selenium/webdriver/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
require 'selenium/webdriver/common/driver_extensions/uploads_files'
require 'selenium/webdriver/common/driver_extensions/has_addons'
require 'selenium/webdriver/common/driver_extensions/has_devtools'
require 'selenium/webdriver/common/driver_extensions/has_authentication'
require 'selenium/webdriver/common/keys'
require 'selenium/webdriver/common/profile_helper'
require 'selenium/webdriver/common/options'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# frozen_string_literal: true

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

module Selenium
module WebDriver
module DriverExtensions
module HasAuthentication

#
# Registers basic authentication handler which is automatically
# used whenever browser gets an authentication required response.
# This currently relies on DevTools so is only supported in
# Chromium browsers.
#
# @example Authenticate any request
# driver.register(username: 'admin', password: '123456')
#
# @example Authenticate based on URL
# driver.register(username: 'admin1', password: '123456', uri: /mysite1\.com/)
# driver.register(username: 'admin2', password: '123456', uri: /mysite2\.com/)
#
# @param [String] username
# @param [String] password
# @param [Regexp] uri to associate the credentials with
#

def register(username:, password:, uri: //)
auth_handlers << {username: username, password: password, uri: uri}

devtools.network.set_cache_disabled(cache_disabled: true)
devtools.fetch.on(:auth_required) do |params|
authenticate(params['requestId'], params.dig('request', 'url'))
end
devtools.fetch.on(:request_paused) do |params|
devtools.fetch.continue_request(request_id: params['requestId'])
end
devtools.fetch.enable(handle_auth_requests: true)
end

private

def auth_handlers
@auth_handlers ||= []
end

def authenticate(request_id, url)
credentials = auth_handlers.find do |handler|
url.match?(handler[:uri])
end

if credentials
devtools.fetch.continue_with_auth(
request_id: request_id,
auth_challenge_response: {
response: 'ProvideCredentials',
username: credentials[:username],
password: credentials[:password]
}
)
else
devtools.fetch.continue_with_auth(
request_id: request_id,
auth_challenge_response: {
response: 'CancelAuth'
}
)
end
end

end # HasAuthentication
end # DriverExtensions
end # WebDriver
end # Selenium
4 changes: 3 additions & 1 deletion rb/lib/selenium/webdriver/devtools.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def callbacks
def send_cmd(method, **params)
id = next_id
data = JSON.generate(id: id, method: method, params: params.reject { |_, v| v.nil? })
WebDriver.logger.debug "DevTools -> #{data}"

out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
socket.write(out_frame.to_s)
Expand Down Expand Up @@ -77,10 +78,11 @@ def attach_socket_listener
while (frame = incoming_frame.next)
message = JSON.parse(frame.to_s)
@messages << message
WebDriver.logger.debug "DevTools <- #{message}"
next unless message['method']

callbacks[message['method']].each do |callback|
callback.call(message['params'])
Thread.new { callback.call(message['params']) }
end
end
end
Expand Down
21 changes: 21 additions & 0 deletions rb/spec/integration/selenium/webdriver/devtools_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
module Selenium
module WebDriver
describe DevTools, only: {driver: %i[chrome edge]} do
let(:username) { SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.first }
let(:password) { SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.last }

after do
quit_driver
end
Expand All @@ -40,6 +43,24 @@ module WebDriver

expect(callback).to have_received(:call)
end

it 'authenticates on any request' do
driver.register(username: username, password: password)

driver.navigate.to url_for('basicAuth')
expect(driver.find_element(tag_name: 'h1').text).to eq('authorized')
end

it 'authenticates based on URL' do
auth_url = url_for('basicAuth')
driver.register(username: username, password: password, uri: /localhost/)

driver.navigate.to auth_url.sub('localhost', '127.0.0.1')
expect { driver.find_element(tag_name: 'h1') }.to raise_error(Error::NoSuchElementError)

driver.navigate.to auth_url
expect(driver.find_element(tag_name: 'h1').text).to eq('authorized')
end
end
end
end
23 changes: 23 additions & 0 deletions rb/spec/integration/selenium/webdriver/spec_support/rack_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,33 @@ def call(env)
time = Rack::Request.new(env).params['time']
sleep Integer(time)
[200, {'Content-Type' => 'text/html'}, ["Slept for #{time}"]]
when '/basicAuth'
authorize(env)
else
@static.call env
end
end

private

def authorize(env)
if authorized?(env)
status = 200
header = {'Content-Type' => 'text/html'}
body = '<h1>authorized</h1>'
else
status = 401
header = {'WWW-Authenticate' => 'Basic realm="basic-auth-test"'}
body = 'Login please'
end

[status, header, [body]]
end

def authorized?(env)
auth = Rack::Auth::Basic::Request.new(env)
auth.provided? && auth.basic? && auth.credentials && auth.credentials == BASIC_AUTH_CREDENTIALS
end
end
end # RackServer
end # SpecSupport
Expand Down

0 comments on commit 4b9458d

Please sign in to comment.