Index: net/http/http_auth_controller.cc |
diff --git a/net/http/http_auth_controller.cc b/net/http/http_auth_controller.cc |
index 52fd2f937e4046034eaa4464e844b72633277bfb..29feeccfcdc2eb871d1645b55f7ba0f80be7d471 100644 |
--- a/net/http/http_auth_controller.cc |
+++ b/net/http/http_auth_controller.cc |
@@ -6,14 +6,17 @@ |
#include "base/bind.h" |
#include "base/bind_helpers.h" |
+#include "base/callback_helpers.h" |
#include "base/metrics/histogram_macros.h" |
#include "base/strings/string_util.h" |
#include "base/strings/utf_string_conversions.h" |
#include "base/threading/platform_thread.h" |
+#include "base/values.h" |
#include "net/base/auth.h" |
#include "net/base/net_util.h" |
#include "net/dns/host_resolver.h" |
#include "net/http/http_auth_challenge_tokenizer.h" |
+#include "net/http/http_auth_challenge_tokenizer.h" |
#include "net/http/http_auth_handler.h" |
#include "net/http/http_auth_handler_factory.h" |
#include "net/http/http_network_session.h" |
@@ -25,35 +28,6 @@ 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, |
@@ -185,6 +159,32 @@ void HistogramAuthEvent(HttpAuthHandler* handler, AuthEvent auth_event) { |
kTargetBucketsEnd); |
} |
+// Hardcoded map of HTTP authentication scheme priorities. Higher priorities are |
+// preferred over lower. |
+struct SchemePriority { |
+ const char* scheme; |
+ int priority; |
+} kSchemeScores[] = { |
+ {"basic", 1}, |
+ {"digest", 2}, |
+ {"ntlm", 3}, |
+ {"negotiate", 4}, |
+}; |
+ |
+// Priority assigned to unknown authentication schemes. They are currently |
+// ranked lower than Basic, which might be a bit too conservative. |
+const int kSchemePriorityDefault = 0; |
+ |
+// Higher priority schemes are preferred over lower priority schemes. |
+int GetSchemePriority(const std::string& scheme) { |
+ DCHECK(HttpAuth::IsValidNormalizedScheme(scheme)); |
+ for (const auto& iter : kSchemeScores) { |
+ if (scheme == iter.scheme) |
+ return iter.priority; |
+ } |
+ return kSchemePriorityDefault; |
+} |
+ |
} // namespace |
HttpAuthController::HttpAuthController( |
@@ -199,7 +199,10 @@ HttpAuthController::HttpAuthController( |
embedded_identity_used_(false), |
default_credentials_used_(false), |
http_auth_cache_(http_auth_cache), |
- http_auth_handler_factory_(http_auth_handler_factory) { |
+ http_auth_handler_factory_(http_auth_handler_factory), |
+ weak_ptr_factory_(this) { |
+ io_callback_ = base::Bind(&HttpAuthController::OnIOComplete, |
+ weak_ptr_factory_.GetWeakPtr()); |
} |
HttpAuthController::~HttpAuthController() { |
@@ -210,25 +213,18 @@ int HttpAuthController::MaybeGenerateAuthToken( |
const HttpRequestInfo* request, const CompletionCallback& callback, |
const BoundNetLog& net_log) { |
DCHECK(CalledOnValidThread()); |
- DCHECK(request); |
+ DCHECK(callback_.is_null()); |
+ DCHECK(!callback.is_null()); |
+ |
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; |
+ request_info_ = request; |
+ next_state_ = STATE_GENERATE_TOKEN; |
+ net_log_ = net_log; |
+ int rv = DoLoop(OK); |
if (rv == ERR_IO_PENDING) |
callback_ = callback; |
- else |
- OnIOComplete(rv); |
return rv; |
} |
@@ -252,11 +248,9 @@ bool HttpAuthController::SelectPreemptiveAuth(const BoundNetLog& net_log) { |
return false; |
// Try to create a handler using the previous auth challenge. |
- std::string challenge = entry->auth_challenge(); |
- HttpAuthChallengeTokenizer tokenizer(challenge.begin(), challenge.end()); |
scoped_ptr<HttpAuthHandler> handler_preemptive = |
http_auth_handler_factory_->CreateAndInitPreemptiveAuthHandler( |
- entry, tokenizer, target_, net_log); |
+ entry, target_, net_log); |
if (!handler_preemptive) |
return false; |
@@ -282,139 +276,24 @@ void HttpAuthController::AddAuthorizationHeader( |
} |
int HttpAuthController::HandleAuthChallenge( |
- scoped_refptr<HttpResponseHeaders> headers, |
- bool do_not_send_server_auth, |
- bool establishing_tunnel, |
+ const HttpResponseInfo& response_info, |
+ const CompletionCallback& callback, |
const BoundNetLog& net_log) { |
DCHECK(CalledOnValidThread()); |
- DCHECK(headers.get()); |
+ DCHECK(response_info.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. |
+ DCHECK(callback_.is_null()); |
+ DCHECK(!callback.is_null()); |
- 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; |
- } |
+ next_state_ = HaveAuthHandler() ? STATE_HANDLE_ANOTHER_CHALLENGE |
+ : STATE_CHOOSE_CHALLENGE; |
+ response_info_ = &response_info; |
+ net_log_ = net_log; |
+ int result = DoLoop(OK); |
- // 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; |
+ if (result == ERR_IO_PENDING) |
+ callback_ = callback; |
+ return result; |
} |
void HttpAuthController::ResetAuth(const AuthCredentials& credentials) { |
@@ -539,6 +418,7 @@ bool HttpAuthController::SelectNextAuthIdentityToTry() { |
void HttpAuthController::PopulateAuthChallenge() { |
DCHECK(CalledOnValidThread()); |
+ DCHECK(!auth_info_); |
// Populates response_.auth_challenge with the authentication challenge info. |
// This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo(). |
@@ -582,12 +462,10 @@ bool HttpAuthController::DisableOnAuthHandlerResult(int result) { |
void HttpAuthController::OnIOComplete(int result) { |
DCHECK(CalledOnValidThread()); |
- if (DisableOnAuthHandlerResult(result)) |
- result = OK; |
- if (!callback_.is_null()) { |
- CompletionCallback c = callback_; |
- callback_.Reset(); |
- c.Run(result); |
+ result = DoLoop(result); |
+ |
+ if (result != ERR_IO_PENDING && !callback_.is_null()) { |
+ base::ResetAndReturn(&callback_).Run(result); |
} |
} |
@@ -611,4 +489,265 @@ void HttpAuthController::DisableEmbeddedIdentity() { |
embedded_identity_used_ = true; |
} |
+int HttpAuthController::DoLoop(int result) { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ do { |
+ State state = next_state_; |
+ next_state_ = STATE_NONE; |
+ |
+ switch (state) { |
+ case STATE_CHOOSE_CHALLENGE: |
+ DCHECK_EQ(OK, result); |
+ result = DoChooseChallenge(); |
+ break; |
+ |
+ case STATE_TRY_NEXT_CHALLENGE: |
+ DCHECK_EQ(OK, result); |
+ result = DoTryNextChallenge(); |
+ break; |
+ |
+ case STATE_TRY_NEXT_CHALLENGE_COMPLETE: |
+ result = DoTryNextChallengeComplete(result); |
+ break; |
+ |
+ case STATE_CHOOSE_IDENTITY: |
+ DCHECK_EQ(OK, result); |
+ result = DoChooseIdentity(); |
+ break; |
+ |
+ case STATE_HANDLE_ANOTHER_CHALLENGE: |
+ DCHECK_EQ(OK, result); |
+ result = DoHandleAnotherChallenge(); |
+ break; |
+ |
+ case STATE_HANDLE_ANOTHER_CHALLENGE_COMPLETE: |
+ result = DoHandleAnotherChallengeComplete(result); |
+ break; |
+ |
+ case STATE_GENERATE_TOKEN: |
+ DCHECK_EQ(OK, result); |
+ result = DoGenerateToken(); |
+ break; |
+ |
+ case STATE_GENERATE_TOKEN_COMPLETE: |
+ result = DoGenerateTokenComplete(result); |
+ break; |
+ |
+ case STATE_NONE: |
+ NOTREACHED() << "next_state_: " << next_state_; |
+ } |
+ } while (next_state_ != STATE_NONE && result != ERR_IO_PENDING); |
+ |
+ if (result != ERR_IO_PENDING) { |
+ // Exiting the state machine. |
+ request_info_ = nullptr; |
+ response_info_ = nullptr; |
+ net_log_ = BoundNetLog(); |
+ } |
+ |
+ return result; |
+} |
+ |
+int HttpAuthController::DoChooseChallenge() { |
+ DCHECK(response_info_); |
+ DCHECK(response_info_->headers.get()); |
+ DCHECK(!handler_); |
+ DCHECK(auth_token_.empty()); |
+ |
+ auth_info_ = nullptr; |
+ |
+ std::priority_queue<CandidateChallenge> old_challenges; |
+ candidate_challenges_.swap(old_challenges); |
+ |
+ void* iter = nullptr; |
+ const std::string header_name = HttpAuth::GetChallengeHeaderName(target_); |
+ std::string current_challenge; |
+ while (response_info_->headers->EnumerateHeader(&iter, header_name, |
+ ¤t_challenge)) { |
+ HttpAuthChallengeTokenizer tokenizer(current_challenge.begin(), |
+ current_challenge.end()); |
+ std::string current_scheme = tokenizer.NormalizedScheme(); |
+ if (current_scheme.empty()) |
+ continue; |
+ if (IsAuthSchemeDisabled(current_scheme)) |
+ continue; |
+ |
+ CandidateChallenge candidate_challenge; |
+ candidate_challenge.scheme = current_scheme; |
+ candidate_challenge.priority = GetSchemePriority(current_scheme); |
+ candidate_challenge.challenge = current_challenge; |
+ candidate_challenges_.push(candidate_challenge); |
+ } |
+ next_state_ = STATE_TRY_NEXT_CHALLENGE; |
+ return OK; |
+} |
+ |
+int HttpAuthController::DoTryNextChallenge() { |
+ DCHECK(!handler_); |
+ DCHECK(auth_token_.empty()); |
+ DCHECK(response_info_); |
+ |
+ for (; !handler_ && !candidate_challenges_.empty(); |
+ candidate_challenges_.pop()) { |
+ const CandidateChallenge& candidate = candidate_challenges_.top(); |
+ selected_auth_challenge_ = candidate.challenge; |
+ if (IsAuthSchemeDisabled(candidate.scheme)) |
+ continue; |
+ handler_ = http_auth_handler_factory_->CreateAuthHandlerForScheme( |
+ candidate.scheme); |
+ } |
+ |
+ if (!handler_) |
+ return OK; |
+ |
+ HttpAuthChallengeTokenizer tokenizer(selected_auth_challenge_.begin(), |
+ selected_auth_challenge_.end()); |
+ next_state_ = STATE_TRY_NEXT_CHALLENGE_COMPLETE; |
+ return handler_->HandleInitialChallenge(tokenizer, *response_info_, target_, |
+ auth_origin_, net_log_, io_callback_); |
+} |
+ |
+int HttpAuthController::DoTryNextChallengeComplete(int result) { |
+ DCHECK(handler_); |
+ |
+ if (result != OK) { |
+ InvalidateCurrentHandler(INVALIDATE_HANDLER); |
+ next_state_ = STATE_TRY_NEXT_CHALLENGE; |
+ return OK; |
+ } |
+ |
+ HistogramAuthEvent(handler_.get(), AUTH_EVENT_START); |
+ next_state_ = STATE_CHOOSE_IDENTITY; |
+ return OK; |
+} |
+ |
+int HttpAuthController::DoChooseIdentity() { |
+ DCHECK(handler_.get()); |
+ DCHECK(identity_.invalid); |
+ DCHECK(!auth_info_); |
+ |
+ if (handler_->NeedsIdentity()) { |
+ SelectNextAuthIdentityToTry(); |
+ } else { |
+ // TODO(asanka): This is a false flag and shouldn't be necessary. Instead |
+ // rely on NeedsIdentity() to determine if the handler needs an identity. |
+ identity_.invalid = false; |
+ } |
+ |
+ if (!identity_.invalid) { |
+ return OK; |
+ } |
+ |
+ if (!handler_->AllowsExplicitCredentials()) { |
+ HistogramAuthEvent(handler_.get(), AUTH_EVENT_REJECT); |
+ InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_DISABLE_SCHEME); |
+ next_state_ = STATE_TRY_NEXT_CHALLENGE; |
+ return OK; |
+ } |
+ |
+ PopulateAuthChallenge(); |
+ return OK; |
+} |
+ |
+int HttpAuthController::DoHandleAnotherChallenge() { |
+ DCHECK(HaveAuth()); |
+ DCHECK(response_info_); |
+ DCHECK(response_info_->headers.get()); |
+ DCHECK(auth_token_.empty()); |
+ |
+ next_state_ = STATE_HANDLE_ANOTHER_CHALLENGE_COMPLETE; |
+ auth_info_ = nullptr; |
+ |
+ selected_auth_challenge_.clear(); |
+ if (disabled_schemes_.Contains(handler_->auth_scheme())) |
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT; |
+ |
+ std::string current_scheme_name = handler_->auth_scheme(); |
+ std::string challenge_header_name = HttpAuth::GetChallengeHeaderName(target_); |
+ |
+ std::string challenge; |
+ void* iter = nullptr; |
+ HttpAuth::AuthorizationResult authorization_result = |
+ HttpAuth::AUTHORIZATION_RESULT_INVALID; |
+ while (response_info_->headers->EnumerateHeader(&iter, challenge_header_name, |
+ &challenge)) { |
+ HttpAuthChallengeTokenizer tokenizer(challenge.begin(), challenge.end()); |
+ if (!tokenizer.SchemeIs(current_scheme_name)) |
+ continue; |
+ |
+ authorization_result = handler_->HandleAnotherChallenge(tokenizer); |
+ if (authorization_result != HttpAuth::AUTHORIZATION_RESULT_INVALID) { |
+ selected_auth_challenge_ = challenge; |
+ return authorization_result; |
+ } |
+ } |
+ |
+ return HttpAuth::AUTHORIZATION_RESULT_REJECT; |
+} |
+ |
+int HttpAuthController::DoHandleAnotherChallengeComplete(int result) { |
+ if (result < 0) |
+ return result; |
+ |
+ HttpAuth::AuthorizationResult auth_result = |
+ static_cast<HttpAuth::AuthorizationResult>(result); |
+ switch (auth_result) { |
+ case HttpAuth::AUTHORIZATION_RESULT_ACCEPT: |
+ return OK; |
+ |
+ 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(), |
+ selected_auth_challenge_)) { |
+ InvalidateCurrentHandler(INVALIDATE_HANDLER); |
+ } else { |
+ InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS); |
+ } |
+ break; |
+ |
+ case HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM: |
+ InvalidateCurrentHandler(identity_.source == |
+ HttpAuth::IDENT_SRC_PATH_LOOKUP |
+ ? INVALIDATE_HANDLER |
+ : INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS); |
+ break; |
+ } |
+ |
+ DCHECK(!handler_.get()); |
+ next_state_ = STATE_CHOOSE_CHALLENGE; |
+ return OK; |
+} |
+ |
+int HttpAuthController::DoGenerateToken() { |
+ DCHECK(handler_.get()); |
+ DCHECK(!identity_.invalid || !handler_->NeedsIdentity()); |
+ DCHECK(request_info_); |
+ DCHECK(HaveAuth()); |
+ DCHECK(auth_token_.empty()); |
+ |
+ next_state_ = STATE_GENERATE_TOKEN_COMPLETE; |
+ const AuthCredentials* credentials = |
+ identity_.source == HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS |
+ ? nullptr |
+ : &identity_.credentials; |
+ return handler_->GenerateAuthToken(credentials, *request_info_, io_callback_, |
+ &auth_token_); |
+} |
+ |
+int HttpAuthController::DoGenerateTokenComplete(int result) { |
+ if (DisableOnAuthHandlerResult(result)) |
+ result = OK; |
+ return result; |
+} |
+ |
} // namespace net |