mirror of
https://github.com/jnunemaker/httparty
synced 2023-03-27 23:23:07 -04:00
add digest auth features
Sandro edited this commit from gilles to utilize his cucumber tests but leave the existing digest auth implementation in place
This commit is contained in:
parent
f6cb0c2140
commit
ab65829f1e
6 changed files with 165 additions and 107 deletions
20
features/digest_authentication.feature
Normal file
20
features/digest_authentication.feature
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
Feature: Digest Authentication
|
||||||
|
|
||||||
|
As a developer
|
||||||
|
I want to be able to use a service that requires Digest Authentication
|
||||||
|
Because that is not an uncommon requirement
|
||||||
|
|
||||||
|
Scenario: Passing no credentials to a page requiring Digest Authentication
|
||||||
|
Given a restricted page at '/protected.html'
|
||||||
|
When I call HTTParty#get with '/protected.html'
|
||||||
|
Then it should return a response with a 401 response code
|
||||||
|
|
||||||
|
Scenario: Passing proper credentials to a page requiring Digest Authentication
|
||||||
|
Given a remote service that returns 'Digest Authenticated Page'
|
||||||
|
And that service is accessed at the path '/protected.html'
|
||||||
|
And that service is protected by Digest Authentication
|
||||||
|
And that service requires the username 'jcash' with the password 'maninblack'
|
||||||
|
When I call HTTParty#get with '/protected.html' and a digest_auth hash:
|
||||||
|
| username | password |
|
||||||
|
| jcash | maninblack |
|
||||||
|
Then the return value should match 'Digest Authenticated Page'
|
|
@ -17,3 +17,11 @@ When /I call HTTParty#get with '(.*)' and a basic_auth hash:/ do |url, auth_tabl
|
||||||
:basic_auth => { :username => h["username"], :password => h["password"] }
|
:basic_auth => { :username => h["username"], :password => h["password"] }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
When /I call HTTParty#get with '(.*)' and a digest_auth hash:/ do |url, auth_table|
|
||||||
|
h = auth_table.hashes.first
|
||||||
|
@response_from_httparty = HTTParty.get(
|
||||||
|
"http://#{@host_and_port}#{url}",
|
||||||
|
:digest_auth => { :username => h["username"], :password => h["password"] }
|
||||||
|
)
|
||||||
|
end
|
|
@ -50,6 +50,35 @@ def add_basic_authentication_to(handler)
|
||||||
handler.extend(m)
|
handler.extend(m)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_digest_authentication_to(handler)
|
||||||
|
m = Module.new do
|
||||||
|
attr_writer :username, :password
|
||||||
|
|
||||||
|
def self.extended(base)
|
||||||
|
base.instance_eval { @custom_headers["WWW-Authenticate"] = 'Digest realm="testrealm@host.com",qop="auth,auth-int",nonce="nonce",opaque="opaque"' }
|
||||||
|
base.class_eval { alias_method_chain :process, :digest_authentication }
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_with_digest_authentication(request, response)
|
||||||
|
if authorized?(request)
|
||||||
|
process_without_digest_authentication(request, response)
|
||||||
|
#does not work. At this point response.body_sent is nil and
|
||||||
|
#response.body.string is set to the correct value
|
||||||
|
# -> it's not a stream issue
|
||||||
|
#The else close is never called after this point, yet the result is whatever I put in the else statement
|
||||||
|
# -> don't get it
|
||||||
|
else
|
||||||
|
reply_with(response, 401, "Incorrect. You have 20 seconds to comply.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorized?(request)
|
||||||
|
request.params["HTTP_AUTHORIZATION"] =~ /Digest.*uri=/
|
||||||
|
end
|
||||||
|
end
|
||||||
|
handler.extend(m)
|
||||||
|
end
|
||||||
|
|
||||||
def new_mongrel_redirector(target_url, relative_path = false)
|
def new_mongrel_redirector(target_url, relative_path = false)
|
||||||
target_url = "http://#{@host_and_port}#{target_url}" unless relative_path
|
target_url = "http://#{@host_and_port}#{target_url}" unless relative_path
|
||||||
Mongrel::RedirectHandler.new(target_url)
|
Mongrel::RedirectHandler.new(target_url)
|
||||||
|
|
|
@ -33,6 +33,10 @@ Given /that service is protected by Basic Authentication/ do
|
||||||
add_basic_authentication_to @handler
|
add_basic_authentication_to @handler
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Given /that service is protected by Digest Authentication/ do
|
||||||
|
add_digest_authentication_to @handler
|
||||||
|
end
|
||||||
|
|
||||||
Given /that service requires the username '(.*)' with the password '(.*)'/ do |username, password|
|
Given /that service requires the username '(.*)' with the password '(.*)'/ do |username, password|
|
||||||
@handler.username = username
|
@handler.username = username
|
||||||
@handler.password = password
|
@handler.password = password
|
||||||
|
@ -54,4 +58,4 @@ end
|
||||||
Then /I wait for the server to recover/ do
|
Then /I wait for the server to recover/ do
|
||||||
timeout = @request_options[:timeout] || 0
|
timeout = @request_options[:timeout] || 0
|
||||||
sleep @server_response_time - timeout
|
sleep @server_response_time - timeout
|
||||||
end
|
end
|
||||||
|
|
|
@ -84,7 +84,6 @@ module HTTParty
|
||||||
default_options[:digest_auth] = {:username => u, :password => p}
|
default_options[:digest_auth] = {:username => u, :password => p}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Allows setting default parameters to be appended to each request.
|
# Allows setting default parameters to be appended to each request.
|
||||||
# Great for api keys and such.
|
# Great for api keys and such.
|
||||||
#
|
#
|
||||||
|
|
|
@ -52,7 +52,6 @@ module HTTParty
|
||||||
options[:parser]
|
options[:parser]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
validate
|
validate
|
||||||
setup_raw_request
|
setup_raw_request
|
||||||
|
@ -62,97 +61,97 @@ module HTTParty
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def http
|
def http
|
||||||
http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
|
http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport])
|
||||||
http.use_ssl = ssl_implied?
|
http.use_ssl = ssl_implied?
|
||||||
|
|
||||||
if options[:timeout] && options[:timeout].is_a?(Integer)
|
if options[:timeout] && options[:timeout].is_a?(Integer)
|
||||||
http.open_timeout = options[:timeout]
|
http.open_timeout = options[:timeout]
|
||||||
http.read_timeout = options[:timeout]
|
http.read_timeout = options[:timeout]
|
||||||
end
|
|
||||||
|
|
||||||
if options[:pem] && http.use_ssl?
|
|
||||||
http.cert = OpenSSL::X509::Certificate.new(options[:pem])
|
|
||||||
http.key = OpenSSL::PKey::RSA.new(options[:pem])
|
|
||||||
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
||||||
else
|
|
||||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
|
||||||
end
|
|
||||||
|
|
||||||
if options[:debug_output]
|
|
||||||
http.set_debug_output(options[:debug_output])
|
|
||||||
end
|
|
||||||
|
|
||||||
http
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def ssl_implied?
|
if options[:pem] && http.use_ssl?
|
||||||
uri.port == 443 || uri.instance_of?(URI::HTTPS)
|
http.cert = OpenSSL::X509::Certificate.new(options[:pem])
|
||||||
|
http.key = OpenSSL::PKey::RSA.new(options[:pem])
|
||||||
|
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||||
|
else
|
||||||
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||||
end
|
end
|
||||||
|
|
||||||
def body
|
if options[:debug_output]
|
||||||
options[:body].is_a?(Hash) ? options[:body].to_params : options[:body]
|
http.set_debug_output(options[:debug_output])
|
||||||
end
|
end
|
||||||
|
|
||||||
def credentials
|
http
|
||||||
options[:basic_auth] || options[:digest_auth]
|
end
|
||||||
|
|
||||||
|
def ssl_implied?
|
||||||
|
uri.port == 443 || uri.instance_of?(URI::HTTPS)
|
||||||
|
end
|
||||||
|
|
||||||
|
def body
|
||||||
|
options[:body].is_a?(Hash) ? options[:body].to_params : options[:body]
|
||||||
|
end
|
||||||
|
|
||||||
|
def credentials
|
||||||
|
options[:basic_auth] || options[:digest_auth]
|
||||||
|
end
|
||||||
|
|
||||||
|
def username
|
||||||
|
credentials[:username]
|
||||||
|
end
|
||||||
|
|
||||||
|
def password
|
||||||
|
credentials[:password]
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup_raw_request
|
||||||
|
@raw_request = http_method.new(uri.request_uri)
|
||||||
|
@raw_request.body = body if body
|
||||||
|
@raw_request.initialize_http_header(options[:headers])
|
||||||
|
@raw_request.basic_auth(username, password) if options[:basic_auth]
|
||||||
|
setup_digest_auth if options[:digest_auth]
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup_digest_auth
|
||||||
|
res = http.head(uri.request_uri)
|
||||||
|
if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
|
||||||
|
@raw_request.digest_auth(username, password, res)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform_actual_request
|
||||||
|
http.request(@raw_request)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_response
|
||||||
|
self.last_response = perform_actual_request
|
||||||
|
options[:format] ||= format_from_mimetype(last_response['content-type'])
|
||||||
|
end
|
||||||
|
|
||||||
|
def query_string(uri)
|
||||||
|
query_string_parts = []
|
||||||
|
query_string_parts << uri.query unless uri.query.nil?
|
||||||
|
|
||||||
|
if options[:query].is_a?(Hash)
|
||||||
|
query_string_parts << options[:default_params].merge(options[:query]).to_params
|
||||||
|
else
|
||||||
|
query_string_parts << options[:default_params].to_params unless options[:default_params].empty?
|
||||||
|
query_string_parts << options[:query] unless options[:query].nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
def username
|
query_string_parts.size > 0 ? query_string_parts.join('&') : nil
|
||||||
credentials[:username]
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def password
|
# Raises exception Net::XXX (http error code) if an http error occured
|
||||||
credentials[:password]
|
def handle_response
|
||||||
end
|
case last_response
|
||||||
|
when Net::HTTPMultipleChoice, # 300
|
||||||
def setup_raw_request
|
Net::HTTPMovedPermanently, # 301
|
||||||
@raw_request = http_method.new(uri.request_uri)
|
Net::HTTPFound, # 302
|
||||||
@raw_request.body = body if body
|
Net::HTTPSeeOther, # 303
|
||||||
@raw_request.initialize_http_header(options[:headers])
|
Net::HTTPUseProxy, # 305
|
||||||
@raw_request.basic_auth(username, password) if options[:basic_auth]
|
Net::HTTPTemporaryRedirect
|
||||||
setup_digest_auth if options[:digest_auth]
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup_digest_auth
|
|
||||||
res = http.head(uri.request_uri)
|
|
||||||
if res['www-authenticate'] != nil && res['www-authenticate'].length > 0
|
|
||||||
@raw_request.digest_auth(username, password, res)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform_actual_request
|
|
||||||
http.request(@raw_request)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_response
|
|
||||||
self.last_response = perform_actual_request
|
|
||||||
options[:format] ||= format_from_mimetype(last_response['content-type'])
|
|
||||||
end
|
|
||||||
|
|
||||||
def query_string(uri)
|
|
||||||
query_string_parts = []
|
|
||||||
query_string_parts << uri.query unless uri.query.nil?
|
|
||||||
|
|
||||||
if options[:query].is_a?(Hash)
|
|
||||||
query_string_parts << options[:default_params].merge(options[:query]).to_params
|
|
||||||
else
|
|
||||||
query_string_parts << options[:default_params].to_params unless options[:default_params].empty?
|
|
||||||
query_string_parts << options[:query] unless options[:query].nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
query_string_parts.size > 0 ? query_string_parts.join('&') : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Raises exception Net::XXX (http error code) if an http error occured
|
|
||||||
def handle_response
|
|
||||||
case last_response
|
|
||||||
when Net::HTTPMultipleChoice, # 300
|
|
||||||
Net::HTTPMovedPermanently, # 301
|
|
||||||
Net::HTTPFound, # 302
|
|
||||||
Net::HTTPSeeOther, # 303
|
|
||||||
Net::HTTPUseProxy, # 305
|
|
||||||
Net::HTTPTemporaryRedirect
|
|
||||||
if last_response.key?('location')
|
if last_response.key?('location')
|
||||||
options[:limit] -= 1
|
options[:limit] -= 1
|
||||||
self.path = last_response['location']
|
self.path = last_response['location']
|
||||||
|
@ -165,30 +164,30 @@ module HTTParty
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
Response.new(parse_response(last_response.body), last_response.body, last_response.code, last_response.message, last_response.to_hash)
|
Response.new(parse_response(last_response.body), last_response.body, last_response.code, last_response.message, last_response.to_hash)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def parse_response(body)
|
def parse_response(body)
|
||||||
parser.call(body, format)
|
parser.call(body, format)
|
||||||
end
|
end
|
||||||
|
|
||||||
def capture_cookies(response)
|
def capture_cookies(response)
|
||||||
return unless response['Set-Cookie']
|
return unless response['Set-Cookie']
|
||||||
cookies_hash = HTTParty::CookieHash.new()
|
cookies_hash = HTTParty::CookieHash.new()
|
||||||
cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
|
cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie']
|
||||||
cookies_hash.add_cookies(response['Set-Cookie'])
|
cookies_hash.add_cookies(response['Set-Cookie'])
|
||||||
options[:headers] ||= {}
|
options[:headers] ||= {}
|
||||||
options[:headers]['Cookie'] = cookies_hash.to_cookie_string
|
options[:headers]['Cookie'] = cookies_hash.to_cookie_string
|
||||||
end
|
end
|
||||||
|
|
||||||
# Uses the HTTP Content-Type header to determine the format of the
|
# Uses the HTTP Content-Type header to determine the format of the
|
||||||
# response It compares the MIME type returned to the types stored in the
|
# response It compares the MIME type returned to the types stored in the
|
||||||
# SupportedFormats hash
|
# SupportedFormats hash
|
||||||
def format_from_mimetype(mimetype)
|
def format_from_mimetype(mimetype)
|
||||||
if mimetype && parser.respond_to?(:format_from_mimetype)
|
if mimetype && parser.respond_to?(:format_from_mimetype)
|
||||||
parser.format_from_mimetype(mimetype)
|
parser.format_from_mimetype(mimetype)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate
|
def validate
|
||||||
raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
|
raise HTTParty::RedirectionTooDeep.new(last_response), 'HTTP redirects too deep' if options[:limit].to_i <= 0
|
||||||
|
@ -200,9 +199,8 @@ module HTTParty
|
||||||
raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].is_a?(Hash)
|
raise ArgumentError, ':query must be hash if using HTTP Post' if post? && !options[:query].nil? && !options[:query].is_a?(Hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
def post?
|
def post?
|
||||||
Net::HTTP::Post == http_method
|
Net::HTTP::Post == http_method
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue