lib/mechanize/http/agent.rb in mechanize-2.3 vs lib/mechanize/http/agent.rb in mechanize-2.4
- old
+ new
@@ -41,15 +41,13 @@
# A list of hooks to call to handle the content-encoding of a request.
attr_reader :content_encoding_hooks
# :section: HTTP Authentication
+ attr_reader :auth_store # :nodoc:
attr_reader :authenticate_methods # :nodoc:
attr_reader :digest_challenges # :nodoc:
- attr_accessor :user
- attr_accessor :password
- attr_accessor :domain
# :section: Redirection
# Follow HTML meta refresh and HTTP Refresh. If set to +:anywhere+ meta
# refresh tags outside of the head element will be followed.
@@ -137,21 +135,19 @@
@robots = false
@user_agent = nil
@webrobots = nil
# HTTP Authentication
+ @auth_store = Mechanize::HTTP::AuthStore.new
@authenticate_parser = Mechanize::HTTP::WWWAuthenticateParser.new
@authenticate_methods = Hash.new do |methods, uri|
methods[uri] = Hash.new do |realms, auth_scheme|
realms[auth_scheme] = []
end
end
@digest_auth = Net::HTTP::DigestAuth.new
@digest_challenges = {}
- @password = nil # HTTP auth password
- @user = nil # HTTP auth user
- @domain = nil # NTLM HTTP domain
# SSL
@pass = nil
@scheme_handlers = Hash.new { |h, scheme|
@@ -168,10 +164,37 @@
@http = Net::HTTP::Persistent.new 'mechanize'
@http.idle_timeout = 5
@http.keep_alive = 300
end
+ ##
+ # Adds credentials +user+, +pass+ for +uri+. If +realm+ is set the
+ # credentials are used only for that realm. If +realm+ is not set the
+ # credentials become the default for any realm on that URI.
+ #
+ # +domain+ and +realm+ are exclusive as NTLM does not follow RFC 2617. If
+ # +domain+ is given it is only used for NTLM authentication.
+
+ def add_auth uri, user, password, realm = nil, domain = nil
+ @auth_store.add_auth uri, user, password, realm, domain
+ end
+
+ ##
+ # USE OF add_default_auth IS NOT RECOMMENDED AS IT MAY EXPOSE PASSWORDS TO
+ # THIRD PARTIES
+ #
+ # Adds credentials +user+, +pass+ as the default authentication credentials.
+ # If no other credentials are available these will be returned from
+ # credentials_for.
+ #
+ # If +domain+ is given it is only used for NTLM authentication.
+
+ def add_default_auth user, password, domain = nil # :nodoc:
+ @auth_store.add_default_auth user, password, domain
+ end
+
+ ##
# Retrieves +uri+ and parses it into a page or other object according to
# PluggableParser. If the URI is an HTTP or HTTPS scheme URI the given HTTP
# +method+ is used to retrieve it, along with the HTTP +headers+, request
# +params+ and HTTP +referer+.
#
@@ -263,11 +286,11 @@
response_redirect response, method, page, redirects, referer
when Net::HTTPUnauthorized
response_authenticate(response, page, uri, request, headers, params,
referer)
else
- raise Mechanize::ResponseCodeError.new(page), "Unhandled response"
+ raise Mechanize::ResponseCodeError.new(page, 'unhandled response')
end
end
# URI for a proxy connection
@@ -468,20 +491,22 @@
if realm = schemes[:digest].find { |r| r.uri == base_uri } then
request_auth_digest request, uri, realm, base_uri, false
elsif realm = schemes[:iis_digest].find { |r| r.uri == base_uri } then
request_auth_digest request, uri, realm, base_uri, true
- elsif schemes[:basic].find { |r| r.uri == base_uri } then
- request.basic_auth @user, @password
+ elsif realm = schemes[:basic].find { |r| r.uri == base_uri } then
+ user, password, = @auth_store.credentials_for uri, realm.realm
+ request.basic_auth user, password
end
end
def request_auth_digest request, uri, realm, base_uri, iis
challenge = @digest_challenges[realm]
- uri.user = @user
- uri.password = @password
+ user, password, = @auth_store.credentials_for uri, realm.realm
+ uri.user = user
+ uri.password = password
auth = @digest_auth.auth_header uri, challenge.to_s, request.method, iis
request['Authorization'] = auth
end
@@ -521,12 +546,12 @@
# Sets a Referer header. Fragment part is removed as demanded by
# RFC 2616 14.36, and user information part is removed just like
# major browsers do.
def request_referer request, uri, referer
return unless referer
- return if 'https' == referer.scheme.downcase and
- 'https' != uri.scheme.downcase
+ return if 'https'.casecmp(referer.scheme) == 0 and
+ 'https'.casecmp(uri.scheme) != 0
if referer.fragment || referer.user || referer.password
referer = referer.dup
referer.fragment = referer.user = referer.password = nil
end
request['Referer'] = referer
@@ -641,18 +666,24 @@
end
end
def response_authenticate(response, page, uri, request, headers, params,
referer)
- raise Mechanize::UnauthorizedError, page unless @user || @password
-
www_authenticate = response['www-authenticate']
- raise Mechanize::UnauthorizedError, page unless www_authenticate
-
+ unless www_authenticate = response['www-authenticate'] then
+ message = 'WWW-Authenticate header missing in response'
+ raise Mechanize::UnauthorizedError.new(page, nil, message)
+ end
+
challenges = @authenticate_parser.parse www_authenticate
+ unless @auth_store.credentials? uri, challenges then
+ message = "no credentials found, provide some with #add_auth"
+ raise Mechanize::UnauthorizedError.new(page, challenges, message)
+ end
+
if challenge = challenges.find { |c| c.scheme =~ /^Digest$/i } then
realm = challenge.realm uri
auth_scheme = if response['server'] =~ /Microsoft-IIS/ then
:iis_digest
@@ -660,27 +691,34 @@
:digest
end
existing_realms = @authenticate_methods[realm.uri][auth_scheme]
- raise Mechanize::UnauthorizedError, page if
- existing_realms.include? realm
+ if existing_realms.include? realm
+ message = 'Digest authentication failed'
+ raise Mechanize::UnauthorizedError.new(page, challeges, message)
+ end
existing_realms << realm
@digest_challenges[realm] = challenge
elsif challenge = challenges.find { |c| c.scheme == 'NTLM' } then
existing_realms = @authenticate_methods[uri + '/'][:ntlm]
- raise Mechanize::UnauthorizedError, page if
- existing_realms.include?(realm) and not challenge.params
+ if existing_realms.include?(realm) and not challenge.params then
+ message = 'NTLM authentication failed'
+ raise Mechanize::UnauthorizedError.new(page, challenges, message)
+ end
existing_realms << realm
if challenge.params then
type_2 = Net::NTLM::Message.decode64 challenge.params
- type_3 = type_2.response({ :user => @user, :password => @password, :domain => @domain },
+ user, password, domain = @auth_store.credentials_for uri, nil
+
+ type_3 = type_2.response({ :user => user, :password => password,
+ :domain => domain },
{ :ntlmv2 => true }).encode64
headers['Authorization'] = "NTLM #{type_3}"
else
type_1 = Net::NTLM::Message::Type1.new.encode64
@@ -689,16 +727,19 @@
elsif challenge = challenges.find { |c| c.scheme == 'Basic' } then
realm = challenge.realm uri
existing_realms = @authenticate_methods[realm.uri][:basic]
- raise Mechanize::UnauthorizedError, page if
- existing_realms.include? realm
+ if existing_realms.include? realm then
+ message = 'Basic authentication failed'
+ raise Mechanize::UnauthorizedError.new(page, challenges, message)
+ end
existing_realms << realm
else
- raise Mechanize::UnauthorizedError, page
+ message = 'unsupported authentication scheme'
+ raise Mechanize::UnauthorizedError.new(page, challenges, message)
end
fetch uri, request.method.downcase.to_sym, headers, params, referer
end
@@ -838,11 +879,11 @@
end
body_io.flush
body_io.rewind
- raise Mechanize::ResponseCodeError, response if
+ raise Mechanize::ResponseCodeError.new(response, uri) if
Net::HTTPUnknownResponse === response
content_length = response.content_length
unless Net::HTTP::Head === request or Net::HTTPRedirection === response then
@@ -1123,6 +1164,8 @@
size >= @max_file_buffer
end
end
+
+require 'mechanize/http/auth_store'