Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(271)

Unified Diff: net/http/http_auth_controller.cc

Issue 2808020: Create HttpAuthController. (Closed)
Patch Set: Fix mem leak Created 10 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « net/http/http_auth_controller.h ('k') | net/http/http_network_transaction.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « net/http/http_auth_controller.h ('k') | net/http/http_network_transaction.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698