Index: net/http/http_auth_controller.cc |
diff --git a/net/http/http_auth_controller.cc b/net/http/http_auth_controller.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b85ae0388bd7a8237db4f5f1e4d40d147bedde4e |
--- /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<HttpNetworkSession> session, |
+ const BoundNetLog& net_log) |
+ : target_(target), |
+ auth_url_(auth_url), |
+ auth_origin_(auth_url.GetOrigin()), |
+ auth_path_(HttpAuth::AUTH_PROXY ? std::string() : auth_url.path()), |
+ embedded_identity_used_(false), |
+ default_credentials_used_(false), |
+ session_(session), |
+ net_log_(net_log) { |
+} |
+ |
+int HttpAuthController::MaybeGenerateAuthToken(const HttpRequestInfo* request, |
+ CompletionCallback* callback) { |
+ bool needs_auth = HaveAuth() || SelectPreemptiveAuth(); |
+ if (!needs_auth) |
+ return OK; |
+ const std::wstring* username = NULL; |
+ const std::wstring* password = NULL; |
+ if (identity_.source != HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS) { |
+ username = &identity_.username; |
+ password = &identity_.password; |
+ } |
+ DCHECK(auth_token_.empty()); |
+ return handler_->GenerateAuthToken(username, password, request, callback, |
+ &auth_token_); |
+} |
+ |
+bool HttpAuthController::SelectPreemptiveAuth() { |
+ DCHECK(!HaveAuth()); |
+ DCHECK(identity_.invalid); |
+ |
+ // Don't do preemptive authorization if the URL contains a username/password, |
+ // since we must first be challenged in order to use the URL's identity. |
+ if (auth_url_.has_username()) |
+ return false; |
+ |
+ // SelectPreemptiveAuth() is on the critical path for each request, so it |
+ // is expected to be fast. LookupByPath() is fast in the common case, since |
+ // the number of http auth cache entries is expected to be very small. |
+ // (For most users in fact, it will be 0.) |
+ HttpAuthCache::Entry* entry = session_->auth_cache()->LookupByPath( |
+ auth_origin_, auth_path_); |
+ if (!entry) |
+ return false; |
+ |
+ // Try to create a handler using the previous auth challenge. |
+ scoped_ptr<HttpAuthHandler> handler_preemptive; |
+ int rv_create = session_->http_auth_handler_factory()-> |
+ CreatePreemptiveAuthHandlerFromString(entry->auth_challenge(), target_, |
+ auth_origin_, |
+ entry->IncrementNonceCount(), |
+ net_log_, &handler_preemptive); |
+ if (rv_create != OK) |
+ return false; |
+ |
+ // Set the state |
+ identity_.source = HttpAuth::IDENT_SRC_PATH_LOOKUP; |
+ identity_.invalid = false; |
+ identity_.username = entry->username(); |
+ identity_.password = entry->password(); |
+ handler_.swap(handler_preemptive); |
+ return true; |
+} |
+ |
+void HttpAuthController::AddAuthorizationHeader( |
+ HttpRequestHeaders* authorization_headers) { |
+ DCHECK(HaveAuth()); |
+ DCHECK(!auth_token_.empty()); |
+ authorization_headers->SetHeader( |
+ HttpAuth::GetAuthorizationHeaderName(target_), auth_token_); |
+ auth_token_.clear(); |
+} |
+ |
+int HttpAuthController::HandleAuthChallenge( |
+ scoped_refptr<HttpResponseHeaders> headers, |
+ int load_flags, |
+ bool establishing_tunnel) { |
+ DCHECK(headers); |
+ DCHECK(auth_origin_.is_valid()); |
+ |
+ LOG(INFO) << "The " << HttpAuth::GetAuthTargetString(target_) << " " |
+ << auth_origin_ << " requested auth" |
+ << AuthChallengeLogMessage(headers.get()); |
+ |
+ // The auth we tried just failed, hence it can't be valid. Remove it from |
+ // the cache so it won't be used again. |
+ // TODO(wtc): IsFinalRound is not the right condition. In a multi-round |
+ // auth sequence, the server may fail the auth in round 1 if our first |
+ // authorization header is broken. We should inspect response_.headers to |
+ // determine if the server already failed the auth or wants us to continue. |
+ // See http://crbug.com/21015. |
+ if (HaveAuth() && handler_->IsFinalRound()) { |
+ InvalidateRejectedAuthFromCache(); |
+ handler_.reset(); |
+ identity_ = HttpAuth::Identity(); |
+ } |
+ |
+ identity_.invalid = true; |
+ |
+ if (target_ != HttpAuth::AUTH_SERVER || |
+ !(load_flags & LOAD_DO_NOT_SEND_AUTH_DATA)) { |
+ // Find the best authentication challenge that we support. |
+ HttpAuth::ChooseBestChallenge(session_->http_auth_handler_factory(), |
+ headers, target_, auth_origin_, net_log_, |
+ &handler_); |
+ } |
+ |
+ if (!handler_.get()) { |
+ if (establishing_tunnel) { |
+ LOG(ERROR) << "Can't perform auth to the " |
+ << HttpAuth::GetAuthTargetString(target_) << " " |
+ << auth_origin_ << " when establishing a tunnel" |
+ << AuthChallengeLogMessage(headers.get()); |
+ |
+ // We are establishing a tunnel, we can't show the error page because an |
+ // active network attacker could control its contents. Instead, we just |
+ // fail to establish the tunnel. |
+ DCHECK(target_ == HttpAuth::AUTH_PROXY); |
+ return ERR_PROXY_AUTH_REQUESTED; |
+ } |
+ // We found no supported challenge -- let the transaction continue |
+ // so we end up displaying the error page. |
+ return OK; |
+ } |
+ |
+ if (handler_->NeedsIdentity()) { |
+ // Pick a new auth identity to try, by looking to the URL and auth cache. |
+ // If an identity to try is found, it is saved to identity_. |
+ SelectNextAuthIdentityToTry(); |
+ } else { |
+ // Proceed with the existing identity or a null identity. |
+ // |
+ // TODO(wtc): Add a safeguard against infinite transaction restarts, if |
+ // the server keeps returning "NTLM". |
+ identity_.invalid = false; |
+ } |
+ |
+ // From this point on, we are restartable. |
+ |
+ if (identity_.invalid) { |
+ // We have exhausted all identity possibilities, all we can do now is |
+ // pass the challenge information back to the client. |
+ PopulateAuthChallenge(); |
+ } |
+ |
+ // SPN determination (for Negotiate) requires a DNS lookup to find the |
+ // canonical name. This needs to be done asynchronously to prevent blocking |
+ // the IO thread. |
+ if (handler_->NeedsCanonicalName()) |
+ return ERR_AUTH_NEEDS_CANONICAL_NAME; |
+ |
+ return OK; |
+} |
+ |
+int HttpAuthController::ResolveCanonicalName(CompletionCallback* callback) { |
+ DCHECK(handler_.get()); |
+ return handler_->ResolveCanonicalName(session_->host_resolver(), callback); |
+} |
+ |
+void HttpAuthController::ResetAuth(const std::wstring& username, |
+ const std::wstring& password) { |
+ DCHECK(identity_.invalid || (username.empty() && password.empty())); |
+ |
+ if (identity_.invalid) { |
+ // Update the username/password. |
+ identity_.source = HttpAuth::IDENT_SRC_EXTERNAL; |
+ identity_.invalid = false; |
+ identity_.username = username; |
+ identity_.password = password; |
+ } |
+ |
+ DCHECK(identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP); |
+ |
+ // Add the auth entry to the cache before restarting. We don't know whether |
+ // the identity is valid yet, but if it is valid we want other transactions |
+ // to know about it. If an entry for (origin, handler->realm()) already |
+ // exists, we update it. |
+ // |
+ // If identity_.source is HttpAuth::IDENT_SRC_NONE or |
+ // HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS, identity_ contains no |
+ // identity because identity is not required yet or we're using default |
+ // credentials. |
+ // |
+ // TODO(wtc): For NTLM_SSPI, we add the same auth entry to the cache in |
+ // round 1 and round 2, which is redundant but correct. It would be nice |
+ // to add an auth entry to the cache only once, preferrably in round 1. |
+ // See http://crbug.com/21015. |
+ switch (identity_.source) { |
+ case HttpAuth::IDENT_SRC_NONE: |
+ case HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS: |
+ break; |
+ default: |
+ session_->auth_cache()->Add(auth_origin_, handler_->realm(), |
+ handler_->scheme(), handler_->challenge(), |
+ identity_.username, identity_.password, |
+ auth_path_); |
+ break; |
+ } |
+} |
+ |
+void HttpAuthController::InvalidateRejectedAuthFromCache() { |
+ DCHECK(HaveAuth()); |
+ |
+ // TODO(eroman): this short-circuit can be relaxed. If the realm of |
+ // the preemptively used auth entry matches the realm of the subsequent |
+ // challenge, then we can invalidate the preemptively used entry. |
+ // Otherwise as-is we may send the failed credentials one extra time. |
+ if (identity_.source == HttpAuth::IDENT_SRC_PATH_LOOKUP) |
+ return; |
+ |
+ // Clear the cache entry for the identity we just failed on. |
+ // Note: we require the username/password to match before invalidating |
+ // since the entry in the cache may be newer than what we used last time. |
+ session_->auth_cache()->Remove(auth_origin_, handler_->realm(), |
+ handler_->scheme(), identity_.username, |
+ identity_.password); |
+} |
+ |
+bool HttpAuthController::SelectNextAuthIdentityToTry() { |
+ DCHECK(handler_.get()); |
+ DCHECK(identity_.invalid); |
+ |
+ // Try to use the username/password encoded into the URL first. |
+ if (target_ == HttpAuth::AUTH_SERVER && auth_url_.has_username() && |
+ !embedded_identity_used_) { |
+ identity_.source = HttpAuth::IDENT_SRC_URL; |
+ identity_.invalid = false; |
+ // Extract the username:password from the URL. |
+ GetIdentityFromURL(auth_url_, |
+ &identity_.username, |
+ &identity_.password); |
+ embedded_identity_used_ = true; |
+ // TODO(eroman): If the password is blank, should we also try combining |
+ // with a password from the cache? |
+ return true; |
+ } |
+ |
+ // Check the auth cache for a realm entry. |
+ HttpAuthCache::Entry* entry = |
+ session_->auth_cache()->Lookup(auth_origin_, handler_->realm(), |
+ handler_->scheme()); |
+ |
+ if (entry) { |
+ identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP; |
+ identity_.invalid = false; |
+ identity_.username = entry->username(); |
+ identity_.password = entry->password(); |
+ return true; |
+ } |
+ |
+ // Use default credentials (single sign on) if this is the first attempt |
+ // at identity. Do not allow multiple times as it will infinite loop. |
+ // We use default credentials after checking the auth cache so that if |
+ // single sign-on doesn't work, we won't try default credentials for future |
+ // transactions. |
+ if (!default_credentials_used_ && handler_->AllowsDefaultCredentials()) { |
+ identity_.source = HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS; |
+ identity_.invalid = false; |
+ default_credentials_used_ = true; |
+ return true; |
+ } |
+ |
+ return false; |
+} |
+ |
+void HttpAuthController::PopulateAuthChallenge() { |
+ // Populates response_.auth_challenge with the authentication challenge info. |
+ // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo(). |
+ |
+ auth_info_ = new AuthChallengeInfo; |
+ auth_info_->is_proxy = target_ == HttpAuth::AUTH_PROXY; |
+ auth_info_->host_and_port = ASCIIToWide(GetHostAndPort(auth_origin_)); |
+ auth_info_->scheme = ASCIIToWide(handler_->scheme()); |
+ // TODO(eroman): decode realm according to RFC 2047. |
+ auth_info_->realm = ASCIIToWide(handler_->realm()); |
+} |
+ |
+} // namespace net |