#include "content/browser/webid/federated_auth_request_impl.h"
#include "base/command_line.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
#include "base/ranges/algorithm.h"
#include "base/strings/escape.h"
#include "base/strings/string_piece.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "components/url_formatter/elide_url.h"
#include "content/browser/bad_message.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/webid/fake_identity_request_dialog_controller.h"
#include "content/browser/webid/federated_auth_request_page_data.h"
#include "content/browser/webid/federated_auth_user_info_request.h"
#include "content/browser/webid/flags.h"
#include "content/browser/webid/mdocs/mdoc_provider.h"
#include "content/browser/webid/webid_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/federated_identity_api_permission_context_delegate.h"
#include "content/public/browser/federated_identity_auto_reauthn_permission_context_delegate.h"
#include "content/public/browser/federated_identity_permission_context_delegate.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/page_visibility_state.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/base/url_util.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h"
using blink::mojom::FederatedAuthRequestResult;
using blink::mojom::IdentityProviderConfig;
using blink::mojom::IdentityProviderConfigPtr;
using blink::mojom::IdentityProviderGetParametersPtr;
using blink::mojom::LogoutRpsStatus;
using blink::mojom::RequestTokenStatus;
using blink::mojom::RequestUserInfoStatus;
using FederatedApiPermissionStatus =
content::FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
using TokenStatus = content::FedCmRequestIdTokenStatus;
using SignInStateMatchStatus = content::FedCmSignInStateMatchStatus;
using LoginState = content::IdentityRequestAccount::LoginState;
using SignInMode = content::IdentityRequestAccount::SignInMode;
using CompleteRequestWithErrorCallback =
base::OnceCallback<void(blink::mojom::FederatedAuthRequestResult,
absl::optional<content::FedCmRequestIdTokenStatus>,
bool)>;
namespace content {
namespace {
static constexpr base::TimeDelta kDefaultTokenRequestDelay = base::Seconds(3);
static constexpr base::TimeDelta kMaxRejectionTime = base::Seconds(60);
std::string ComputeUrlEncodedTokenPostData(
const std::string& client_id,
const std::string& nonce,
const std::string& account_id,
bool is_sign_in,
const std::vector<std::string>& scope,
const std::vector<std::string>& responseType,
const base::flat_map<std::string, std::string>& params) {
std::string query;
if (!client_id.empty())
query +=
"client_id=" + base::EscapeUrlEncodedData(client_id, true);
if (!nonce.empty()) {
if (!query.empty())
query += "&";
query += "nonce=" + base::EscapeUrlEncodedData(nonce, true);
}
if (!account_id.empty()) {
if (!query.empty())
query += "&";
query += "account_id=" +
base::EscapeUrlEncodedData(account_id, true);
}
std::string disclosure_text_shown = is_sign_in ? "false" : "true";
if (!query.empty())
query += "&disclosure_text_shown=" + disclosure_text_shown;
if (IsFedCmAuthzEnabled()) {
if (!scope.empty()) {
query += "&scope=" + base::EscapeUrlEncodedData(
base::JoinString(scope, " "), true);
}
if (!responseType.empty()) {
query += "&response_type=" +
base::EscapeUrlEncodedData(base::JoinString(responseType, " "),
true);
}
for (const auto& pair : params) {
query += "&" + base::EscapeUrlEncodedData(pair.first, true) +
"=" + base::EscapeUrlEncodedData(pair.second, true);
}
}
return query;
}
RequestTokenStatus FederatedAuthRequestResultToRequestTokenStatus(
FederatedAuthRequestResult result) {
switch (result) {
case FederatedAuthRequestResult::kSuccess: {
return RequestTokenStatus::kSuccess;
}
case FederatedAuthRequestResult::kErrorTooManyRequests: {
return RequestTokenStatus::kErrorTooManyRequests;
}
case FederatedAuthRequestResult::kErrorCanceled: {
return RequestTokenStatus::kErrorCanceled;
}
case FederatedAuthRequestResult::kShouldEmbargo:
case FederatedAuthRequestResult::kErrorDisabledInSettings:
case FederatedAuthRequestResult::kErrorFetchingWellKnownHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingWellKnownNoResponse:
case FederatedAuthRequestResult::kErrorFetchingWellKnownInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingWellKnownListEmpty:
case FederatedAuthRequestResult::kErrorFetchingWellKnownInvalidContentType:
case FederatedAuthRequestResult::kErrorConfigNotInWellKnown:
case FederatedAuthRequestResult::kErrorWellKnownTooBig:
case FederatedAuthRequestResult::kErrorFetchingConfigHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingConfigNoResponse:
case FederatedAuthRequestResult::kErrorFetchingConfigInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingConfigInvalidContentType:
case FederatedAuthRequestResult::kErrorFetchingClientMetadataHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingClientMetadataNoResponse:
case FederatedAuthRequestResult::
kErrorFetchingClientMetadataInvalidResponse:
case FederatedAuthRequestResult::
kErrorFetchingClientMetadataInvalidContentType:
case FederatedAuthRequestResult::kErrorFetchingAccountsHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingAccountsNoResponse:
case FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingAccountsListEmpty:
case FederatedAuthRequestResult::kErrorFetchingAccountsInvalidContentType:
case FederatedAuthRequestResult::kErrorFetchingIdTokenHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingIdTokenNoResponse:
case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidContentType:
case FederatedAuthRequestResult::kErrorRpPageNotVisible:
case FederatedAuthRequestResult::kError: {
return RequestTokenStatus::kError;
}
}
}
IdpNetworkRequestManager::MetricsEndpointErrorCode
FederatedAuthRequestResultToMetricsEndpointErrorCode(
blink::mojom::FederatedAuthRequestResult result) {
switch (result) {
case FederatedAuthRequestResult::kSuccess: {
return IdpNetworkRequestManager::MetricsEndpointErrorCode::kNone;
}
case FederatedAuthRequestResult::kErrorTooManyRequests:
case FederatedAuthRequestResult::kErrorCanceled: {
return IdpNetworkRequestManager::MetricsEndpointErrorCode::kRpFailure;
}
case FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingAccountsListEmpty:
case FederatedAuthRequestResult::kErrorFetchingAccountsInvalidContentType: {
return IdpNetworkRequestManager::MetricsEndpointErrorCode::
kAccountsEndpointInvalidResponse;
}
case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidContentType: {
return IdpNetworkRequestManager::MetricsEndpointErrorCode::
kTokenEndpointInvalidResponse;
}
case FederatedAuthRequestResult::kShouldEmbargo:
case FederatedAuthRequestResult::kErrorDisabledInSettings:
case FederatedAuthRequestResult::kErrorRpPageNotVisible: {
return IdpNetworkRequestManager::MetricsEndpointErrorCode::kUserFailure;
}
case FederatedAuthRequestResult::kErrorFetchingWellKnownHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingWellKnownNoResponse:
case FederatedAuthRequestResult::kErrorFetchingConfigHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingConfigNoResponse:
case FederatedAuthRequestResult::kErrorFetchingClientMetadataHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingClientMetadataNoResponse:
case FederatedAuthRequestResult::kErrorFetchingAccountsHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingAccountsNoResponse:
case FederatedAuthRequestResult::kErrorFetchingIdTokenHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingIdTokenNoResponse: {
return IdpNetworkRequestManager::MetricsEndpointErrorCode::
kIdpServerUnavailable;
}
case FederatedAuthRequestResult::kErrorConfigNotInWellKnown:
case FederatedAuthRequestResult::kErrorWellKnownTooBig: {
return IdpNetworkRequestManager::MetricsEndpointErrorCode::kManifestError;
}
case FederatedAuthRequestResult::kErrorFetchingWellKnownListEmpty:
case FederatedAuthRequestResult::kErrorFetchingWellKnownInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingConfigInvalidResponse:
case FederatedAuthRequestResult::
kErrorFetchingClientMetadataInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingWellKnownInvalidContentType:
case FederatedAuthRequestResult::kErrorFetchingConfigInvalidContentType:
case FederatedAuthRequestResult::
kErrorFetchingClientMetadataInvalidContentType: {
return IdpNetworkRequestManager::MetricsEndpointErrorCode::
kIdpServerInvalidResponse;
}
case FederatedAuthRequestResult::kError: {
return IdpNetworkRequestManager::MetricsEndpointErrorCode::kOther;
}
}
}
base::TimeDelta GetRandomRejectionTime() {
return kMaxRejectionTime * base::RandDouble();
}
std::string FormatUrlForDisplay(const GURL& url) {
std::string formatted_url_str =
net::IsLocalhost(url)
? url.host()
: net::registry_controlled_domains::GetDomainAndRegistry(
url,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
return base::UTF16ToUTF8(url_formatter::FormatUrlForSecurityDisplay(
GURL(url.scheme() + "://" + formatted_url_str),
url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS));
}
std::string FormatOriginForDisplay(const url::Origin& origin) {
return FormatUrlForDisplay(origin.GetURL());
}
bool ShouldSuppressIdpSigninFailureDialog(
absl::optional<TokenStatus> token_status) {
if (!token_status) {
return false;
}
return token_status == TokenStatus::kAborted ||
token_status == TokenStatus::kUnhandledRequest;
}
FederatedAuthRequestPageData* GetPageData(RenderFrameHost* render_frame_host) {
return FederatedAuthRequestPageData::GetOrCreateForPage(
render_frame_host->GetPage());
}
void FilterAccountsWithLoginHint(
const std::string& login_hint,
IdpNetworkRequestManager::AccountList& accounts) {
if (login_hint.empty()) {
return;
}
auto Filter = [&login_hint](const IdentityRequestAccount& account) {
return std::find(account.hints.begin(), account.hints.end(), login_hint) ==
account.hints.end();
};
accounts.erase(std::remove_if(accounts.begin(), accounts.end(), Filter),
accounts.end());
}
std::unique_ptr<FedCmMetrics> CreateFedCmMetrics(
const GURL& provider_config_url,
const ukm::SourceId& source_id,
bool is_disabled) {
return std::make_unique<FedCmMetrics>(provider_config_url, source_id,
base::RandInt(1, 1 << 30), is_disabled);
}
std::string GetTopFrameOriginForDisplay(const url::Origin& top_frame_origin) {
return FormatOriginForDisplay(top_frame_origin);
}
absl::optional<std::string> GetIframeOriginForDisplay(
const url::Origin& top_frame_origin,
const url::Origin& iframe_origin,
CompleteRequestWithErrorCallback callback) {
bool exclude_iframe = net::registry_controlled_domains::SameDomainOrHost(
top_frame_origin, iframe_origin,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
absl::optional<std::string> iframe_for_display = absl::nullopt;
if (!exclude_iframe) {
iframe_for_display = FormatOriginForDisplay(iframe_origin);
if (iframe_origin.opaque()) {
std::move(callback).Run(FederatedAuthRequestResult::kError,
absl::nullopt,
false);
}
}
return iframe_for_display;
}
}
FederatedAuthRequestImpl::FetchData::FetchData() = default;
FederatedAuthRequestImpl::FetchData::~FetchData() = default;
FederatedAuthRequestImpl::IdentityProviderGetInfo::IdentityProviderGetInfo(
blink::mojom::IdentityProviderConfigPtr provider,
bool auto_reauthn,
blink::mojom::RpContext rp_context)
: provider(std::move(provider)),
auto_reauthn(auto_reauthn),
rp_context(rp_context) {}
FederatedAuthRequestImpl::IdentityProviderGetInfo::~IdentityProviderGetInfo() =
default;
FederatedAuthRequestImpl::IdentityProviderGetInfo::IdentityProviderGetInfo(
const IdentityProviderGetInfo& other) {
*this = other;
}
FederatedAuthRequestImpl::IdentityProviderGetInfo&
FederatedAuthRequestImpl::IdentityProviderGetInfo::operator=(
const IdentityProviderGetInfo& other) {
provider = other.provider->Clone();
auto_reauthn = other.auto_reauthn;
rp_context = other.rp_context;
return *this;
}
FederatedAuthRequestImpl::IdentityProviderInfo::IdentityProviderInfo(
const blink::mojom::IdentityProviderConfigPtr& provider,
IdpNetworkRequestManager::Endpoints endpoints,
IdentityProviderMetadata metadata,
bool auto_reauthn,
blink::mojom::RpContext rp_context)
: provider(provider->Clone()),
endpoints(std::move(endpoints)),
metadata(std::move(metadata)),
auto_reauthn(auto_reauthn),
rp_context(rp_context) {}
FederatedAuthRequestImpl::IdentityProviderInfo::~IdentityProviderInfo() =
default;
FederatedAuthRequestImpl::IdentityProviderInfo::IdentityProviderInfo(
const IdentityProviderInfo& other) {
provider = other.provider->Clone();
endpoints = other.endpoints;
metadata = other.metadata;
auto_reauthn = other.auto_reauthn;
has_failing_idp_signin_status = other.has_failing_idp_signin_status;
rp_context = other.rp_context;
data = other.data;
}
FederatedAuthRequestImpl::FederatedAuthRequestImpl(
RenderFrameHost& host,
FederatedIdentityApiPermissionContextDelegate* api_permission_context,
FederatedIdentityAutoReauthnPermissionContextDelegate*
auto_reauthn_permission_context,
FederatedIdentityPermissionContextDelegate* permission_context,
mojo::PendingReceiver<blink::mojom::FederatedAuthRequest> receiver)
: DocumentService(host, std::move(receiver)),
api_permission_delegate_(api_permission_context),
auto_reauthn_permission_delegate_(auto_reauthn_permission_context),
permission_delegate_(permission_context),
token_request_delay_(kDefaultTokenRequestDelay) {}
FederatedAuthRequestImpl::~FederatedAuthRequestImpl() {
if (auth_request_token_callback_) {
DCHECK(!logout_callback_);
CompleteRequestWithError(FederatedAuthRequestResult::kError,
TokenStatus::kUnhandledRequest,
false);
}
if (user_info_request_) {
user_info_request_.reset();
}
if (logout_callback_) {
GetPageData(&render_frame_host())->SetPendingWebIdentityRequest(nullptr);
}
}
void FederatedAuthRequestImpl::Create(
RenderFrameHost* host,
mojo::PendingReceiver<blink::mojom::FederatedAuthRequest> receiver) {
CHECK(host);
BrowserContext* browser_context = host->GetBrowserContext();
raw_ptr<FederatedIdentityApiPermissionContextDelegate>
api_permission_context =
browser_context->GetFederatedIdentityApiPermissionContext();
raw_ptr<FederatedIdentityAutoReauthnPermissionContextDelegate>
auto_reauthn_permission_context =
browser_context->GetFederatedIdentityAutoReauthnPermissionContext();
raw_ptr<FederatedIdentityPermissionContextDelegate> permission_context =
browser_context->GetFederatedIdentityPermissionContext();
if (!api_permission_context || !auto_reauthn_permission_context ||
!permission_context) {
return;
}
new FederatedAuthRequestImpl(*host, api_permission_context,
auto_reauthn_permission_context,
permission_context, std::move(receiver));
}
FederatedAuthRequestImpl& FederatedAuthRequestImpl::CreateForTesting(
RenderFrameHost& host,
FederatedIdentityApiPermissionContextDelegate* api_permission_context,
FederatedIdentityAutoReauthnPermissionContextDelegate*
auto_reauthn_permission_context,
FederatedIdentityPermissionContextDelegate* permission_context,
mojo::PendingReceiver<blink::mojom::FederatedAuthRequest> receiver) {
return *new FederatedAuthRequestImpl(host, api_permission_context,
auto_reauthn_permission_context,
permission_context, std::move(receiver));
}
void FederatedAuthRequestImpl::CompleteMDocRequest(std::string mdoc) {
if (!mdoc_provider_) {
std::move(mdoc_request_callback_)
.Run(RequestTokenStatus::kError, absl::nullopt, "");
return;
}
if (!mdoc.empty()) {
std::move(mdoc_request_callback_)
.Run(RequestTokenStatus::kSuccess, absl::nullopt, mdoc);
} else {
std::move(mdoc_request_callback_)
.Run(RequestTokenStatus::kError, absl::nullopt, "");
}
}
void FederatedAuthRequestImpl::RequestToken(
std::vector<IdentityProviderGetParametersPtr> idp_get_params_ptrs,
RequestTokenCallback callback) {
if (idp_get_params_ptrs.empty()) {
mojo::ReportBadMessage("idp_get_params_ptrs is empty.");
return;
}
for (auto& idp_get_params_ptr : idp_get_params_ptrs) {
if (idp_get_params_ptr->providers.size() == 0) {
mojo::ReportBadMessage("The provider list should not be empty.");
return;
}
}
const bool is_multi_idp_input = idp_get_params_ptrs.size() > 1u ||
idp_get_params_ptrs[0]->providers.size() > 1u;
if (is_multi_idp_input && !IsFedCmMultipleIdentityProvidersEnabled()) {
std::move(callback).Run(RequestTokenStatus::kError, absl::nullopt, "");
return;
}
if (idp_get_params_ptrs[0]->providers[0]->is_mdoc()) {
if (!IsWebIdentityMDocsEnabled() ||
IsFedCmMultipleIdentityProvidersEnabled()) {
std::move(callback).Run(RequestTokenStatus::kError, absl::nullopt, "");
return;
}
if (mdoc_request_callback_) {
std::move(callback).Run(RequestTokenStatus::kErrorTooManyRequests,
absl::nullopt, "");
return;
}
mdoc_request_callback_ = std::move(callback);
if (!mdoc_provider_) {
mdoc_provider_ = CreateMDocProvider();
}
if (!mdoc_provider_) {
std::move(mdoc_request_callback_)
.Run(RequestTokenStatus::kError, absl::nullopt, "");
return;
}
auto mdoc = std::move(idp_get_params_ptrs[0]->providers[0]->get_mdoc());
std::string reader_public_key = mdoc->reader_public_key;
std::string document_type = mdoc->document_type;
mdoc_provider_->RequestMDoc(
WebContents::FromRenderFrameHost(&render_frame_host()),
reader_public_key, document_type, mdoc->requested_elements,
base::BindOnce(&FederatedAuthRequestImpl::CompleteMDocRequest,
weak_ptr_factory_.GetWeakPtr()));
return;
}
if (!fedcm_metrics_) {
fedcm_metrics_ = CreateFedCmMetrics(
idp_get_params_ptrs[0]->providers[0]->get_federated()->config_url,
render_frame_host().GetPageUkmSourceId(),
idp_get_params_ptrs.size() > 1);
}
if (HasPendingRequest()) {
fedcm_metrics_->RecordRequestTokenStatus(TokenStatus::kTooManyRequests);
std::move(callback).Run(RequestTokenStatus::kErrorTooManyRequests,
absl::nullopt, "");
return;
}
bool intercept = false;
bool should_complete_request_immediately = false;
devtools_instrumentation::WillSendFedCmRequest(
&render_frame_host(), &intercept, &should_complete_request_immediately);
should_complete_request_immediately_ =
(intercept && should_complete_request_immediately) ||
api_permission_delegate_->ShouldCompleteRequestImmediately();
auth_request_token_callback_ = std::move(callback);
GetPageData(&render_frame_host())->SetPendingWebIdentityRequest(this);
network_manager_ = CreateNetworkManager();
request_dialog_controller_ = CreateDialogController();
start_time_ = base::TimeTicks::Now();
FederatedApiPermissionStatus permission_status =
api_permission_delegate_->GetApiPermissionStatus(GetEmbeddingOrigin());
absl::optional<TokenStatus> error_token_status;
FederatedAuthRequestResult request_result =
FederatedAuthRequestResult::kError;
switch (permission_status) {
case FederatedApiPermissionStatus::BLOCKED_VARIATIONS:
error_token_status = TokenStatus::kDisabledInFlags;
break;
case FederatedApiPermissionStatus::BLOCKED_THIRD_PARTY_COOKIES_BLOCKED:
error_token_status = TokenStatus::kThirdPartyCookiesBlocked;
break;
case FederatedApiPermissionStatus::BLOCKED_SETTINGS:
error_token_status = TokenStatus::kDisabledInSettings;
request_result = FederatedAuthRequestResult::kErrorDisabledInSettings;
break;
case FederatedApiPermissionStatus::BLOCKED_EMBARGO:
error_token_status = TokenStatus::kDisabledEmbargo;
request_result = FederatedAuthRequestResult::kErrorDisabledInSettings;
break;
case FederatedApiPermissionStatus::GRANTED:
break;
default:
NOTREACHED();
break;
}
if (error_token_status) {
CompleteRequestWithError(request_result, *error_token_status,
true);
return;
}
std::set<GURL> unique_idps;
for (auto& idp_get_params_ptr : idp_get_params_ptrs) {
for (auto& idp_ptr : idp_get_params_ptr->providers) {
const bool is_unique_idp =
unique_idps.insert(idp_ptr->get_federated()->config_url).second;
if (!is_unique_idp) {
CompleteRequestWithError(FederatedAuthRequestResult::kError,
absl::nullopt,
false);
return;
}
if (!network::IsOriginPotentiallyTrustworthy(
url::Origin::Create(idp_ptr->get_federated()->config_url))) {
CompleteRequestWithError(FederatedAuthRequestResult::kError,
TokenStatus::kIdpNotPotentiallyTrustworthy,
false);
return;
}
bool has_failing_idp_signin_status =
webid::ShouldFailAccountsEndpointRequestBecauseNotSignedInWithIdp(
idp_ptr->get_federated()->config_url, permission_delegate_);
if (has_failing_idp_signin_status &&
GetFedCmIdpSigninStatusMode() == FedCmIdpSigninStatusMode::ENABLED) {
CompleteRequestWithError(FederatedAuthRequestResult::kError,
TokenStatus::kNotSignedInWithIdp,
true);
return;
}
}
}
for (auto& idp_get_params_ptr : idp_get_params_ptrs) {
for (auto& idp_ptr : idp_get_params_ptr->providers) {
idp_order_.push_back(idp_ptr->get_federated()->config_url);
blink::mojom::RpContext rp_context =
IsFedCmRpContextEnabled() ? idp_get_params_ptr->context
: blink::mojom::RpContext::kSignIn;
const GURL& idp_config_url = idp_ptr->get_federated()->config_url;
token_request_get_infos_.emplace(
idp_config_url,
IdentityProviderGetInfo(
std::move(idp_ptr->get_federated()),
idp_get_params_ptr->auto_reauthn && IsFedCmAutoReauthnEnabled(),
rp_context));
}
}
FetchEndpointsForIdps(std::move(unique_idps), false);
}
void FederatedAuthRequestImpl::RequestUserInfo(
blink::mojom::IdentityProviderConfigPtr provider,
RequestUserInfoCallback callback) {
if (!IsFedCmUserInfoEnabled()) {
std::move(callback).Run(RequestUserInfoStatus::kError, absl::nullopt);
return;
}
if (user_info_request_) {
std::move(callback).Run(RequestUserInfoStatus::kErrorTooManyRequests,
absl::nullopt);
return;
}
if (!fedcm_metrics_) {
fedcm_metrics_ = CreateFedCmMetrics(
provider->config_url, render_frame_host().GetPageUkmSourceId(),
false);
}
auto network_manager = IdpNetworkRequestManager::Create(
static_cast<RenderFrameHostImpl*>(&render_frame_host()));
user_info_request_ = FederatedAuthUserInfoRequest::CreateAndStart(
std::move(network_manager), api_permission_delegate_.get(),
permission_delegate_.get(), &render_frame_host(), fedcm_metrics_.get(),
std::move(provider),
base::BindOnce(&FederatedAuthRequestImpl::CompleteUserInfoRequest,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void FederatedAuthRequestImpl::CancelTokenRequest() {
if (!auth_request_token_callback_) {
return;
}
CompleteRequestWithError(FederatedAuthRequestResult::kErrorCanceled,
TokenStatus::kAborted,
false);
}
void FederatedAuthRequestImpl::LogoutRps(
std::vector<blink::mojom::LogoutRpsRequestPtr> logout_requests,
LogoutRpsCallback callback) {
if (HasPendingRequest()) {
std::move(callback).Run(LogoutRpsStatus::kErrorTooManyRequests);
return;
}
DCHECK(logout_requests_.empty());
logout_callback_ = std::move(callback);
GetPageData(&render_frame_host())->SetPendingWebIdentityRequest(this);
if (logout_requests.empty()) {
CompleteLogoutRequest(LogoutRpsStatus::kError);
return;
}
if (base::ranges::any_of(logout_requests, [](auto& request) {
return !request->url.is_valid();
})) {
bad_message::ReceivedBadMessage(render_frame_host().GetProcess(),
bad_message::FARI_LOGOUT_BAD_ENDPOINT);
CompleteLogoutRequest(LogoutRpsStatus::kError);
return;
}
for (auto& request : logout_requests) {
logout_requests_.push(std::move(request));
}
if (!network::IsOriginPotentiallyTrustworthy(origin())) {
CompleteLogoutRequest(LogoutRpsStatus::kError);
return;
}
network_manager_ = CreateNetworkManager();
if (!IsFedCmIdpSignoutEnabled()) {
CompleteLogoutRequest(LogoutRpsStatus::kError);
return;
}
if (api_permission_delegate_->GetApiPermissionStatus(GetEmbeddingOrigin()) !=
FederatedApiPermissionStatus::GRANTED) {
CompleteLogoutRequest(LogoutRpsStatus::kError);
return;
}
DispatchOneLogout();
}
void FederatedAuthRequestImpl::SetIdpSigninStatus(
const url::Origin& idp_origin,
blink::mojom::IdpSigninStatus status) {
if (!origin().IsSameOriginWith(idp_origin))
return;
permission_delegate_->SetIdpSigninStatus(
idp_origin, status == blink::mojom::IdpSigninStatus::kSignedIn);
}
void FederatedAuthRequestImpl::RegisterIdP(const GURL& idp,
RegisterIdPCallback callback) {
if (!IsFedCmIdPRegistrationEnabled()) {
std::move(callback).Run(false);
return;
}
if (!origin().IsSameOriginWith(url::Origin::Create(idp))) {
std::move(callback).Run(false);
return;
}
permission_delegate_->RegisterIdP(idp);
std::move(callback).Run(true);
}
void FederatedAuthRequestImpl::UnregisterIdP(const GURL& idp,
UnregisterIdPCallback callback) {
if (!IsFedCmIdPRegistrationEnabled()) {
std::move(callback).Run(false);
return;
}
if (!origin().IsSameOriginWith(url::Origin::Create(idp))) {
std::move(callback).Run(false);
return;
}
permission_delegate_->UnregisterIdP(idp);
std::move(callback).Run(true);
}
void FederatedAuthRequestImpl::OnIdpSigninStatusChanged(
const url::Origin& idp_config_origin,
bool idp_signin_status) {
if (!idp_signin_status) {
return;
}
for (const auto& [get_idp_config_url, get_info] : token_request_get_infos_) {
if (url::Origin::Create(get_idp_config_url) == idp_config_origin) {
permission_delegate_->RemoveIdpSigninStatusObserver(this);
FetchEndpointsForIdps({get_idp_config_url}, true);
break;
}
}
}
bool FederatedAuthRequestImpl::HasPendingRequest() const {
bool has_pending_request =
GetPageData(&render_frame_host())->PendingWebIdentityRequest() != nullptr;
DCHECK(has_pending_request ||
(!auth_request_token_callback_ && !logout_callback_));
return has_pending_request;
}
void FederatedAuthRequestImpl::FetchEndpointsForIdps(
const std::set<GURL>& idp_config_urls,
bool for_idp_signin) {
int icon_ideal_size = request_dialog_controller_->GetBrandIconIdealSize();
int icon_minimum_size = request_dialog_controller_->GetBrandIconMinimumSize();
{
std::set<GURL> pending_idps = std::move(fetch_data_.pending_idps);
pending_idps.insert(idp_config_urls.begin(), idp_config_urls.end());
fetch_data_ = FetchData();
fetch_data_.pending_idps = std::move(pending_idps);
fetch_data_.for_idp_signin = for_idp_signin;
}
provider_fetcher_ =
std::make_unique<FederatedProviderFetcher>(network_manager_.get());
provider_fetcher_->Start(
fetch_data_.pending_idps, icon_ideal_size, icon_minimum_size,
base::BindOnce(&FederatedAuthRequestImpl::OnAllConfigAndWellKnownFetched,
weak_ptr_factory_.GetWeakPtr()));
}
void FederatedAuthRequestImpl::OnAllConfigAndWellKnownFetched(
std::vector<FederatedProviderFetcher::FetchResult> fetch_results) {
provider_fetcher_.reset();
for (const FederatedProviderFetcher::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.auto_reauthn, get_info_it->second.rp_context);
if (fetch_result.error) {
const FederatedProviderFetcher::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);
}
OnFetchDataForIdpFailed(std::move(idp_info), fetch_error.result,
fetch_error.token_status,
true);
continue;
}
idp_info->has_failing_idp_signin_status =
webid::ShouldFailAccountsEndpointRequestBecauseNotSignedInWithIdp(
identity_provider_config_url, permission_delegate_);
if (idp_info->has_failing_idp_signin_status &&
GetFedCmIdpSigninStatusMode() == FedCmIdpSigninStatusMode::ENABLED) {
idp_info->endpoints.metrics = GURL();
OnFetchDataForIdpFailed(std::move(idp_info),
FederatedAuthRequestResult::kError,
TokenStatus::kNotSignedInWithIdp,
true);
continue;
}
GURL accounts_endpoint = idp_info->endpoints.accounts;
std::string client_id = idp_info->provider->client_id;
network_manager_->SendAccountsRequest(
accounts_endpoint, client_id,
base::BindOnce(&FederatedAuthRequestImpl::OnAccountsResponseReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(idp_info)));
}
}
void FederatedAuthRequestImpl::OnClientMetadataResponseReceived(
std::unique_ptr<IdentityProviderInfo> idp_info,
const IdpNetworkRequestManager::AccountList& accounts,
IdpNetworkRequestManager::FetchStatus status,
IdpNetworkRequestManager::ClientMetadata client_metadata) {
OnFetchDataForIdpSucceeded(std::move(idp_info), accounts, client_metadata);
}
bool HasScope(const std::vector<std::string>& scope, std::string name) {
auto it = std::find(std::begin(scope), std::end(scope), name);
if (it == std::end(scope)) {
return false;
}
return true;
}
bool ShouldRequestPermission(const std::vector<std::string>& scope) {
if (scope.size() == 0) {
return true;
}
return HasScope(scope, "sub") && HasScope(scope, "name") &&
HasScope(scope, "email") && HasScope(scope, "picture");
}
void FederatedAuthRequestImpl::OnFetchDataForIdpSucceeded(
std::unique_ptr<IdentityProviderInfo> idp_info,
const IdpNetworkRequestManager::AccountList& accounts,
const IdpNetworkRequestManager::ClientMetadata& client_metadata) {
fetch_data_.did_succeed_for_at_least_one_idp = true;
const GURL& idp_config_url = idp_info->provider->config_url;
bool request_permission = true;
if (IsFedCmAuthzEnabled()) {
request_permission = ShouldRequestPermission(idp_info->provider->scope);
}
const std::string idp_for_display = FormatUrlForDisplay(idp_config_url);
idp_info->data = IdentityProviderData(
idp_for_display, accounts, idp_info->metadata,
ClientMetadata{client_metadata.terms_of_service_url,
client_metadata.privacy_policy_url},
idp_info->rp_context, request_permission);
idp_infos_[idp_config_url] = std::move(idp_info);
fetch_data_.pending_idps.erase(idp_config_url);
MaybeShowAccountsDialog();
}
void FederatedAuthRequestImpl::OnFetchDataForIdpFailed(
const std::unique_ptr<IdentityProviderInfo> idp_info,
blink::mojom::FederatedAuthRequestResult result,
absl::optional<TokenStatus> token_status,
bool should_delay_callback) {
const GURL& idp_config_url = idp_info->provider->config_url;
fetch_data_.pending_idps.erase(idp_config_url);
if (fetch_data_.pending_idps.empty() &&
!fetch_data_.did_succeed_for_at_least_one_idp) {
CompleteRequestWithError(result, token_status, should_delay_callback);
return;
}
AddInspectorIssue(result);
AddConsoleErrorMessage(result);
if (IsFedCmMetricsEndpointEnabled())
SendFailedTokenRequestMetrics(idp_info->endpoints.metrics, result);
metrics_endpoints_.erase(idp_config_url);
idp_infos_.erase(idp_config_url);
MaybeShowAccountsDialog();
}
void FederatedAuthRequestImpl::MaybeShowAccountsDialog() {
if (!fetch_data_.pending_idps.empty()) {
return;
}
if (api_permission_delegate_->GetApiPermissionStatus(GetEmbeddingOrigin()) !=
FederatedApiPermissionStatus::GRANTED) {
CompleteRequestWithError(
FederatedAuthRequestResult::kErrorDisabledInSettings,
TokenStatus::kDisabledInSettings,
true);
return;
}
if (!fetch_data_.for_idp_signin) {
bool is_visible = (render_frame_host().IsActive() &&
render_frame_host().GetVisibilityState() ==
content::PageVisibilityState::kVisible);
fedcm_metrics_->RecordWebContentsVisibilityUponReadyToShowDialog(
is_visible);
if (!is_visible) {
CompleteRequestWithError(
FederatedAuthRequestResult::kErrorRpPageNotVisible,
TokenStatus::kRpPageNotVisible,
true);
return;
}
show_accounts_dialog_time_ = base::TimeTicks::Now();
fedcm_metrics_->RecordShowAccountsDialogTime(show_accounts_dialog_time_ -
start_time_);
}
fetch_data_ = FetchData();
DCHECK(idp_data_for_display_.empty());
bool idp_enabled_auto_reauthn = true;
for (const auto& idp : idp_order_) {
auto idp_info_it = idp_infos_.find(idp);
if (idp_info_it != idp_infos_.end() && idp_info_it->second->data) {
idp_data_for_display_.push_back(*idp_info_it->second->data);
idp_enabled_auto_reauthn &= idp_info_it->second->auto_reauthn;
}
}
DCHECK(render_frame_host().GetPage().IsPrimary());
bool auto_reauthn_enabled =
idp_enabled_auto_reauthn && IsFedCmAutoReauthnEnabled();
bool auto_reauthn = auto_reauthn_enabled;
bool has_auto_reauthn_content_setting = false;
bool is_auto_reauthn_embargoed = false;
absl::optional<base::TimeDelta> time_from_embargo;
if (auto_reauthn_enabled) {
has_auto_reauthn_content_setting =
auto_reauthn_permission_delegate_->HasAutoReauthnContentSetting();
auto_reauthn &= has_auto_reauthn_content_setting;
is_auto_reauthn_embargoed =
auto_reauthn_permission_delegate_->IsAutoReauthnEmbargoed(
GetEmbeddingOrigin());
if (is_auto_reauthn_embargoed) {
time_from_embargo =
base::Time::Now() -
auto_reauthn_permission_delegate_->GetAutoReauthnEmbargoStartTime(
GetEmbeddingOrigin());
render_frame_host().AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kInfo,
"Auto re-authn was previously triggered less than 10 minutes ago. "
"Only one auto re-authn request can be made every 10 minutes.");
}
auto_reauthn &= !is_auto_reauthn_embargoed;
}
const IdentityProviderData* auto_reauthn_idp = nullptr;
const IdentityRequestAccount* auto_reauthn_account = nullptr;
bool has_single_returning_account = false;
if (auto_reauthn_enabled) {
has_single_returning_account =
GetSingleReturningAccount(&auto_reauthn_idp, &auto_reauthn_account);
auto_reauthn &= has_single_returning_account;
}
if (auto_reauthn) {
IdentityRequestAccount account{*auto_reauthn_account};
IdentityProviderData idp{*auto_reauthn_idp};
idp.accounts = {account};
idp_data_for_display_ = {idp};
}
bool show_auto_reauthn_checkbox = false;
bool intercept = false;
devtools_instrumentation::WillShowFedCmDialog(&render_frame_host(),
&intercept);
if (intercept) {
request_dialog_controller_->SetIsInterceptionEnabled(intercept);
}
absl::optional<std::string> iframe_for_display = GetIframeOriginForDisplay(
GetEmbeddingOrigin(), origin(),
base::BindOnce(&FederatedAuthRequestImpl::CompleteRequestWithError,
weak_ptr_factory_.GetWeakPtr()));
request_dialog_controller_->ShowAccountsDialog(
WebContents::FromRenderFrameHost(&render_frame_host()),
GetTopFrameOriginForDisplay(GetEmbeddingOrigin()), iframe_for_display,
idp_data_for_display_,
auto_reauthn ? SignInMode::kAuto : SignInMode::kExplicit,
show_auto_reauthn_checkbox,
base::BindOnce(&FederatedAuthRequestImpl::OnAccountSelected,
weak_ptr_factory_.GetWeakPtr(), auto_reauthn),
base::BindOnce(&FederatedAuthRequestImpl::OnDialogDismissed,
weak_ptr_factory_.GetWeakPtr()));
devtools_instrumentation::OnFedCmAccountsDialogShown(&render_frame_host());
if (auto_reauthn_enabled) {
fedcm_metrics_->RecordAutoReauthnMetrics(
has_single_returning_account, auto_reauthn_account, auto_reauthn,
!has_auto_reauthn_content_setting, is_auto_reauthn_embargoed,
time_from_embargo);
}
}
void FederatedAuthRequestImpl::HandleAccountsFetchFailure(
std::unique_ptr<IdentityProviderInfo> idp_info,
absl::optional<bool> old_idp_signin_status,
blink::mojom::FederatedAuthRequestResult result,
absl::optional<TokenStatus> token_status) {
if (GetFedCmIdpSigninStatusMode() == FedCmIdpSigninStatusMode::DISABLED) {
OnFetchDataForIdpFailed(std::move(idp_info), result, token_status,
true);
return;
}
url::Origin idp_origin = url::Origin::Create(idp_info->provider->config_url);
if (!old_idp_signin_status.has_value() ||
GetFedCmIdpSigninStatusMode() == FedCmIdpSigninStatusMode::METRICS_ONLY) {
OnFetchDataForIdpFailed(std::move(idp_info), result, token_status,
true);
return;
}
bool is_visible = (render_frame_host().IsActive() &&
render_frame_host().GetVisibilityState() ==
content::PageVisibilityState::kVisible);
if (!is_visible) {
CompleteRequestWithError(FederatedAuthRequestResult::kErrorRpPageNotVisible,
TokenStatus::kRpPageNotVisible,
true);
return;
}
WebContents* rp_web_contents =
WebContents::FromRenderFrameHost(&render_frame_host());
DCHECK(render_frame_host().GetPage().IsPrimary());
fetch_data_ = FetchData();
permission_delegate_->AddIdpSigninStatusObserver(this);
absl::optional<std::string> iframe_for_display = GetIframeOriginForDisplay(
GetEmbeddingOrigin(), origin(),
base::BindOnce(&FederatedAuthRequestImpl::CompleteRequestWithError,
weak_ptr_factory_.GetWeakPtr()));
request_dialog_controller_->ShowFailureDialog(
rp_web_contents, GetTopFrameOriginForDisplay(GetEmbeddingOrigin()),
iframe_for_display, FormatOriginForDisplay(idp_origin),
idp_info->metadata,
base::BindOnce(&FederatedAuthRequestImpl::OnDismissFailureDialog,
weak_ptr_factory_.GetWeakPtr(),
FederatedAuthRequestResult::kError,
TokenStatus::kNotSignedInWithIdp,
true));
}
void FederatedAuthRequestImpl::OnAccountsResponseReceived(
std::unique_ptr<IdentityProviderInfo> idp_info,
IdpNetworkRequestManager::FetchStatus status,
IdpNetworkRequestManager::AccountList accounts) {
GURL idp_config_url = idp_info->provider->config_url;
const absl::optional<bool> old_idp_signin_status =
permission_delegate_->GetIdpSigninStatus(
url::Origin::Create(idp_config_url));
webid::UpdateIdpSigninStatusForAccountsEndpointResponse(
idp_config_url, status, idp_info->has_failing_idp_signin_status,
permission_delegate_, fedcm_metrics_.get());
constexpr char kAccountsUrl[] = "accounts endpoint";
switch (status.parse_status) {
case IdpNetworkRequestManager::ParseStatus::kHttpNotFoundError: {
MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code);
HandleAccountsFetchFailure(
std::move(idp_info), old_idp_signin_status,
FederatedAuthRequestResult::kErrorFetchingAccountsHttpNotFound,
TokenStatus::kAccountsHttpNotFound);
return;
}
case IdpNetworkRequestManager::ParseStatus::kNoResponseError: {
MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code);
HandleAccountsFetchFailure(
std::move(idp_info), old_idp_signin_status,
FederatedAuthRequestResult::kErrorFetchingAccountsNoResponse,
TokenStatus::kAccountsNoResponse);
return;
}
case IdpNetworkRequestManager::ParseStatus::kInvalidResponseError: {
MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code);
HandleAccountsFetchFailure(
std::move(idp_info), old_idp_signin_status,
FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse,
TokenStatus::kAccountsInvalidResponse);
return;
}
case IdpNetworkRequestManager::ParseStatus::kEmptyListError: {
MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code);
HandleAccountsFetchFailure(
std::move(idp_info), old_idp_signin_status,
FederatedAuthRequestResult::kErrorFetchingAccountsListEmpty,
TokenStatus::kAccountsListEmpty);
return;
}
case IdpNetworkRequestManager::ParseStatus::kInvalidContentTypeError: {
MaybeAddResponseCodeToConsole(kAccountsUrl, status.response_code);
HandleAccountsFetchFailure(
std::move(idp_info), old_idp_signin_status,
FederatedAuthRequestResult::kErrorFetchingAccountsInvalidContentType,
TokenStatus::kAccountsInvalidContentType);
return;
}
case IdpNetworkRequestManager::ParseStatus::kSuccess: {
if (IsFedCmLoginHintEnabled()) {
FilterAccountsWithLoginHint(idp_info->provider->login_hint, accounts);
if (accounts.empty()) {
HandleAccountsFetchFailure(
std::move(idp_info), old_idp_signin_status,
FederatedAuthRequestResult::kErrorFetchingAccountsListEmpty,
TokenStatus::kAccountsListEmpty);
return;
}
}
ComputeLoginStateAndReorderAccounts(idp_info->provider, accounts);
bool need_client_metadata = false;
for (const IdentityRequestAccount& account : accounts) {
DCHECK(account.login_state);
if (*account.login_state == LoginState::kSignUp) {
need_client_metadata = true;
break;
}
}
if (need_client_metadata &&
webid::IsEndpointUrlValid(idp_info->provider->config_url,
idp_info->endpoints.client_metadata)) {
GURL client_metadata_endpoint = idp_info->endpoints.client_metadata;
std::string client_id = idp_info->provider->client_id;
network_manager_->FetchClientMetadata(
client_metadata_endpoint, client_id,
base::BindOnce(
&FederatedAuthRequestImpl::OnClientMetadataResponseReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(idp_info),
std::move(accounts)));
} else {
OnFetchDataForIdpSucceeded(std::move(idp_info), accounts,
IdpNetworkRequestManager::ClientMetadata());
}
}
}
}
void FederatedAuthRequestImpl::ComputeLoginStateAndReorderAccounts(
const IdentityProviderConfigPtr& idp,
IdpNetworkRequestManager::AccountList& accounts) {
for (auto& account : accounts) {
bool idp_claimed_sign_in = account.login_state == LoginState::kSignIn;
bool browser_observed_sign_in = permission_delegate_->HasSharingPermission(
origin(), GetEmbeddingOrigin(), url::Origin::Create(idp->config_url),
account.id);
if (idp_claimed_sign_in == browser_observed_sign_in) {
fedcm_metrics_->RecordSignInStateMatchStatus(
SignInStateMatchStatus::kMatch);
} else if (idp_claimed_sign_in) {
fedcm_metrics_->RecordSignInStateMatchStatus(
SignInStateMatchStatus::kIdpClaimedSignIn);
} else {
fedcm_metrics_->RecordSignInStateMatchStatus(
SignInStateMatchStatus::kBrowserObservedSignIn);
}
if (account.login_state)
continue;
LoginState login_state = LoginState::kSignUp;
if (browser_observed_sign_in) {
login_state = LoginState::kSignIn;
}
account.login_state = login_state;
}
std::sort(accounts.begin(), accounts.end(), [](const auto& a, const auto& b) {
return a.login_state < b.login_state;
});
}
void FederatedAuthRequestImpl::OnAccountSelected(bool auto_reauthn,
const GURL& idp_config_url,
const std::string& account_id,
bool is_sign_in) {
DCHECK(!account_id.empty());
const IdentityProviderInfo& idp_info = *idp_infos_[idp_config_url];
if (api_permission_delegate_->GetApiPermissionStatus(GetEmbeddingOrigin()) !=
FederatedApiPermissionStatus::GRANTED) {
CompleteRequestWithError(
FederatedAuthRequestResult::kErrorDisabledInSettings,
TokenStatus::kDisabledInSettings,
true);
return;
}
if (auto_reauthn) {
auto_reauthn_permission_delegate_->RecordDisplayAndEmbargo(
GetEmbeddingOrigin());
}
fedcm_metrics_->RecordIsSignInUser(is_sign_in);
api_permission_delegate_->RemoveEmbargoAndResetCounts(GetEmbeddingOrigin());
account_id_ = account_id;
select_account_time_ = base::TimeTicks::Now();
fedcm_metrics_->RecordContinueOnDialogTime(select_account_time_ -
show_accounts_dialog_time_);
network_manager_->SendTokenRequest(
idp_info.endpoints.token, account_id_,
ComputeUrlEncodedTokenPostData(
idp_info.provider->client_id, idp_info.provider->nonce, account_id,
is_sign_in, idp_info.provider->scope, idp_info.provider->responseType,
idp_info.provider->params),
base::BindOnce(&FederatedAuthRequestImpl::OnTokenResponseReceived,
weak_ptr_factory_.GetWeakPtr(),
idp_info.provider->Clone()),
base::BindOnce(&FederatedAuthRequestImpl::OnContinueOnResponseReceived,
weak_ptr_factory_.GetWeakPtr(),
idp_info.provider->Clone()));
}
void FederatedAuthRequestImpl::OnDismissFailureDialog(
blink::mojom::FederatedAuthRequestResult result,
absl::optional<TokenStatus> token_status,
bool should_delay_callback,
IdentityRequestDialogController::DismissReason dismiss_reason) {
CompleteRequestWithError(result, token_status, should_delay_callback);
}
void FederatedAuthRequestImpl::OnDialogDismissed(
IdentityRequestDialogController::DismissReason dismiss_reason) {
bool should_embargo = false;
switch (dismiss_reason) {
case IdentityRequestDialogController::DismissReason::kCloseButton:
case IdentityRequestDialogController::DismissReason::kSwipe:
should_embargo = true;
break;
default:
break;
}
if (should_embargo) {
base::TimeTicks dismiss_dialog_time = base::TimeTicks::Now();
fedcm_metrics_->RecordCancelOnDialogTime(dismiss_dialog_time -
show_accounts_dialog_time_);
}
fedcm_metrics_->RecordCancelReason(dismiss_reason);
if (should_embargo) {
api_permission_delegate_->RecordDismissAndEmbargo(GetEmbeddingOrigin());
}
CompleteRequestWithError(should_embargo
? FederatedAuthRequestResult::kShouldEmbargo
: FederatedAuthRequestResult::kError,
should_embargo ? TokenStatus::kShouldEmbargo
: TokenStatus::kNotSelectAccount,
false);
}
void FederatedAuthRequestImpl::OnContinueOnResponseReceived(
IdentityProviderConfigPtr idp,
IdpNetworkRequestManager::FetchStatus status,
const GURL& continue_on) {
if (!IsFedCmAuthzEnabled()) {
CompleteRequestWithError(
FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse,
TokenStatus::kIdTokenInvalidResponse,
true);
return;
}
request_dialog_controller_->ShowPopUpWindow(
std::move(continue_on),
base::BindOnce(&FederatedAuthRequestImpl::CompleteTokenRequest,
weak_ptr_factory_.GetWeakPtr(), std::move(idp),
std::move(status)),
base::BindOnce(&FederatedAuthRequestImpl::OnDialogDismissed,
weak_ptr_factory_.GetWeakPtr()));
}
void FederatedAuthRequestImpl::OnTokenResponseReceived(
IdentityProviderConfigPtr idp,
IdpNetworkRequestManager::FetchStatus status,
const std::string& id_token) {
token_response_time_ = base::TimeTicks::Now();
base::TimeDelta fetch_time = token_response_time_ - select_account_time_;
if (should_complete_request_immediately_ ||
fetch_time >= token_request_delay_) {
CompleteTokenRequest(std::move(idp), status, id_token);
return;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FederatedAuthRequestImpl::CompleteTokenRequest,
weak_ptr_factory_.GetWeakPtr(), std::move(idp), status,
id_token),
token_request_delay_ - fetch_time);
}
void FederatedAuthRequestImpl::CompleteTokenRequest(
IdentityProviderConfigPtr idp,
IdpNetworkRequestManager::FetchStatus status,
const std::string& token) {
DCHECK(!start_time_.is_null());
constexpr char kIdAssertionUrl[] = "id assertion endpoint";
switch (status.parse_status) {
case IdpNetworkRequestManager::ParseStatus::kHttpNotFoundError: {
MaybeAddResponseCodeToConsole(kIdAssertionUrl, status.response_code);
CompleteRequestWithError(
FederatedAuthRequestResult::kErrorFetchingIdTokenHttpNotFound,
TokenStatus::kIdTokenHttpNotFound,
true);
return;
}
case IdpNetworkRequestManager::ParseStatus::kNoResponseError: {
MaybeAddResponseCodeToConsole(kIdAssertionUrl, status.response_code);
CompleteRequestWithError(
FederatedAuthRequestResult::kErrorFetchingIdTokenNoResponse,
TokenStatus::kIdTokenNoResponse,
true);
return;
}
case IdpNetworkRequestManager::ParseStatus::kInvalidResponseError: {
MaybeAddResponseCodeToConsole(kIdAssertionUrl, status.response_code);
CompleteRequestWithError(
FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse,
TokenStatus::kIdTokenInvalidResponse,
true);
return;
}
case IdpNetworkRequestManager::ParseStatus::kInvalidContentTypeError: {
MaybeAddResponseCodeToConsole(kIdAssertionUrl, status.response_code);
CompleteRequestWithError(
FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidContentType,
TokenStatus::kIdTokenInvalidContentType,
true);
return;
}
case IdpNetworkRequestManager::ParseStatus::kEmptyListError: {
NOTREACHED() << "kEmptyListError is undefined for CompleteTokenRequest";
return;
}
case IdpNetworkRequestManager::ParseStatus::kSuccess: {
CHECK(!account_id_.empty());
permission_delegate_->GrantSharingPermission(
origin(), GetEmbeddingOrigin(), url::Origin::Create(idp->config_url),
account_id_);
permission_delegate_->GrantActiveSession(
origin(), url::Origin::Create(idp->config_url), account_id_);
fedcm_metrics_->RecordTokenResponseAndTurnaroundTime(
token_response_time_ - select_account_time_,
token_response_time_ - start_time_);
if (IsFedCmMetricsEndpointEnabled()) {
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, show_accounts_dialog_time_ - start_time_,
select_account_time_ - show_accounts_dialog_time_,
token_response_time_ - select_account_time_,
token_response_time_ - start_time_);
} else {
network_manager_->SendFailedTokenRequestMetrics(
metrics_endpoint, IdpNetworkRequestManager::
MetricsEndpointErrorCode::kUserFailure);
}
}
}
CompleteRequest(FederatedAuthRequestResult::kSuccess,
TokenStatus::kSuccess, idp->config_url, token,
false);
return;
}
}
}
void FederatedAuthRequestImpl::DispatchOneLogout() {
auto logout_request = std::move(logout_requests_.front());
DCHECK(logout_request->url.is_valid());
std::string account_id = logout_request->account_id;
auto logout_origin = url::Origin::Create(logout_request->url);
logout_requests_.pop();
if (permission_delegate_->HasActiveSession(logout_origin, origin(),
account_id)) {
network_manager_->SendLogout(
logout_request->url,
base::BindOnce(&FederatedAuthRequestImpl::OnLogoutCompleted,
weak_ptr_factory_.GetWeakPtr()));
permission_delegate_->RevokeActiveSession(logout_origin, origin(),
account_id);
} else {
if (logout_requests_.empty()) {
CompleteLogoutRequest(LogoutRpsStatus::kSuccess);
return;
}
DispatchOneLogout();
}
}
void FederatedAuthRequestImpl::OnLogoutCompleted() {
if (logout_requests_.empty()) {
CompleteLogoutRequest(LogoutRpsStatus::kSuccess);
return;
}
DispatchOneLogout();
}
void FederatedAuthRequestImpl::CompleteRequestWithError(
blink::mojom::FederatedAuthRequestResult result,
absl::optional<TokenStatus> token_status,
bool should_delay_callback) {
CompleteRequest(result, token_status,
absl::nullopt,
"", should_delay_callback);
}
void FederatedAuthRequestImpl::CompleteRequest(
blink::mojom::FederatedAuthRequestResult result,
absl::optional<TokenStatus> token_status,
const absl::optional<GURL>& selected_idp_config_url,
const std::string& id_token,
bool should_delay_callback) {
DCHECK(result == FederatedAuthRequestResult::kSuccess || id_token.empty());
if (!auth_request_token_callback_) {
return;
}
if (result != FederatedAuthRequestResult::kSuccess &&
fetch_data_.for_idp_signin &&
!ShouldSuppressIdpSigninFailureDialog(token_status)) {
fetch_data_ = FetchData();
request_dialog_controller_->ShowIdpSigninFailureDialog(base::BindOnce(
&FederatedAuthRequestImpl::CompleteRequest,
weak_ptr_factory_.GetWeakPtr(), result, std::move(token_status),
selected_idp_config_url, id_token, should_delay_callback));
return;
}
if (token_status)
fedcm_metrics_->RecordRequestTokenStatus(*token_status);
if (!errors_logged_to_console_ &&
result != FederatedAuthRequestResult::kSuccess) {
errors_logged_to_console_ = true;
AddInspectorIssue(result);
AddConsoleErrorMessage(result);
if (IsFedCmMetricsEndpointEnabled()) {
for (const auto& metrics_endpoint_kv : metrics_endpoints_)
SendFailedTokenRequestMetrics(metrics_endpoint_kv.second, result);
}
}
CleanUp();
if (!should_delay_callback || should_complete_request_immediately_) {
GetPageData(&render_frame_host())->SetPendingWebIdentityRequest(nullptr);
errors_logged_to_console_ = false;
RequestTokenStatus status =
FederatedAuthRequestResultToRequestTokenStatus(result);
std::move(auth_request_token_callback_)
.Run(status, selected_idp_config_url, id_token);
auth_request_token_callback_.Reset();
} else {
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FederatedAuthRequestImpl::OnRejectRequest,
weak_ptr_factory_.GetWeakPtr()),
GetRandomRejectionTime());
}
}
void FederatedAuthRequestImpl::SendFailedTokenRequestMetrics(
const GURL& metrics_endpoint,
blink::mojom::FederatedAuthRequestResult result) {
DCHECK(IsFedCmMetricsEndpointEnabled());
if (!metrics_endpoint.is_valid())
return;
network_manager_->SendFailedTokenRequestMetrics(
metrics_endpoint,
FederatedAuthRequestResultToMetricsEndpointErrorCode(result));
}
void FederatedAuthRequestImpl::CleanUp() {
weak_ptr_factory_.InvalidateWeakPtrs();
permission_delegate_->RemoveIdpSigninStatusObserver(this);
request_dialog_controller_.reset();
network_manager_.reset();
provider_fetcher_.reset();
account_id_ = std::string();
start_time_ = base::TimeTicks();
show_accounts_dialog_time_ = base::TimeTicks();
select_account_time_ = base::TimeTicks();
token_response_time_ = base::TimeTicks();
idp_infos_.clear();
idp_data_for_display_.clear();
fetch_data_ = FetchData();
idp_order_.clear();
metrics_endpoints_.clear();
token_request_get_infos_.clear();
}
void FederatedAuthRequestImpl::AddInspectorIssue(
FederatedAuthRequestResult result) {
DCHECK_NE(result, FederatedAuthRequestResult::kSuccess);
auto details = blink::mojom::InspectorIssueDetails::New();
auto federated_auth_request_details =
blink::mojom::FederatedAuthRequestIssueDetails::New(result);
details->federated_auth_request_details =
std::move(federated_auth_request_details);
render_frame_host().ReportInspectorIssue(
blink::mojom::InspectorIssueInfo::New(
blink::mojom::InspectorIssueCode::kFederatedAuthRequestIssue,
std::move(details)));
}
void FederatedAuthRequestImpl::AddConsoleErrorMessage(
FederatedAuthRequestResult result) {
render_frame_host().AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
webid::GetConsoleErrorMessageFromResult(result));
}
void FederatedAuthRequestImpl::MaybeAddResponseCodeToConsole(
const char* fetch_description,
int response_code) {
absl::optional<std::string> console_message =
webid::ComputeConsoleMessageForHttpResponseCode(fetch_description,
response_code);
if (console_message) {
render_frame_host().AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError, *console_message);
}
}
url::Origin FederatedAuthRequestImpl::GetEmbeddingOrigin() const {
return render_frame_host().GetMainFrame()->GetLastCommittedOrigin();
}
void FederatedAuthRequestImpl::CompleteLogoutRequest(
blink::mojom::LogoutRpsStatus status) {
network_manager_.reset();
base::queue<blink::mojom::LogoutRpsRequestPtr>().swap(logout_requests_);
if (logout_callback_) {
std::move(logout_callback_).Run(status);
logout_callback_.Reset();
GetPageData(&render_frame_host())->SetPendingWebIdentityRequest(nullptr);
}
}
void FederatedAuthRequestImpl::CompleteUserInfoRequest(
RequestUserInfoCallback callback,
blink::mojom::RequestUserInfoStatus status,
absl::optional<std::vector<blink::mojom::IdentityUserInfoPtr>> user_info) {
if (!user_info_request_) {
return;
}
std::move(callback).Run(status, std::move(user_info));
user_info_request_.reset();
}
std::unique_ptr<IdpNetworkRequestManager>
FederatedAuthRequestImpl::CreateNetworkManager() {
if (mock_network_manager_)
return std::move(mock_network_manager_);
return IdpNetworkRequestManager::Create(
static_cast<RenderFrameHostImpl*>(&render_frame_host()));
}
std::unique_ptr<IdentityRequestDialogController>
FederatedAuthRequestImpl::CreateDialogController() {
if (mock_dialog_controller_)
return std::move(mock_dialog_controller_);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kUseFakeUIForFedCM)) {
std::string selected_account =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kUseFakeUIForFedCM);
return std::make_unique<FakeIdentityRequestDialogController>(
selected_account.empty()
? absl::nullopt
: absl::optional<std::string>(selected_account));
}
return GetContentClient()->browser()->CreateIdentityRequestDialogController();
}
std::unique_ptr<MDocProvider> FederatedAuthRequestImpl::CreateMDocProvider() {
std::unique_ptr<MDocProvider> provider =
GetContentClient()->browser()->CreateMDocProvider();
if (!provider) {
return MDocProvider::Create();
}
return provider;
}
void FederatedAuthRequestImpl::SetTokenRequestDelayForTests(
base::TimeDelta delay) {
token_request_delay_ = delay;
}
void FederatedAuthRequestImpl::SetNetworkManagerForTests(
std::unique_ptr<IdpNetworkRequestManager> manager) {
mock_network_manager_ = std::move(manager);
}
void FederatedAuthRequestImpl::SetDialogControllerForTests(
std::unique_ptr<IdentityRequestDialogController> controller) {
mock_dialog_controller_ = std::move(controller);
}
void FederatedAuthRequestImpl::OnRejectRequest() {
if (auth_request_token_callback_) {
DCHECK(!logout_callback_);
DCHECK(errors_logged_to_console_);
CompleteRequestWithError(FederatedAuthRequestResult::kError, absl::nullopt,
false);
}
}
void FederatedAuthRequestImpl::AcceptAccountsDialogForDevtools(
const GURL& config_url,
const IdentityRequestAccount& account) {
bool is_sign_in =
account.login_state == IdentityRequestAccount::LoginState::kSignIn;
OnAccountSelected(false, config_url, account.id, is_sign_in);
}
void FederatedAuthRequestImpl::DismissAccountsDialogForDevtools(
bool should_embargo) {
IdentityRequestDialogController::DismissReason reason =
should_embargo
? IdentityRequestDialogController::DismissReason::kCloseButton
: IdentityRequestDialogController::DismissReason::kOther;
OnDialogDismissed(reason);
}
bool FederatedAuthRequestImpl::GetSingleReturningAccount(
const IdentityProviderData** out_idp_data,
const IdentityRequestAccount** out_account) {
for (const auto& idp_info : idp_infos_) {
for (const auto& account : idp_info.second->data->accounts) {
if (account.login_state == LoginState::kSignUp) {
continue;
}
if (!permission_delegate_->HasSharingPermission(
origin(), GetEmbeddingOrigin(),
url::Origin::Create(idp_info.first), account.id)) {
continue;
}
if (*out_account) {
return false;
}
*out_idp_data = &(*idp_info.second->data);
*out_account = &account;
}
}
if (*out_account) {
return true;
}
return false;
}
}