blob: de55d145d8a15c3dd6f9659f3c4c4d84f18d8aa9 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/webid/federated_auth_disconnect_request.h"
#include "content/browser/webid/webid_utils.h"
#include "content/public/browser/federated_identity_api_permission_context_delegate.h"
#include "content/public/browser/federated_identity_permission_context_delegate.h"
#include "content/public/browser/render_frame_host.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h"
namespace content {
using FederatedApiPermissionStatus =
FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
using LoginState = IdentityRequestAccount::LoginState;
using DisconnectStatusForMetrics = FedCmDisconnectStatus;
using blink::mojom::DisconnectStatus;
using blink::mojom::FederatedAuthRequestResult;
// static
std::unique_ptr<FederatedAuthDisconnectRequest>
FederatedAuthDisconnectRequest::Create(
std::unique_ptr<IdpNetworkRequestManager> network_manager,
FederatedIdentityPermissionContextDelegate* permission_delegate,
RenderFrameHost* render_frame_host,
FedCmMetrics* metrics,
blink::mojom::IdentityCredentialDisconnectOptionsPtr options) {
std::unique_ptr<FederatedAuthDisconnectRequest> request =
base::WrapUnique<FederatedAuthDisconnectRequest>(
new FederatedAuthDisconnectRequest(
std::move(network_manager), permission_delegate,
render_frame_host, metrics, std::move(options)));
return request;
}
FederatedAuthDisconnectRequest::~FederatedAuthDisconnectRequest() {
Complete(DisconnectStatus::kError, FedCmDisconnectStatus::kUnhandledRequest);
}
FederatedAuthDisconnectRequest::FederatedAuthDisconnectRequest(
std::unique_ptr<IdpNetworkRequestManager> network_manager,
FederatedIdentityPermissionContextDelegate* permission_delegate,
RenderFrameHost* render_frame_host,
FedCmMetrics* metrics,
blink::mojom::IdentityCredentialDisconnectOptionsPtr options)
: network_manager_(std::move(network_manager)),
permission_delegate_(permission_delegate),
metrics_(metrics),
render_frame_host_(render_frame_host),
options_(std::move(options)),
origin_(render_frame_host->GetLastCommittedOrigin()),
start_time_(base::TimeTicks::Now()) {
RenderFrameHost* main_frame = render_frame_host->GetMainFrame();
DCHECK(main_frame->IsInPrimaryMainFrame());
embedding_origin_ = main_frame->GetLastCommittedOrigin();
}
void FederatedAuthDisconnectRequest::SetCallbackAndStart(
blink::mojom::FederatedAuthRequest::DisconnectCallback callback,
FederatedIdentityApiPermissionContextDelegate* api_permission_delegate) {
callback_ = std::move(callback);
url::Origin config_origin = url::Origin::Create(options_->config->config_url);
if (!network::IsOriginPotentiallyTrustworthy(config_origin)) {
Complete(DisconnectStatus::kError,
DisconnectStatusForMetrics::kIdpNotPotentiallyTrustworthy);
return;
}
FederatedApiPermissionStatus permission_status =
api_permission_delegate->GetApiPermissionStatus(embedding_origin_);
std::optional<DisconnectStatusForMetrics> error_disconnect_status;
switch (permission_status) {
case FederatedApiPermissionStatus::BLOCKED_VARIATIONS:
error_disconnect_status = DisconnectStatusForMetrics::kDisabledInFlags;
break;
case FederatedApiPermissionStatus::BLOCKED_SETTINGS:
error_disconnect_status = DisconnectStatusForMetrics::kDisabledInSettings;
break;
// We do not block disconnect on FedCM cooldown.
case FederatedApiPermissionStatus::BLOCKED_EMBARGO:
case FederatedApiPermissionStatus::GRANTED:
// Intentional fall-through.
break;
default:
NOTREACHED_IN_MIGRATION();
break;
}
if (error_disconnect_status) {
Complete(DisconnectStatus::kError, *error_disconnect_status);
return;
}
// Reject if we know that there are no sharing permissions with the given IdP
// and the IdP doesn't have third party cookies access on the RP site.
if (!webid::HasSharingPermissionOrIdpHasThirdPartyCookiesAccess(
*render_frame_host_, options_->config->config_url, embedding_origin_,
origin_, /*account_id=*/std::nullopt, permission_delegate_,
api_permission_delegate)) {
Complete(DisconnectStatus::kError,
DisconnectStatusForMetrics::kNoAccountToDisconnect);
return;
}
provider_fetcher_ = std::make_unique<FederatedProviderFetcher>(
*render_frame_host_, network_manager_.get());
GURL config_url = options_->config->config_url;
provider_fetcher_->Start(
{GURL(config_url)}, blink::mojom::RpMode::kWidget, /*icon_ideal_size=*/0,
/*icon_minimum_size=*/0,
base::BindOnce(
&FederatedAuthDisconnectRequest::OnAllConfigAndWellKnownFetched,
weak_ptr_factory_.GetWeakPtr()));
}
void FederatedAuthDisconnectRequest::OnAllConfigAndWellKnownFetched(
std::vector<FederatedProviderFetcher::FetchResult> fetch_results) {
provider_fetcher_.reset();
DCHECK(fetch_results.size() == 1u);
const FederatedProviderFetcher::FetchResult& fetch_result = fetch_results[0];
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);
}
// TODO (crbug.com/1473134): add devtools issues and console errors.
FedCmDisconnectStatus status;
switch (fetch_error.result) {
case FederatedAuthRequestResult::kErrorFetchingWellKnownHttpNotFound: {
status = FedCmDisconnectStatus::kWellKnownHttpNotFound;
break;
}
case FederatedAuthRequestResult::kErrorFetchingWellKnownNoResponse: {
status = FedCmDisconnectStatus::kWellKnownNoResponse;
break;
}
case FederatedAuthRequestResult::kErrorFetchingWellKnownInvalidResponse: {
status = FedCmDisconnectStatus::kWellKnownInvalidResponse;
break;
}
case FederatedAuthRequestResult::kErrorFetchingWellKnownListEmpty: {
status = FedCmDisconnectStatus::kWellKnownListEmpty;
break;
}
case FederatedAuthRequestResult::
kErrorFetchingWellKnownInvalidContentType: {
status = FedCmDisconnectStatus::kWellKnownInvalidContentType;
break;
}
case FederatedAuthRequestResult::kErrorFetchingConfigHttpNotFound: {
status = FedCmDisconnectStatus::kConfigHttpNotFound;
break;
}
case FederatedAuthRequestResult::kErrorFetchingConfigNoResponse: {
status = FedCmDisconnectStatus::kConfigNoResponse;
break;
}
case FederatedAuthRequestResult::kErrorFetchingConfigInvalidResponse: {
status = FedCmDisconnectStatus::kConfigInvalidResponse;
break;
}
case FederatedAuthRequestResult::kErrorFetchingConfigInvalidContentType: {
status = FedCmDisconnectStatus::kConfigInvalidContentType;
break;
}
case FederatedAuthRequestResult::kErrorWellKnownTooBig: {
status = FedCmDisconnectStatus::kWellKnownTooBig;
break;
}
case FederatedAuthRequestResult::kErrorConfigNotInWellKnown: {
status = FedCmDisconnectStatus::kConfigNotInWellKnown;
break;
}
default: {
status = FedCmDisconnectStatus::kUnhandledRequest;
// The FederatedProviderFetcher does not return any other type of
// result.
CHECK(false);
break;
}
}
Complete(DisconnectStatus::kError, status);
return;
}
if (!webid::IsEndpointSameOrigin(options_->config->config_url,
fetch_result.endpoints.disconnect)) {
render_frame_host_->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Config is missing or has an invalid or cross-origin "
"\"disconnect_endpoint\" URL.");
Complete(DisconnectStatus::kError,
DisconnectStatusForMetrics::kDisconnectUrlIsCrossOrigin);
return;
}
disconnect_request_sent_ = true;
network_manager_->SendDisconnectRequest(
fetch_result.endpoints.disconnect, options_->account_hint,
options_->config->client_id,
base::BindOnce(&FederatedAuthDisconnectRequest::OnDisconnectResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void FederatedAuthDisconnectRequest::OnDisconnectResponse(
IdpNetworkRequestManager::FetchStatus fetch_status,
const std::string& account_id) {
CHECK(callback_);
// Matches the GrantSharingPermission() call in
// FederatedAuthRequestImpl::CompleteTokenRequest(). Note that the IDP origin
// cannot be an arbitrary origin, but rather needs to be a potentially
// trustworthy one.
url::Origin idp_origin = url::Origin::Create(options_->config->config_url);
if (fetch_status.parse_status !=
IdpNetworkRequestManager::ParseStatus::kSuccess) {
// Even though the response was unsuccessful, the credentialed fetch was
// sent to the IDP, so disconnect all permissions associated with the triple
// (`origin_`, `embedding_origin`, `idp_origin`).
permission_delegate_->RevokeSharingPermission(
origin_, embedding_origin_, idp_origin, /*account_id=*/"");
Complete(DisconnectStatus::kError,
DisconnectStatusForMetrics::kDisconnectFailedOnServer);
return;
}
permission_delegate_->RevokeSharingPermission(origin_, embedding_origin_,
idp_origin, account_id);
Complete(DisconnectStatus::kSuccess, DisconnectStatusForMetrics::kSuccess);
}
void FederatedAuthDisconnectRequest::Complete(
blink::mojom::DisconnectStatus status,
FedCmDisconnectStatus disconnect_status_for_metrics) {
if (!callback_) {
return;
}
if (disconnect_status_for_metrics != FedCmDisconnectStatus::kSuccess) {
AddConsoleErrorMessage(disconnect_status_for_metrics);
}
std::optional<base::TimeDelta> duration =
disconnect_request_sent_
? std::optional<base::TimeDelta>{base::TimeTicks::Now() - start_time_}
: std::nullopt;
metrics_->RecordDisconnectMetrics(
disconnect_status_for_metrics, duration, *render_frame_host_, origin_,
embedding_origin_, options_->config->config_url,
webid::GetNewSessionID());
std::move(callback_).Run(status);
}
void FederatedAuthDisconnectRequest::AddConsoleErrorMessage(
FedCmDisconnectStatus disconnect_status_for_metrics) {
render_frame_host_->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
webid::GetDisconnectConsoleErrorMessage(disconnect_status_for_metrics));
}
} // namespace content