blob: d5e92b5aa1aa1b432fd81531ea73b73f435a3afd [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/endpoint_fetcher/endpoint_fetcher.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/version_info/channel.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/google_api_keys.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace {
const char kContentTypeKey[] = "Content-Type";
const char kDeveloperKey[] = "X-Developer-Key";
const int kNumRetries = 3;
const int64_t kDefaultTimeOutMs = 30000;
} // namespace
EndpointFetcher::EndpointFetcher(
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
const std::string& oauth_consumer_name,
const GURL& url,
const std::string& http_method,
const std::string& content_type,
const std::vector<std::string>& scopes,
int64_t timeout_ms,
const std::string& post_data,
const net::NetworkTrafficAnnotationTag& annotation_tag,
signin::IdentityManager* const identity_manager)
: EndpointFetcher(oauth_consumer_name,
url,
http_method,
content_type,
scopes,
timeout_ms,
post_data,
annotation_tag,
url_loader_factory,
identity_manager) {}
EndpointFetcher::EndpointFetcher(
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
const GURL& url,
const std::string& http_method,
const std::string& content_type,
int64_t timeout_ms,
const std::string& post_data,
const std::vector<std::string>& headers,
const net::NetworkTrafficAnnotationTag& annotation_tag,
bool is_stable_channel)
: auth_type_(CHROME_API_KEY),
url_(url),
http_method_(http_method),
content_type_(content_type),
timeout_ms_(timeout_ms),
post_data_(post_data),
headers_(headers),
annotation_tag_(annotation_tag),
url_loader_factory_(url_loader_factory),
identity_manager_(nullptr),
sanitize_response_(true),
is_stable_channel_(is_stable_channel) {}
EndpointFetcher::EndpointFetcher(
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
const GURL& url,
const net::NetworkTrafficAnnotationTag& annotation_tag)
: auth_type_(NO_AUTH),
url_(url),
http_method_("GET"),
content_type_(std::string()),
timeout_ms_(0),
post_data_(std::string()),
annotation_tag_(annotation_tag),
url_loader_factory_(url_loader_factory),
identity_manager_(nullptr),
sanitize_response_(false) {}
EndpointFetcher::EndpointFetcher(
const std::string& oauth_consumer_name,
const GURL& url,
const std::string& http_method,
const std::string& content_type,
const std::vector<std::string>& scopes,
int64_t timeout_ms,
const std::string& post_data,
const net::NetworkTrafficAnnotationTag& annotation_tag,
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
signin::IdentityManager* const identity_manager)
: auth_type_(OAUTH),
oauth_consumer_name_(oauth_consumer_name),
url_(url),
http_method_(http_method),
content_type_(content_type),
timeout_ms_(timeout_ms),
post_data_(post_data),
annotation_tag_(annotation_tag),
url_loader_factory_(url_loader_factory),
identity_manager_(identity_manager),
sanitize_response_(true) {
for (auto scope : scopes) {
oauth_scopes_.insert(scope);
}
}
EndpointFetcher::EndpointFetcher(
const GURL& url,
const std::string& http_method,
const std::string& content_type,
int64_t timeout_ms,
const std::string& post_data,
const std::vector<std::string>& headers,
const std::vector<std::string>& cors_exempt_headers,
const net::NetworkTrafficAnnotationTag& annotation_tag,
const scoped_refptr<network::SharedURLLoaderFactory>& url_loader_factory,
const bool is_oauth_fetch)
: auth_type_(is_oauth_fetch ? OAUTH : CHROME_API_KEY),
url_(url),
http_method_(http_method),
content_type_(content_type),
timeout_ms_(timeout_ms),
post_data_(post_data),
headers_(headers),
cors_exempt_headers_(cors_exempt_headers),
annotation_tag_(annotation_tag),
url_loader_factory_(url_loader_factory),
identity_manager_(nullptr),
sanitize_response_(true) {}
EndpointFetcher::EndpointFetcher(
const net::NetworkTrafficAnnotationTag& annotation_tag)
: timeout_ms_(kDefaultTimeOutMs),
annotation_tag_(annotation_tag),
identity_manager_(nullptr),
sanitize_response_(true) {}
EndpointFetcher::~EndpointFetcher() = default;
void EndpointFetcher::Fetch(EndpointFetcherCallback endpoint_fetcher_callback) {
DCHECK(!access_token_fetcher_);
DCHECK(!simple_url_loader_);
DCHECK(identity_manager_);
// Check if we have a primary account with the default consent level "sync"
// before attempting to fetch a token.
if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
auto response = std::make_unique<EndpointResponse>();
VLOG(1) << __func__ << " No primary accounts found";
response->response = "No primary accounts found";
response->error_type =
absl::make_optional<FetchErrorType>(FetchErrorType::kAuthError);
// TODO(crbug.com/993393) Add more detailed error messaging
std::move(endpoint_fetcher_callback).Run(std::move(response));
return;
}
signin::AccessTokenFetcher::TokenCallback token_callback = base::BindOnce(
&EndpointFetcher::OnAuthTokenFetched, weak_ptr_factory_.GetWeakPtr(),
std::move(endpoint_fetcher_callback));
// TODO(crbug.com/997018) Make access_token_fetcher_ local variable passed
// to callback
access_token_fetcher_ =
std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
oauth_consumer_name_, identity_manager_, oauth_scopes_,
std::move(token_callback),
signin::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable);
}
void EndpointFetcher::OnAuthTokenFetched(
EndpointFetcherCallback endpoint_fetcher_callback,
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info) {
access_token_fetcher_.reset();
if (error.state() != GoogleServiceAuthError::NONE) {
auto response = std::make_unique<EndpointResponse>();
response->response = "There was an authentication error";
response->error_type =
absl::make_optional<FetchErrorType>(FetchErrorType::kAuthError);
// TODO(crbug.com/993393) Add more detailed error messaging
std::move(endpoint_fetcher_callback).Run(std::move(response));
return;
}
PerformRequest(std::move(endpoint_fetcher_callback),
access_token_info.token.c_str());
}
void EndpointFetcher::PerformRequest(
EndpointFetcherCallback endpoint_fetcher_callback,
const char* key) {
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->method = http_method_;
resource_request->url = url_;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
if (base::EqualsCaseInsensitiveASCII(http_method_, "POST")) {
resource_request->headers.SetHeader(kContentTypeKey, content_type_);
}
DCHECK_EQ(headers_.size() % 2, 0UL);
for (size_t i = 0; i + 1 < headers_.size(); i += 2) {
resource_request->headers.SetHeader(headers_[i], headers_[i + 1]);
}
DCHECK_EQ(cors_exempt_headers_.size() % 2, 0UL);
for (size_t i = 0; i + 1 < cors_exempt_headers_.size(); i += 2) {
resource_request->cors_exempt_headers.SetHeaderIfMissing(
cors_exempt_headers_[i], cors_exempt_headers_[i + 1]);
}
switch (auth_type_) {
case OAUTH:
resource_request->headers.SetHeader(
kDeveloperKey, GaiaUrls::GetInstance()->oauth2_chrome_client_id());
resource_request->headers.SetHeader(
net::HttpRequestHeaders::kAuthorization,
base::StringPrintf("Bearer %s", key));
break;
case CHROME_API_KEY: {
std::string api_key = is_stable_channel_
? google_apis::GetAPIKey()
: google_apis::GetNonStableAPIKey();
resource_request->headers.SetHeader("x-goog-api-key", api_key);
break;
}
default:
break;
}
// TODO(crbug.com/997018) Make simple_url_loader_ local variable passed to
// callback
simple_url_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), annotation_tag_);
if (base::EqualsCaseInsensitiveASCII(http_method_, "POST")) {
simple_url_loader_->AttachStringForUpload(post_data_, content_type_);
}
simple_url_loader_->SetRetryOptions(kNumRetries,
network::SimpleURLLoader::RETRY_ON_5XX);
simple_url_loader_->SetTimeoutDuration(base::Milliseconds(timeout_ms_));
simple_url_loader_->SetAllowHttpErrorResults(true);
network::SimpleURLLoader::BodyAsStringCallback body_as_string_callback =
base::BindOnce(&EndpointFetcher::OnResponseFetched,
weak_ptr_factory_.GetWeakPtr(),
std::move(endpoint_fetcher_callback));
simple_url_loader_->DownloadToString(
url_loader_factory_.get(), std::move(body_as_string_callback),
network::SimpleURLLoader::kMaxBoundedStringDownloadSize);
}
void EndpointFetcher::OnResponseFetched(
EndpointFetcherCallback endpoint_fetcher_callback,
std::unique_ptr<std::string> response_body) {
int http_status_code = -1;
if (simple_url_loader_->ResponseInfo() &&
simple_url_loader_->ResponseInfo()->headers) {
http_status_code =
simple_url_loader_->ResponseInfo()->headers->response_code();
}
int net_error_code = simple_url_loader_->NetError();
// The EndpointFetcher and its members will be destroyed after
// any of the below callbacks. Do not access The EndpointFetcher
// or its members after the callbacks.
simple_url_loader_.reset();
auto response = std::make_unique<EndpointResponse>();
response->http_status_code = http_status_code;
if (http_status_code == net::HTTP_UNAUTHORIZED ||
http_status_code == net::HTTP_FORBIDDEN) {
response->error_type =
absl::make_optional<FetchErrorType>(FetchErrorType::kAuthError);
// We cannot assume that the response was in JSON, and hence cannot sanitize
// the response. Send the respond as-is.
// TODO: Think about how to better handle different MIME-types here.
response->response = *response_body;
std::move(endpoint_fetcher_callback).Run(std::move(response));
return;
}
if (net_error_code != net::OK) {
response->error_type =
absl::make_optional<FetchErrorType>(FetchErrorType::kNetError);
}
if (response_body) {
if (sanitize_response_) {
data_decoder::JsonSanitizer::Sanitize(
std::move(*response_body),
base::BindOnce(&EndpointFetcher::OnSanitizationResult,
weak_ptr_factory_.GetWeakPtr(), std::move(response),
std::move(endpoint_fetcher_callback)));
} else {
response->response = *response_body;
std::move(endpoint_fetcher_callback).Run(std::move(response));
}
} else {
std::string net_error = net::ErrorToString(net_error_code);
VLOG(1) << __func__ << " with response error: " << net_error;
response->response = "There was a response error";
std::move(endpoint_fetcher_callback).Run(std::move(response));
}
}
void EndpointFetcher::OnSanitizationResult(
std::unique_ptr<EndpointResponse> response,
EndpointFetcherCallback endpoint_fetcher_callback,
data_decoder::JsonSanitizer::Result result) {
if (result.value.has_value()) {
response->response = result.value.value();
} else if (result.error.has_value()) {
response->error_type =
absl::make_optional<FetchErrorType>(FetchErrorType::kResultParseError);
response->response =
"There was a sanitization error: " + result.error.value();
} else {
response->error_type =
absl::make_optional<FetchErrorType>(FetchErrorType::kResultParseError);
response->response = "There was an unknown sanitization error";
}
// The EndpointFetcher and its members will be destroyed after
// any the below callback. Do not access The EndpointFetcher
// or its members after the callback.
std::move(endpoint_fetcher_callback).Run(std::move(response));
}
std::string EndpointFetcher::GetUrlForTesting() {
return url_.spec();
}