#include "net/http/http_auth_controller.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/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/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_capture_mode.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 {
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 (GURL::SchemeIsCryptographic(handler->scheme_host_port().scheme())) {
return AUTH_TARGET_SECURE_PROXY;
} else {
return AUTH_TARGET_PROXY;
}
case HttpAuth::AUTH_SERVER:
if (GURL::SchemeIsCryptographic(handler->scheme_host_port().scheme())) {
return AUTH_TARGET_SECURE_SERVER;
} else {
return AUTH_TARGET_SERVER;
}
default:
NOTREACHED();
}
}
base::Value::Dict ControllerParamsToValue(HttpAuth::Target target,
const GURL& url,
NetLogCaptureMode capture_mode) {
base::Value::Dict params;
params.Set("target", HttpAuth::GetAuthTargetString(target));
params.Set("url", SanitizeUrlForNetLog(url, capture_mode));
return params;
}
}
HttpAuthController::HttpAuthController(
HttpAuth::Target target,
const GURL& auth_url,
const NetworkAnonymizationKey& network_anonymization_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.GetPath()),
network_anonymization_key_(network_anonymization_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, [&](NetLogCaptureMode capture_mode) {
return ControllerParamsToValue(target_, auth_url_, capture_mode);
});
}
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);
if (auth_url_.has_username())
return false;
HttpAuthCache::Entry* entry = http_auth_cache_->LookupByPath(
auth_scheme_host_port_, target_, network_anonymization_key_, auth_path_);
if (!entry)
return false;
BindToCallingNetLog(caller_net_log);
std::unique_ptr<HttpAuthHandler> handler_preemptive;
int rv_create =
http_auth_handler_factory_->CreatePreemptiveAuthHandlerFromString(
entry->auth_challenge(), target_, network_anonymization_key_,
auth_scheme_host_port_, entry->IncrementNonceCount(), net_log_,
host_resolver_, &handler_preemptive);
if (rv_create != OK)
return false;
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());
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());
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(AUTH_EVENT_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_anonymization_key_,
challenge_used)) {
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;
default:
NOTREACHED();
}
}
identity_.invalid = true;
bool can_send_auth = (target_ != HttpAuth::AUTH_SERVER ||
!do_not_send_server_auth);
do {
if (!handler_.get() && can_send_auth) {
HttpAuth::ChooseBestChallenge(
http_auth_handler_factory_, *headers, ssl_info,
network_anonymization_key_, target_, auth_scheme_host_port_,
disabled_schemes_, net_log_, host_resolver_, &handler_);
if (handler_.get()) {
HistogramAuthEvent(AUTH_EVENT_START);
}
}
if (!handler_.get()) {
if (establishing_tunnel) {
DCHECK_EQ(target_, HttpAuth::AUTH_PROXY);
net_log_.EndEventWithNetErrorCode(
NetLogEventType::AUTH_HANDLE_CHALLENGE, ERR_PROXY_AUTH_UNSUPPORTED);
return ERR_PROXY_AUTH_UNSUPPORTED;
}
net_log_.EndEvent(NetLogEventType::AUTH_HANDLE_CHALLENGE);
return OK;
}
if (handler_->NeedsIdentity()) {
SelectNextAuthIdentityToTry();
} else {
identity_.invalid = false;
}
if (identity_.invalid) {
if (!handler_->AllowsExplicitCredentials()) {
HistogramAuthEvent(AUTH_EVENT_REJECT);
InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_DISABLE_SCHEME);
} else {
PopulateAuthChallenge();
}
}
} 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) {
identity_.source = HttpAuth::IDENT_SRC_EXTERNAL;
identity_.invalid = false;
identity_.credentials = credentials;
auth_info_ = std::nullopt;
}
DCHECK(identity_.source != HttpAuth::IDENT_SRC_PATH_LOOKUP);
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_anonymization_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());
http_auth_cache_->Remove(auth_scheme_host_port_, target_, handler_->realm(),
handler_->auth_scheme(), network_anonymization_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);
if (target_ == HttpAuth::AUTH_SERVER && auth_url_.has_username() &&
!embedded_identity_used_) {
identity_.source = HttpAuth::IDENT_SRC_URL;
identity_.invalid = false;
std::u16string username;
std::u16string password;
GetIdentityFromURL(auth_url_, &username, &password);
identity_.credentials.Set(username, password);
embedded_identity_used_ = true;
return true;
}
HttpAuthCache::Entry* entry = http_auth_cache_->Lookup(
auth_scheme_host_port_, target_, handler_->realm(),
handler_->auth_scheme(), network_anonymization_key_);
if (entry) {
identity_.source = HttpAuth::IDENT_SRC_REALM_LOOKUP;
identity_.invalid = false;
identity_.credentials = entry->credentials();
return true;
}
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_);
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) {
case ERR_INVALID_HANDLE:
case ERR_INVALID_AUTH_CREDENTIALS:
InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
auth_token_.clear();
return OK;
case ERR_MISSING_AUTH_CREDENTIALS:
if (!handler_->AllowsExplicitCredentials()) {
InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_DISABLE_SCHEME);
} else {
InvalidateCurrentHandler(INVALIDATE_HANDLER_AND_CACHED_CREDENTIALS);
}
auth_token_.clear();
return OK;
case ERR_UNSUPPORTED_AUTH_SCHEME:
case ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS:
case ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS:
case ERR_MISCONFIGURED_AUTH_ENVIRONMENT:
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::HistogramAuthEvent(AuthEvent auth_event) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
HttpAuth::Scheme auth_scheme = handler_->auth_scheme();
DCHECK(auth_scheme >= 0 && auth_scheme < HttpAuth::AUTH_SCHEME_MAX);
static constexpr int kEventBucketsEnd =
int{HttpAuth::AUTH_SCHEME_MAX} * AUTH_EVENT_MAX;
int event_bucket = int{auth_scheme} * AUTH_EVENT_MAX + auth_event;
DCHECK(event_bucket >= 0 && event_bucket < kEventBucketsEnd);
UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthCount", event_bucket,
kEventBucketsEnd);
if (auth_event != AUTH_EVENT_START) {
return;
}
static constexpr int kTargetBucketsEnd =
int{HttpAuth::AUTH_SCHEME_MAX} * AUTH_TARGET_MAX;
AuthTarget auth_target = DetermineAuthTarget(handler_.get());
int target_bucket = int{auth_scheme} * AUTH_TARGET_MAX + auth_target;
DCHECK(target_bucket >= 0 && target_bucket < kTargetBucketsEnd);
UMA_HISTOGRAM_ENUMERATION("Net.HttpAuthTarget", target_bucket,
kTargetBucketsEnd);
}
void HttpAuthController::TakeAuthInfo(std::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);
}
}