blob: b92f390ac412e06952f75a651d99ed8e3237dd65 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/webid/idp_network_request_manager.h"
#include "base/base64.h"
#include "base/containers/flat_set.h"
#include "base/json/json_writer.h"
#include "base/strings/escape.h"
#include "base/task/sequenced_task_runner.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/webid/fedcm_metrics.h"
#include "content/public/browser/identity_request_dialog_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/color_parser.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/isolation_info.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/base/url_util.h"
#include "net/cookies/site_for_cookies.h"
#include "net/http/http_request_headers.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/client_security_state.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/common/manifest/manifest_icon_selector.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/color_utils.h"
#include "url/origin.h"
namespace content {
namespace {
using LoginState = IdentityRequestAccount::LoginState;
using AccountList = IdpNetworkRequestManager::AccountList;
using ClientMetadata = IdpNetworkRequestManager::ClientMetadata;
using Endpoints = IdpNetworkRequestManager::Endpoints;
using FetchStatus = content::IdpNetworkRequestManager::FetchStatus;
using ParseStatus = content::IdpNetworkRequestManager::ParseStatus;
// TODO(kenrb): These need to be defined in the explainer or draft spec and
// referenced here.
// Path to find the well-known file on the eTLD+1 host.
constexpr char kWellKnownPath[] = "/.well-known/web-identity";
// Well-known file JSON keys
constexpr char kProviderUrlListKey[] = "provider_urls";
// fedcm.json configuration keys.
constexpr char kIdAssertionEndpoint[] = "id_assertion_endpoint";
constexpr char kAccountsEndpointKey[] = "accounts_endpoint";
constexpr char kClientMetadataEndpointKey[] = "client_metadata_endpoint";
constexpr char kMetricsEndpoint[] = "metrics_endpoint";
constexpr char kSigninUrlKey[] = "signin_url";
// Keys in fedcm.json 'branding' dictionary.
constexpr char kIdpBrandingBackgroundColor[] = "background_color";
constexpr char kIdpBrandingForegroundColor[] = "color";
constexpr char kIdpBrandingIcons[] = "icons";
// Client metadata keys.
constexpr char kPrivacyPolicyKey[] = "privacy_policy_url";
constexpr char kTermsOfServiceKey[] = "terms_of_service_url";
// Accounts endpoint response keys.
constexpr char kAccountsKey[] = "accounts";
constexpr char kIdpBrandingKey[] = "branding";
// Keys in 'account' dictionary in accounts endpoint.
constexpr char kAccountIdKey[] = "id";
constexpr char kAccountEmailKey[] = "email";
constexpr char kAccountNameKey[] = "name";
constexpr char kAccountGivenNameKey[] = "given_name";
constexpr char kAccountPictureKey[] = "picture";
constexpr char kAccountApprovedClientsKey[] = "approved_clients";
// Keys in 'branding' 'icons' dictionary in accounts endpoint.
constexpr char kIdpBrandingIconUrl[] = "url";
constexpr char kIdpBrandingIconSize[] = "size";
constexpr char kTokenKey[] = "token";
// Body content types.
constexpr char kUrlEncodedContentType[] = "application/x-www-form-urlencoded";
constexpr char kResponseBodyContentType[] = "application/json";
// 1 MiB is an arbitrary upper bound that should account for any reasonable
// response size that is a part of this protocol.
constexpr int maxResponseSizeInKiB = 1024;
net::NetworkTrafficAnnotationTag CreateTrafficAnnotation() {
return net::DefineNetworkTrafficAnnotation("fedcm", R"(
semantics {
sender: "FedCM Backend"
description:
"The FedCM API allows websites to initiate user account login "
"with identity providers which provide federated sign-in "
"capabilities using OpenID Connect. The API provides a "
"browser-mediated alternative to previously existing federated "
"sign-in implementations."
trigger:
"A website executes the navigator.credentials.get() JavaScript "
"method to initiate federated user sign-in to a designated "
"provider."
data:
"An identity request contains a scope of claims specifying what "
"user information is being requested from the identity provider, "
"a label identifying the calling website application, and some "
"OpenID Connect protocol functional fields."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting: "Not user controlled. But the verification is a trusted "
"API that doesn't use user data."
policy_exception_justification:
"Not implemented, considered not useful as no content is being "
"uploaded; this request merely downloads the resources on the web."
})");
}
GURL ResolveConfigUrl(const GURL& config_url, const std::string& endpoint) {
if (endpoint.empty())
return GURL();
return config_url.Resolve(endpoint);
}
absl::optional<content::IdentityRequestAccount> ParseAccount(
const base::Value::Dict& account,
const std::string& client_id) {
auto* id = account.FindString(kAccountIdKey);
auto* email = account.FindString(kAccountEmailKey);
auto* name = account.FindString(kAccountNameKey);
auto* given_name = account.FindString(kAccountGivenNameKey);
auto* picture = account.FindString(kAccountPictureKey);
auto* approved_clients = account.FindList(kAccountApprovedClientsKey);
// required fields
if (!(id && email && name))
return absl::nullopt;
RecordApprovedClientsExistence(approved_clients != nullptr);
absl::optional<LoginState> approved_value;
if (approved_clients) {
for (const base::Value& entry : *approved_clients) {
if (entry.is_string() && entry.GetString() == client_id) {
approved_value = LoginState::kSignIn;
break;
}
}
if (!approved_value) {
// We did get an approved_clients list, but the client ID was not found.
// This means we are certain that the client is not approved; set to
// kSignUp instead of leaving as nullopt.
approved_value = LoginState::kSignUp;
}
RecordApprovedClientsSize(approved_clients->size());
}
return content::IdentityRequestAccount(
*id, *email, *name, given_name ? *given_name : "",
picture ? GURL(*picture) : GURL(), approved_value);
}
// Parses accounts from given Value. Returns true if parse is successful and
// adds parsed accounts to the |account_list|.
bool ParseAccounts(const base::Value::List& accounts,
AccountList& account_list,
const std::string& client_id) {
DCHECK(account_list.empty());
base::flat_set<std::string> account_ids;
for (auto& account : accounts) {
const base::Value::Dict* account_dict = account.GetIfDict();
if (!account_dict) {
return false;
}
auto parsed_account = ParseAccount(*account_dict, client_id);
if (parsed_account) {
if (account_ids.count(parsed_account->id))
return false;
account_list.push_back(parsed_account.value());
account_ids.insert(parsed_account->id);
}
}
return !account_list.empty();
}
absl::optional<SkColor> ParseCssColor(const std::string* value) {
if (value == nullptr)
return absl::nullopt;
SkColor color;
if (!content::ParseCssColorString(*value, &color))
return absl::nullopt;
return SkColorSetA(color, 0xff);
}
// Parse IdentityProviderMetadata from given value. Overwrites |idp_metadata|
// with the parsed value.
void ParseIdentityProviderMetadata(const base::Value::Dict& idp_metadata_value,
int brand_icon_ideal_size,
int brand_icon_minimum_size,
IdentityProviderMetadata& idp_metadata) {
idp_metadata.brand_background_color =
ParseCssColor(idp_metadata_value.FindString(kIdpBrandingBackgroundColor));
if (idp_metadata.brand_background_color) {
idp_metadata.brand_text_color = ParseCssColor(
idp_metadata_value.FindString(kIdpBrandingForegroundColor));
if (idp_metadata.brand_text_color) {
float text_contrast_ratio = color_utils::GetContrastRatio(
*idp_metadata.brand_background_color, *idp_metadata.brand_text_color);
if (text_contrast_ratio < color_utils::kMinimumReadableContrastRatio)
idp_metadata.brand_text_color = absl::nullopt;
}
}
const base::Value::List* icons_value =
idp_metadata_value.FindList(kIdpBrandingIcons);
if (!icons_value) {
return;
}
std::vector<blink::Manifest::ImageResource> icons;
for (const base::Value& icon_value : *icons_value) {
const base::Value::Dict* icon_value_dict = icon_value.GetIfDict();
if (!icon_value_dict) {
continue;
}
const std::string* icon_src =
icon_value_dict->FindString(kIdpBrandingIconUrl);
if (!icon_src) {
continue;
}
blink::Manifest::ImageResource icon;
icon.src = GURL(*icon_src);
if (!icon.src.is_valid()) {
continue;
}
icon.purpose = {blink::mojom::ManifestImageResource_Purpose::MASKABLE};
absl::optional<int> icon_size =
icon_value_dict->FindInt(kIdpBrandingIconSize);
int icon_size_int = icon_size.value_or(0);
icon.sizes.emplace_back(icon_size_int, icon_size_int);
icons.push_back(icon);
}
idp_metadata.brand_icon_url =
blink::ManifestIconSelector::FindBestMatchingSquareIcon(
icons, brand_icon_ideal_size, brand_icon_minimum_size,
blink::mojom::ManifestImageResource_Purpose::MASKABLE);
}
ParseStatus GetResponseError(std::string* response_body, int response_code) {
if (response_code == net::HTTP_NOT_FOUND)
return ParseStatus::kHttpNotFoundError;
if (!response_body)
return ParseStatus::kNoResponseError;
return ParseStatus::kSuccess;
}
ParseStatus GetParsingError(
const data_decoder::DataDecoder::ValueOrError& result) {
if (!result.has_value())
return ParseStatus::kInvalidResponseError;
const base::Value::Dict* response = result->GetIfDict();
if (!response) {
return ParseStatus::kInvalidResponseError;
}
return ParseStatus::kSuccess;
}
void OnJsonParsed(
IdpNetworkRequestManager::ParseJsonCallback parse_json_callback,
int response_code,
data_decoder::DataDecoder::ValueOrError result) {
ParseStatus parse_status = GetParsingError(result);
std::move(parse_json_callback)
.Run({parse_status, response_code}, std::move(result));
}
void OnDownloadedJson(
IdpNetworkRequestManager::ParseJsonCallback parse_json_callback,
std::unique_ptr<std::string> response_body,
int response_code) {
ParseStatus parse_status =
GetResponseError(response_body.get(), response_code);
if (parse_status != ParseStatus::kSuccess) {
std::move(parse_json_callback)
.Run({parse_status, response_code},
data_decoder::DataDecoder::ValueOrError());
return;
}
data_decoder::DataDecoder::ParseJsonIsolated(
*response_body,
base::BindOnce(&OnJsonParsed, std::move(parse_json_callback),
response_code));
}
void OnWellKnownParsed(
IdpNetworkRequestManager::FetchWellKnownCallback callback,
const GURL& well_known_url,
FetchStatus fetch_status,
data_decoder::DataDecoder::ValueOrError result) {
if (callback.IsCancelled())
return;
std::set<GURL> urls;
if (fetch_status.parse_status != ParseStatus::kSuccess) {
std::move(callback).Run(fetch_status, urls);
return;
}
const base::Value::Dict* dict = result->GetIfDict();
if (!dict) {
std::move(callback).Run(
{ParseStatus::kInvalidResponseError, fetch_status.response_code}, urls);
return;
}
const base::Value::List* list = dict->FindList(kProviderUrlListKey);
if (!list) {
std::move(callback).Run(
{ParseStatus::kInvalidResponseError, fetch_status.response_code}, urls);
return;
}
if (list->empty()) {
std::move(callback).Run(
{ParseStatus::kEmptyListError, fetch_status.response_code}, urls);
return;
}
for (const auto& value : *list) {
const std::string* url_str = value.GetIfString();
if (!url_str) {
std::move(callback).Run(
{ParseStatus::kInvalidResponseError, fetch_status.response_code},
std::set<GURL>());
return;
}
GURL url(*url_str);
if (!url.is_valid()) {
url = well_known_url.Resolve(*url_str);
}
urls.insert(url);
}
std::move(callback).Run({ParseStatus::kSuccess, fetch_status.response_code},
urls);
}
void OnConfigParsed(const GURL& provider,
int idp_brand_icon_ideal_size,
int idp_brand_icon_minimum_size,
IdpNetworkRequestManager::FetchConfigCallback callback,
FetchStatus fetch_status,
data_decoder::DataDecoder::ValueOrError result) {
if (fetch_status.parse_status != ParseStatus::kSuccess) {
std::move(callback).Run(fetch_status, Endpoints(),
IdentityProviderMetadata());
return;
}
const base::Value::Dict& response = result->GetDict();
auto ExtractEndpoint = [&](const char* key) {
const std::string* endpoint = response.FindString(key);
if (!endpoint) {
return GURL();
}
return ResolveConfigUrl(provider, *endpoint);
};
Endpoints endpoints;
endpoints.token = ExtractEndpoint(kIdAssertionEndpoint);
endpoints.accounts = ExtractEndpoint(kAccountsEndpointKey);
endpoints.client_metadata = ExtractEndpoint(kClientMetadataEndpointKey);
endpoints.metrics = ExtractEndpoint(kMetricsEndpoint);
const base::Value::Dict* idp_metadata_value =
response.FindDict(kIdpBrandingKey);
IdentityProviderMetadata idp_metadata;
idp_metadata.config_url = provider;
if (idp_metadata_value) {
ParseIdentityProviderMetadata(*idp_metadata_value,
idp_brand_icon_ideal_size,
idp_brand_icon_minimum_size, idp_metadata);
}
idp_metadata.idp_signin_url = ExtractEndpoint(kSigninUrlKey);
std::move(callback).Run({ParseStatus::kSuccess, fetch_status.response_code},
endpoints, std::move(idp_metadata));
}
void OnClientMetadataParsed(
IdpNetworkRequestManager::FetchClientMetadataCallback callback,
FetchStatus fetch_status,
data_decoder::DataDecoder::ValueOrError result) {
if (fetch_status.parse_status != ParseStatus::kSuccess) {
std::move(callback).Run(fetch_status, ClientMetadata());
return;
}
const base::Value::Dict& response = result->GetDict();
auto ExtractUrl = [&](const char* key) {
const std::string* endpoint = response.FindString(key);
if (!endpoint) {
return GURL();
}
GURL url = GURL(*endpoint);
if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) {
return GURL();
}
return url;
};
IdpNetworkRequestManager::ClientMetadata data;
data.privacy_policy_url = ExtractUrl(kPrivacyPolicyKey);
data.terms_of_service_url = ExtractUrl(kTermsOfServiceKey);
std::move(callback).Run({ParseStatus::kSuccess, fetch_status.response_code},
data);
}
void OnAccountsRequestParsed(
std::string client_id,
IdpNetworkRequestManager::AccountsRequestCallback callback,
FetchStatus fetch_status,
data_decoder::DataDecoder::ValueOrError result) {
if (fetch_status.parse_status != ParseStatus::kSuccess) {
std::move(callback).Run(fetch_status, AccountList());
return;
}
AccountList account_list;
const base::Value::Dict& response = result->GetDict();
const base::Value::List* accounts = response.FindList(kAccountsKey);
if (accounts && accounts->empty()) {
std::move(callback).Run(
{ParseStatus::kEmptyListError, fetch_status.response_code},
AccountList());
return;
}
bool accounts_valid =
accounts && ParseAccounts(*accounts, account_list, client_id);
if (!accounts_valid) {
std::move(callback).Run(
{ParseStatus::kInvalidResponseError, fetch_status.response_code},
AccountList());
return;
}
std::move(callback).Run({ParseStatus::kSuccess, fetch_status.response_code},
std::move(account_list));
}
void OnTokenRequestParsed(
IdpNetworkRequestManager::TokenRequestCallback callback,
FetchStatus fetch_status,
data_decoder::DataDecoder::ValueOrError result) {
if (fetch_status.parse_status != ParseStatus::kSuccess) {
std::move(callback).Run(fetch_status, std::string());
return;
}
const base::Value::Dict& response = result->GetDict();
const std::string* token = response.FindString(kTokenKey);
if (!token) {
std::move(callback).Run(
{ParseStatus::kInvalidResponseError, fetch_status.response_code},
std::string());
return;
}
std::move(callback).Run({ParseStatus::kSuccess, fetch_status.response_code},
*token);
}
void OnLogoutCompleted(IdpNetworkRequestManager::LogoutCallback callback,
std::unique_ptr<std::string> response_body,
int response_code) {
std::move(callback).Run();
}
} // namespace
IdpNetworkRequestManager::Endpoints::Endpoints() = default;
IdpNetworkRequestManager::Endpoints::~Endpoints() = default;
IdpNetworkRequestManager::Endpoints::Endpoints(const Endpoints& other) =
default;
// static
std::unique_ptr<IdpNetworkRequestManager> IdpNetworkRequestManager::Create(
RenderFrameHostImpl* host) {
// Use the browser process URL loader factory because it has cross-origin
// read blocking disabled. This is safe because even though these are
// renderer-initiated fetches, the browser parses the responses and does not
// leak the values to the renderer. The renderer should only learn information
// when the user selects an account to sign in.
return std::make_unique<IdpNetworkRequestManager>(
host->GetLastCommittedOrigin(),
host->GetStoragePartition()->GetURLLoaderFactoryForBrowserProcess(),
host->BuildClientSecurityState());
}
IdpNetworkRequestManager::IdpNetworkRequestManager(
const url::Origin& relying_party_origin,
scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
network::mojom::ClientSecurityStatePtr client_security_state)
: relying_party_origin_(relying_party_origin),
loader_factory_(loader_factory),
client_security_state_(std::move(client_security_state)) {
DCHECK(client_security_state_);
// If COEP:credentialless was used, this would break FedCM credentialled
// requests. We clear the Cross-Origin-Embedder-Policy because FedCM responses
// are not really embedded in the page. They do not enter the renderer
// process. This is safe because FedCM does not leak any data to the
// requesting page except for the final issued token, and we only get that
// token if the server is a new FedCM server, on which we can rely to validate
// requestors if they want to.
client_security_state_->cross_origin_embedder_policy =
network::CrossOriginEmbedderPolicy();
}
IdpNetworkRequestManager::~IdpNetworkRequestManager() = default;
// static
absl::optional<GURL> IdpNetworkRequestManager::ComputeWellKnownUrl(
const GURL& provider) {
GURL well_known_url;
if (net::IsLocalhost(provider)) {
well_known_url = provider.GetWithEmptyPath();
} else {
std::string etld_plus_one = GetDomainAndRegistry(
provider, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
if (etld_plus_one.empty())
return absl::nullopt;
well_known_url = GURL(provider.scheme() + "://" + etld_plus_one);
}
GURL::Replacements replacements;
replacements.SetPathStr(kWellKnownPath);
return well_known_url.ReplaceComponents(replacements);
}
void IdpNetworkRequestManager::FetchWellKnown(const GURL& provider,
FetchWellKnownCallback callback) {
absl::optional<GURL> well_known_url =
IdpNetworkRequestManager::ComputeWellKnownUrl(provider);
if (!well_known_url) {
// Pass net::HTTP_OK as the |response_code| so we do not add a console error
// message about a fetch we didn't even attempt.
FetchStatus fetch_status = {ParseStatus::kHttpNotFoundError, net::HTTP_OK};
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&OnWellKnownParsed, std::move(callback),
/*well_known_url=*/GURL(), fetch_status,
data_decoder::DataDecoder::ValueOrError()));
return;
}
std::unique_ptr<network::ResourceRequest> resource_request =
CreateUncredentialedResourceRequest(*well_known_url,
/*send_origin=*/false,
/* follow_redirects= */ true);
DownloadJsonAndParse(
std::move(resource_request),
/*url_encoded_post_data=*/absl::nullopt,
base::BindOnce(&OnWellKnownParsed, std::move(callback), *well_known_url),
maxResponseSizeInKiB * 1024);
}
void IdpNetworkRequestManager::FetchConfig(const GURL& provider,
int idp_brand_icon_ideal_size,
int idp_brand_icon_minimum_size,
FetchConfigCallback callback) {
std::unique_ptr<network::ResourceRequest> resource_request =
CreateUncredentialedResourceRequest(provider,
/* send_origin= */ false);
DownloadJsonAndParse(
std::move(resource_request),
/*url_encoded_post_data=*/absl::nullopt,
base::BindOnce(&OnConfigParsed, provider, idp_brand_icon_ideal_size,
idp_brand_icon_minimum_size, std::move(callback)),
maxResponseSizeInKiB * 1024);
}
void IdpNetworkRequestManager::SendAccountsRequest(
const GURL& accounts_url,
const std::string& client_id,
AccountsRequestCallback callback) {
std::unique_ptr<network::ResourceRequest> resource_request =
CreateCredentialedResourceRequest(accounts_url,
/* send_origin= */ false);
DownloadJsonAndParse(
std::move(resource_request),
/*url_encoded_post_data=*/absl::nullopt,
base::BindOnce(&OnAccountsRequestParsed, client_id, std::move(callback)),
maxResponseSizeInKiB * 1024);
}
void IdpNetworkRequestManager::SendTokenRequest(
const GURL& token_url,
const std::string& account,
const std::string& url_encoded_post_data,
TokenRequestCallback callback) {
std::unique_ptr<network::ResourceRequest> resource_request =
CreateCredentialedResourceRequest(token_url,
/* send_origin= */ true);
DownloadJsonAndParse(
std::move(resource_request), url_encoded_post_data,
base::BindOnce(&OnTokenRequestParsed, std::move(callback)),
maxResponseSizeInKiB * 1024);
}
void IdpNetworkRequestManager::SendSuccessfulTokenRequestMetrics(
const GURL& metrics_endpoint_url,
base::TimeDelta api_call_to_show_dialog_time,
base::TimeDelta show_dialog_to_continue_clicked_time,
base::TimeDelta account_selected_to_token_response_time,
base::TimeDelta api_call_to_token_response_time) {
std::string url_encoded_post_data = base::StringPrintf(
"time_to_show_ui=%d"
"&time_to_continue=%d"
"&time_to_receive_token=%d"
"&turnaround_time=%d",
static_cast<int>(api_call_to_show_dialog_time.InMilliseconds()),
static_cast<int>(show_dialog_to_continue_clicked_time.InMilliseconds()),
static_cast<int>(
account_selected_to_token_response_time.InMilliseconds()),
static_cast<int>(api_call_to_token_response_time.InMilliseconds()));
std::unique_ptr<network::ResourceRequest> resource_request =
CreateCredentialedResourceRequest(metrics_endpoint_url,
/* send_origin= */ true);
DownloadJsonAndParse(std::move(resource_request), url_encoded_post_data,
IdpNetworkRequestManager::ParseJsonCallback(),
maxResponseSizeInKiB * 1024);
}
void IdpNetworkRequestManager::SendFailedTokenRequestMetrics(
const GURL& metrics_endpoint_url,
MetricsEndpointErrorCode error_code) {
std::string url_encoded_post_data =
base::StringPrintf("error_code=%d", static_cast<int>(error_code));
std::unique_ptr<network::ResourceRequest> resource_request =
CreateUncredentialedResourceRequest(metrics_endpoint_url,
/*send_origin=*/false);
DownloadJsonAndParse(std::move(resource_request), url_encoded_post_data,
IdpNetworkRequestManager::ParseJsonCallback(),
maxResponseSizeInKiB * 1024);
}
void IdpNetworkRequestManager::SendLogout(const GURL& logout_url,
LogoutCallback callback) {
// TODO(kenrb): Add browser test verifying that the response to this can
// clear cookies. https://crbug.com/1155312.
auto resource_request =
CreateCredentialedResourceRequest(logout_url, /* send_origin= */ false);
resource_request->headers.SetHeader(net::HttpRequestHeaders::kAccept, "*/*");
DownloadUrl(std::move(resource_request),
/*url_encoded_post_data=*/absl::nullopt,
base::BindOnce(&OnLogoutCompleted, std::move(callback)),
maxResponseSizeInKiB * 1024);
}
void IdpNetworkRequestManager::DownloadJsonAndParse(
std::unique_ptr<network::ResourceRequest> resource_request,
absl::optional<std::string> url_encoded_post_data,
ParseJsonCallback parse_json_callback,
size_t max_download_size) {
DownloadUrl(std::move(resource_request), std::move(url_encoded_post_data),
base::BindOnce(&OnDownloadedJson, std::move(parse_json_callback)),
max_download_size);
}
void IdpNetworkRequestManager::DownloadUrl(
std::unique_ptr<network::ResourceRequest> resource_request,
absl::optional<std::string> url_encoded_post_data,
DownloadCallback callback,
size_t max_download_size) {
if (url_encoded_post_data) {
resource_request->method = net::HttpRequestHeaders::kPostMethod;
resource_request->headers.SetHeader(net::HttpRequestHeaders::kContentType,
kUrlEncodedContentType);
}
std::unique_ptr<network::SimpleURLLoader> url_loader =
network::SimpleURLLoader::Create(std::move(resource_request),
CreateTrafficAnnotation());
if (url_encoded_post_data) {
url_loader->AttachStringForUpload(*url_encoded_post_data,
kUrlEncodedContentType);
}
network::SimpleURLLoader* url_loader_ptr = url_loader.get();
// Callback is a member of IdpNetworkRequestManager in order to cancel
// callback if IdpNetworkRequestManager object is destroyed prior to callback
// being run.
url_loader_ptr->DownloadToString(
loader_factory_.get(),
base::BindOnce(&IdpNetworkRequestManager::OnDownloadedUrl,
weak_ptr_factory_.GetWeakPtr(), std::move(url_loader),
std::move(callback)),
max_download_size);
}
void IdpNetworkRequestManager::OnDownloadedUrl(
std::unique_ptr<network::SimpleURLLoader> url_loader,
IdpNetworkRequestManager::DownloadCallback callback,
std::unique_ptr<std::string> response_body) {
auto* response_info = url_loader->ResponseInfo();
// Use the HTTP response code, if available. If it is not available, use the
// NetError(). Note that it is acceptable to put these in the same int because
// NetErrors are not positive, so they do not conflict with HTTP error codes.
int response_code = response_info && response_info->headers
? response_info->headers->response_code()
: url_loader->NetError();
url_loader.reset();
std::move(callback).Run(std::move(response_body), response_code);
}
void IdpNetworkRequestManager::FetchClientMetadata(
const GURL& endpoint,
const std::string& client_id,
FetchClientMetadataCallback callback) {
GURL target_url = endpoint.Resolve(
"?client_id=" + base::EscapeQueryParamValue(client_id, true));
std::unique_ptr<network::ResourceRequest> resource_request =
CreateUncredentialedResourceRequest(target_url,
/* send_origin= */ true);
DownloadJsonAndParse(
std::move(resource_request),
/*url_encoded_post_data=*/absl::nullopt,
base::BindOnce(&OnClientMetadataParsed, std::move(callback)),
maxResponseSizeInKiB * 1024);
}
std::unique_ptr<network::ResourceRequest>
IdpNetworkRequestManager::CreateUncredentialedResourceRequest(
const GURL& target_url,
bool send_origin,
bool follow_redirects) const {
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = target_url;
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->headers.SetHeader(net::HttpRequestHeaders::kAccept,
kResponseBodyContentType);
resource_request->destination =
network::mojom::RequestDestination::kWebIdentity;
// See https://github.com/fedidcg/FedCM/issues/379 for why the Origin header
// is sent instead of the Referrer header.
if (send_origin) {
resource_request->headers.SetHeader(net::HttpRequestHeaders::kOrigin,
relying_party_origin_.Serialize());
DCHECK(!follow_redirects);
}
if (follow_redirects) {
resource_request->redirect_mode = network::mojom::RedirectMode::kFollow;
} else {
resource_request->redirect_mode = network::mojom::RedirectMode::kError;
}
resource_request->request_initiator = relying_party_origin_;
resource_request->trusted_params = network::ResourceRequest::TrustedParams();
resource_request->trusted_params->isolation_info =
net::IsolationInfo::CreateTransient();
DCHECK(client_security_state_);
resource_request->trusted_params->client_security_state =
client_security_state_.Clone();
return resource_request;
}
std::unique_ptr<network::ResourceRequest>
IdpNetworkRequestManager::CreateCredentialedResourceRequest(
const GURL& target_url,
bool send_origin) const {
auto resource_request = std::make_unique<network::ResourceRequest>();
auto target_origin = url::Origin::Create(target_url);
auto site_for_cookies = net::SiteForCookies::FromOrigin(target_origin);
// We set the initiator to nullopt to denote browser-initiated so that this
// request is considered first-party. We want to send first-party cookies
// because this is not a real third-party request as it is mediated by the
// browser, and third-party cookies will be going away with 3pc deprecation,
// but we still need to send cookies in these requests.
// We use nullopt instead of target_origin because we want to send a
// `Sec-Fetch-Site: none` header instead of `Sec-Fetch-Site: same-origin`.
resource_request->request_initiator = absl::nullopt;
resource_request->destination =
network::mojom::RequestDestination::kWebIdentity;
resource_request->url = target_url;
resource_request->site_for_cookies = site_for_cookies;
if (send_origin) {
resource_request->headers.SetHeader(net::HttpRequestHeaders::kOrigin,
relying_party_origin_.Serialize());
}
resource_request->redirect_mode = network::mojom::RedirectMode::kError;
resource_request->headers.SetHeader(net::HttpRequestHeaders::kAccept,
kResponseBodyContentType);
resource_request->credentials_mode =
network::mojom::CredentialsMode::kInclude;
resource_request->trusted_params = network::ResourceRequest::TrustedParams();
resource_request->trusted_params->isolation_info = net::IsolationInfo::Create(
net::IsolationInfo::RequestType::kOther, target_origin, target_origin,
site_for_cookies);
DCHECK(client_security_state_);
resource_request->trusted_params->client_security_state =
client_security_state_.Clone();
return resource_request;
}
} // namespace content