blob: 286fd8cd711b2c27a700a009762ca356ed1e3a19 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/webid/federated_auth_request_impl.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_piece.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "content/browser/bad_message.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/webid/fake_identity_request_dialog_controller.h"
#include "content/browser/webid/fedcm_metrics.h"
#include "content/browser/webid/flags.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_active_session_permission_context_delegate.h"
#include "content/public/browser/federated_identity_api_permission_context_delegate.h"
#include "content/public/browser/federated_identity_sharing_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 "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
#include "ui/accessibility/ax_mode.h"
#include "url/url_constants.h"
using blink::mojom::FederatedAuthRequestResult;
using blink::mojom::LogoutRpsStatus;
using blink::mojom::LogoutStatus;
using blink::mojom::RequestIdTokenStatus;
using blink::mojom::RevokeStatus;
using LoginState = content::IdentityRequestAccount::LoginState;
using SignInMode = content::IdentityRequestAccount::SignInMode;
using IdTokenStatus = content::FedCmRequestIdTokenStatus;
using RevokeStatusForMetrics = content::FedCmRevokeStatus;
namespace content {
namespace {
static constexpr base::TimeDelta kDefaultIdTokenRequestDelay = base::Seconds(3);
// TODO(yigu): We need to make sure the delay is greater than the time required
// for a successful flow based on `Blink.FedCm.Timing.TurnaroundTime`.
// https://crbug.com/1298316.
static constexpr base::TimeDelta kRequestRejectionDelay = base::Seconds(60);
// Maximum number of provider URLs in the manifest list.
// TODO(cbiesinger): Determine what the right number is.
static constexpr size_t kMaxProvidersInManifestList = 1ul;
std::string FormatRequestParamsWithoutScope(const std::string& client_id,
const std::string& nonce,
const std::string& account_id,
bool is_sign_in) {
std::string query;
if (!client_id.empty())
query += "client_id=" + client_id;
if (!nonce.empty()) {
if (!query.empty())
query += "&";
query += "nonce=" + nonce;
}
if (!account_id.empty()) {
if (!query.empty())
query += "&";
query += "account_id=" + account_id;
}
// For returning users who are signing in instead of signing up, we do not
// show the privacy policy and terms of service on the consent sheet. This
// field indicates in the request that whether the user has granted consent
// after seeing the sheet with privacy policy and terms of service.
std::string consent_acquired = is_sign_in ? "false" : "true";
if (!query.empty())
query += "&consent_acquired=" + consent_acquired;
return query;
}
std::string GetConsoleErrorMessage(FederatedAuthRequestResult status) {
switch (status) {
case FederatedAuthRequestResult::kApprovalDeclined: {
return "User declined the sign-in attempt.";
}
case FederatedAuthRequestResult::kErrorDisabledInSettings: {
return "Third-party sign in was disabled in browser Site Settings.";
}
case FederatedAuthRequestResult::kErrorTooManyRequests: {
return "Only one navigator.credentials.get request may be outstanding at "
"one time.";
}
case FederatedAuthRequestResult::kErrorFetchingManifestListHttpNotFound: {
return "The provider's FedCM manifest list file cannot be found.";
}
case FederatedAuthRequestResult::kErrorFetchingManifestListNoResponse: {
return "The provider's FedCM manifest list file fetch resulted in an "
"error response code.";
}
case FederatedAuthRequestResult::
kErrorFetchingManifestListInvalidResponse: {
return "Provider's FedCM manifest list file is invalid.";
}
case FederatedAuthRequestResult::kErrorManifestNotInManifestList: {
return "Provider's FedCM manifest not listed in its manifest list.";
}
case FederatedAuthRequestResult::kErrorManifestListTooBig: {
return "Provider's FedCM manifest list contains too many providers.";
}
case FederatedAuthRequestResult::kErrorFetchingManifestHttpNotFound: {
return "The provider's FedCM manifest configuration cannot be found.";
}
case FederatedAuthRequestResult::kErrorFetchingManifestNoResponse: {
return "The provider's FedCM manifest configuration fetch resulted in an "
"error response code.";
}
case FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse: {
return "Provider's FedCM manifest configuration is invalid.";
}
case FederatedAuthRequestResult::kErrorFetchingClientMetadataHttpNotFound: {
return "The provider's client metadata endpoint cannot be found.";
}
case FederatedAuthRequestResult::kErrorFetchingClientMetadataNoResponse: {
return "The provider's client metadata fetch resulted in an error "
"response code.";
}
case FederatedAuthRequestResult::
kErrorFetchingClientMetadataInvalidResponse: {
return "Provider's client metadata is invalid.";
}
case FederatedAuthRequestResult::
kErrorClientMetadataMissingPrivacyPolicyUrl: {
return "Provider's client metadata is missing or has an invalid privacy "
"policy url.";
}
case FederatedAuthRequestResult::kErrorFetchingAccountsHttpNotFound: {
return "The provider's accounts list endpoint cannot be found.";
}
case FederatedAuthRequestResult::kErrorFetchingAccountsNoResponse: {
return "The provider's accounts list fetch resulted in an error response "
"code.";
}
case FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse: {
return "Provider's accounts list is invalid. Should have received an "
"\"accounts\" list, where each account must have at least \"id\", "
"\"name\", and \"email\".";
}
case FederatedAuthRequestResult::kErrorFetchingIdTokenHttpNotFound: {
return "The provider's id token endpoint cannot be found.";
}
case FederatedAuthRequestResult::kErrorFetchingIdTokenNoResponse: {
return "The provider's id token fetch resulted in an error response "
"code.";
}
case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse: {
return "Provider's id token is invalid.";
}
case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidRequest: {
return "The id token fetching request is invalid.";
}
case FederatedAuthRequestResult::kErrorCanceled: {
return "The request has been aborted.";
}
case FederatedAuthRequestResult::kError: {
return "Error retrieving an id token.";
}
case FederatedAuthRequestResult::kSuccess: {
DCHECK(false);
return "";
}
}
}
RequestIdTokenStatus FederatedAuthRequestResultToRequestIdTokenStatus(
FederatedAuthRequestResult result) {
// Avoids exposing to renderer detailed error messages which may leak cross
// site information to the API call site.
switch (result) {
case FederatedAuthRequestResult::kSuccess: {
return RequestIdTokenStatus::kSuccess;
}
case FederatedAuthRequestResult::kApprovalDeclined: {
return RequestIdTokenStatus::kApprovalDeclined;
}
case FederatedAuthRequestResult::kErrorTooManyRequests: {
return RequestIdTokenStatus::kErrorTooManyRequests;
}
case FederatedAuthRequestResult::kErrorCanceled: {
return RequestIdTokenStatus::kErrorCanceled;
}
case FederatedAuthRequestResult::kErrorDisabledInSettings:
case FederatedAuthRequestResult::kErrorFetchingManifestListHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingManifestListNoResponse:
case FederatedAuthRequestResult::kErrorFetchingManifestListInvalidResponse:
case FederatedAuthRequestResult::kErrorManifestNotInManifestList:
case FederatedAuthRequestResult::kErrorManifestListTooBig:
case FederatedAuthRequestResult::kErrorFetchingManifestHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingManifestNoResponse:
case FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingClientMetadataHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingClientMetadataNoResponse:
case FederatedAuthRequestResult::
kErrorClientMetadataMissingPrivacyPolicyUrl:
case FederatedAuthRequestResult::
kErrorFetchingClientMetadataInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingAccountsHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingAccountsNoResponse:
case FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingIdTokenHttpNotFound:
case FederatedAuthRequestResult::kErrorFetchingIdTokenNoResponse:
case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse:
case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidRequest:
case FederatedAuthRequestResult::kError: {
return RequestIdTokenStatus::kError;
}
}
}
} // namespace
FederatedAuthRequestImpl::FederatedAuthRequestImpl(RenderFrameHostImpl* host,
const url::Origin& origin)
: render_frame_host_(host),
origin_(origin),
delay_timer_(FROM_HERE,
kRequestRejectionDelay,
this,
&FederatedAuthRequestImpl::OnRejectRequest),
id_token_request_delay_(kDefaultIdTokenRequestDelay) {}
FederatedAuthRequestImpl::~FederatedAuthRequestImpl() {
// Ensures key data members are destructed in proper order and resolves any
// pending promise.
if (auth_request_callback_) {
DCHECK(!revoke_callback_);
DCHECK(!logout_callback_);
RecordRequestIdTokenStatus(IdTokenStatus::kUnhandledRequest,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(FederatedAuthRequestResult::kError, "",
/*should_call_callback=*/true);
}
if (revoke_callback_) {
DCHECK(!auth_request_callback_);
DCHECK(!logout_callback_);
RecordRevokeStatus(RevokeStatusForMetrics::kUnhandledRequest,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/true);
}
}
void FederatedAuthRequestImpl::RequestIdToken(
const GURL& provider,
const std::string& client_id,
const std::string& nonce,
bool prefer_auto_sign_in,
blink::mojom::FederatedAuthRequest::RequestIdTokenCallback callback) {
if (HasPendingRequest()) {
RecordRequestIdTokenStatus(IdTokenStatus::kTooManyRequests,
render_frame_host_->GetPageUkmSourceId());
std::move(callback).Run(RequestIdTokenStatus::kErrorTooManyRequests, "");
return;
}
auth_request_callback_ = std::move(callback);
provider_ = provider;
client_id_ = client_id;
nonce_ = nonce;
prefer_auto_sign_in_ = prefer_auto_sign_in && IsFedCmAutoSigninEnabled();
start_time_ = base::TimeTicks::Now();
delay_timer_.Reset();
if (!IsFedCmEnabled()) {
RecordRequestIdTokenStatus(IdTokenStatus::kDisabledInFlags,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(FederatedAuthRequestResult::kError, "",
/*should_call_callback=*/false);
return;
}
// TODO(npm): FedCM is currently restricted to contexts where third party
// cookies are not blocked. Once the privacy improvements for the API are
// implemented, remove this restriction. See https://crbug.com/1304396.
if (GetApiPermissionContext() &&
GetApiPermissionContext()->AreThirdPartyCookiesBlocked()) {
RecordRequestIdTokenStatus(IdTokenStatus::kThirdPartyCookiesBlocked,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(FederatedAuthRequestResult::kError, "",
/*should_call_callback=*/false);
return;
}
network_manager_ = CreateNetworkManager(provider);
if (!network_manager_) {
RecordRequestIdTokenStatus(IdTokenStatus::kNoNetworkManager,
render_frame_host_->GetPageUkmSourceId());
// TODO(yigu): this is due to provider url being non-secure. We should
// reject early in the renderer process.
CompleteRequest(FederatedAuthRequestResult::kError, "",
/*should_call_callback=*/true);
return;
}
if (GetApiPermissionContext() &&
!GetApiPermissionContext()->HasApiPermission(origin_)) {
RecordRequestIdTokenStatus(IdTokenStatus::kDisabledInSettings,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(FederatedAuthRequestResult::kErrorDisabledInSettings, "",
/*should_call_callback=*/false);
return;
}
request_dialog_controller_ = CreateDialogController();
FetchManifest(kForToken);
}
void FederatedAuthRequestImpl::CancelTokenRequest() {
if (!auth_request_callback_)
return;
// Dialog will be hidden by the destructor for request_dialog_controller_,
// triggered by CompleteRequest.
RecordRequestIdTokenStatus(IdTokenStatus::kAborted,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(FederatedAuthRequestResult::kErrorCanceled, "",
/*should_call_callback=*/true);
}
void FederatedAuthRequestImpl::Revoke(
const GURL& provider,
const std::string& client_id,
const std::string& hint,
blink::mojom::FederatedAuthRequest::RevokeCallback callback) {
if (HasPendingRequest()) {
RecordRevokeStatus(RevokeStatusForMetrics::kTooManyRequests,
render_frame_host_->GetPageUkmSourceId());
std::move(callback).Run(RevokeStatus::kError);
return;
}
provider_ = provider;
client_id_ = client_id;
hint_ = hint;
delay_timer_.Reset();
revoke_callback_ = std::move(callback);
if (!IsFedCmEnabled()) {
RecordRevokeStatus(RevokeStatusForMetrics::kDisabledInFlags,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
network_manager_ = CreateNetworkManager(provider);
if (!network_manager_) {
RecordRevokeStatus(RevokeStatusForMetrics::kNoNetworkManager,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
if (GetApiPermissionContext() &&
!GetApiPermissionContext()->HasApiPermission(origin_)) {
RecordRevokeStatus(RevokeStatusForMetrics::kDisabledInSettings,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError, /*should_call_callback=*/false);
return;
}
if (!GetSharingPermissionContext() ||
!GetSharingPermissionContext()->HasSharingPermissionForAnyAccount(
origin_, url::Origin::Create(provider_))) {
RecordRevokeStatus(RevokeStatusForMetrics::kNoAccountToRevoke,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
FetchManifest(kForRevoke);
}
void FederatedAuthRequestImpl::Logout(
const GURL& provider,
const std::string& account_id,
blink::mojom::FederatedAuthRequest::LogoutCallback callback) {
url::Origin idp_origin(url::Origin::Create(provider));
auto* context = GetActiveSessionPermissionContext();
if (!context || !context->HasActiveSession(origin_, idp_origin, account_id) ||
!IsFedCmEnabled()) {
std::move(callback).Run(LogoutStatus::kNotLoggedIn);
return;
}
context->RevokeActiveSession(origin_, idp_origin, account_id);
std::move(callback).Run(LogoutStatus::kSuccess);
}
// TODO(kenrb): Depending on how this code evolves, it might make sense to
// spin session management code into its own service. The prohibition on
// making authentication requests and logout requests at the same time, while
// not problematic for any plausible use case, need not be strictly necessary
// if there is a good way to not have to resource contention between requests.
// https://crbug.com/1200581
void FederatedAuthRequestImpl::LogoutRps(
std::vector<blink::mojom::LogoutRpsRequestPtr> logout_requests,
blink::mojom::FederatedAuthRequest::LogoutRpsCallback callback) {
if (!IsFedCmEnabled()) {
std::move(callback).Run(LogoutRpsStatus::kError);
return;
}
if (!IsFedCmIdpSignoutEnabled()) {
std::move(callback).Run(LogoutRpsStatus::kError);
return;
}
if (HasPendingRequest()) {
std::move(callback).Run(LogoutRpsStatus::kErrorTooManyRequests);
return;
}
DCHECK(logout_requests_.empty());
logout_callback_ = std::move(callback);
if (GetApiPermissionContext() &&
!GetApiPermissionContext()->HasApiPermission(origin_)) {
CompleteLogoutRequest(LogoutRpsStatus::kError);
return;
}
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));
}
network_manager_ = CreateNetworkManager(origin_.GetURL());
if (!network_manager_) {
CompleteLogoutRequest(LogoutRpsStatus::kError);
return;
}
// TODO(kenrb): These should be parallelized rather than being dispatched
// serially. https://crbug.com/1200581.
DispatchOneLogout();
}
bool FederatedAuthRequestImpl::HasPendingRequest() const {
return auth_request_callback_ || logout_callback_ || revoke_callback_;
}
GURL FederatedAuthRequestImpl::ResolveManifestUrl(const std::string& endpoint) {
if (endpoint.empty())
return GURL();
GURL manifest_url =
provider_.Resolve(IdpNetworkRequestManager::kManifestFilePath);
return manifest_url.Resolve(endpoint);
}
bool FederatedAuthRequestImpl::IsEndpointUrlValid(const GURL& endpoint_url) {
return url::Origin::Create(provider_).IsSameOriginWith(endpoint_url);
}
void FederatedAuthRequestImpl::FetchManifest(FetchManifestType type) {
absl::optional<int> icon_ideal_size = absl::nullopt;
absl::optional<int> icon_minimum_size = absl::nullopt;
if (request_dialog_controller_) {
icon_ideal_size = request_dialog_controller_->GetBrandIconIdealSize();
icon_minimum_size = request_dialog_controller_->GetBrandIconMinimumSize();
}
IdpNetworkRequestManager::FetchManifestCallback manifest_callback;
IdpNetworkRequestManager::FetchManifestListCallback manifest_list_callback;
switch (type) {
case kForToken: {
manifest_callback =
base::BindOnce(&FederatedAuthRequestImpl::OnManifestFetched,
weak_ptr_factory_.GetWeakPtr());
manifest_list_callback =
base::BindOnce(&FederatedAuthRequestImpl::OnManifestListFetched,
weak_ptr_factory_.GetWeakPtr());
break;
}
case kForRevoke: {
manifest_callback =
base::BindOnce(&FederatedAuthRequestImpl::OnManifestFetchedForRevoke,
weak_ptr_factory_.GetWeakPtr());
manifest_list_callback = base::BindOnce(
&FederatedAuthRequestImpl::OnManifestListFetchedForRevoke,
weak_ptr_factory_.GetWeakPtr());
break;
}
}
if (IsFedCmManifestValidationEnabled()) {
network_manager_->FetchManifestList(std::move(manifest_list_callback));
} else {
manifest_list_checked_ = true;
}
// network_manager_ can be null here during tests when FetchManifestList
// synchronously calls the callback with an error, in which case CleanUp()
// will set the network_manager_ to null. If that happens we can safely
// skip calling FetchManifest.
if (network_manager_) {
network_manager_->FetchManifest(icon_ideal_size, icon_minimum_size,
std::move(manifest_callback));
}
}
void FederatedAuthRequestImpl::OnManifestListFetched(
IdpNetworkRequestManager::FetchStatus status,
const std::set<GURL>& urls) {
switch (status) {
case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
RecordRequestIdTokenStatus(IdTokenStatus::kManifestListHttpNotFound,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingManifestListHttpNotFound,
"",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
RecordRequestIdTokenStatus(IdTokenStatus::kManifestListNoResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingManifestListNoResponse, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
RecordRequestIdTokenStatus(IdTokenStatus::kManifestListInvalidResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingManifestListInvalidResponse,
"",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
NOTREACHED();
return;
}
case IdpNetworkRequestManager::FetchStatus::kSuccess: {
// Intentional fall-through.
}
}
if (urls.size() > kMaxProvidersInManifestList) {
RecordRequestIdTokenStatus(IdTokenStatus::kManifestListTooBig,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(FederatedAuthRequestResult::kErrorManifestListTooBig, "",
/*should_call_callback=*/false);
return;
}
// The provider url from the API call:
// navigator.credentials.get({
// federated: {
// providers: [{
// url: "https://foo.idp.example",
// clientId: "1234"
// }],
// }
// });
// must match the one in the manifest list:
// {
// "provider_urls": [
// "https://foo.idp.example"
// ]
// }
// However, it's possible for developers to append a trailing slash in one of
// them but not in the other one especially when there's path involved.
// Besides, for GURL without path, |provider_.spec()| will append a trailing
// slash automatically. Therefore we relax the requirement by allowing
// mismatch on trailing slash.
GURL provider_url = IdpNetworkRequestManager::FixupProviderUrl(provider_);
DCHECK_EQ(provider_url.path().back(), '/');
bool provider_url_is_valid = (urls.count(provider_url) != 0);
if (!provider_url_is_valid) {
RecordRequestIdTokenStatus(IdTokenStatus::kManifestNotInManifestList,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(FederatedAuthRequestResult::kErrorManifestNotInManifestList,
"",
/*should_call_callback=*/false);
return;
}
manifest_list_checked_ = true;
if (idp_metadata_)
OnManifestReady(*idp_metadata_);
}
void FederatedAuthRequestImpl::OnManifestListFetchedForRevoke(
IdpNetworkRequestManager::FetchStatus status,
const std::set<GURL>& urls) {
switch (status) {
case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
RecordRevokeStatus(RevokeStatusForMetrics::kManifestListHttpNotFound,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
RecordRevokeStatus(RevokeStatusForMetrics::kManifestListNoResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
RecordRevokeStatus(RevokeStatusForMetrics::kManifestListInvalidResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
NOTREACHED();
return;
}
case IdpNetworkRequestManager::FetchStatus::kSuccess: {
// Intentional fall-through.
}
}
if (urls.size() > kMaxProvidersInManifestList) {
RecordRevokeStatus(RevokeStatusForMetrics::kManifestListTooBig,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
GURL provider_url = IdpNetworkRequestManager::FixupProviderUrl(provider_);
if (urls.count(provider_url) == 0) {
RecordRevokeStatus(RevokeStatusForMetrics::kManifestNotInManifestList,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
manifest_list_checked_ = true;
if (idp_metadata_)
OnManifestReadyForRevoke(*idp_metadata_);
}
void FederatedAuthRequestImpl::OnManifestFetched(
IdpNetworkRequestManager::FetchStatus status,
IdpNetworkRequestManager::Endpoints endpoints,
IdentityProviderMetadata idp_metadata) {
switch (status) {
case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
RecordRequestIdTokenStatus(IdTokenStatus::kManifestHttpNotFound,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingManifestHttpNotFound, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
RecordRequestIdTokenStatus(IdTokenStatus::kManifestNoResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingManifestNoResponse, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
RecordRequestIdTokenStatus(IdTokenStatus::kManifestInvalidResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
NOTREACHED();
return;
}
case IdpNetworkRequestManager::FetchStatus::kSuccess: {
// Intentional fall-through.
}
}
endpoints_.token = ResolveManifestUrl(endpoints.token);
endpoints_.accounts = ResolveManifestUrl(endpoints.accounts);
endpoints_.client_metadata = ResolveManifestUrl(endpoints.client_metadata);
idp_metadata_ = idp_metadata;
if (manifest_list_checked_)
OnManifestReady(idp_metadata);
}
void FederatedAuthRequestImpl::OnManifestReady(
IdentityProviderMetadata idp_metadata) {
bool is_token_valid = IsEndpointUrlValid(endpoints_.token);
bool is_accounts_valid = IsEndpointUrlValid(endpoints_.accounts);
bool is_client_metadata_valid =
IsEndpointUrlValid(endpoints_.client_metadata);
if (!is_token_valid || !is_accounts_valid || !is_client_metadata_valid) {
std::string message =
"Manifest is missing or has an invalid URL for the following "
"endpoints:\n";
if (!is_token_valid) {
message += "\"id_token_endpoint\"\n";
}
if (!is_accounts_valid) {
message += "\"accounts_endpoint\"\n";
}
if (!is_client_metadata_valid) {
message += "\"client_metadata_endpoint\"\n";
}
render_frame_host_->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError, message);
RecordRequestIdTokenStatus(IdTokenStatus::kManifestInvalidResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse, "",
/*should_call_callback=*/false);
return;
}
GURL brand_icon_url = idp_metadata.brand_icon_url;
DownloadBitmap(
brand_icon_url, request_dialog_controller_->GetBrandIconIdealSize(),
base::BindOnce(&FederatedAuthRequestImpl::OnBrandIconDownloaded,
weak_ptr_factory_.GetWeakPtr(),
request_dialog_controller_->GetBrandIconMinimumSize(),
std::move(idp_metadata)));
}
void FederatedAuthRequestImpl::OnManifestFetchedForRevoke(
IdpNetworkRequestManager::FetchStatus status,
IdpNetworkRequestManager::Endpoints endpoints,
IdentityProviderMetadata idp_metadata) {
switch (status) {
case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
RecordRevokeStatus(RevokeStatusForMetrics::kManifestHttpNotFound,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
RecordRevokeStatus(RevokeStatusForMetrics::kManifestNoResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
RecordRevokeStatus(RevokeStatusForMetrics::kManifestInvalidResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
NOTREACHED();
return;
}
case IdpNetworkRequestManager::FetchStatus::kSuccess: {
// Intentional fall-through.
}
}
endpoints_.revoke = ResolveManifestUrl(endpoints.revocation);
if (!IsEndpointUrlValid(endpoints_.revoke)) {
render_frame_host_->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Manifest is missing or has an invalid URL for the following required "
"endpoint: \"revocation_endpoint\"");
RecordRevokeStatus(RevokeStatusForMetrics::kRevokeUrlIsCrossOrigin,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(RevokeStatus::kError,
/*should_call_callback=*/false);
return;
}
idp_metadata_ = idp_metadata;
if (manifest_list_checked_)
OnManifestReadyForRevoke(idp_metadata);
}
void FederatedAuthRequestImpl::OnManifestReadyForRevoke(
IdentityProviderMetadata idp_metadata) {
network_manager_->SendRevokeRequest(
endpoints_.revoke, client_id_, hint_,
base::BindOnce(&FederatedAuthRequestImpl::OnRevokeResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void FederatedAuthRequestImpl::OnRevokeResponse(
IdpNetworkRequestManager::RevokeResponse response) {
RevokeStatus status =
response == IdpNetworkRequestManager::RevokeResponse::kSuccess
? RevokeStatus::kSuccess
: RevokeStatus::kError;
if (status == RevokeStatus::kSuccess) {
url::Origin idp_origin{url::Origin::Create(provider_)};
// Since the account is now deleted, revoke the permission.
if (GetSharingPermissionContext()) {
GetSharingPermissionContext()->RevokeSharingPermission(origin_,
idp_origin, hint_);
}
if (GetActiveSessionPermissionContext()) {
GetActiveSessionPermissionContext()->RevokeActiveSession(
origin_, idp_origin, hint_);
}
RecordRevokeStatus(RevokeStatusForMetrics::kSuccess,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(status, /*should_call_callback=*/true);
} else {
RecordRevokeStatus(RevokeStatusForMetrics::kRevocationFailedOnServer,
render_frame_host_->GetPageUkmSourceId());
CompleteRevokeRequest(status, /*should_call_callback=*/false);
}
}
void FederatedAuthRequestImpl::CompleteRevokeRequest(
RevokeStatus status,
bool should_call_callback) {
if (!revoke_callback_)
return;
network_manager_.reset();
provider_ = GURL();
hint_ = std::string();
client_id_ = std::string();
manifest_list_checked_ = false;
idp_metadata_.reset();
if (should_call_callback)
std::move(revoke_callback_).Run(status);
}
void FederatedAuthRequestImpl::OnBrandIconDownloaded(
int icon_minimum_size,
IdentityProviderMetadata idp_metadata,
int id,
int http_status_code,
const GURL& image_url,
const std::vector<SkBitmap>& bitmaps,
const std::vector<gfx::Size>& sizes) {
// For the sake of FedCM spec simplicity do not support multi-resolution .ico
// files.
if (bitmaps.size() == 1 && bitmaps[0].width() == bitmaps[0].height() &&
bitmaps[0].width() >= icon_minimum_size) {
idp_metadata.brand_icon = bitmaps[0];
}
network_manager_->FetchClientMetadata(
endpoints_.client_metadata, client_id_,
base::BindOnce(
&FederatedAuthRequestImpl::OnClientMetadataResponseReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(idp_metadata)));
}
void FederatedAuthRequestImpl::OnClientMetadataResponseReceived(
IdentityProviderMetadata idp_metadata,
IdpNetworkRequestManager::FetchStatus status,
IdpNetworkRequestManager::ClientMetadata data) {
switch (status) {
case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
RecordRequestIdTokenStatus(IdTokenStatus::kClientMetadataHttpNotFound,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingClientMetadataHttpNotFound,
"",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
RecordRequestIdTokenStatus(IdTokenStatus::kClientMetadataNoResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingClientMetadataNoResponse,
"",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
RecordRequestIdTokenStatus(IdTokenStatus::kClientMetadataInvalidResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(FederatedAuthRequestResult::
kErrorFetchingClientMetadataInvalidResponse,
"",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kSuccess: {
// Since the |privacy_policy_url| is required, consider the result an
// invalid response in the case where the parser returns an empty value
// for it or an invalid url.
GURL pp_url(data.privacy_policy_url);
if (!pp_url.is_valid()) {
RecordRequestIdTokenStatus(
IdTokenStatus::kClientMetadataMissingPrivacyPolicyUrl,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(FederatedAuthRequestResult::
kErrorClientMetadataMissingPrivacyPolicyUrl,
"", /*should_call_callback=*/false);
return;
}
client_metadata_ = data;
network_manager_->SendAccountsRequest(
endpoints_.accounts, client_id_,
base::BindOnce(&FederatedAuthRequestImpl::OnAccountsResponseReceived,
weak_ptr_factory_.GetWeakPtr(), idp_metadata));
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
NOTREACHED();
}
}
}
void FederatedAuthRequestImpl::DownloadBitmap(
const GURL& icon_url,
int ideal_icon_size,
WebContents::ImageDownloadCallback callback) {
if (!icon_url.is_valid()) {
std::move(callback).Run(0 /* id */, 404 /* http_status_code */, icon_url,
std::vector<SkBitmap>(), std::vector<gfx::Size>());
return;
}
WebContents::FromRenderFrameHost(render_frame_host_)
->DownloadImage(icon_url, /*is_favicon*/ false,
gfx::Size(ideal_icon_size, ideal_icon_size),
0 /* max_bitmap_size */, false /* bypass_cache */,
std::move(callback));
}
void FederatedAuthRequestImpl::OnAccountsResponseReceived(
IdentityProviderMetadata idp_metadata,
IdpNetworkRequestManager::FetchStatus status,
IdpNetworkRequestManager::AccountList accounts) {
switch (status) {
case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
RecordRequestIdTokenStatus(IdTokenStatus::kAccountsHttpNotFound,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingAccountsHttpNotFound, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
RecordRequestIdTokenStatus(IdTokenStatus::kAccountsNoResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingAccountsNoResponse, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
RecordRequestIdTokenStatus(IdTokenStatus::kAccountsInvalidResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingAccountsInvalidResponse, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kSuccess: {
WebContents* rp_web_contents =
WebContents::FromRenderFrameHost(render_frame_host_);
bool is_visible = rp_web_contents && (rp_web_contents->GetVisibility() ==
Visibility::VISIBLE);
RecordWebContentsVisibilityUponReadyToShowDialog(is_visible);
// Does not show the dialog if the user has left the page. e.g. they may
// open a new tab before browser is ready to show the dialog.
if (!is_visible) {
CompleteRequest(FederatedAuthRequestResult::kError, "",
/*should_call_callback=*/false);
return;
}
// Populate the accounts login state.
for (auto& account : accounts) {
// We set the login state based on the IDP response if it sends
// back an approved_clients list. If it does not, we need to set
// it here based on browser state.
if (account.login_state)
continue;
LoginState login_state = LoginState::kSignUp;
// Consider this a sign-in if we have seen a successful sign-up for
// this account before.
if (GetSharingPermissionContext() &&
GetSharingPermissionContext()->HasSharingPermission(
origin_, url::Origin::Create(provider_), account.id)) {
login_state = LoginState::kSignIn;
}
account.login_state = login_state;
}
bool screen_reader_is_on =
rp_web_contents->GetAccessibilityMode().has_mode(
ui::AXMode::kScreenReader);
// Auto signs in returning users if they have a single account and are
// signing in.
// TODO(yigu): Add additional controls for RP/IDP/User for this flow.
// https://crbug.com/1236678.
bool is_auto_sign_in = prefer_auto_sign_in_ && accounts.size() == 1 &&
accounts[0].login_state == LoginState::kSignIn &&
!screen_reader_is_on;
// TODO(cbiesinger): Check that the URLs are valid.
ClientIdData data{GURL(client_metadata_.terms_of_service_url),
GURL(client_metadata_.privacy_policy_url)};
show_accounts_dialog_time_ = base::TimeTicks::Now();
RecordShowAccountsDialogTime(show_accounts_dialog_time_ - start_time_,
render_frame_host_->GetPageUkmSourceId());
request_dialog_controller_->ShowAccountsDialog(
rp_web_contents, provider_, accounts, idp_metadata, data,
is_auto_sign_in ? SignInMode::kAuto : SignInMode::kExplicit,
base::BindOnce(&FederatedAuthRequestImpl::OnAccountSelected,
weak_ptr_factory_.GetWeakPtr()));
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
NOTREACHED();
}
}
}
void FederatedAuthRequestImpl::OnAccountSelected(const std::string& account_id,
bool is_sign_in,
bool should_embargo) {
// This could happen if user didn't select any accounts.
if (account_id.empty()) {
base::TimeTicks dismiss_dialog_time = base::TimeTicks::Now();
RecordCancelOnDialogTime(dismiss_dialog_time - show_accounts_dialog_time_,
render_frame_host_->GetPageUkmSourceId());
RecordRequestIdTokenStatus(IdTokenStatus::kNotSelectAccount,
render_frame_host_->GetPageUkmSourceId());
if (should_embargo && GetApiPermissionContext()) {
GetApiPermissionContext()->RecordDismissAndEmbargo(origin_);
}
CompleteRequest(FederatedAuthRequestResult::kError, "",
/*should_call_callback=*/false);
return;
}
RecordIsSignInUser(is_sign_in);
if (GetApiPermissionContext()) {
GetApiPermissionContext()->RemoveEmbargoAndResetCounts(origin_);
}
account_id_ = account_id;
select_account_time_ = base::TimeTicks::Now();
RecordContinueOnDialogTime(select_account_time_ - show_accounts_dialog_time_,
render_frame_host_->GetPageUkmSourceId());
network_manager_->SendTokenRequest(
endpoints_.token, account_id_,
FormatRequestParamsWithoutScope(client_id_, nonce_, account_id,
is_sign_in),
base::BindOnce(&FederatedAuthRequestImpl::OnTokenResponseReceived,
weak_ptr_factory_.GetWeakPtr()));
}
void FederatedAuthRequestImpl::OnTokenResponseReceived(
IdpNetworkRequestManager::FetchStatus status,
const std::string& id_token) {
// When fetching id tokens we show a "Verify" sheet to users in case fetching
// takes a long time due to latency etc.. In case that the fetching process is
// fast, we still want to show the "Verify" sheet for at least
// |id_token_request_delay_| seconds for better UX.
id_token_response_time_ = base::TimeTicks::Now();
base::TimeDelta fetch_time = id_token_response_time_ - select_account_time_;
if (fetch_time >= id_token_request_delay_) {
CompleteIdTokenRequest(status, id_token);
return;
}
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FederatedAuthRequestImpl::CompleteIdTokenRequest,
weak_ptr_factory_.GetWeakPtr(), status, id_token),
id_token_request_delay_ - fetch_time);
}
void FederatedAuthRequestImpl::CompleteIdTokenRequest(
IdpNetworkRequestManager::FetchStatus status,
const std::string& id_token) {
DCHECK(!start_time_.is_null());
switch (status) {
case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenHttpNotFound,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingIdTokenHttpNotFound, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenNoResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingIdTokenNoResponse, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenInvalidRequest,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidRequest, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
RecordRequestIdTokenStatus(IdTokenStatus::kIdTokenInvalidResponse,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(
FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse, "",
/*should_call_callback=*/false);
return;
}
case IdpNetworkRequestManager::FetchStatus::kSuccess: {
if (GetSharingPermissionContext()) {
// Grant sharing permission specific to *this account*.
//
// TODO(majidvp): But wait which account?
// 1) The account that user selected in our UI (i.e., account_id_) or
// 2) The one for which the IDP generated a token.
//
// Ideally these are one and the same but currently there is no
// enforcement for that equality so they could be different. In the
// future we may want to enforce that the token account (aka subject)
// matches the user selected account. But for now these questions are
// moot since we don't actually inspect the returned idtoken.
// https://crbug.com/1199088
CHECK(!account_id_.empty());
GetSharingPermissionContext()->GrantSharingPermission(
origin_, url::Origin::Create(provider_), account_id_);
}
if (GetActiveSessionPermissionContext()) {
GetActiveSessionPermissionContext()->GrantActiveSession(
origin_, url::Origin::Create(provider_), account_id_);
}
RecordIdTokenResponseAndTurnaroundTime(
id_token_response_time_ - select_account_time_,
id_token_response_time_ - start_time_,
render_frame_host_->GetPageUkmSourceId());
RecordRequestIdTokenStatus(IdTokenStatus::kSuccess,
render_frame_host_->GetPageUkmSourceId());
CompleteRequest(FederatedAuthRequestResult::kSuccess, id_token,
/*should_call_callback=*/true);
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 (!GetActiveSessionPermissionContext()) {
CompleteLogoutRequest(LogoutRpsStatus::kError);
return;
}
if (GetActiveSessionPermissionContext()->HasActiveSession(
logout_origin, origin_, account_id)) {
network_manager_->SendLogout(
logout_request->url,
base::BindOnce(&FederatedAuthRequestImpl::OnLogoutCompleted,
weak_ptr_factory_.GetWeakPtr()));
GetActiveSessionPermissionContext()->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::CompleteRequest(
blink::mojom::FederatedAuthRequestResult result,
const std::string& id_token,
bool should_call_callback) {
DCHECK(result == FederatedAuthRequestResult::kSuccess || id_token.empty());
if (!auth_request_callback_)
return;
if (!errors_logged_to_console_ &&
result != FederatedAuthRequestResult::kSuccess) {
errors_logged_to_console_ = true;
// It would be possible to add this inspector issue on the renderer, which
// will receive the callback. However, it is preferable to do so on the
// browser because this is closer to the source, which means adding
// additional metadata is easier. In addition, in the future we may only
// need to pass a small amount of information to the renderer in the case of
// an error, so it would be cleaner to do this by reporting the inspector
// issue from the browser.
AddInspectorIssue(result);
AddConsoleErrorMessage(result);
}
CleanUp();
if (should_call_callback) {
errors_logged_to_console_ = false;
RequestIdTokenStatus status =
FederatedAuthRequestResultToRequestIdTokenStatus(result);
std::move(auth_request_callback_).Run(status, id_token);
}
}
void FederatedAuthRequestImpl::CleanUp() {
request_dialog_controller_.reset();
network_manager_.reset();
// Given that |request_dialog_controller_| has reference to this web content
// instance we destroy that first.
account_id_ = std::string();
start_time_ = base::TimeTicks();
show_accounts_dialog_time_ = base::TimeTicks();
select_account_time_ = base::TimeTicks();
id_token_response_time_ = base::TimeTicks();
manifest_list_checked_ = false;
idp_metadata_.reset();
}
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) {
std::string message = GetConsoleErrorMessage(result);
render_frame_host_->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError, message);
}
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);
}
std::unique_ptr<IdpNetworkRequestManager>
FederatedAuthRequestImpl::CreateNetworkManager(const GURL& provider) {
if (mock_network_manager_)
return std::move(mock_network_manager_);
return IdpNetworkRequestManager::Create(provider, 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();
}
void FederatedAuthRequestImpl::SetIdTokenRequestDelayForTests(
base::TimeDelta delay) {
id_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::SetActiveSessionPermissionDelegateForTests(
FederatedIdentityActiveSessionPermissionContextDelegate*
active_session_permission_delegate) {
active_session_permission_delegate_ = active_session_permission_delegate;
}
void FederatedAuthRequestImpl::SetSharingPermissionDelegateForTests(
FederatedIdentitySharingPermissionContextDelegate*
sharing_permission_delegate) {
sharing_permission_delegate_ = sharing_permission_delegate;
}
void FederatedAuthRequestImpl::SetApiPermissionDelegateForTests(
FederatedIdentityApiPermissionContextDelegate* api_permission_delegate) {
api_permission_delegate_ = api_permission_delegate;
}
FederatedIdentityActiveSessionPermissionContextDelegate*
FederatedAuthRequestImpl::GetActiveSessionPermissionContext() {
if (!active_session_permission_delegate_) {
active_session_permission_delegate_ =
render_frame_host_->GetBrowserContext()
->GetFederatedIdentityActiveSessionPermissionContext();
}
return active_session_permission_delegate_;
}
FederatedIdentityApiPermissionContextDelegate*
FederatedAuthRequestImpl::GetApiPermissionContext() {
if (!api_permission_delegate_) {
api_permission_delegate_ = render_frame_host_->GetBrowserContext()
->GetFederatedIdentityApiPermissionContext();
}
return api_permission_delegate_;
}
FederatedIdentitySharingPermissionContextDelegate*
FederatedAuthRequestImpl::GetSharingPermissionContext() {
if (!sharing_permission_delegate_) {
sharing_permission_delegate_ =
render_frame_host_->GetBrowserContext()
->GetFederatedIdentitySharingPermissionContext();
}
return sharing_permission_delegate_;
}
void FederatedAuthRequestImpl::OnRejectRequest() {
if (auth_request_callback_) {
DCHECK(!revoke_callback_);
DCHECK(!logout_callback_);
CompleteRequest(FederatedAuthRequestResult::kError, "",
/*should_call_callback=*/true);
}
if (revoke_callback_) {
DCHECK(!auth_request_callback_);
DCHECK(!logout_callback_);
CompleteRevokeRequest(RevokeStatus::kError, /*should_call_callback=*/true);
}
}
} // namespace content