Index: net/http/http_auth_controller.cc |
diff --git a/net/http/http_auth_controller.cc b/net/http/http_auth_controller.cc |
deleted file mode 100644 |
index 9cc57de35aadec7396d8c383bc6584f93bc1fb90..0000000000000000000000000000000000000000 |
--- a/net/http/http_auth_controller.cc |
+++ /dev/null |
@@ -1,578 +0,0 @@ |
-// Copyright (c) 2012 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/bind.h" |
-#include "base/bind_helpers.h" |
-#include "base/metrics/histogram.h" |
-#include "base/strings/string_util.h" |
-#include "base/strings/utf_string_conversions.h" |
-#include "base/threading/platform_thread.h" |
-#include "net/base/auth.h" |
-#include "net/base/net_util.h" |
-#include "net/dns/host_resolver.h" |
-#include "net/http/http_auth_handler.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" |
-#include "net/http/http_response_headers.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; |
-} |
- |
-enum AuthEvent { |
- AUTH_EVENT_START = 0, |
- AUTH_EVENT_REJECT, |
- AUTH_EVENT_MAX, |
-}; |
- |
-enum AuthTarget { |
- AUTH_TARGET_PROXY = 0, |
- AUTH_TARGET_SECURE_PROXY, |
- AUTH_TARGET_SERVER, |
- AUTH_TARGET_SECURE_SERVER, |
- AUTH_TARGET_MAX, |
-}; |
- |
-AuthTarget DetermineAuthTarget(const HttpAuthHandler* handler) { |
- switch (handler->target()) { |
- case HttpAuth::AUTH_PROXY: |
- if (handler->origin().SchemeIsSecure()) |
- return AUTH_TARGET_SECURE_PROXY; |
- else |
- return AUTH_TARGET_PROXY; |
- case HttpAuth::AUTH_SERVER: |
- if (handler->origin().SchemeIsSecure()) |
- return AUTH_TARGET_SECURE_SERVER; |
- else |
- return AUTH_TARGET_SERVER; |
- default: |
- NOTREACHED(); |
- return AUTH_TARGET_MAX; |
- } |
-} |
- |
-// Records the number of authentication events per authentication scheme. |
-void HistogramAuthEvent(HttpAuthHandler* handler, AuthEvent auth_event) { |
-#if !defined(NDEBUG) |
- // Note: The on-same-thread check is intentionally not using a lock |
- // to protect access to first_thread. This method is meant to be only |
- // used on the same thread, in which case there are no race conditions. If |
- // there are race conditions (say, a read completes during a partial write), |
- // the DCHECK will correctly fail. |
- static base::PlatformThreadId first_thread = |
- base::PlatformThread::CurrentId(); |
- DCHECK_EQ(first_thread, base::PlatformThread::CurrentId()); |
-#endif |
- |
- HttpAuth::Scheme auth_scheme = handler->auth_scheme(); |
- DCHECK(auth_scheme >= 0 && auth_scheme < HttpAuth::AUTH_SCHEME_MAX); |
- |
- // Record start and rejection events for authentication. |
- // |
- // The results map to: |
- // Basic Start: 0 |
- // Basic Reject: 1 |
- // Digest Start: 2 |
- // Digest Reject: 3 |
- // NTLM Start: 4 |
- // NTLM Reject: 5 |
- // Negotiate Start: 6 |
- // Negotiate Reject: 7 |
- static const int kEventBucketsEnd = |
- HttpAuth::AUTH_SCHEME_MAX * AUTH_EVENT_MAX; |
- int event_bucket = auth_scheme * AUTH_EVENT_MAX + auth_event; |
- DCHECK(event_bucket >= 0 && event_bucket < kEventBucketsEnd); |
- UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthCount", event_bucket, |
- kEventBucketsEnd); |
- |
- // Record the target of the authentication. |
- // |
- // The results map to: |
- // Basic Proxy: 0 |
- // Basic Secure Proxy: 1 |
- // Basic Server: 2 |
- // Basic Secure Server: 3 |
- // Digest Proxy: 4 |
- // Digest Secure Proxy: 5 |
- // Digest Server: 6 |
- // Digest Secure Server: 7 |
- // NTLM Proxy: 8 |
- // NTLM Secure Proxy: 9 |
- // NTLM Server: 10 |
- // NTLM Secure Server: 11 |
- // Negotiate Proxy: 12 |
- // Negotiate Secure Proxy: 13 |
- // Negotiate Server: 14 |
- // Negotiate Secure Server: 15 |
- if (auth_event != AUTH_EVENT_START) |
- return; |
- static const int kTargetBucketsEnd = |
- HttpAuth::AUTH_SCHEME_MAX * AUTH_TARGET_MAX; |
- AuthTarget auth_target = DetermineAuthTarget(handler); |
- int target_bucket = auth_scheme * AUTH_TARGET_MAX + auth_target; |
- DCHECK(target_bucket >= 0 && target_bucket < kTargetBucketsEnd); |
- UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthTarget", target_bucket, |
- kTargetBucketsEnd); |
-} |
- |
-} // namespace |
- |
-HttpAuthController::HttpAuthController( |
- HttpAuth::Target target, |
- const GURL& auth_url, |
- HttpAuthCache* http_auth_cache, |
- HttpAuthHandlerFactory* http_auth_handler_factory) |
- : 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), |
- http_auth_cache_(http_auth_cache), |
- http_auth_handler_factory_(http_auth_handler_factory) { |
-} |
- |
-HttpAuthController::~HttpAuthController() { |
- DCHECK(CalledOnValidThread()); |
-} |
- |
-int HttpAuthController::MaybeGenerateAuthToken( |
- const HttpRequestInfo* request, const CompletionCallback& callback, |
- const BoundNetLog& net_log) { |
- DCHECK(CalledOnValidThread()); |
- bool needs_auth = HaveAuth() || SelectPreemptiveAuth(net_log); |
- if (!needs_auth) |
- return OK; |
- const AuthCredentials* credentials = NULL; |
- if (identity_.source != HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS) |
- credentials = &identity_.credentials; |
- DCHECK(auth_token_.empty()); |
- DCHECK(callback_.is_null()); |
- int rv = handler_->GenerateAuthToken( |
- credentials, request, |
- base::Bind(&HttpAuthController::OnIOComplete, base::Unretained(this)), |
- &auth_token_); |
- if (DisableOnAuthHandlerResult(rv)) |
- rv = OK; |
- if (rv == ERR_IO_PENDING) |
- callback_ = callback; |
- else |
- OnIOComplete(rv); |
- return rv; |
-} |
- |
-bool HttpAuthController::SelectPreemptiveAuth(const BoundNetLog& net_log) { |
- DCHECK(CalledOnValidThread()); |
- 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 = http_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 = 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_.credentials = entry->credentials(); |
- handler_.swap(handler_preemptive); |
- return true; |
-} |
- |
-void HttpAuthController::AddAuthorizationHeader( |
- HttpRequestHeaders* authorization_headers) { |
- DCHECK(CalledOnValidThread()); |
- DCHECK(HaveAuth()); |
- // auth_token_ can be empty if we encountered a permanent error with |
- // the auth scheme and want to retry. |
- if (!auth_token_.empty()) { |
- authorization_headers->SetHeader( |
- HttpAuth::GetAuthorizationHeaderName(target_), auth_token_); |
- auth_token_.clear(); |
- } |
-} |
- |
-int HttpAuthController::HandleAuthChallenge( |
- scoped_refptr<HttpResponseHeaders> headers, |
- bool do_not_send_server_auth, |
- bool establishing_tunnel, |
- const BoundNetLog& net_log) { |
- DCHECK(CalledOnValidThread()); |
- DCHECK(headers.get()); |
- DCHECK(auth_origin_.is_valid()); |
- VLOG(1) << "The " << HttpAuth::GetAuthTargetString(target_) << " " |
- << auth_origin_ << " requested auth " |
- << AuthChallengeLogMessage(headers.get()); |
- |
- // Give the existing auth handler first try at the authentication headers. |
- // This will also evict the entry in the HttpAuthCache if the previous |
- // challenge appeared to be rejected, or is using a stale nonce in the Digest |
- // case. |
- if (HaveAuth()) { |
- std::string challenge_used; |
- HttpAuth::AuthorizationResult result = |
- HttpAuth::HandleChallengeResponse(handler_.get(), |
- headers.get(), |
- target_, |
- disabled_schemes_, |
- &challenge_used); |
- switch (result) { |
- case HttpAuth::AUTHORIZATION_RESULT_ACCEPT: |
- break; |
- case HttpAuth::AUTHORIZATION_RESULT_INVALID: |
- InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS); |
- break; |
- case HttpAuth::AUTHORIZATION_RESULT_REJECT: |
- HistogramAuthEvent(handler_.get(), AUTH_EVENT_REJECT); |
- InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS); |
- break; |
- case HttpAuth::AUTHORIZATION_RESULT_STALE: |
- if (http_auth_cache_->UpdateStaleChallenge(auth_origin_, |
- handler_->realm(), |
- handler_->auth_scheme(), |
- challenge_used)) { |
- InvalidateCurrentHandler(INVALIDATE_HANDLER); |
- } else { |
- // It's possible that a server could incorrectly issue a stale |
- // response when the entry is not in the cache. Just evict the |
- // current value from the cache. |
- InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS); |
- } |
- break; |
- case HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM: |
- // If the server changes the authentication realm in a |
- // subsequent challenge, invalidate cached credentials for the |
- // previous realm. If the server rejects a preemptive |
- // authorization and requests credentials for a different |
- // realm, we keep the cached credentials. |
- InvalidateCurrentHandler( |
- (identity_.source == HttpAuth::IDENT_SRC_PATH_LOOKUP) ? |
- INVALIDATE_HANDLER : |
- INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS); |
- break; |
- default: |
- NOTREACHED(); |
- break; |
- } |
- } |
- |
- identity_.invalid = true; |
- |
- bool can_send_auth = (target_ != HttpAuth::AUTH_SERVER || |
- !do_not_send_server_auth); |
- |
- do { |
- if (!handler_.get() && can_send_auth) { |
- // Find the best authentication challenge that we support. |
- HttpAuth::ChooseBestChallenge(http_auth_handler_factory_, |
- headers.get(), |
- target_, |
- auth_origin_, |
- disabled_schemes_, |
- net_log, |
- &handler_); |
- if (handler_.get()) |
- HistogramAuthEvent(handler_.get(), AUTH_EVENT_START); |
- } |
- |
- 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_UNSUPPORTED; |
- } |
- // 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. |
- identity_.invalid = false; |
- } |
- |
- // From this point on, we are restartable. |
- |
- if (identity_.invalid) { |
- // We have exhausted all identity possibilities. |
- if (!handler_->AllowsExplicitCredentials()) { |
- // If the handler doesn't accept explicit credentials, then we need to |
- // choose a different auth scheme. |
- HistogramAuthEvent(handler_.get(), AUTH_EVENT_REJECT); |
- InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_DISABLE_SCHEME); |
- } else { |
- // Pass the challenge information back to the client. |
- PopulateAuthChallenge(); |
- } |
- } else { |
- auth_info_ = NULL; |
- } |
- |
- // If we get here and we don't have a handler_, that's because we |
- // invalidated it due to not having any viable identities to use with it. Go |
- // back and try again. |
- // TODO(asanka): Instead we should create a priority list of |
- // <handler,identity> and iterate through that. |
- } while(!handler_.get()); |
- return OK; |
-} |
- |
-void HttpAuthController::ResetAuth(const AuthCredentials& credentials) { |
- DCHECK(CalledOnValidThread()); |
- DCHECK(identity_.invalid || credentials.Empty()); |
- |
- if (identity_.invalid) { |
- // Update the credentials. |
- identity_.source = HttpAuth::IDENT_SRC_EXTERNAL; |
- identity_.invalid = false; |
- identity_.credentials = credentials; |
- } |
- |
- 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: |
- http_auth_cache_->Add(auth_origin_, handler_->realm(), |
- handler_->auth_scheme(), handler_->challenge(), |
- identity_.credentials, auth_path_); |
- break; |
- } |
-} |
- |
-bool HttpAuthController::HaveAuthHandler() const { |
- return handler_.get() != NULL; |
-} |
- |
-bool HttpAuthController::HaveAuth() const { |
- return handler_.get() && !identity_.invalid; |
-} |
- |
-void HttpAuthController::InvalidateCurrentHandler( |
- InvalidateHandlerAction action) { |
- DCHECK(CalledOnValidThread()); |
- DCHECK(handler_.get()); |
- |
- if (action == INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS) |
- InvalidateRejectedAuthFromCache(); |
- if (action == INVALIDATE_HANDLER_AND_DISABLE_SCHEME) |
- DisableAuthScheme(handler_->auth_scheme()); |
- handler_.reset(); |
- identity_ = HttpAuth::Identity(); |
-} |
- |
-void HttpAuthController::InvalidateRejectedAuthFromCache() { |
- DCHECK(CalledOnValidThread()); |
- DCHECK(HaveAuth()); |
- |
- // Clear the cache entry for the identity we just failed on. |
- // Note: we require the credentials to match before invalidating |
- // since the entry in the cache may be newer than what we used last time. |
- http_auth_cache_->Remove(auth_origin_, handler_->realm(), |
- handler_->auth_scheme(), identity_.credentials); |
-} |
- |
-bool HttpAuthController::SelectNextAuthIdentityToTry() { |
- DCHECK(CalledOnValidThread()); |
- 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. |
- base::string16 username; |
- base::string16 password; |
- GetIdentityFromURL(auth_url_, &username, &password); |
- identity_.credentials.Set(username, password); |
- embedded_identity_used_ = true; |
- // TODO(eroman): If the password is blank, should we also try combining |
- // with a password from the cache? |
- UMA_HISTOGRAM_BOOLEAN("net.HttpIdentSrcURL", true); |
- return true; |
- } |
- |
- // Check the auth cache for a realm entry. |
- HttpAuthCache::Entry* entry = |
- http_auth_cache_->Lookup(auth_origin_, handler_->realm(), |
- handler_->auth_scheme()); |
- |
- if (entry) { |
- identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP; |
- identity_.invalid = false; |
- identity_.credentials = entry->credentials(); |
- 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() { |
- DCHECK(CalledOnValidThread()); |
- |
- // 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_->challenger = HostPortPair::FromURL(auth_origin_); |
- auth_info_->scheme = HttpAuth::SchemeToString(handler_->auth_scheme()); |
- auth_info_->realm = handler_->realm(); |
-} |
- |
-bool HttpAuthController::DisableOnAuthHandlerResult(int result) { |
- DCHECK(CalledOnValidThread()); |
- |
- switch (result) { |
- // Occurs with GSSAPI, if the user has not already logged in. |
- case ERR_MISSING_AUTH_CREDENTIALS: |
- |
- // Can occur with GSSAPI or SSPI if the underlying library reports |
- // a permanent error. |
- case ERR_UNSUPPORTED_AUTH_SCHEME: |
- |
- // These two error codes represent failures we aren't handling. |
- case ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS: |
- case ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS: |
- |
- // Can be returned by SSPI if the authenticating authority or |
- // target is not known. |
- case ERR_MISCONFIGURED_AUTH_ENVIRONMENT: |
- |
- // In these cases, disable the current scheme as it cannot |
- // succeed. |
- DisableAuthScheme(handler_->auth_scheme()); |
- auth_token_.clear(); |
- return true; |
- |
- default: |
- return false; |
- } |
-} |
- |
-void HttpAuthController::OnIOComplete(int result) { |
- DCHECK(CalledOnValidThread()); |
- if (DisableOnAuthHandlerResult(result)) |
- result = OK; |
- if (!callback_.is_null()) { |
- CompletionCallback c = callback_; |
- callback_.Reset(); |
- c.Run(result); |
- } |
-} |
- |
-scoped_refptr<AuthChallengeInfo> HttpAuthController::auth_info() { |
- DCHECK(CalledOnValidThread()); |
- return auth_info_; |
-} |
- |
-bool HttpAuthController::IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const { |
- DCHECK(CalledOnValidThread()); |
- return disabled_schemes_.find(scheme) != disabled_schemes_.end(); |
-} |
- |
-void HttpAuthController::DisableAuthScheme(HttpAuth::Scheme scheme) { |
- DCHECK(CalledOnValidThread()); |
- disabled_schemes_.insert(scheme); |
-} |
- |
-void HttpAuthController::DisableEmbeddedIdentity() { |
- DCHECK(CalledOnValidThread()); |
- embedded_identity_used_ = true; |
-} |
- |
-} // namespace net |