From 228404f535e3fcadc063c0659f1986bf8dc79995 Mon Sep 17 00:00:00 2001 From: "vandebo@chromium.org" Date: Thu, 24 Jun 2010 04:31:41 +0000 Subject: Create HttpAuthController. (again) This packages up the auth state into a single class to enable a HttpProxyClientSocket class (which is needed for SSLClientSocketPool). Fix memory leak. BUG=30357 TEST=existing unit tests Review URL: http://codereview.chromium.org/2808020 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50696 0039d316-1c4b-4281-b951-d872f2087c98 --- net/base/net_error_list.h | 3 + net/http/http_auth.cc | 6 + net/http/http_auth.h | 4 + net/http/http_auth_controller.cc | 331 ++++++++++++++++++++++++++++++ net/http/http_auth_controller.h | 134 ++++++++++++ net/http/http_network_transaction.cc | 387 +++++------------------------------ net/http/http_network_transaction.h | 80 +------- net/net.gyp | 2 + 8 files changed, 538 insertions(+), 409 deletions(-) create mode 100644 net/http/http_auth_controller.cc create mode 100644 net/http/http_auth_controller.h (limited to 'net') diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index 517ee4da..1b21084 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -156,6 +156,9 @@ 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 09214e4..75b55a3 100644 --- a/net/http/http_auth.cc +++ b/net/http/http_auth.cc @@ -169,4 +169,10 @@ 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 bba633d..09b6f369 100644 --- a/net/http/http_auth.h +++ b/net/http/http_auth.h @@ -78,6 +78,10 @@ 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 new file mode 100644 index 0000000..b85ae03 --- /dev/null +++ b/net/http/http_auth_controller.cc @@ -0,0 +1,331 @@ +// 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 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 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 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 new file mode 100644 index 0000000..bc1395f --- /dev/null +++ b/net/http/http_auth_controller.h @@ -0,0 +1,134 @@ +// 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 + +#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 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 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; + } + + scoped_refptr auth_info() { + return auth_info_; + } + + 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 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 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 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 72b1b1b..c2c7cbe 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -296,8 +296,6 @@ 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_); @@ -397,19 +395,9 @@ int HttpNetworkTransaction::RestartWithAuth( NOTREACHED(); return ERR_UNEXPECTED; } - pending_auth_target_ = HttpAuth::AUTH_NONE; - 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; - } + auth_controllers_[target]->ResetAuth(username, password); PrepareForAuthRestart(target); @@ -423,38 +411,6 @@ 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. @@ -856,6 +812,16 @@ 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(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") || @@ -1003,7 +969,8 @@ void HttpNetworkTransaction::ClearTunnelState() { int HttpNetworkTransaction::DoTunnelGenerateAuthToken() { next_state_ = STATE_TUNNEL_GENERATE_AUTH_TOKEN_COMPLETE; - return MaybeGenerateAuthToken(HttpAuth::AUTH_PROXY); + return auth_controllers_[HttpAuth::AUTH_PROXY]->MaybeGenerateAuthToken( + request_, &io_callback_); } int HttpNetworkTransaction::DoTunnelGenerateAuthTokenComplete(int rv) { @@ -1021,7 +988,8 @@ int HttpNetworkTransaction::DoTunnelSendRequest() { if (request_headers_.empty()) { HttpRequestHeaders authorization_headers; if (HaveAuth(HttpAuth::AUTH_PROXY)) - AddAuthorizationHeader(HttpAuth::AUTH_PROXY, &authorization_headers); + auth_controllers_[HttpAuth::AUTH_PROXY]->AddAuthorizationHeader( + &authorization_headers); std::string request_line; HttpRequestHeaders request_headers; BuildTunnelRequest(request_, authorization_headers, endpoint_, @@ -1213,7 +1181,8 @@ int HttpNetworkTransaction::DoGenerateProxyAuthToken() { next_state_ = STATE_GENERATE_PROXY_AUTH_TOKEN_COMPLETE; if (!ShouldApplyProxyAuth()) return OK; - return MaybeGenerateAuthToken(HttpAuth::AUTH_PROXY); + return auth_controllers_[HttpAuth::AUTH_PROXY]->MaybeGenerateAuthToken( + request_, &io_callback_); } int HttpNetworkTransaction::DoGenerateProxyAuthTokenComplete(int rv) { @@ -1227,7 +1196,8 @@ int HttpNetworkTransaction::DoGenerateServerAuthToken() { next_state_ = STATE_GENERATE_SERVER_AUTH_TOKEN_COMPLETE; if (!ShouldApplyServerAuth()) return OK; - return MaybeGenerateAuthToken(HttpAuth::AUTH_SERVER); + return auth_controllers_[HttpAuth::AUTH_SERVER]->MaybeGenerateAuthToken( + request_, &io_callback_); } int HttpNetworkTransaction::DoGenerateServerAuthTokenComplete(int rv) { @@ -1259,9 +1229,11 @@ int HttpNetworkTransaction::DoSendRequest() { bool have_server_auth = (ShouldApplyServerAuth() && HaveAuth(HttpAuth::AUTH_SERVER)); if (have_proxy_auth) - AddAuthorizationHeader(HttpAuth::AUTH_PROXY, &authorization_headers); + auth_controllers_[HttpAuth::AUTH_PROXY]->AddAuthorizationHeader( + &authorization_headers); if (have_server_auth) - AddAuthorizationHeader(HttpAuth::AUTH_SERVER, &authorization_headers); + auth_controllers_[HttpAuth::AUTH_SERVER]->AddAuthorizationHeader( + &authorization_headers); std::string request_line; HttpRequestHeaders request_headers; BuildRequestHeaders(request_, authorization_headers, request_body, @@ -1409,10 +1381,10 @@ int HttpNetworkTransaction::DoReadHeadersComplete(int result) { } int HttpNetworkTransaction::DoResolveCanonicalName() { - DCHECK(auth_handler_[pending_auth_target_].get()); + DCHECK(auth_controllers_[pending_auth_target_].get()); next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE; - return auth_handler_[pending_auth_target_]-> - ResolveCanonicalName(session_->host_resolver(), &io_callback_); + return auth_controllers_[pending_auth_target_]->ResolveCanonicalName( + &io_callback_); } int HttpNetworkTransaction::DoResolveCanonicalNameComplete(int result) { @@ -1973,203 +1945,6 @@ 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 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 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 headers = GetResponseHeaders(); DCHECK(headers); @@ -2179,99 +1954,41 @@ 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; - } - 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; - } + int rv = auth_controllers_[target]->HandleAuthChallenge(headers, + request_->load_flags, + establishing_tunnel); + if (auth_controllers_[target]->HaveAuthHandler()) + pending_auth_target_ = target; - // 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; + scoped_refptr auth_info = + auth_controllers_[target]->auth_info(); + if (auth_info.get()) + response_.auth_challenge = auth_info; - 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); + if (rv == ERR_AUTH_NEEDS_CANONICAL_NAME) { + next_state_ = STATE_RESOLVE_CANONICAL_NAME; + rv = OK; } - // 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; + return rv; } -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; +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::MarkBrokenAlternateProtocolAndFallback() { diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h index 147c5e2..f02deb4 100644 --- a/net/http/http_network_transaction.h +++ b/net/http/http_network_transaction.h @@ -20,6 +20,7 @@ #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" @@ -240,84 +241,24 @@ 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_handler_[target].get() && !auth_identity_[target].invalid; + return auth_controllers_[target].get() && + auth_controllers_[target]->HaveAuth(); } - // 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); + // Get the {scheme, host, path, port} for the authentication target + GURL AuthURL(HttpAuth::Target target) const; 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; - // |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 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]; + scoped_ptr auth_controllers_[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 @@ -359,15 +300,6 @@ 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 257825d..1ed0833 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -326,6 +326,8 @@ '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', -- cgit v1.1