123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- // 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 <utility>
- #include "base/bind.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/values.h"
- #include "net/base/auth.h"
- #include "net/base/url_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"
- #include "net/log/net_log_event_type.h"
- #include "net/log/net_log_source.h"
- #include "net/log/net_log_source_type.h"
- #include "net/log/net_log_with_source.h"
- #include "url/scheme_host_port.h"
- namespace net {
- namespace {
- base::Value ControllerParamsToValue(HttpAuth::Target target, const GURL& url) {
- base::Value::Dict params;
- params.Set("target", HttpAuth::GetAuthTargetString(target));
- params.Set("url", url.spec());
- return base::Value(std::move(params));
- }
- } // namespace
- HttpAuthController::HttpAuthController(
- HttpAuth::Target target,
- const GURL& auth_url,
- const NetworkIsolationKey& network_isolation_key,
- HttpAuthCache* http_auth_cache,
- HttpAuthHandlerFactory* http_auth_handler_factory,
- HostResolver* host_resolver)
- : target_(target),
- auth_url_(auth_url),
- auth_scheme_host_port_(auth_url),
- auth_path_(auth_url.path()),
- network_isolation_key_(network_isolation_key),
- http_auth_cache_(http_auth_cache),
- http_auth_handler_factory_(http_auth_handler_factory),
- host_resolver_(host_resolver) {
- DCHECK(target != HttpAuth::AUTH_PROXY || auth_path_ == "/");
- DCHECK(auth_scheme_host_port_.IsValid());
- }
- HttpAuthController::~HttpAuthController() {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- if (net_log_.source().IsValid())
- net_log_.EndEvent(NetLogEventType::AUTH_CONTROLLER);
- }
- void HttpAuthController::BindToCallingNetLog(
- const NetLogWithSource& caller_net_log) {
- if (!net_log_.source().IsValid()) {
- net_log_ = NetLogWithSource::Make(caller_net_log.net_log(),
- NetLogSourceType::HTTP_AUTH_CONTROLLER);
- net_log_.BeginEvent(NetLogEventType::AUTH_CONTROLLER, [&] {
- return ControllerParamsToValue(target_, auth_url_);
- });
- }
- caller_net_log.AddEventReferencingSource(
- NetLogEventType::AUTH_BOUND_TO_CONTROLLER, net_log_.source());
- }
- int HttpAuthController::MaybeGenerateAuthToken(
- const HttpRequestInfo* request,
- CompletionOnceCallback callback,
- const NetLogWithSource& caller_net_log) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- DCHECK(!auth_info_);
- bool needs_auth = HaveAuth() || SelectPreemptiveAuth(caller_net_log);
- if (!needs_auth)
- return OK;
- net_log_.BeginEventReferencingSource(NetLogEventType::AUTH_GENERATE_TOKEN,
- caller_net_log.source());
- const AuthCredentials* credentials = nullptr;
- 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::BindOnce(&HttpAuthController::OnGenerateAuthTokenDone,
- base::Unretained(this)),
- &auth_token_);
- if (rv == ERR_IO_PENDING) {
- callback_ = std::move(callback);
- return rv;
- }
- return HandleGenerateTokenResult(rv);
- }
- bool HttpAuthController::SelectPreemptiveAuth(
- const NetLogWithSource& caller_net_log) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- 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_scheme_host_port_, target_, network_isolation_key_, auth_path_);
- if (!entry)
- return false;
- BindToCallingNetLog(caller_net_log);
- // Try to create a handler using the previous auth challenge.
- std::unique_ptr<HttpAuthHandler> handler_preemptive;
- int rv_create =
- http_auth_handler_factory_->CreatePreemptiveAuthHandlerFromString(
- entry->auth_challenge(), target_, network_isolation_key_,
- auth_scheme_host_port_, entry->IncrementNonceCount(), net_log_,
- host_resolver_, &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_CALLED_ON_VALID_THREAD(thread_checker_);
- 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,
- const SSLInfo& ssl_info,
- bool do_not_send_server_auth,
- bool establishing_tunnel,
- const NetLogWithSource& caller_net_log) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- DCHECK(headers.get());
- DCHECK(auth_scheme_host_port_.IsValid());
- DCHECK(!auth_info_);
- BindToCallingNetLog(caller_net_log);
- net_log_.BeginEventReferencingSource(NetLogEventType::AUTH_HANDLE_CHALLENGE,
- caller_net_log.source());
- // 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:
- InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
- break;
- case HttpAuth::AUTHORIZATION_RESULT_STALE:
- if (http_auth_cache_->UpdateStaleChallenge(
- auth_scheme_host_port_, target_, handler_->realm(),
- handler_->auth_scheme(), network_isolation_key_,
- 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,
- ssl_info, network_isolation_key_, target_,
- auth_scheme_host_port_, disabled_schemes_,
- net_log_, host_resolver_, &handler_);
- }
- if (!handler_.get()) {
- if (establishing_tunnel) {
- // 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_EQ(target_, HttpAuth::AUTH_PROXY);
- net_log_.EndEventWithNetErrorCode(
- NetLogEventType::AUTH_HANDLE_CHALLENGE, ERR_PROXY_AUTH_UNSUPPORTED);
- return ERR_PROXY_AUTH_UNSUPPORTED;
- }
- // We found no supported challenge -- let the transaction continue so we
- // end up displaying the error page.
- net_log_.EndEvent(NetLogEventType::AUTH_HANDLE_CHALLENGE);
- 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.
- InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_DISABLE_SCHEME);
- } else {
- // Pass the challenge information back to the client.
- PopulateAuthChallenge();
- }
- }
- // 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());
- net_log_.EndEvent(NetLogEventType::AUTH_HANDLE_CHALLENGE);
- return OK;
- }
- void HttpAuthController::ResetAuth(const AuthCredentials& credentials) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- DCHECK(identity_.invalid || credentials.Empty());
- if (identity_.invalid) {
- // Update the credentials.
- identity_.source = HttpAuth::IDENT_SRC_EXTERNAL;
- identity_.invalid = false;
- identity_.credentials = credentials;
- // auth_info_ is no longer necessary.
- auth_info_ = absl::nullopt;
- }
- 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_scheme_host_port_, target_, handler_->realm(),
- handler_->auth_scheme(), network_isolation_key_,
- handler_->challenge(), identity_.credentials,
- auth_path_);
- break;
- }
- }
- bool HttpAuthController::HaveAuthHandler() const {
- return handler_.get() != nullptr;
- }
- bool HttpAuthController::HaveAuth() const {
- return handler_.get() && !identity_.invalid;
- }
- bool HttpAuthController::NeedsHTTP11() const {
- return handler_ && handler_->is_connection_based();
- }
- void HttpAuthController::InvalidateCurrentHandler(
- InvalidateHandlerAction action) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- DCHECK(handler_.get());
- switch (action) {
- case INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS:
- InvalidateRejectedAuthFromCache();
- break;
- case INVALIDATE_HANDLER_AND_DISABLE_SCHEME:
- DisableAuthScheme(handler_->auth_scheme());
- break;
- case INVALIDATE_HANDLER:
- PrepareIdentityForReuse();
- break;
- }
- handler_.reset();
- identity_ = HttpAuth::Identity();
- }
- void HttpAuthController::InvalidateRejectedAuthFromCache() {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- 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_scheme_host_port_, target_, handler_->realm(),
- handler_->auth_scheme(), network_isolation_key_,
- identity_.credentials);
- }
- void HttpAuthController::PrepareIdentityForReuse() {
- if (identity_.invalid)
- return;
- switch (identity_.source) {
- case HttpAuth::IDENT_SRC_DEFAULT_CREDENTIALS:
- DCHECK(default_credentials_used_);
- default_credentials_used_ = false;
- break;
- case HttpAuth::IDENT_SRC_URL:
- DCHECK(embedded_identity_used_);
- embedded_identity_used_ = false;
- break;
- case HttpAuth::IDENT_SRC_NONE:
- case HttpAuth::IDENT_SRC_PATH_LOOKUP:
- case HttpAuth::IDENT_SRC_REALM_LOOKUP:
- case HttpAuth::IDENT_SRC_EXTERNAL:
- break;
- }
- }
- bool HttpAuthController::SelectNextAuthIdentityToTry() {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- 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.
- std::u16string username;
- std::u16string 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_scheme_host_port_, target_, handler_->realm(),
- handler_->auth_scheme(), network_isolation_key_);
- 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 they're allowed and this is the
- // first attempt at using an 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_CALLED_ON_VALID_THREAD(thread_checker_);
- // Populates response_.auth_challenge with the authentication challenge info.
- // This info is consumed by URLRequestHttpJob::GetAuthChallengeInfo().
- auth_info_ = AuthChallengeInfo();
- auth_info_->is_proxy = (target_ == HttpAuth::AUTH_PROXY);
- auth_info_->challenger = auth_scheme_host_port_;
- auth_info_->scheme = HttpAuth::SchemeToString(handler_->auth_scheme());
- auth_info_->realm = handler_->realm();
- auth_info_->path = auth_path_;
- auth_info_->challenge = handler_->challenge();
- }
- int HttpAuthController::HandleGenerateTokenResult(int result) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- net_log_.EndEventWithNetErrorCode(NetLogEventType::AUTH_GENERATE_TOKEN,
- result);
- switch (result) {
- // Occurs if the credential handle is found to be invalid at the point it is
- // exercised (i.e. GenerateAuthToken stage). We are going to consider this
- // to be an error that invalidates the identity but not necessarily the
- // scheme. Doing so allows a different identity to be used with the same
- // scheme. See https://crbug.com/648366.
- case ERR_INVALID_HANDLE:
- // If the GenerateAuthToken call fails with this error, this means that the
- // handler can no longer be used. However, the authentication scheme is
- // considered still usable. This allows a scheme that attempted and failed
- // to use default credentials to recover and use explicit credentials.
- //
- // The current handler may be tied to external state that is no longer
- // valid, hence should be discarded. Since the scheme is still valid, a new
- // handler can be created for the current scheme.
- case ERR_INVALID_AUTH_CREDENTIALS:
- InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
- auth_token_.clear();
- return OK;
- // 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.
- InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_DISABLE_SCHEME);
- auth_token_.clear();
- return OK;
- default:
- return result;
- }
- }
- void HttpAuthController::OnGenerateAuthTokenDone(int result) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- result = HandleGenerateTokenResult(result);
- if (!callback_.is_null()) {
- std::move(callback_).Run(result);
- }
- }
- void HttpAuthController::TakeAuthInfo(
- absl::optional<AuthChallengeInfo>* other) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- auth_info_.swap(*other);
- }
- bool HttpAuthController::IsAuthSchemeDisabled(HttpAuth::Scheme scheme) const {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- return disabled_schemes_.find(scheme) != disabled_schemes_.end();
- }
- void HttpAuthController::DisableAuthScheme(HttpAuth::Scheme scheme) {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- disabled_schemes_.insert(scheme);
- }
- void HttpAuthController::DisableEmbeddedIdentity() {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- embedded_identity_used_ = true;
- }
- void HttpAuthController::OnConnectionClosed() {
- DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
- InvalidateCurrentHandler(INVALIDATE_HANDLER);
- }
- } // namespace net
|