diff options
author | vandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-23 23:36:07 +0000 |
---|---|---|
committer | vandebo@chromium.org <vandebo@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-23 23:36:07 +0000 |
commit | 7fdbbf8d47bf4c2f5d2e86e1ad68443af8cfa8ff (patch) | |
tree | 21d98440a37f086a000a4ca4a318172da4d825b1 /net | |
parent | bb2d13e580c488173714e54b0f277936c33654cb (diff) | |
download | chromium_src-7fdbbf8d47bf4c2f5d2e86e1ad68443af8cfa8ff.zip chromium_src-7fdbbf8d47bf4c2f5d2e86e1ad68443af8cfa8ff.tar.gz chromium_src-7fdbbf8d47bf4c2f5d2e86e1ad68443af8cfa8ff.tar.bz2 |
Revert 50647 - Create HttpAuthController.
'Memory tests' is not happy.
This packages up the auth state into a single class to enable a HttpProxyClientSocket class (which is needed for SSLClientSocketPool).
BUG=30357
TEST=existing unit tests
Review URL: http://codereview.chromium.org/2808020
TBR=vandebo@chromium.org
Review URL: http://codereview.chromium.org/2866018
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50669 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/net_error_list.h | 3 | ||||
-rw-r--r-- | net/http/http_auth.cc | 6 | ||||
-rw-r--r-- | net/http/http_auth.h | 4 | ||||
-rw-r--r-- | net/http/http_auth_controller.cc | 331 | ||||
-rw-r--r-- | net/http/http_auth_controller.h | 135 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 387 | ||||
-rw-r--r-- | net/http/http_network_transaction.h | 80 | ||||
-rw-r--r-- | net/net.gyp | 2 |
8 files changed, 409 insertions, 539 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index 1b21084..517ee4da 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -156,9 +156,6 @@ NET_ERROR(SSL_DECOMPRESSION_FAILURE_ALERT, -125) // from servers with buggy DEFLATE support. NET_ERROR(SSL_BAD_RECORD_MAC_ALERT, -126) -// The HTTP auth handler requires a DNS lookup to find the canonical name. -NET_ERROR(AUTH_NEEDS_CANONICAL_NAME, -127) - // Certificate error codes // // The values of certificate error codes must be consecutive. diff --git a/net/http/http_auth.cc b/net/http/http_auth.cc index 75b55a3..09214e4 100644 --- a/net/http/http_auth.cc +++ b/net/http/http_auth.cc @@ -169,10 +169,4 @@ std::string HttpAuth::GetAuthorizationHeaderName(Target target) { } } -// static -std::string HttpAuth::GetAuthTargetString( - HttpAuth::Target target) { - return target == HttpAuth::AUTH_PROXY ? "proxy" : "server"; -} - } // namespace net diff --git a/net/http/http_auth.h b/net/http/http_auth.h index 09b6f369..bba633d 100644 --- a/net/http/http_auth.h +++ b/net/http/http_auth.h @@ -78,10 +78,6 @@ class HttpAuth { // (either Authorization or Proxy-Authorization). static std::string GetAuthorizationHeaderName(Target target); - // Returns a string representation of a Target value that can be used in log - // messages. - static std::string GetAuthTargetString(Target target); - // Iterate through the challenge headers, and pick the best one that // we support. Obtains the implementation class for handling the challenge, // and passes it back in |*handler|. If the existing handler in |*handler| diff --git a/net/http/http_auth_controller.cc b/net/http/http_auth_controller.cc deleted file mode 100644 index b85ae03..0000000 --- a/net/http/http_auth_controller.cc +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "net/http/http_auth_controller.h" - -#include "base/string_util.h" -#include "net/base/host_resolver.h" -#include "net/base/net_util.h" -#include "net/http/http_auth_handler_factory.h" -#include "net/http/http_network_session.h" -#include "net/http/http_request_headers.h" -#include "net/http/http_request_info.h" - -namespace net { - -namespace { - -// Returns a log message for all the response headers related to the auth -// challenge. -std::string AuthChallengeLogMessage(HttpResponseHeaders* headers) { - std::string msg; - std::string header_val; - void* iter = NULL; - while (headers->EnumerateHeader(&iter, "proxy-authenticate", &header_val)) { - msg.append("\n Has header Proxy-Authenticate: "); - msg.append(header_val); - } - - iter = NULL; - while (headers->EnumerateHeader(&iter, "www-authenticate", &header_val)) { - msg.append("\n Has header WWW-Authenticate: "); - msg.append(header_val); - } - - // RFC 4559 requires that a proxy indicate its support of NTLM/Negotiate - // authentication with a "Proxy-Support: Session-Based-Authentication" - // response header. - iter = NULL; - while (headers->EnumerateHeader(&iter, "proxy-support", &header_val)) { - msg.append("\n Has header Proxy-Support: "); - msg.append(header_val); - } - - return msg; -} - -} // namespace - -HttpAuthController::HttpAuthController( - HttpAuth::Target target, - const GURL& auth_url, - scoped_refptr<HttpNetworkSession> session, - const BoundNetLog& net_log) - : target_(target), - auth_url_(auth_url), - auth_origin_(auth_url.GetOrigin()), - auth_path_(HttpAuth::AUTH_PROXY ? std::string() : auth_url.path()), - embedded_identity_used_(false), - default_credentials_used_(false), - session_(session), - net_log_(net_log) { -} - -int HttpAuthController::MaybeGenerateAuthToken(const HttpRequestInfo* request, - CompletionCallback* callback) { - bool needs_auth = HaveAuth() || SelectPreemptiveAuth(); - if (!needs_auth) - return OK; - const std::wstring* username = NULL; - const std::wstring* password = NULL; - if (identity_.source != HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS) { - username = &identity_.username; - password = &identity_.password; - } - DCHECK(auth_token_.empty()); - return handler_->GenerateAuthToken(username, password, request, callback, - &auth_token_); -} - -bool HttpAuthController::SelectPreemptiveAuth() { - DCHECK(!HaveAuth()); - DCHECK(identity_.invalid); - - // Don't do preemptive authorization if the URL contains a username/password, - // since we must first be challenged in order to use the URL's identity. - if (auth_url_.has_username()) - return false; - - // SelectPreemptiveAuth() is on the critical path for each request, so it - // is expected to be fast. LookupByPath() is fast in the common case, since - // the number of http auth cache entries is expected to be very small. - // (For most users in fact, it will be 0.) - HttpAuthCache::Entry* entry = session_->auth_cache()->LookupByPath( - auth_origin_, auth_path_); - if (!entry) - return false; - - // Try to create a handler using the previous auth challenge. - scoped_ptr<HttpAuthHandler> handler_preemptive; - int rv_create = session_->http_auth_handler_factory()-> - CreatePreemptiveAuthHandlerFromString(entry->auth_challenge(), target_, - auth_origin_, - entry->IncrementNonceCount(), - net_log_, &handler_preemptive); - if (rv_create != OK) - return false; - - // Set the state - identity_.source = HttpAuth::IDENT_SRC_PATH_LOOKUP; - identity_.invalid = false; - identity_.username = entry->username(); - identity_.password = entry->password(); - handler_.swap(handler_preemptive); - return true; -} - -void HttpAuthController::AddAuthorizationHeader( - HttpRequestHeaders* authorization_headers) { - DCHECK(HaveAuth()); - DCHECK(!auth_token_.empty()); - authorization_headers->SetHeader( - HttpAuth::GetAuthorizationHeaderName(target_), auth_token_); - auth_token_.clear(); -} - -int HttpAuthController::HandleAuthChallenge( - scoped_refptr<HttpResponseHeaders> headers, - int load_flags, - bool establishing_tunnel) { - DCHECK(headers); - DCHECK(auth_origin_.is_valid()); - - LOG(INFO) << "The " << HttpAuth::GetAuthTargetString(target_) << " " - << auth_origin_ << " requested auth" - << AuthChallengeLogMessage(headers.get()); - - // The auth we tried just failed, hence it can't be valid. Remove it from - // the cache so it won't be used again. - // TODO(wtc): IsFinalRound is not the right condition. In a multi-round - // auth sequence, the server may fail the auth in round 1 if our first - // authorization header is broken. We should inspect response_.headers to - // determine if the server already failed the auth or wants us to continue. - // See http://crbug.com/21015. - if (HaveAuth() && handler_->IsFinalRound()) { - InvalidateRejectedAuthFromCache(); - handler_.reset(); - identity_ = HttpAuth::Identity(); - } - - identity_.invalid = true; - - if (target_ != HttpAuth::AUTH_SERVER || - !(load_flags & LOAD_DO_NOT_SEND_AUTH_DATA)) { - // Find the best authentication challenge that we support. - HttpAuth::ChooseBestChallenge(session_->http_auth_handler_factory(), - headers, target_, auth_origin_, net_log_, - &handler_); - } - - if (!handler_.get()) { - if (establishing_tunnel) { - LOG(ERROR) << "Can't perform auth to the " - << HttpAuth::GetAuthTargetString(target_) << " " - << auth_origin_ << " when establishing a tunnel" - << AuthChallengeLogMessage(headers.get()); - - // We are establishing a tunnel, we can't show the error page because an - // active network attacker could control its contents. Instead, we just - // fail to establish the tunnel. - DCHECK(target_ == HttpAuth::AUTH_PROXY); - return ERR_PROXY_AUTH_REQUESTED; - } - // We found no supported challenge -- let the transaction continue - // so we end up displaying the error page. - return OK; - } - - if (handler_->NeedsIdentity()) { - // Pick a new auth identity to try, by looking to the URL and auth cache. - // If an identity to try is found, it is saved to identity_. - SelectNextAuthIdentityToTry(); - } else { - // Proceed with the existing identity or a null identity. - // - // TODO(wtc): Add a safeguard against infinite transaction restarts, if - // the server keeps returning "NTLM". - identity_.invalid = false; - } - - // From this point on, we are restartable. - - if (identity_.invalid) { - // We have exhausted all identity possibilities, all we can do now is - // pass the challenge information back to the client. - PopulateAuthChallenge(); - } - - // SPN determination (for Negotiate) requires a DNS lookup to find the - // canonical name. This needs to be done asynchronously to prevent blocking - // the IO thread. - if (handler_->NeedsCanonicalName()) - return ERR_AUTH_NEEDS_CANONICAL_NAME; - - return OK; -} - -int HttpAuthController::ResolveCanonicalName(CompletionCallback* callback) { - DCHECK(handler_.get()); - return handler_->ResolveCanonicalName(session_->host_resolver(), callback); -} - -void HttpAuthController::ResetAuth(const std::wstring& username, - const std::wstring& password) { - DCHECK(identity_.invalid || (username.empty() && password.empty())); - - if (identity_.invalid) { - // Update the username/password. - identity_.source = HttpAuth::IDENT_SRC_EXTERNAL; - identity_.invalid = false; - identity_.username = username; - identity_.password = password; - } - - DCHECK(identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP); - - // Add the auth entry to the cache before restarting. We don't know whether - // the identity is valid yet, but if it is valid we want other transactions - // to know about it. If an entry for (origin, handler->realm()) already - // exists, we update it. - // - // If identity_.source is HttpAuth::IDENT_SRC_NONE or - // HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS, identity_ contains no - // identity because identity is not required yet or we're using default - // credentials. - // - // TODO(wtc): For NTLM_SSPI, we add the same auth entry to the cache in - // round 1 and round 2, which is redundant but correct. It would be nice - // to add an auth entry to the cache only once, preferrably in round 1. - // See http://crbug.com/21015. - switch (identity_.source) { - case HttpAuth::IDENT_SRC_NONE: - case HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS: - break; - default: - session_->auth_cache()->Add(auth_origin_, handler_->realm(), - handler_->scheme(), handler_->challenge(), - identity_.username, identity_.password, - auth_path_); - break; - } -} - -void HttpAuthController::InvalidateRejectedAuthFromCache() { - DCHECK(HaveAuth()); - - // TODO(eroman): this short-circuit can be relaxed. If the realm of - // the preemptively used auth entry matches the realm of the subsequent - // challenge, then we can invalidate the preemptively used entry. - // Otherwise as-is we may send the failed credentials one extra time. - if (identity_.source == HttpAuth::IDENT_SRC_PATH_LOOKUP) - return; - - // Clear the cache entry for the identity we just failed on. - // Note: we require the username/password to match before invalidating - // since the entry in the cache may be newer than what we used last time. - session_->auth_cache()->Remove(auth_origin_, handler_->realm(), - handler_->scheme(), identity_.username, - identity_.password); -} - -bool HttpAuthController::SelectNextAuthIdentityToTry() { - DCHECK(handler_.get()); - DCHECK(identity_.invalid); - - // Try to use the username/password encoded into the URL first. - if (target_ == HttpAuth::AUTH_SERVER && auth_url_.has_username() && - !embedded_identity_used_) { - identity_.source = HttpAuth::IDENT_SRC_URL; - identity_.invalid = false; - // Extract the username:password from the URL. - GetIdentityFromURL(auth_url_, - &identity_.username, - &identity_.password); - embedded_identity_used_ = true; - // TODO(eroman): If the password is blank, should we also try combining - // with a password from the cache? - return true; - } - - // Check the auth cache for a realm entry. - HttpAuthCache::Entry* entry = - session_->auth_cache()->Lookup(auth_origin_, handler_->realm(), - handler_->scheme()); - - if (entry) { - identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP; - identity_.invalid = false; - identity_.username = entry->username(); - identity_.password = entry->password(); - return true; - } - - // Use default credentials (single sign on) if this is the first attempt - // at identity. Do not allow multiple times as it will infinite loop. - // We use default credentials after checking the auth cache so that if - // single sign-on doesn't work, we won't try default credentials for future - // transactions. - if (!default_credentials_used_ && handler_->AllowsDefaultCredentials()) { - identity_.source = HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS; - identity_.invalid = false; - default_credentials_used_ = true; - return true; - } - - return false; -} - -void HttpAuthController::PopulateAuthChallenge() { - // Populates response_.auth_challenge with the authentication challenge info. - // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo(). - - auth_info_ = new AuthChallengeInfo; - auth_info_->is_proxy = target_ == HttpAuth::AUTH_PROXY; - auth_info_->host_and_port = ASCIIToWide(GetHostAndPort(auth_origin_)); - auth_info_->scheme = ASCIIToWide(handler_->scheme()); - // TODO(eroman): decode realm according to RFC 2047. - auth_info_->realm = ASCIIToWide(handler_->realm()); -} - -} // namespace net diff --git a/net/http/http_auth_controller.h b/net/http/http_auth_controller.h deleted file mode 100644 index 80bdb28..0000000 --- a/net/http/http_auth_controller.h +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef NET_HTTP_HTTP_AUTH_CONTROLLER_H_ -#define NET_HTTP_HTTP_AUTH_CONTROLLER_H_ - -#include <string> - -#include "base/basictypes.h" -#include "base/ref_counted.h" -#include "base/scoped_ptr.h" -#include "googleurl/src/gurl.h" -#include "net/base/completion_callback.h" -#include "net/base/net_log.h" -#include "net/http/http_auth.h" - -namespace net { - -class AuthChallengeInfo; -class HostResolver; -class HttpNetworkSession; -class HttpRequestHeaders; -struct HttpRequestInfo; - -class HttpAuthController { - public: - // The arguments are self explanatory except possibly for |auth_url|, which - // should be both the auth target and auth path in a single url argument. - HttpAuthController(HttpAuth::Target target, const GURL& auth_url, - scoped_refptr<HttpNetworkSession> session, - const BoundNetLog& net_log); - - // Generate an authentication token for |target| if necessary. The return - // value is a net error code. |OK| will be returned both in the case that - // a token is correctly generated synchronously, as well as when no tokens - // were necessary. - int MaybeGenerateAuthToken(const HttpRequestInfo* request, - CompletionCallback* callback); - - // Adds either the proxy auth header, or the origin server auth header, - // as specified by |target_|. - void AddAuthorizationHeader(HttpRequestHeaders* authorization_headers); - - // Checks for and handles HTTP status code 401 or 407. - // |HandleAuthChallenge()| returns OK on success, - // ERR_AUTH_NEEDS_CANONICAL_NAME if the handler needs the canonical name - // resolved, or a network error code. It may also populate |auth_info_|. - int HandleAuthChallenge(scoped_refptr<HttpResponseHeaders> headers, - int load_flags, bool establishing_tunnel); - - int ResolveCanonicalName(CompletionCallback* callback); - - // Store the supplied credentials and prepare to restart the auth. - void ResetAuth(const std::wstring& username, const std::wstring& password); - - bool HaveAuthHandler() const { - return handler_.get() != NULL; - } - - bool HaveAuth() const { - return handler_.get() && !identity_.invalid; - } - - // The caller receives ownership of the return AuthChallengeInfo. - AuthChallengeInfo* auth_info() { - return auth_info_.release(); - } - - private: - // Searches the auth cache for an entry that encompasses the request's path. - // If such an entry is found, updates |identity_| and |handler_| with the - // cache entry's data and returns true. - bool SelectPreemptiveAuth(); - - // Invalidates any auth cache entries after authentication has failed. - // The identity that was rejected is |identity_|. - void InvalidateRejectedAuthFromCache(); - - // Sets |identity_| to the next identity that the transaction should try. It - // chooses candidates by searching the auth cache and the URL for a - // username:password. Returns true if an identity was found. - bool SelectNextAuthIdentityToTry(); - - // Populates auth_info_ with the challenge information, so that - // URLRequestHttpJob can prompt for a username/password. - void PopulateAuthChallenge(); - - // Indicates if this handler is for Proxy auth or Server auth. - HttpAuth::Target target_; - - // Holds the {scheme, host, path, port} for the authentication target. - const GURL auth_url_; - - // Holds the {scheme, host, port} for the authentication target. - const GURL auth_origin_; - - // The absolute path of the resource needing authentication. - // For proxy authentication the path is empty. - const std::string auth_path_; - - // |handler_| encapsulates the logic for the particular auth-scheme. - // This includes the challenge's parameters. If NULL, then there is no - // associated auth handler. - scoped_ptr<HttpAuthHandler> handler_; - - // |identity_| holds the (username/password) that should be used by - // the handler_ to generate credentials. This identity can come from - // a number of places (url, cache, prompt). - HttpAuth::Identity identity_; - - // |auth_token_| contains the opaque string to pass to the proxy or - // server to authenticate the client. - std::string auth_token_; - - // Contains information about the auth challenge. - scoped_refptr<AuthChallengeInfo> auth_info_; - - // True if we've used the username/password embedded in the URL. This - // makes sure we use the embedded identity only once for the transaction, - // preventing an infinite auth restart loop. - bool embedded_identity_used_; - - // True if default credentials have already been tried for this transaction - // in response to an HTTP authentication challenge. - bool default_credentials_used_; - - scoped_refptr<HttpNetworkSession> session_; - - BoundNetLog net_log_; -}; - -} // namespace net - -#endif // NET_HTTP_HTTP_AUTH_CONTROLLER_H_ diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index fe5498e..3269f42 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -296,6 +296,8 @@ HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session) alternate_protocol_mode_( g_use_alternate_protocols ? kUnspecified : kDoNotUseAlternateProtocol), + embedded_identity_used_(false), + default_credentials_used_(false), read_buf_len_(0), next_state_(STATE_NONE) { session->ssl_config_service()->GetSSLConfig(&ssl_config_); @@ -395,9 +397,19 @@ int HttpNetworkTransaction::RestartWithAuth( NOTREACHED(); return ERR_UNEXPECTED; } + pending_auth_target_ = HttpAuth::AUTH_NONE; - auth_controllers_[target]->ResetAuth(username, password); + DCHECK(auth_identity_[target].invalid || + (username.empty() && password.empty())); + + if (auth_identity_[target].invalid) { + // Update the username/password. + auth_identity_[target].source = HttpAuth::IDENT_SRC_EXTERNAL; + auth_identity_[target].invalid = false; + auth_identity_[target].username = username; + auth_identity_[target].password = password; + } PrepareForAuthRestart(target); @@ -411,6 +423,38 @@ int HttpNetworkTransaction::RestartWithAuth( void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) { DCHECK(HaveAuth(target)); + DCHECK(auth_identity_[target].source != HttpAuth::IDENT_SRC_PATH_LOOKUP); + + // Add the auth entry to the cache before restarting. We don't know whether + // the identity is valid yet, but if it is valid we want other transactions + // to know about it. If an entry for (origin, handler->realm()) already + // exists, we update it. + // + // If auth_identity_[target].source is HttpAuth::IDENT_SRC_NONE or + // HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS, auth_identity_[target] contains + // no identity because identity is not required yet or we're using default + // credentials. + // + // TODO(wtc): For NTLM_SSPI, we add the same auth entry to the cache in + // round 1 and round 2, which is redundant but correct. It would be nice + // to add an auth entry to the cache only once, preferrably in round 1. + // See http://crbug.com/21015. + switch (auth_identity_[target].source) { + case HttpAuth::IDENT_SRC_NONE: + case HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS: + break; + default: + session_->auth_cache()->Add( + AuthOrigin(target), + auth_handler_[target]->realm(), + auth_handler_[target]->scheme(), + auth_handler_[target]->challenge(), + auth_identity_[target].username, + auth_identity_[target].password, + AuthPath(target)); + break; + } + bool keep_alive = false; // Even if the server says the connection is keep-alive, we have to be // able to find the end of each response in order to reuse the connection. @@ -812,16 +856,6 @@ int HttpNetworkTransaction::DoInitConnection() { DCHECK(!connection_->is_initialized()); DCHECK(proxy_info_.proxy_server().is_valid()); - // Now that the proxy server has been resolved, create the auth_controllers_. - for (int i = 0; i < HttpAuth::AUTH_NUM_TARGETS; i++) { - HttpAuth::Target target = static_cast<HttpAuth::Target>(i); - if (!auth_controllers_[target].get()) - auth_controllers_[target].reset(new HttpAuthController(target, - AuthURL(target), - session_, - net_log_)); - } - next_state_ = STATE_INIT_CONNECTION_COMPLETE; using_ssl_ = request_->url.SchemeIs("https") || @@ -969,8 +1003,7 @@ void HttpNetworkTransaction::ClearTunnelState() { int HttpNetworkTransaction::DoTunnelGenerateAuthToken() { next_state_ = STATE_TUNNEL_GENERATE_AUTH_TOKEN_COMPLETE; - return auth_controllers_[HttpAuth::AUTH_PROXY]->MaybeGenerateAuthToken( - request_, &io_callback_); + return MaybeGenerateAuthToken(HttpAuth::AUTH_PROXY); } int HttpNetworkTransaction::DoTunnelGenerateAuthTokenComplete(int rv) { @@ -988,8 +1021,7 @@ int HttpNetworkTransaction::DoTunnelSendRequest() { if (request_headers_.empty()) { HttpRequestHeaders authorization_headers; if (HaveAuth(HttpAuth::AUTH_PROXY)) - auth_controllers_[HttpAuth::AUTH_PROXY]->AddAuthorizationHeader( - &authorization_headers); + AddAuthorizationHeader(HttpAuth::AUTH_PROXY, &authorization_headers); std::string request_line; HttpRequestHeaders request_headers; BuildTunnelRequest(request_, authorization_headers, endpoint_, @@ -1181,8 +1213,7 @@ int HttpNetworkTransaction::DoGenerateProxyAuthToken() { next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE; if (!ShouldApplyProxyAuth()) return OK; - return auth_controllers_[HttpAuth::AUTH_PROXY]->MaybeGenerateAuthToken( - request_, &io_callback_); + return MaybeGenerateAuthToken(HttpAuth::AUTH_PROXY); } int HttpNetworkTransaction::DoGenerateProxyAuthTokenComplete(int rv) { @@ -1196,8 +1227,7 @@ int HttpNetworkTransaction::DoGenerateServerAuthToken() { next_state_ = STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE; if (!ShouldApplyServerAuth()) return OK; - return auth_controllers_[HttpAuth::AUTH_SERVER]->MaybeGenerateAuthToken( - request_, &io_callback_); + return MaybeGenerateAuthToken(HttpAuth::AUTH_SERVER); } int HttpNetworkTransaction::DoGenerateServerAuthTokenComplete(int rv) { @@ -1229,11 +1259,9 @@ int HttpNetworkTransaction::DoSendRequest() { bool have_server_auth = (ShouldApplyServerAuth() && HaveAuth(HttpAuth::AUTH_SERVER)); if (have_proxy_auth) - auth_controllers_[HttpAuth::AUTH_PROXY]->AddAuthorizationHeader( - &authorization_headers); + AddAuthorizationHeader(HttpAuth::AUTH_PROXY, &authorization_headers); if (have_server_auth) - auth_controllers_[HttpAuth::AUTH_SERVER]->AddAuthorizationHeader( - &authorization_headers); + AddAuthorizationHeader(HttpAuth::AUTH_SERVER, &authorization_headers); std::string request_line; HttpRequestHeaders request_headers; BuildRequestHeaders(request_, authorization_headers, request_body, @@ -1381,10 +1409,10 @@ int HttpNetworkTransaction::DoReadHeadersComplete(int result) { } int HttpNetworkTransaction::DoResolveCanonicalName() { - DCHECK(auth_controllers_[pending_auth_target_].get()); + DCHECK(auth_handler_[pending_auth_target_].get()); next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE; - return auth_controllers_[pending_auth_target_]->ResolveCanonicalName( - &io_callback_); + return auth_handler_[pending_auth_target_]-> + ResolveCanonicalName(session_->host_resolver(), &io_callback_); } int HttpNetworkTransaction::DoResolveCanonicalNameComplete(int result) { @@ -1943,6 +1971,203 @@ bool HttpNetworkTransaction::ShouldApplyServerAuth() const { return !(request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA); } +int HttpNetworkTransaction::MaybeGenerateAuthToken(HttpAuth::Target target) { + bool needs_auth = HaveAuth(target) || SelectPreemptiveAuth(target); + if (!needs_auth) + return OK; + const std::wstring* username = NULL; + const std::wstring* password = NULL; + const HttpAuth::Identity& identity = auth_identity_[target]; + if (identity.source != HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS) { + username = &identity.username; + password = &identity.password; + } + DCHECK(auth_token_[target].empty()); + return auth_handler_[target]->GenerateAuthToken( + username, password, request_, &io_callback_, &auth_token_[target]); +} + +void HttpNetworkTransaction::AddAuthorizationHeader( + HttpAuth::Target target, HttpRequestHeaders* authorization_headers) { + DCHECK(HaveAuth(target)); + DCHECK(!auth_token_[target].empty()); + authorization_headers->SetHeader( + HttpAuth::GetAuthorizationHeaderName(target), + auth_token_[target]); + auth_token_[target].clear(); +} + +GURL HttpNetworkTransaction::AuthOrigin(HttpAuth::Target target) const { + GURL origin = PossiblyInvalidAuthOrigin(target); + DCHECK(origin.is_valid()); + return origin; +} + +GURL HttpNetworkTransaction::PossiblyInvalidAuthOrigin( + HttpAuth::Target target) const { + switch (target) { + case HttpAuth::AUTH_PROXY: + if (!proxy_info_.proxy_server().is_valid() || + proxy_info_.proxy_server().is_direct()) { + return GURL(); // There is no proxy server. + } + return GURL("http://" + proxy_info_.proxy_server().host_and_port()); + case HttpAuth::AUTH_SERVER: + return request_->url.GetOrigin(); + default: + return GURL(); + } +} + +std::string HttpNetworkTransaction::AuthPath(HttpAuth::Target target) + const { + // Proxy authentication realms apply to all paths. So we will use + // empty string in place of an absolute path. + return target == HttpAuth::AUTH_PROXY ? + std::string() : request_->url.path(); +} + +// static +std::string HttpNetworkTransaction::AuthTargetString( + HttpAuth::Target target) { + return target == HttpAuth::AUTH_PROXY ? "proxy" : "server"; +} + +void HttpNetworkTransaction::InvalidateRejectedAuthFromCache( + HttpAuth::Target target, + const GURL& auth_origin) { + DCHECK(HaveAuth(target)); + + // TODO(eroman): this short-circuit can be relaxed. If the realm of + // the preemptively used auth entry matches the realm of the subsequent + // challenge, then we can invalidate the preemptively used entry. + // Otherwise as-is we may send the failed credentials one extra time. + if (auth_identity_[target].source == HttpAuth::IDENT_SRC_PATH_LOOKUP) + return; + + // Clear the cache entry for the identity we just failed on. + // Note: we require the username/password to match before invalidating + // since the entry in the cache may be newer than what we used last time. + session_->auth_cache()->Remove(auth_origin, + auth_handler_[target]->realm(), + auth_handler_[target]->scheme(), + auth_identity_[target].username, + auth_identity_[target].password); +} + +bool HttpNetworkTransaction::SelectPreemptiveAuth(HttpAuth::Target target) { + DCHECK(!HaveAuth(target)); + + // Don't do preemptive authorization if the URL contains a username/password, + // since we must first be challenged in order to use the URL's identity. + if (request_->url.has_username()) + return false; + + // SelectPreemptiveAuth() is on the critical path for each request, so it + // is expected to be fast. LookupByPath() is fast in the common case, since + // the number of http auth cache entries is expected to be very small. + // (For most users in fact, it will be 0.) + HttpAuthCache::Entry* entry = session_->auth_cache()->LookupByPath( + AuthOrigin(target), AuthPath(target)); + if (!entry) + return false; + + // Try to create a handler using the previous auth challenge. + scoped_ptr<HttpAuthHandler> handler_preemptive; + int rv_create = session_->http_auth_handler_factory()-> + CreatePreemptiveAuthHandlerFromString( + entry->auth_challenge(), target, AuthOrigin(target), + entry->IncrementNonceCount(), net_log_, &handler_preemptive); + if (rv_create != OK) + return false; + + // Set the state + auth_identity_[target].source = HttpAuth::IDENT_SRC_PATH_LOOKUP; + auth_identity_[target].invalid = false; + auth_identity_[target].username = entry->username(); + auth_identity_[target].password = entry->password(); + auth_handler_[target].swap(handler_preemptive); + return true; +} + +bool HttpNetworkTransaction::SelectNextAuthIdentityToTry( + HttpAuth::Target target, + const GURL& auth_origin) { + DCHECK(auth_handler_[target].get()); + DCHECK(auth_identity_[target].invalid); + + // Try to use the username/password encoded into the URL first. + if (target == HttpAuth::AUTH_SERVER && request_->url.has_username() && + !embedded_identity_used_) { + auth_identity_[target].source = HttpAuth::IDENT_SRC_URL; + auth_identity_[target].invalid = false; + // Extract the username:password from the URL. + GetIdentityFromURL(request_->url, + &auth_identity_[target].username, + &auth_identity_[target].password); + embedded_identity_used_ = true; + // TODO(eroman): If the password is blank, should we also try combining + // with a password from the cache? + return true; + } + + // Check the auth cache for a realm entry. + HttpAuthCache::Entry* entry = + session_->auth_cache()->Lookup(auth_origin, auth_handler_[target]->realm(), + auth_handler_[target]->scheme()); + + if (entry) { + auth_identity_[target].source = HttpAuth::IDENT_SRC_REALM_LOOKUP; + auth_identity_[target].invalid = false; + auth_identity_[target].username = entry->username(); + auth_identity_[target].password = entry->password(); + return true; + } + + // Use default credentials (single sign on) if this is the first attempt + // at identity. Do not allow multiple times as it will infinite loop. + // We use default credentials after checking the auth cache so that if + // single sign-on doesn't work, we won't try default credentials for future + // transactions. + if (!default_credentials_used_ && + auth_handler_[target]->AllowsDefaultCredentials()) { + auth_identity_[target].source = HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS; + auth_identity_[target].invalid = false; + default_credentials_used_ = true; + return true; + } + + return false; +} + +std::string HttpNetworkTransaction::AuthChallengeLogMessage() const { + std::string msg; + std::string header_val; + void* iter = NULL; + scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders(); + while (headers->EnumerateHeader(&iter, "proxy-authenticate", &header_val)) { + msg.append("\n Has header Proxy-Authenticate: "); + msg.append(header_val); + } + + iter = NULL; + while (headers->EnumerateHeader(&iter, "www-authenticate", &header_val)) { + msg.append("\n Has header WWW-Authenticate: "); + msg.append(header_val); + } + + // RFC 4559 requires that a proxy indicate its support of NTLM/Negotiate + // authentication with a "Proxy-Support: Session-Based-Authentication" + // response header. + iter = NULL; + while (headers->EnumerateHeader(&iter, "proxy-support", &header_val)) { + msg.append("\n Has header Proxy-Support: "); + msg.append(header_val); + } + + return msg; +} + int HttpNetworkTransaction::HandleAuthChallenge(bool establishing_tunnel) { scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders(); DCHECK(headers); @@ -1952,41 +2177,99 @@ int HttpNetworkTransaction::HandleAuthChallenge(bool establishing_tunnel) { return OK; HttpAuth::Target target = status == 407 ? HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER; + GURL auth_origin = PossiblyInvalidAuthOrigin(target); + + LOG(INFO) << "The " << AuthTargetString(target) << " " + << auth_origin << " requested auth" + << AuthChallengeLogMessage(); + if (target == HttpAuth::AUTH_PROXY && proxy_info_.is_direct()) return ERR_UNEXPECTED_PROXY_AUTH; + DCHECK(auth_origin.is_valid()); + + // The auth we tried just failed, hence it can't be valid. Remove it from + // the cache so it won't be used again. + // TODO(wtc): IsFinalRound is not the right condition. In a multi-round + // auth sequence, the server may fail the auth in round 1 if our first + // authorization header is broken. We should inspect response_.headers to + // determine if the server already failed the auth or wants us to continue. + // See http://crbug.com/21015. + if (HaveAuth(target) && auth_handler_[target]->IsFinalRound()) { + InvalidateRejectedAuthFromCache(target, auth_origin); + auth_handler_[target].reset(); + auth_identity_[target] = HttpAuth::Identity(); + } + + auth_identity_[target].invalid = true; + + if (target != HttpAuth::AUTH_SERVER || + !(request_->load_flags & LOAD_DO_NOT_SEND_AUTH_DATA)) { + // Find the best authentication challenge that we support. + HttpAuth::ChooseBestChallenge(session_->http_auth_handler_factory(), + headers, target, auth_origin, net_log_, + &auth_handler_[target]); + } + + if (!auth_handler_[target].get()) { + if (establishing_tunnel) { + LOG(ERROR) << "Can't perform auth to the " << AuthTargetString(target) + << " " << auth_origin << " when establishing a tunnel" + << AuthChallengeLogMessage(); + + // We are establishing a tunnel, we can't show the error page because an + // active network attacker could control its contents. Instead, we just + // fail to establish the tunnel. + DCHECK(target == HttpAuth::AUTH_PROXY); + return ERR_PROXY_AUTH_REQUESTED; + } + // We found no supported challenge -- let the transaction continue + // so we end up displaying the error page. + return OK; + } - int rv = auth_controllers_[target]->HandleAuthChallenge(headers, - request_->load_flags, - establishing_tunnel); - if (auth_controllers_[target]->HaveAuthHandler()) - pending_auth_target_ = target; + if (auth_handler_[target]->NeedsIdentity()) { + // Pick a new auth identity to try, by looking to the URL and auth cache. + // If an identity to try is found, it is saved to auth_identity_[target]. + SelectNextAuthIdentityToTry(target, auth_origin); + } else { + // Proceed with the existing identity or a null identity. + // + // TODO(wtc): Add a safeguard against infinite transaction restarts, if + // the server keeps returning "NTLM". + auth_identity_[target].invalid = false; + } - scoped_refptr<AuthChallengeInfo> auth_info = - auth_controllers_[target]->auth_info(); - if (auth_info.get()) - response_.auth_challenge = auth_info; + // Make a note that we are waiting for auth. This variable is inspected + // when the client calls RestartWithAuth() to pick up where we left off. + pending_auth_target_ = target; - if (rv == ERR_AUTH_NEEDS_CANONICAL_NAME) { - next_state_ = STATE_RESOLVE_CANONICAL_NAME; - rv = OK; + if (auth_identity_[target].invalid) { + // We have exhausted all identity possibilities, all we can do now is + // pass the challenge information back to the client. + PopulateAuthChallenge(target, auth_origin); } - return rv; + // SPN determination (for Negotiate) requires a DNS lookup to find the + // canonical name. This needs to be done asynchronously to prevent blocking + // the IO thread. + if (auth_handler_[target]->NeedsCanonicalName()) + next_state_ = STATE_RESOLVE_CANONICAL_NAME; + + return OK; } -GURL HttpNetworkTransaction::AuthURL(HttpAuth::Target target) const { - switch (target) { - case HttpAuth::AUTH_PROXY: - if (!proxy_info_.proxy_server().is_valid() || - proxy_info_.proxy_server().is_direct()) { - return GURL(); // There is no proxy server. - } - return GURL("http://" + proxy_info_.proxy_server().host_and_port()); - case HttpAuth::AUTH_SERVER: - return request_->url; - default: - return GURL(); - } +void HttpNetworkTransaction::PopulateAuthChallenge(HttpAuth::Target target, + const GURL& auth_origin) { + // Populates response_.auth_challenge with the authentication challenge info. + // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo(). + + AuthChallengeInfo* auth_info = new AuthChallengeInfo; + auth_info->is_proxy = target == HttpAuth::AUTH_PROXY; + auth_info->host_and_port = ASCIIToWide(GetHostAndPort(auth_origin)); + auth_info->scheme = ASCIIToWide(auth_handler_[target]->scheme()); + // TODO(eroman): decode realm according to RFC 2047. + auth_info->realm = ASCIIToWide(auth_handler_[target]->realm()); + response_.auth_challenge = auth_info; } void HttpNetworkTransaction::MarkBrokenAlternateProtocolAndFallback() { diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h index f02deb4..147c5e2 100644 --- a/net/http/http_network_transaction.h +++ b/net/http/http_network_transaction.h @@ -20,7 +20,6 @@ #include "net/base/ssl_config_service.h" #include "net/http/http_alternate_protocols.h" #include "net/http/http_auth.h" -#include "net/http/http_auth_controller.h" #include "net/http/http_auth_handler.h" #include "net/http/http_response_info.h" #include "net/http/http_transaction.h" @@ -241,24 +240,84 @@ class HttpNetworkTransaction : public HttpTransaction { // Returns true if we should try to add an Authorization header. bool ShouldApplyServerAuth() const; + // Adds either the proxy auth header, or the origin server auth header, + // as specified by |target|. + void AddAuthorizationHeader( + HttpAuth::Target target, HttpRequestHeaders* authorization_headers); + + // Returns a log message for all the response headers related to the auth + // challenge. + std::string AuthChallengeLogMessage() const; + // Handles HTTP status code 401 or 407. // HandleAuthChallenge() returns a network error code, or OK on success. // May update |pending_auth_target_| or |response_.auth_challenge|. int HandleAuthChallenge(bool establishing_tunnel); + // Populates response_.auth_challenge with the challenge information, so that + // URLRequestHttpJob can prompt for a username/password. + void PopulateAuthChallenge(HttpAuth::Target target, + const GURL& auth_origin); + + // Invalidates any auth cache entries after authentication has failed. + // The identity that was rejected is auth_identity_[target]. + void InvalidateRejectedAuthFromCache(HttpAuth::Target target, + const GURL& auth_origin); + + // Sets auth_identity_[target] to the next identity that the transaction + // should try. It chooses candidates by searching the auth cache + // and the URL for a username:password. Returns true if an identity + // was found. + bool SelectNextAuthIdentityToTry(HttpAuth::Target target, + const GURL& auth_origin); + + // Searches the auth cache for an entry that encompasses the request's path. + // If such an entry is found, updates auth_identity_[target] and + // auth_handler_[target] with the cache entry's data and returns true. + bool SelectPreemptiveAuth(HttpAuth::Target target); + bool HaveAuth(HttpAuth::Target target) const { - return auth_controllers_[target].get() && - auth_controllers_[target]->HaveAuth(); + return auth_handler_[target].get() && !auth_identity_[target].invalid; } - // Get the {scheme, host, path, port} for the authentication target - GURL AuthURL(HttpAuth::Target target) const; + // Get the {scheme, host, port} for the authentication target + GURL AuthOrigin(HttpAuth::Target target) const; + + // Same as AuthOrigin(), but will return an invalid GURL if the target is + // invalid. + GURL PossiblyInvalidAuthOrigin(HttpAuth::Target target) const; + + // Get the absolute path of the resource needing authentication. + // For proxy authentication the path is always empty string. + std::string AuthPath(HttpAuth::Target target) const; + + // Generate an authentication token for |target| if necessary. The return + // value is a net error code. |OK| will be returned both in the case that + // a token is correctly generated synchronously, as well as when no tokens + // were necessary. + int MaybeGenerateAuthToken(HttpAuth::Target target); void MarkBrokenAlternateProtocolAndFallback(); + // Returns a string representation of a HttpAuth::Target value that can be + // used in log messages. + static std::string AuthTargetString(HttpAuth::Target target); + static bool g_ignore_certificate_errors; - scoped_ptr<HttpAuthController> auth_controllers_[HttpAuth::AUTH_NUM_TARGETS]; + // |auth_handler_| encapsulates the logic for the particular auth-scheme. + // This includes the challenge's parameters. If NULL, then there is no + // associated auth handler. + scoped_ptr<HttpAuthHandler> auth_handler_[HttpAuth::AUTH_NUM_TARGETS]; + + // |auth_identity_| holds the (username/password) that should be used by + // the |auth_handler_| to generate credentials. This identity can come from + // a number of places (url, cache, prompt). + HttpAuth::Identity auth_identity_[HttpAuth::AUTH_NUM_TARGETS]; + + // |auth_token_| contains the opaque string to pass to the proxy or + // server to authenticate the client. + std::string auth_token_[HttpAuth::AUTH_NUM_TARGETS]; // Whether this transaction is waiting for proxy auth, server auth, or is // not waiting for any auth at all. |pending_auth_target_| is read and @@ -300,6 +359,15 @@ class HttpNetworkTransaction : public HttpTransaction { // Only valid if |alternate_protocol_mode_| == kUsingAlternateProtocol. HttpAlternateProtocols::Protocol alternate_protocol_; + // True if we've used the username/password embedded in the URL. This + // makes sure we use the embedded identity only once for the transaction, + // preventing an infinite auth restart loop. + bool embedded_identity_used_; + + // True if default credentials have already been tried for this transaction + // in response to an HTTP authentication challenge. + bool default_credentials_used_; + SSLConfig ssl_config_; std::string request_headers_; diff --git a/net/net.gyp b/net/net.gyp index 5e5d971..77f4457e 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -327,8 +327,6 @@ 'http/http_auth.h', 'http/http_auth_cache.cc', 'http/http_auth_cache.h', - 'http/http_auth_controller.cc', - 'http/http_auth_controller.h', 'http/http_auth_filter.cc', 'http/http_auth_filter.h', 'http/http_auth_filter_win.h', |