|  | // 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/string_util.h" | 
|  | #include "base/threading/platform_thread.h" | 
|  | #include "base/utf_string_conversions.h" | 
|  | #include "net/base/auth.h" | 
|  | #include "net/base/host_resolver.h" | 
|  | #include "net/base/net_util.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); | 
|  | 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, 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, 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. | 
|  | string16 username; | 
|  | 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 |