summaryrefslogtreecommitdiff
path: root/chromium/net/http/http_auth_controller.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/http/http_auth_controller.cc')
-rw-r--r--chromium/net/http/http_auth_controller.cc573
1 files changed, 573 insertions, 0 deletions
diff --git a/chromium/net/http/http_auth_controller.cc b/chromium/net/http/http_auth_controller.cc
new file mode 100644
index 00000000000..e9d6171ab5a
--- /dev/null
+++ b/chromium/net/http/http_auth_controller.cc
@@ -0,0 +1,573 @@
+// 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);
+}
+
+} // namespace net