blob: d252b946dfdb0790dd20d329e86932446980b6e1 [file] [log] [blame]
// Copyright 2025 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/accounts_fetcher.h"
#include <set>
#include "base/containers/contains.h"
#include "content/browser/webid/config_fetcher.h"
#include "content/browser/webid/flags.h"
#include "content/browser/webid/mappers.h"
#include "content/browser/webid/request_service.h"
#include "content/browser/webid/webid_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom-data-view.h"
using ::blink::mojom::FederatedAuthRequestResult;
using LoginState = content::IdentityRequestAccount::LoginState;
using SignInStateMatchStatus = content::webid::SignInStateMatchStatus;
using TokenStatus = content::webid::RequestIdTokenStatus;
namespace content::webid {
namespace {
static constexpr char kVcSdJwt[] = "vc+sd-jwt";
bool IsFrameActive(RenderFrameHost* frame) {
return frame && frame->IsActive();
}
} // namespace
AccountsFetcher::IdentityProviderGetInfo::IdentityProviderGetInfo(
blink::mojom::IdentityProviderRequestOptionsPtr provider,
blink::mojom::RpContext rp_context,
blink::mojom::RpMode rp_mode,
std::optional<blink::mojom::Format> format)
: provider(std::move(provider)),
rp_context(rp_context),
rp_mode(rp_mode),
format(format) {}
AccountsFetcher::IdentityProviderGetInfo::~IdentityProviderGetInfo() = default;
AccountsFetcher::IdentityProviderGetInfo::IdentityProviderGetInfo(
const IdentityProviderGetInfo& other) {
*this = other;
}
AccountsFetcher::IdentityProviderGetInfo&
AccountsFetcher::IdentityProviderGetInfo::operator=(
const IdentityProviderGetInfo& other) {
provider = other.provider->Clone();
rp_context = other.rp_context;
rp_mode = other.rp_mode;
format = other.format;
return *this;
}
AccountsFetcher::FedCmFetchingParams::FedCmFetchingParams(
blink::mojom::RpMode rp_mode,
int icon_ideal_size,
int icon_minimum_size,
MediationRequirement mediation_requirement)
: rp_mode(rp_mode),
icon_ideal_size(icon_ideal_size),
icon_minimum_size(icon_minimum_size),
mediation_requirement(mediation_requirement) {}
AccountsFetcher::FedCmFetchingParams::~FedCmFetchingParams() = default;
AccountsFetcher::AccountsFetcher(
RenderFrameHost& render_frame_host,
IdpNetworkRequestManager* network_manager,
FederatedIdentityApiPermissionContextDelegate* api_permission_delegate,
FederatedIdentityPermissionContextDelegate* permission_delegate,
FedCmFetchingParams params,
RequestService* federated_auth_request_impl)
: render_frame_host_(render_frame_host),
network_manager_(network_manager),
api_permission_delegate_(api_permission_delegate),
permission_delegate_(permission_delegate),
params_(params),
federated_auth_request_impl_(federated_auth_request_impl) {}
AccountsFetcher::~AccountsFetcher() = default;
void AccountsFetcher::FetchEndpointsForIdps(
const std::set<GURL>& idp_config_urls) {
std::vector<ConfigFetcher::FetchRequest> idps;
base::flat_map<GURL, IdentityProviderGetInfo>& token_request_get_infos =
federated_auth_request_impl_->GetTokenRequestGetInfos();
for (const auto& idp : idp_config_urls) {
auto idp_get = token_request_get_infos.find(idp);
CHECK(idp_get != token_request_get_infos.end());
idps.emplace_back(
idp, idp_get->second.provider->config->from_idp_registration_api);
}
config_fetcher_ =
std::make_unique<ConfigFetcher>(*render_frame_host_, network_manager_);
config_fetcher_->Start(
idps, params_.rp_mode, params_.icon_ideal_size, params_.icon_minimum_size,
base::BindOnce(&AccountsFetcher::OnAllConfigAndWellKnownFetched,
weak_ptr_factory_.GetWeakPtr()));
}
void AccountsFetcher::SendAllFailedTokenRequestMetrics(
blink::mojom::FederatedAuthRequestResult result,
bool did_show_ui) {
DCHECK(IsMetricsEndpointEnabled());
for (const auto& metrics_endpoint_kv : metrics_endpoints_) {
SendFailedTokenRequestMetrics(metrics_endpoint_kv.second, result,
did_show_ui);
}
}
void AccountsFetcher::SendSuccessfulTokenRequestMetrics(
const GURL& idp_config_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,
bool did_show_ui) {
DCHECK(IsMetricsEndpointEnabled());
for (const auto& metrics_endpoint_kv : metrics_endpoints_) {
const GURL& metrics_endpoint = metrics_endpoint_kv.second;
if (!metrics_endpoint.is_valid()) {
continue;
}
if (metrics_endpoint_kv.first == idp_config_url) {
network_manager_->SendSuccessfulTokenRequestMetrics(
metrics_endpoint, api_call_to_show_dialog_time,
show_dialog_to_continue_clicked_time,
account_selected_to_token_response_time,
api_call_to_token_response_time);
} else {
// Send kUserFailure so that IDP cannot tell difference between user
// selecting a different IDP and user dismissing dialog without
// selecting any IDP.
network_manager_->SendFailedTokenRequestMetrics(
metrics_endpoint, did_show_ui,
MetricsEndpointErrorCode::kUserFailure);
}
}
}
void AccountsFetcher::OnAllConfigAndWellKnownFetched(
std::vector<ConfigFetcher::FetchResult> fetch_results) {
config_fetcher_.reset();
base::TimeTicks well_known_and_config_fetched_time = base::TimeTicks::Now();
federated_auth_request_impl_->SetWellKnownAndConfigFetchedTime(
well_known_and_config_fetched_time);
base::flat_map<GURL, IdentityProviderGetInfo>& token_request_get_infos =
federated_auth_request_impl_->GetTokenRequestGetInfos();
for (const ConfigFetcher::FetchResult& fetch_result : fetch_results) {
const GURL& identity_provider_config_url =
fetch_result.identity_provider_config_url;
auto get_info_it =
token_request_get_infos.find(identity_provider_config_url);
CHECK(get_info_it != token_request_get_infos.end());
metrics_endpoints_[identity_provider_config_url] =
fetch_result.endpoints.metrics;
std::unique_ptr<IdentityProviderInfo> idp_info =
std::make_unique<IdentityProviderInfo>(
get_info_it->second.provider, std::move(fetch_result.endpoints),
fetch_result.metadata ? std::move(*fetch_result.metadata)
: IdentityProviderMetadata(),
get_info_it->second.rp_context, get_info_it->second.rp_mode,
get_info_it->second.format);
if (fetch_result.error) {
const ConfigFetcher::FetchError& fetch_error = *fetch_result.error;
if (fetch_error.additional_console_error_message) {
render_frame_host_->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
*fetch_error.additional_console_error_message);
}
federated_auth_request_impl_->OnFetchDataForIdpFailed(
std::move(idp_info), fetch_error.result, fetch_error.token_status,
/*should_delay_callback=*/params_.rp_mode == RpMode::kPassive);
continue;
}
if (IsIdPRegistrationEnabled()) {
if (get_info_it->second.provider->config->type) {
if (!base::Contains(fetch_result.metadata->types,
get_info_it->second.provider->config->type)) {
federated_auth_request_impl_->OnFetchDataForIdpFailed(
std::move(idp_info), FederatedAuthRequestResult::kTypeNotMatching,
TokenStatus::kConfigNotMatchingType,
/*should_delay_callback=*/false);
continue;
}
}
}
if (get_info_it->second.provider->format) {
// If a token format was specified, make sure that the configURL
// supports it as well as the feature is enabled.
if (!IsDelegationEnabled() ||
!base::Contains(fetch_result.metadata->formats, kVcSdJwt)) {
federated_auth_request_impl_->OnFetchDataForIdpFailed(
std::move(idp_info),
FederatedAuthRequestResult::kConfigInvalidResponse,
TokenStatus::kConfigInvalidResponse,
/*should_delay_callback=*/false);
continue;
}
}
federated_auth_request_impl_->SetIdpLoginInfo(
idp_info->metadata.idp_login_url, idp_info->provider->login_hint,
idp_info->provider->domain_hint);
// Make sure that we don't fetch accounts if the IDP sign-in bit is
// reset to false during the API call. e.g. by the login/logout HEADER.
// In the active flow we get here even if the IDP sign-in bit was false
// originally, because we need the well-known and config files to find
// the login URL.
idp_info->has_failing_idp_signin_status =
webid::ShouldFailAccountsEndpointRequestBecauseNotSignedInWithIdp(
*render_frame_host_, identity_provider_config_url,
permission_delegate_);
if (idp_info->has_failing_idp_signin_status) {
// If the user is logged out and we are in a active-mode, allow the
// user to sign-in to the IdP and return early.
if (params_.rp_mode == blink::mojom::RpMode::kActive) {
federated_auth_request_impl_->MaybeShowActiveModeModalDialog(
identity_provider_config_url, idp_info->metadata.idp_login_url);
return;
}
// Do not send metrics for IDP where the user is not signed-in in
// order to prevent IDP from using the user IP to make a probabilistic
// model of which websites a user visits.
idp_info->endpoints.metrics = GURL();
federated_auth_request_impl_->OnFetchDataForIdpFailed(
std::move(idp_info), FederatedAuthRequestResult::kNotSignedInWithIdp,
TokenStatus::kNotSignedInWithIdp,
/*should_delay_callback=*/true);
continue;
}
GURL accounts_endpoint = idp_info->endpoints.accounts;
std::string client_id = idp_info->provider->config->client_id;
const GURL& config_url = idp_info->provider->config->config_url;
network_manager_->SendAccountsRequest(
url::Origin::Create(config_url), accounts_endpoint, client_id,
base::BindOnce(&AccountsFetcher::OnAccountsResponseReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(idp_info)));
federated_auth_request_impl_->fedcm_metrics()->RecordAccountsRequestSent(
config_url);
}
}
void AccountsFetcher::OnAccountsResponseReceived(
std::unique_ptr<IdentityProviderInfo> idp_info,
IdpNetworkRequestManager::FetchStatus status,
std::vector<IdentityRequestAccountPtr> accounts) {
federated_auth_request_impl_->SetAccountsFetchedTime(base::TimeTicks::Now());
GURL idp_config_url = idp_info->provider->config->config_url;
const std::optional<bool> old_idp_signin_status =
permission_delegate_->GetIdpSigninStatus(
url::Origin::Create(idp_config_url));
webid::UpdateIdpSigninStatusForAccountsEndpointResponse(
*render_frame_host_, idp_config_url, status,
idp_info->has_failing_idp_signin_status, permission_delegate_);
if (status.parse_status != IdpNetworkRequestManager::ParseStatus::kSuccess) {
std::pair<FederatedAuthRequestResult, TokenStatus> resultAndTokenStatus =
AccountParseStatusToRequestResultAndTokenStatus(status.parse_status);
HandleAccountsFetchFailure(std::move(idp_info), old_idp_signin_status,
resultAndTokenStatus.first,
resultAndTokenStatus.second, status);
return;
}
RecordRawAccountsSize(accounts.size());
RecordAccountFieldsType(accounts);
FilterAccountsWithLabel(idp_info->metadata.requested_label, accounts);
FilterAccountsWithLoginHint(idp_info->provider->login_hint, accounts);
FilterAccountsWithDomainHint(idp_info->provider->domain_hint, accounts);
auto filter = [](const IdentityRequestAccountPtr& account) {
return account->is_filtered_out;
};
if (!federated_auth_request_impl_->HasUserTriedToSignInToIdp(
idp_config_url) ||
federated_auth_request_impl_->login_url() !=
idp_info->metadata.idp_login_url) {
std::erase_if(accounts, filter);
} else {
// If the user is logging in to new accounts, only show filtered
// accounts if there are no new unfiltered accounts. This includes in
// particular the case where all accounts are filtered out.
size_t new_unfiltered = std::count_if(
accounts.begin(), accounts.end(),
[&](const IdentityRequestAccountPtr& account) {
return !account->is_filtered_out &&
!federated_auth_request_impl_->HadAccountIdBeforeLogin(
account->id);
});
if (new_unfiltered > 0u) {
std::erase_if(accounts, filter);
}
}
if (accounts.size() == 0u) {
// No accounts remain, so treat as account fetch failure.
render_frame_host_->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Accounts were received, but none matched the login hint, domain "
"hint, and/or account labels provided.");
// If there are no accounts after filtering,treat this exactly the same
// as if we had received an empty accounts list, i.e.
// IdpNetworkRequestManager::ParseStatus::kEmptyListError.
HandleAccountsFetchFailure(std::move(idp_info), old_idp_signin_status,
FederatedAuthRequestResult::kAccountsListEmpty,
TokenStatus::kAccountsListEmpty, status);
return;
}
RecordReadyToShowAccountsSize(accounts.size());
ComputeLoginStates(idp_info->provider->config->config_url, accounts);
ComputeAccountFields(GetDisclosureFields(idp_info->provider->fields),
accounts);
OnAccountsFetchSucceeded(std::move(idp_info), status, std::move(accounts));
}
void AccountsFetcher::OnAccountsFetchSucceeded(
std::unique_ptr<IdentityProviderInfo> idp_info,
IdpNetworkRequestManager::FetchStatus status,
std::vector<IdentityRequestAccountPtr> accounts) {
bool need_client_metadata = false;
if (IsIframeOriginEnabled()) {
// For cross-site iframes, we need to fetch client metadata in case the
// IDP sends `client_matches_top_frame_origin: false`.
url::Origin embedding_origin =
render_frame_host_->GetMainFrame()->GetLastCommittedOrigin();
url::Origin rp_origin = render_frame_host_->GetLastCommittedOrigin();
need_client_metadata |=
!net::SchemefulSite::IsSameSite(embedding_origin, rp_origin);
}
if (!need_client_metadata &&
!idp_info->provider->config->from_idp_registration_api &&
!GetDisclosureFields(idp_info->provider->fields).empty()) {
for (const auto& account : accounts) {
if (account->idp_claimed_login_state.value_or(
account->browser_trusted_login_state) == LoginState::kSignUp) {
need_client_metadata |= true;
break;
}
}
}
if (need_client_metadata &&
webid::IsEndpointSameOrigin(idp_info->provider->config->config_url,
idp_info->endpoints.client_metadata)) {
// Copy OnClientMetadataResponseReceived() parameters because `idp_info`
// is moved.
GURL client_metadata_endpoint = idp_info->endpoints.client_metadata;
std::string client_id = idp_info->provider->config->client_id;
network_manager_->FetchClientMetadata(
client_metadata_endpoint, client_id, params_.icon_ideal_size,
params_.icon_minimum_size,
base::BindOnce(&AccountsFetcher::OnClientMetadataResponseReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(idp_info),
std::move(accounts)));
} else {
GURL idp_brand_icon_url = idp_info->metadata.brand_icon_url;
network_manager_->FetchAccountPicturesAndBrandIcons(
std::move(accounts), std::move(idp_info),
/*rp_brand_icon_url=*/GURL(),
base::BindOnce(&AccountsFetcher::OnFetchDataForIdpSucceeded,
weak_ptr_factory_.GetWeakPtr(),
IdpNetworkRequestManager::ClientMetadata()));
}
}
void AccountsFetcher::OnClientMetadataResponseReceived(
std::unique_ptr<IdentityProviderInfo> idp_info,
std::vector<IdentityRequestAccountPtr>&& accounts,
IdpNetworkRequestManager::FetchStatus status,
IdpNetworkRequestManager::ClientMetadata client_metadata) {
federated_auth_request_impl_->SetClientMetadataFetchedTime(
base::TimeTicks::Now());
// TODO(yigu): Clean up the client metadata related errors for metrics and
// console logs.
GURL rp_brand_icon_url = client_metadata.brand_icon_url;
network_manager_->FetchAccountPicturesAndBrandIcons(
std::move(accounts), std::move(idp_info), rp_brand_icon_url,
base::BindOnce(&AccountsFetcher::OnFetchDataForIdpSucceeded,
weak_ptr_factory_.GetWeakPtr(),
std::move(client_metadata)));
}
void AccountsFetcher::OnFetchDataForIdpSucceeded(
const IdpNetworkRequestManager::ClientMetadata& client_metadata,
std::vector<IdentityRequestAccountPtr> accounts,
std::unique_ptr<IdentityProviderInfo> idp_info,
const gfx::Image& rp_brand_icon) {
const GURL& idp_config_url = idp_info->provider->config->config_url;
std::vector<IdentityRequestDialogDisclosureField> disclosure_fields =
GetDisclosureFields(idp_info->provider->fields);
const std::string idp_for_display =
webid::FormatUrlForDisplay(idp_config_url);
idp_info->data = base::MakeRefCounted<IdentityProviderData>(
idp_for_display, idp_info->metadata,
ClientMetadata{client_metadata.terms_of_service_url,
client_metadata.privacy_policy_url,
client_metadata.brand_icon_url, rp_brand_icon},
idp_info->rp_context, idp_info->format, disclosure_fields,
/*has_login_status_mismatch=*/false);
idp_info->client_matches_top_frame_origin =
client_metadata.client_matches_top_frame_origin;
for (auto& account : accounts) {
account->identity_provider = idp_info->data;
}
federated_auth_request_impl_->OnFetchDataForIdpSucceeded(std::move(accounts),
std::move(idp_info));
}
void AccountsFetcher::FilterAccountsWithLabel(
const std::string& label,
std::vector<IdentityRequestAccountPtr>& accounts) {
if (label.empty()) {
return;
}
// Filter out all accounts whose labels do not match the requested label.
// Note that it is technically possible for us to end up with more than
// one account afterwards, in which case the multiple account chooser
// would be shown.
size_t accounts_remaining = 0u;
for (auto& account : accounts) {
if (!base::Contains(account->labels, label)) {
account->is_filtered_out = true;
} else {
++accounts_remaining;
}
}
federated_auth_request_impl_->fedcm_metrics()->RecordNumMatchingAccounts(
accounts_remaining, "AccountLabel");
}
void AccountsFetcher::FilterAccountsWithLoginHint(
const std::string& login_hint,
std::vector<IdentityRequestAccountPtr>& accounts) {
if (login_hint.empty()) {
return;
}
// Filter out all accounts whose ID and whose email do not match the login
// hint. Note that it is technically possible for us to end up with more
// than one account afterwards, in which case the multiple account chooser
// would be shown.
size_t accounts_remaining = 0u;
for (auto& account : accounts) {
if (account->is_filtered_out) {
continue;
}
if (!base::Contains(account->login_hints, login_hint)) {
account->is_filtered_out = true;
} else {
++accounts_remaining;
}
}
federated_auth_request_impl_->fedcm_metrics()->RecordNumMatchingAccounts(
accounts_remaining, "LoginHint");
}
void AccountsFetcher::FilterAccountsWithDomainHint(
const std::string& domain_hint,
std::vector<IdentityRequestAccountPtr>& accounts) {
if (domain_hint.empty()) {
return;
}
size_t accounts_remaining = 0u;
for (auto& account : accounts) {
if (account->is_filtered_out) {
continue;
}
if (domain_hint == RequestService::kWildcardDomainHint) {
if (account->domain_hints.empty()) {
account->is_filtered_out = true;
continue;
}
} else if (!base::Contains(account->domain_hints, domain_hint)) {
account->is_filtered_out = true;
continue;
}
++accounts_remaining;
}
federated_auth_request_impl_->fedcm_metrics()->RecordNumMatchingAccounts(
accounts_remaining, "DomainHint");
}
void AccountsFetcher::ComputeLoginStates(
const GURL& idp_config_url,
std::vector<IdentityRequestAccountPtr>& accounts) {
url::Origin idp_origin = url::Origin::Create(idp_config_url);
// Populate the accounts login state.
for (auto& account : accounts) {
// Record when IDP and browser have different user sign-in states.
bool idp_claimed_sign_in =
account->idp_claimed_login_state == LoginState::kSignIn;
account->last_used_timestamp = permission_delegate_->GetLastUsedTimestamp(
render_frame_host_->GetLastCommittedOrigin(),
federated_auth_request_impl_->GetEmbeddingOrigin(), idp_origin,
account->id);
if (idp_claimed_sign_in == account->last_used_timestamp.has_value()) {
federated_auth_request_impl_->fedcm_metrics()
->RecordSignInStateMatchStatus(idp_config_url,
SignInStateMatchStatus::kMatch);
} else if (idp_claimed_sign_in) {
federated_auth_request_impl_->fedcm_metrics()
->RecordSignInStateMatchStatus(
idp_config_url, SignInStateMatchStatus::kIdpClaimedSignIn);
} else {
federated_auth_request_impl_->fedcm_metrics()
->RecordSignInStateMatchStatus(
idp_config_url, SignInStateMatchStatus::kBrowserObservedSignIn);
}
if (webid::HasSharingPermissionOrIdpHasThirdPartyCookiesAccess(
*render_frame_host_, /*provider_url=*/idp_config_url,
federated_auth_request_impl_->GetEmbeddingOrigin(),
render_frame_host_->GetLastCommittedOrigin(), account->id,
permission_delegate_, api_permission_delegate_)) {
LoginState browser_observed_login_state =
account->last_used_timestamp.has_value() ? LoginState::kSignIn
: LoginState::kSignUp;
// At this moment we can trust login_state even though it's controlled
// by IdP. If it's kSignUp, it could mean that the browser's sharing
// permission is obsolete.
account->browser_trusted_login_state =
account->idp_claimed_login_state.value_or(
browser_observed_login_state);
}
}
}
void AccountsFetcher::HandleAccountsFetchFailure(
std::unique_ptr<IdentityProviderInfo> idp_info,
std::optional<bool> old_idp_signin_status,
blink::mojom::FederatedAuthRequestResult result,
std::optional<TokenStatus> token_status,
const IdpNetworkRequestManager::FetchStatus& status) {
if (status.parse_status != IdpNetworkRequestManager::ParseStatus::kSuccess) {
webid::MaybeAddResponseCodeToConsole(
*render_frame_host_, "accounts endpoint", status.response_code);
}
if (!old_idp_signin_status.has_value()) {
if (params_.rp_mode == blink::mojom::RpMode::kActive) {
federated_auth_request_impl_->MaybeShowActiveModeModalDialog(
idp_info->provider->config->config_url,
idp_info->metadata.idp_login_url);
return;
}
federated_auth_request_impl_->OnFetchDataForIdpFailed(
std::move(idp_info), result, token_status,
/*should_delay_callback=*/true);
return;
}
if (!IsFrameActive(render_frame_host_->GetMainFrame())) {
federated_auth_request_impl_->CompleteRequestWithError(
FederatedAuthRequestResult::kRpPageNotVisible,
TokenStatus::kRpPageNotVisible,
/*should_delay_callback=*/true);
return;
}
if (params_.mediation_requirement == MediationRequirement::kSilent) {
// By this moment we know that the user has granted permission in the
// past for the RP/IdP. Because otherwise we have returned already in
// `ShouldFailBeforeFetchingAccounts`. It means that we don't need to show
// any UI to respect `mediation: silent`.
federated_auth_request_impl_->OnFetchDataForIdpFailed(
std::move(idp_info),
FederatedAuthRequestResult::kSilentMediationFailure,
TokenStatus::kSilentMediationFailure,
/*should_delay_callback=*/true);
return;
}
// We are going to show mismatch UI, so fetch the brand icon URL (needed
// at least for passive mode). We currently never need the RP icon for
// mismatch UI.
network_manager_->FetchIdpBrandIcon(
std::move(idp_info), base::BindOnce(&AccountsFetcher::OnIdpMismatch,
weak_ptr_factory_.GetWeakPtr()));
}
void AccountsFetcher::OnIdpMismatch(
std::unique_ptr<IdentityProviderInfo> idp_info) {
const std::string idp_for_display =
webid::FormatUrlForDisplay(idp_info->provider->config->config_url);
idp_info->data = base::MakeRefCounted<IdentityProviderData>(
idp_for_display, idp_info->metadata,
ClientMetadata{GURL(), GURL(), GURL(), gfx::Image()},
idp_info->rp_context, idp_info->format,
GetDisclosureFields(idp_info->provider->fields),
/*has_login_status_mismatch=*/true);
federated_auth_request_impl_->OnIdpMismatch(std::move(idp_info));
}
void AccountsFetcher::SendFailedTokenRequestMetrics(
const GURL& metrics_endpoint,
blink::mojom::FederatedAuthRequestResult result,
bool did_show_ui) {
DCHECK(IsMetricsEndpointEnabled());
if (!metrics_endpoint.is_valid()) {
return;
}
network_manager_->SendFailedTokenRequestMetrics(
metrics_endpoint, did_show_ui,
FederatedAuthRequestResultToMetricsEndpointErrorCode(result));
}
} // namespace content::webid