0

[FedCM] Rename revoke to disconnect

The feature has not shipped yet so it is OK to rename metrics.

DanglingUntriaged-notes: this CL is just a rename...

Bug: 1473134
Change-Id: I8616fd8b68ab2255897f0cb8b1486d9e17eac5cf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5049262
Reviewed-by: John Abd-El-Malek <jam@chromium.org>
Reviewed-by: Robert Kaplow <rkaplow@chromium.org>
Reviewed-by: Brendon Tiszka <tiszka@chromium.org>
Reviewed-by: Steve Kobes <skobes@chromium.org>
Commit-Queue: Nicolás Peña <npm@chromium.org>
Reviewed-by: Yi Gu <yigu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1228089}
This commit is contained in:
Nicolás Peña
2023-11-22 18:10:39 +00:00
committed by Chromium LUCI CQ
parent 60c5e02c30
commit 87f8ab1b7a
48 changed files with 459 additions and 428 deletions

@ -9171,6 +9171,10 @@ const FeatureEntry kFeatureEntries[] = {
flag_descriptions::kFedCmAutoSelectedFlagDescription, kOsAll,
FEATURE_VALUE_TYPE(features::kFedCmAutoSelectedFlag)},
{"fedcm-disconnect", flag_descriptions::kFedCmDisconnectName,
flag_descriptions::kFedCmDisconnectDescription, kOsAll,
FEATURE_VALUE_TYPE(features::kFedCmDisconnect)},
{"fedcm-domain-hint", flag_descriptions::kFedCmDomainHintName,
flag_descriptions::kFedCmDomainHintDescription, kOsAll,
FEATURE_VALUE_TYPE(features::kFedCmDomainHint)},
@ -9201,10 +9205,6 @@ const FeatureEntry kFeatureEntries[] = {
flag_descriptions::kFedCmMultiIdpDescription, kOsDesktop,
FEATURE_VALUE_TYPE(features::kFedCmMultipleIdentityProviders)},
{"fedcm-revoke", flag_descriptions::kFedCmRevokeName,
flag_descriptions::kFedCmRevokeDescription, kOsAll,
FEATURE_VALUE_TYPE(features::kFedCmRevoke)},
{"fedcm-selective-disclosure",
flag_descriptions::kFedCmSelectiveDisclosureName,
flag_descriptions::kFedCmSelectiveDisclosureDescription, kOsAll,

@ -4084,6 +4084,11 @@
"owners": [ "yigu@chromium.org", "web-identity-eng@google.com"],
"expiry_milestone": 125
},
{
"name": "fedcm-disconnect",
"owners": ["npm@chromium.org", "web-identity-eng@google.com"],
"expiry_milestone": 125
},
{
"name": "fedcm-domain-hint",
"owners": [ "npm@chromium.org", "web-identity-eng@google.com"],
@ -4124,11 +4129,6 @@
"owners": ["npm@chromium.org", "web-identity-eng@google.com"],
"expiry_milestone": 125
},
{
"name": "fedcm-revoke",
"owners": ["npm@chromium.org", "web-identity-eng@google.com"],
"expiry_milestone": 125
},
{
"name": "fedcm-selective-disclosure",
"owners": ["goto@chromium.org", "web-identity-eng@google.com"],

@ -1652,6 +1652,11 @@ const char kFedCmAutoSelectedFlagDescription[] =
"auto-selected with developers post user permission to continue with the "
"IdP.";
const char kFedCmDisconnectName[] = "FedCmDisconnect";
const char kFedCmDisconnectDescription[] =
"Enables the IdentityCredential.disconnect() API which allows "
"disconnecting accounts created via federated login through FedCM.";
const char kFedCmDomainHintName[] = "FedCmDomainHint";
const char kFedCmDomainHintDescription[] =
"Enables RPs to request only FedCM invocations to only show accounts "
@ -1683,11 +1688,6 @@ const char kFedCmMultiIdpDescription[] =
"Allows the FedCM API to request multiple identity providers "
"simultaneously. Requires FedCM to be enabled as well.";
const char kFedCmRevokeName[] = "FedCmRevoke";
const char kFedCmRevokeDescription[] =
"Enables the IdentityCredential.revoke() API which allows revoking "
"accounts created via federated login through FedCM.";
const char kFedCmSelectiveDisclosureName[] = "FedCmSelectiveDisclosure";
const char kFedCmSelectiveDisclosureDescription[] =
"Allows a relying party to selectively request a set of identity "

@ -931,6 +931,9 @@ extern const char kFedCmAuthzDescription[];
extern const char kFedCmAutoSelectedFlagName[];
extern const char kFedCmAutoSelectedFlagDescription[];
extern const char kFedCmDisconnectName[];
extern const char kFedCmDisconnectDescription[];
extern const char kFedCmDomainHintName[];
extern const char kFedCmDomainHintDescription[];
@ -949,9 +952,6 @@ extern const char kFedCmMetricsEndpointDescription[];
extern const char kFedCmMultiIdpName[];
extern const char kFedCmMultiIdpDescription[];
extern const char kFedCmRevokeName[];
extern const char kFedCmRevokeDescription[];
extern const char kFedCmSelectiveDisclosureName[];
extern const char kFedCmSelectiveDisclosureDescription[];

@ -2314,12 +2314,12 @@ source_set("browser") {
"webid/fake_identity_request_dialog_controller.h",
"webid/fedcm_metrics.cc",
"webid/fedcm_metrics.h",
"webid/federated_auth_disconnect_request.cc",
"webid/federated_auth_disconnect_request.h",
"webid/federated_auth_request_impl.cc",
"webid/federated_auth_request_impl.h",
"webid/federated_auth_request_page_data.cc",
"webid/federated_auth_request_page_data.h",
"webid/federated_auth_revoke_request.cc",
"webid/federated_auth_revoke_request.h",
"webid/federated_auth_user_info_request.cc",
"webid/federated_auth_user_info_request.h",
"webid/federated_provider_fetcher.cc",

@ -395,12 +395,12 @@ void FedCmMetrics::RecordNumRequestsPerDocument(const int num_requests) {
num_requests);
}
void FedCmMetrics::RecordRevokeStatus(FedCmRevokeStatus status) {
void FedCmMetrics::RecordDisconnectStatus(FedCmDisconnectStatus status) {
if (is_disabled_) {
return;
}
auto RecordUkm = [&](auto& ukm_builder) {
ukm_builder.SetStatus_Revoke2(static_cast<int>(status));
ukm_builder.SetStatus_Disconnect(static_cast<int>(status));
ukm_builder.SetFedCmSessionID(session_id_);
ukm_builder.Record(ukm::UkmRecorder::Get());
};
@ -410,7 +410,7 @@ void FedCmMetrics::RecordRevokeStatus(FedCmRevokeStatus status) {
ukm::builders::Blink_FedCmIdp fedcm_idp_builder(provider_source_id_);
RecordUkm(fedcm_idp_builder);
base::UmaHistogramEnumeration("Blink.FedCm.Status.Revoke2", status);
base::UmaHistogramEnumeration("Blink.FedCm.Status.Disconnect", status);
}
void FedCmMetrics::RecordErrorDialogResult(FedCmErrorDialogResult result) {

@ -111,16 +111,16 @@ enum class PreventSilentAccessFrameType {
kMaxValue = kCrossSiteIframe
};
// This enum describes the status of a revocation call to the FedCM API.
enum class FedCmRevokeStatus {
// This enum describes the status of a disconnect call to the FedCM API.
enum class FedCmDisconnectStatus {
// Don't change the meaning or the order of these values because they are
// being recorded in metrics and in sync with the counterpart in enums.xml.
kSuccess,
kTooManyRequests,
kUnhandledRequest,
kNoAccountToRevoke,
kRevokeUrlIsCrossOrigin,
kRevocationFailedOnServer,
kNoAccountToDisconnect,
kDisconnectUrlIsCrossOrigin,
kDisconnectFailedOnServer,
kConfigHttpNotFound,
kConfigNoResponse,
kConfigInvalidResponse,
@ -264,8 +264,8 @@ class CONTENT_EXPORT FedCmMetrics {
// FedCM request or for the purpose of MDocs or multi-IDP are not counted.
void RecordNumRequestsPerDocument(const int num_requests);
// Records the status of the |Revoke| call.
void RecordRevokeStatus(FedCmRevokeStatus status);
// Records the status of the disconnect call.
void RecordDisconnectStatus(FedCmDisconnectStatus status);
// Records the type of error dialog shown.
void RecordErrorDialogType(

@ -2,7 +2,7 @@
// 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_revoke_request.h"
#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"
@ -26,38 +26,39 @@ base::TimeDelta GetRandomRejectionTime() {
using FederatedApiPermissionStatus =
FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
using LoginState = IdentityRequestAccount::LoginState;
using RevokeStatusForMetrics = content::FedCmRevokeStatus;
using DisconnectStatusForMetrics = content::FedCmDisconnectStatus;
using blink::mojom::DisconnectStatus;
using blink::mojom::FederatedAuthRequestResult;
using blink::mojom::RevokeStatus;
// static
std::unique_ptr<FederatedAuthRevokeRequest> FederatedAuthRevokeRequest::Create(
std::unique_ptr<FederatedAuthDisconnectRequest>
FederatedAuthDisconnectRequest::Create(
std::unique_ptr<IdpNetworkRequestManager> network_manager,
FederatedIdentityPermissionContextDelegate* permission_delegate,
RenderFrameHost* render_frame_host,
FedCmMetrics* metrics,
blink::mojom::IdentityCredentialRevokeOptionsPtr options,
blink::mojom::IdentityCredentialDisconnectOptionsPtr options,
bool should_complete_request_immediately) {
std::unique_ptr<FederatedAuthRevokeRequest> request =
base::WrapUnique<FederatedAuthRevokeRequest>(
new FederatedAuthRevokeRequest(std::move(network_manager),
permission_delegate, render_frame_host,
metrics, std::move(options),
should_complete_request_immediately));
std::unique_ptr<FederatedAuthDisconnectRequest> request =
base::WrapUnique<FederatedAuthDisconnectRequest>(
new FederatedAuthDisconnectRequest(
std::move(network_manager), permission_delegate,
render_frame_host, metrics, std::move(options),
should_complete_request_immediately));
return request;
}
FederatedAuthRevokeRequest::~FederatedAuthRevokeRequest() {
Complete(RevokeStatus::kError, FedCmRevokeStatus::kUnhandledRequest,
FederatedAuthDisconnectRequest::~FederatedAuthDisconnectRequest() {
Complete(DisconnectStatus::kError, FedCmDisconnectStatus::kUnhandledRequest,
/*should_delay_callback=*/false);
}
FederatedAuthRevokeRequest::FederatedAuthRevokeRequest(
FederatedAuthDisconnectRequest::FederatedAuthDisconnectRequest(
std::unique_ptr<IdpNetworkRequestManager> network_manager,
FederatedIdentityPermissionContextDelegate* permission_delegate,
RenderFrameHost* render_frame_host,
FedCmMetrics* metrics,
blink::mojom::IdentityCredentialRevokeOptionsPtr options,
blink::mojom::IdentityCredentialDisconnectOptionsPtr options,
bool should_complete_request_immediately)
: network_manager_(std::move(network_manager)),
permission_delegate_(permission_delegate),
@ -71,15 +72,15 @@ FederatedAuthRevokeRequest::FederatedAuthRevokeRequest(
embedding_origin_ = main_frame->GetLastCommittedOrigin();
}
void FederatedAuthRevokeRequest::SetCallbackAndStart(
blink::mojom::FederatedAuthRequest::RevokeCallback callback,
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(RevokeStatus::kError,
RevokeStatusForMetrics::kIdpNotPotentiallyTrustworthy,
Complete(DisconnectStatus::kError,
DisconnectStatusForMetrics::kIdpNotPotentiallyTrustworthy,
/*should_delay_callback=*/false);
return;
}
@ -87,15 +88,15 @@ void FederatedAuthRevokeRequest::SetCallbackAndStart(
FederatedApiPermissionStatus permission_status =
api_permission_delegate->GetApiPermissionStatus(embedding_origin_);
absl::optional<RevokeStatusForMetrics> error_revoke_status;
absl::optional<DisconnectStatusForMetrics> error_disconnect_status;
switch (permission_status) {
case FederatedApiPermissionStatus::BLOCKED_VARIATIONS:
error_revoke_status = RevokeStatusForMetrics::kDisabledInFlags;
error_disconnect_status = DisconnectStatusForMetrics::kDisabledInFlags;
break;
case FederatedApiPermissionStatus::BLOCKED_SETTINGS:
error_revoke_status = RevokeStatusForMetrics::kDisabledInSettings;
error_disconnect_status = DisconnectStatusForMetrics::kDisabledInSettings;
break;
// We do not block revocation on FedCM cooldown.
// We do not block disconnect on FedCM cooldown.
case FederatedApiPermissionStatus::BLOCKED_EMBARGO:
case FederatedApiPermissionStatus::GRANTED:
// Intentional fall-through.
@ -104,8 +105,8 @@ void FederatedAuthRevokeRequest::SetCallbackAndStart(
NOTREACHED();
break;
}
if (error_revoke_status) {
Complete(RevokeStatus::kError, *error_revoke_status,
if (error_disconnect_status) {
Complete(DisconnectStatus::kError, *error_disconnect_status,
/*should_delay_callback=*/true);
return;
}
@ -115,7 +116,8 @@ void FederatedAuthRevokeRequest::SetCallbackAndStart(
*render_frame_host_, options_->config->config_url, embedding_origin_,
origin_, /*account_id=*/absl::nullopt, permission_delegate_,
api_permission_delegate)) {
Complete(RevokeStatus::kError, RevokeStatusForMetrics::kNoAccountToRevoke,
Complete(DisconnectStatus::kError,
DisconnectStatusForMetrics::kNoAccountToDisconnect,
/*should_delay_callback=*/true);
return;
}
@ -127,11 +129,11 @@ void FederatedAuthRevokeRequest::SetCallbackAndStart(
{GURL(config_url)}, /*icon_ideal_size=*/0,
/*icon_minimum_size=*/0,
base::BindOnce(
&FederatedAuthRevokeRequest::OnAllConfigAndWellKnownFetched,
&FederatedAuthDisconnectRequest::OnAllConfigAndWellKnownFetched,
weak_ptr_factory_.GetWeakPtr()));
}
void FederatedAuthRevokeRequest::OnAllConfigAndWellKnownFetched(
void FederatedAuthDisconnectRequest::OnAllConfigAndWellKnownFetched(
std::vector<FederatedProviderFetcher::FetchResult> fetch_results) {
provider_fetcher_.reset();
DCHECK(fetch_results.size() == 1u);
@ -147,85 +149,85 @@ void FederatedAuthRevokeRequest::OnAllConfigAndWellKnownFetched(
// TODO (crbug.com/1473134): add devtools issues and console errors.
FedCmRevokeStatus status;
FedCmDisconnectStatus status;
switch (fetch_error.result) {
case FederatedAuthRequestResult::kErrorFetchingWellKnownHttpNotFound: {
status = FedCmRevokeStatus::kWellKnownHttpNotFound;
status = FedCmDisconnectStatus::kWellKnownHttpNotFound;
break;
}
case FederatedAuthRequestResult::kErrorFetchingWellKnownNoResponse: {
status = FedCmRevokeStatus::kWellKnownNoResponse;
status = FedCmDisconnectStatus::kWellKnownNoResponse;
break;
}
case FederatedAuthRequestResult::kErrorFetchingWellKnownInvalidResponse: {
status = FedCmRevokeStatus::kWellKnownInvalidResponse;
status = FedCmDisconnectStatus::kWellKnownInvalidResponse;
break;
}
case FederatedAuthRequestResult::kErrorFetchingWellKnownListEmpty: {
status = FedCmRevokeStatus::kWellKnownListEmpty;
status = FedCmDisconnectStatus::kWellKnownListEmpty;
break;
}
case FederatedAuthRequestResult::
kErrorFetchingWellKnownInvalidContentType: {
status = FedCmRevokeStatus::kWellKnownInvalidContentType;
status = FedCmDisconnectStatus::kWellKnownInvalidContentType;
break;
}
case FederatedAuthRequestResult::kErrorFetchingConfigHttpNotFound: {
status = FedCmRevokeStatus::kConfigHttpNotFound;
status = FedCmDisconnectStatus::kConfigHttpNotFound;
break;
}
case FederatedAuthRequestResult::kErrorFetchingConfigNoResponse: {
status = FedCmRevokeStatus::kConfigNoResponse;
status = FedCmDisconnectStatus::kConfigNoResponse;
break;
}
case FederatedAuthRequestResult::kErrorFetchingConfigInvalidResponse: {
status = FedCmRevokeStatus::kConfigInvalidResponse;
status = FedCmDisconnectStatus::kConfigInvalidResponse;
break;
}
case FederatedAuthRequestResult::kErrorFetchingConfigInvalidContentType: {
status = FedCmRevokeStatus::kConfigInvalidContentType;
status = FedCmDisconnectStatus::kConfigInvalidContentType;
break;
}
case FederatedAuthRequestResult::kErrorWellKnownTooBig: {
status = FedCmRevokeStatus::kWellKnownTooBig;
status = FedCmDisconnectStatus::kWellKnownTooBig;
break;
}
case FederatedAuthRequestResult::kErrorConfigNotInWellKnown: {
status = FedCmRevokeStatus::kConfigNotInWellKnown;
status = FedCmDisconnectStatus::kConfigNotInWellKnown;
break;
}
default: {
status = FedCmRevokeStatus::kUnhandledRequest;
status = FedCmDisconnectStatus::kUnhandledRequest;
// The FederatedProviderFetcher does not return any other type of
// result.
CHECK(false);
break;
}
}
Complete(RevokeStatus::kError, status,
Complete(DisconnectStatus::kError, status,
/*should_delay_callback=*/false);
return;
}
if (!webid::IsEndpointSameOrigin(options_->config->config_url,
fetch_result.endpoints.revoke)) {
fetch_result.endpoints.disconnect)) {
render_frame_host_->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Config is missing or has an invalid or cross-origin "
"\"revocation_endpoint\" URL.");
Complete(RevokeStatus::kError,
RevokeStatusForMetrics::kRevokeUrlIsCrossOrigin,
"\"disconnect_endpoint\" URL.");
Complete(DisconnectStatus::kError,
DisconnectStatusForMetrics::kDisconnectUrlIsCrossOrigin,
/*should_delay_callback=*/false);
return;
}
network_manager_->SendRevokeRequest(
fetch_result.endpoints.revoke, options_->account_hint,
network_manager_->SendDisconnectRequest(
fetch_result.endpoints.disconnect, options_->account_hint,
options_->config->client_id,
base::BindOnce(&FederatedAuthRevokeRequest::OnRevokeResponse,
base::BindOnce(&FederatedAuthDisconnectRequest::OnDisconnectResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void FederatedAuthRevokeRequest::OnRevokeResponse(
void FederatedAuthDisconnectRequest::OnDisconnectResponse(
IdpNetworkRequestManager::FetchStatus fetch_status,
const std::string& account_id) {
CHECK(callback_);
@ -237,31 +239,32 @@ void FederatedAuthRevokeRequest::OnRevokeResponse(
if (fetch_status.parse_status !=
IdpNetworkRequestManager::ParseStatus::kSuccess) {
// Even though the response was unsuccessful, the credentialed fetch was
// sent to the IDP, so revoke all permissions associated with the triple
// 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(RevokeStatus::kError,
RevokeStatusForMetrics::kRevocationFailedOnServer,
Complete(DisconnectStatus::kError,
DisconnectStatusForMetrics::kDisconnectFailedOnServer,
/*should_delay_callback=*/false);
return;
}
permission_delegate_->RevokeSharingPermission(origin_, embedding_origin_,
idp_origin, account_id);
Complete(RevokeStatus::kSuccess, RevokeStatusForMetrics::kSuccess,
Complete(DisconnectStatus::kSuccess, DisconnectStatusForMetrics::kSuccess,
/*should_delay_callback=*/false);
}
void FederatedAuthRevokeRequest::Complete(
blink::mojom::RevokeStatus status,
absl::optional<content::FedCmRevokeStatus> revoke_status_for_metrics,
void FederatedAuthDisconnectRequest::Complete(
blink::mojom::DisconnectStatus status,
absl::optional<content::FedCmDisconnectStatus>
disconnect_status_for_metrics,
bool should_delay_callback) {
if (!callback_) {
return;
}
if (revoke_status_for_metrics) {
metrics_->RecordRevokeStatus(*revoke_status_for_metrics);
if (disconnect_status_for_metrics) {
metrics_->RecordDisconnectStatus(*disconnect_status_for_metrics);
}
if (!should_delay_callback || should_complete_request_immediately_) {
@ -270,7 +273,7 @@ void FederatedAuthRevokeRequest::Complete(
}
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&FederatedAuthRevokeRequest::Complete,
base::BindOnce(&FederatedAuthDisconnectRequest::Complete,
weak_ptr_factory_.GetWeakPtr(), status, absl::nullopt,
/*should_delay_callback=*/false),
GetRandomRejectionTime());

@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_WEBID_FEDERATED_AUTH_REVOKE_REQUEST_H_
#define CONTENT_BROWSER_WEBID_FEDERATED_AUTH_REVOKE_REQUEST_H_
#ifndef CONTENT_BROWSER_WEBID_FEDERATED_AUTH_DISCONNECT_REQUEST_H_
#define CONTENT_BROWSER_WEBID_FEDERATED_AUTH_DISCONNECT_REQUEST_H_
#include <memory>
#include <vector>
@ -23,53 +23,54 @@ class FederatedIdentityPermissionContextDelegate;
class FederatedProviderFetcher;
class RenderFrameHost;
// Fetches data for a FedCM revoke request.
class CONTENT_EXPORT FederatedAuthRevokeRequest {
// Fetches data for a FedCM disconnect request.
class CONTENT_EXPORT FederatedAuthDisconnectRequest {
public:
// Returns an object which fetches data for revoke request.
static std::unique_ptr<FederatedAuthRevokeRequest> Create(
// Returns an object which fetches data for disconnect request.
static std::unique_ptr<FederatedAuthDisconnectRequest> Create(
std::unique_ptr<IdpNetworkRequestManager> network_manager,
FederatedIdentityPermissionContextDelegate* permission_delegate,
RenderFrameHost* render_frame_host,
FedCmMetrics* metrics,
blink::mojom::IdentityCredentialRevokeOptionsPtr options,
blink::mojom::IdentityCredentialDisconnectOptionsPtr options,
bool should_complete_request_immediately);
FederatedAuthRevokeRequest(const FederatedAuthRevokeRequest&) = delete;
FederatedAuthRevokeRequest& operator=(const FederatedAuthRevokeRequest&) =
FederatedAuthDisconnectRequest(const FederatedAuthDisconnectRequest&) =
delete;
~FederatedAuthRevokeRequest();
FederatedAuthDisconnectRequest& operator=(
const FederatedAuthDisconnectRequest&) = delete;
~FederatedAuthDisconnectRequest();
// There is a separate method to set the callback because the callback relies
// on having a pointer to this object, hence cannot be passed in the
// constructor. Once the callback is set, start fetching.
void SetCallbackAndStart(
blink::mojom::FederatedAuthRequest::RevokeCallback callback,
blink::mojom::FederatedAuthRequest::DisconnectCallback callback,
FederatedIdentityApiPermissionContextDelegate* api_permission_delegate);
private:
FederatedAuthRevokeRequest(
FederatedAuthDisconnectRequest(
std::unique_ptr<IdpNetworkRequestManager> network_manager,
FederatedIdentityPermissionContextDelegate* permission_delegate,
RenderFrameHost* render_frame_host,
FedCmMetrics* metrics,
blink::mojom::IdentityCredentialRevokeOptionsPtr options,
blink::mojom::IdentityCredentialDisconnectOptionsPtr options,
bool should_complete_request_immediately);
void OnAllConfigAndWellKnownFetched(
std::vector<FederatedProviderFetcher::FetchResult> fetch_results);
void OnRevokeResponse(IdpNetworkRequestManager::FetchStatus fetch_status,
const std::string& account_id);
void OnDisconnectResponse(IdpNetworkRequestManager::FetchStatus fetch_status,
const std::string& account_id);
// `should_delay_callback` represents whether we should call the callback
// with some delay or immediately. For some failures we choose to reject
// with some delay for privacy reasons. `revoke_status_for_metrics` is
// with some delay for privacy reasons. `disconnect_status_for_metrics` is
// non-nullopt if metrics have not yet been recorded for this request.
void Complete(
blink::mojom::RevokeStatus status,
absl::optional<content::FedCmRevokeStatus> revoke_status_for_metrics,
bool should_delay_callback);
void Complete(blink::mojom::DisconnectStatus status,
absl::optional<content::FedCmDisconnectStatus>
disconnect_status_for_metrics,
bool should_delay_callback);
std::unique_ptr<IdpNetworkRequestManager> network_manager_;
// Owned by |BrowserContext|
@ -80,17 +81,17 @@ class CONTENT_EXPORT FederatedAuthRevokeRequest {
raw_ptr<RenderFrameHost, DanglingUntriaged> render_frame_host_;
std::unique_ptr<FederatedProviderFetcher> provider_fetcher_;
blink::mojom::IdentityCredentialRevokeOptionsPtr options_;
blink::mojom::IdentityCredentialDisconnectOptionsPtr options_;
bool should_complete_request_immediately_{false};
url::Origin origin_;
url::Origin embedding_origin_;
blink::mojom::FederatedAuthRequest::RevokeCallback callback_;
blink::mojom::FederatedAuthRequest::DisconnectCallback callback_;
base::WeakPtrFactory<FederatedAuthRevokeRequest> weak_ptr_factory_{this};
base::WeakPtrFactory<FederatedAuthDisconnectRequest> weak_ptr_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_WEBID_FEDERATED_AUTH_REVOKE_REQUEST_H_
#endif // CONTENT_BROWSER_WEBID_FEDERATED_AUTH_DISCONNECT_REQUEST_H_

@ -2,7 +2,7 @@
// 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_revoke_request.h"
#include "content/browser/webid/federated_auth_disconnect_request.h"
#include <memory>
#include <set>
@ -36,11 +36,12 @@ using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
using DisconnectResponse =
content::IdpNetworkRequestManager::DisconnectResponse;
using DisconnectStatusForMetrics = content::FedCmDisconnectStatus;
using FedCmEntry = ukm::builders::Blink_FedCm;
using LoginState = content::IdentityRequestAccount::LoginState;
using RevokeResponse = content::IdpNetworkRequestManager::RevokeResponse;
using RevokeStatusForMetrics = content::FedCmRevokeStatus;
using blink::mojom::RevokeStatus;
using blink::mojom::DisconnectStatus;
namespace content {
namespace {
@ -48,13 +49,13 @@ namespace {
constexpr char kRpUrl[] = "https://rp.example";
constexpr char kProviderUrl[] = "https://idp.example/fedcm.json";
constexpr char kAccountsEndpoint[] = "https://idp.example/accounts";
constexpr char kRevokeEndpoint[] = "https://idp.example/revoke";
constexpr char kDisconnectEndpoint[] = "https://idp.example/disconnect";
constexpr char kTokenEndpoint[] = "https://idp.example/token";
constexpr char kLoginUrl[] = "https://idp.example/login";
constexpr char kClientId[] = "client_id_123";
// Not used?
// constexpr char kIdpRevokeUrl[] = "https://idp.example/revoke";
// constexpr char kIdpDisconnectUrl[] = "https://idp.example/disconnect";
struct AccountConfig {
std::string id;
@ -65,7 +66,7 @@ struct AccountConfig {
struct Config {
std::vector<AccountConfig> accounts;
FetchStatus config_fetch_status;
FetchStatus revoke_fetch_status;
FetchStatus disconnect_fetch_status;
std::string config_url;
};
@ -74,24 +75,25 @@ Config kValidConfig = {
{{"account1", /*login_state=*/absl::nullopt,
/*was_granted_sharing_permission=*/true}},
/*config_fetch_status=*/{ParseStatus::kSuccess, net::HTTP_OK},
/*revoke_fetch_status=*/{ParseStatus::kSuccess, net::HTTP_OK},
/*disconnect_fetch_status=*/{ParseStatus::kSuccess, net::HTTP_OK},
kProviderUrl};
// Helper class for receiving the Revoke method callback.
class RevokeRequestCallbackHelper {
// Helper class for receiving the Disconnect method callback.
class DisconnectRequestCallbackHelper {
public:
RevokeRequestCallbackHelper() = default;
~RevokeRequestCallbackHelper() = default;
DisconnectRequestCallbackHelper() = default;
~DisconnectRequestCallbackHelper() = default;
RevokeRequestCallbackHelper(const RevokeRequestCallbackHelper&) = delete;
RevokeRequestCallbackHelper& operator=(const RevokeRequestCallbackHelper&) =
DisconnectRequestCallbackHelper(const DisconnectRequestCallbackHelper&) =
delete;
DisconnectRequestCallbackHelper& operator=(
const DisconnectRequestCallbackHelper&) = delete;
RevokeStatus status() const { return status_; }
DisconnectStatus status() const { return status_; }
// This can only be called once per lifetime of this object.
base::OnceCallback<void(RevokeStatus)> callback() {
return base::BindOnce(&RevokeRequestCallbackHelper::ReceiverMethod,
base::OnceCallback<void(DisconnectStatus)> callback() {
return base::BindOnce(&DisconnectRequestCallbackHelper::ReceiverMethod,
base::Unretained(this));
}
@ -105,7 +107,7 @@ class RevokeRequestCallbackHelper {
}
private:
void ReceiverMethod(RevokeStatus status) {
void ReceiverMethod(DisconnectStatus status) {
status_ = status;
was_called_ = true;
wait_for_callback_loop_.Quit();
@ -113,7 +115,7 @@ class RevokeRequestCallbackHelper {
bool was_called_ = false;
base::RunLoop wait_for_callback_loop_;
RevokeStatus status_;
DisconnectStatus status_;
};
class TestIdpNetworkRequestManager : public MockIdpNetworkRequestManager {
@ -143,7 +145,7 @@ class TestIdpNetworkRequestManager : public MockIdpNetworkRequestManager {
IdpNetworkRequestManager::Endpoints endpoints;
endpoints.accounts = GURL(kAccountsEndpoint);
endpoints.token = GURL(kTokenEndpoint);
endpoints.revoke = GURL(kRevokeEndpoint);
endpoints.disconnect = GURL(kDisconnectEndpoint);
IdentityProviderMetadata idp_metadata;
idp_metadata.config_url = GURL(config_.config_url);
@ -154,19 +156,20 @@ class TestIdpNetworkRequestManager : public MockIdpNetworkRequestManager {
endpoints, idp_metadata));
}
void SendRevokeRequest(const GURL& revoke_url,
const std::string& account_hint,
const std::string& client_id,
RevokeCallback callback) override {
has_fetched_revoke_ = true;
void SendDisconnectRequest(const GURL& disconnect_url,
const std::string& account_hint,
const std::string& client_id,
DisconnectCallback callback) override {
has_fetched_disconnect_ = true;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
config_.revoke_fetch_status, account_hint));
FROM_HERE,
base::BindOnce(std::move(callback), config_.disconnect_fetch_status,
account_hint));
}
bool has_fetched_well_known_{false};
bool has_fetched_config_{false};
bool has_fetched_revoke_{false};
bool has_fetched_disconnect_{false};
private:
const Config config_;
@ -219,16 +222,17 @@ class TestPermissionDelegate : public MockPermissionDelegate {
} // namespace
class FederatedAuthRevokeRequestTest : public RenderViewHostImplTestHarness {
class FederatedAuthDisconnectRequestTest
: public RenderViewHostImplTestHarness {
public:
FederatedAuthRevokeRequestTest() {
FederatedAuthDisconnectRequestTest() {
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
~FederatedAuthRevokeRequestTest() override = default;
~FederatedAuthDisconnectRequestTest() override = default;
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
scoped_feature_list_.InitAndEnableFeature(features::kFedCmRevoke);
scoped_feature_list_.InitAndEnableFeature(features::kFedCmDisconnect);
api_permission_delegate_ = std::make_unique<TestApiPermissionDelegate>();
permission_delegate_ = std::make_unique<TestPermissionDelegate>();
@ -242,8 +246,8 @@ class FederatedAuthRevokeRequestTest : public RenderViewHostImplTestHarness {
RenderViewHostImplTestHarness::TearDown();
}
void RunRevokeTest(const Config& config,
RevokeStatus expected_revoke_status) {
void RunDisconnectTest(const Config& config,
DisconnectStatus expected_disconnect_status) {
permission_delegate_->SetConfig(config);
auto network_manager =
@ -254,15 +258,15 @@ class FederatedAuthRevokeRequestTest : public RenderViewHostImplTestHarness {
GURL(config.config_url), main_test_rfh()->GetPageUkmSourceId(),
/*session_id=*/1, /*is_disabled=*/false);
blink::mojom::IdentityCredentialRevokeOptionsPtr options =
blink::mojom::IdentityCredentialRevokeOptions::New();
blink::mojom::IdentityCredentialDisconnectOptionsPtr options =
blink::mojom::IdentityCredentialDisconnectOptions::New();
options->config = blink::mojom::IdentityProviderConfig::New();
options->config->config_url = GURL(config.config_url);
options->config->client_id = kClientId;
options->account_hint = "accountHint";
RevokeRequestCallbackHelper callback_helper;
request_ = FederatedAuthRevokeRequest::Create(
DisconnectRequestCallbackHelper callback_helper;
request_ = FederatedAuthDisconnectRequest::Create(
std::move(network_manager), permission_delegate_.get(), main_rfh(),
metrics_.get(), std::move(options),
/*should_complete_request_immediately=*/true);
@ -270,11 +274,11 @@ class FederatedAuthRevokeRequestTest : public RenderViewHostImplTestHarness {
api_permission_delegate_.get());
callback_helper.WaitForCallback();
EXPECT_EQ(expected_revoke_status, callback_helper.status());
EXPECT_EQ(expected_disconnect_status, callback_helper.status());
}
void ExpectRevokeStatusUKM(RevokeStatusForMetrics status,
const char* entry_name) {
void ExpectDisconnectStatusUKM(DisconnectStatusForMetrics status,
const char* entry_name) {
auto entries = ukm_recorder()->GetEntriesByName(entry_name);
ASSERT_FALSE(entries.empty())
@ -285,24 +289,25 @@ class FederatedAuthRevokeRequestTest : public RenderViewHostImplTestHarness {
bool metric_found = false;
for (const auto* const entry : entries) {
const int64_t* metric =
ukm_recorder()->GetEntryMetric(entry, "Status.Revoke2");
ukm_recorder()->GetEntryMetric(entry, "Status.Disconnect");
if (!metric) {
continue;
}
EXPECT_FALSE(metric_found)
<< "Found more than one entry with Status.Revoke2 in " << entry_name;
<< "Found more than one entry with Status.Disconnect in "
<< entry_name;
metric_found = true;
EXPECT_EQ(static_cast<int>(status), *metric)
<< "Unexpected status recorded in " << entry_name;
}
EXPECT_TRUE(metric_found)
<< "No Status.Revoke2 entry was found in " << entry_name;
<< "No Status.Disconnect entry was found in " << entry_name;
}
bool DidFetchAnyEndpoint() {
return network_manager_->has_fetched_well_known_ ||
network_manager_->has_fetched_config_ ||
network_manager_->has_fetched_revoke_;
network_manager_->has_fetched_disconnect_;
}
ukm::TestAutoSetUkmRecorder* ukm_recorder() { return ukm_recorder_.get(); }
@ -313,38 +318,39 @@ class FederatedAuthRevokeRequestTest : public RenderViewHostImplTestHarness {
std::unique_ptr<TestApiPermissionDelegate> api_permission_delegate_;
std::unique_ptr<TestPermissionDelegate> permission_delegate_;
std::unique_ptr<FedCmMetrics> metrics_;
std::unique_ptr<FederatedAuthRevokeRequest> request_;
std::unique_ptr<FederatedAuthDisconnectRequest> request_;
base::HistogramTester histogram_tester_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
};
TEST_F(FederatedAuthRevokeRequestTest, Success) {
TEST_F(FederatedAuthDisconnectRequestTest, Success) {
Config config = kValidConfig;
RunRevokeTest(config, RevokeStatus::kSuccess);
RunDisconnectTest(config, DisconnectStatus::kSuccess);
EXPECT_TRUE(network_manager_->has_fetched_well_known_);
EXPECT_TRUE(network_manager_->has_fetched_config_);
EXPECT_TRUE(network_manager_->has_fetched_revoke_);
EXPECT_TRUE(network_manager_->has_fetched_disconnect_);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.Revoke2",
RevokeStatusForMetrics::kSuccess, 1);
ExpectRevokeStatusUKM(RevokeStatusForMetrics::kSuccess,
FedCmEntry::kEntryName);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.Disconnect",
DisconnectStatusForMetrics::kSuccess, 1);
ExpectDisconnectStatusUKM(DisconnectStatusForMetrics::kSuccess,
FedCmEntry::kEntryName);
}
TEST_F(FederatedAuthRevokeRequestTest, NotTrustworthyIdP) {
TEST_F(FederatedAuthDisconnectRequestTest, NotTrustworthyIdP) {
Config config = kValidConfig;
config.config_url = "http://idp.example/fedcm.json";
RunRevokeTest(config, RevokeStatus::kError);
RunDisconnectTest(config, DisconnectStatus::kError);
EXPECT_FALSE(DidFetchAnyEndpoint());
histogram_tester_.ExpectUniqueSample(
"Blink.FedCm.Status.Revoke2",
RevokeStatusForMetrics::kIdpNotPotentiallyTrustworthy, 1);
ExpectRevokeStatusUKM(RevokeStatusForMetrics::kIdpNotPotentiallyTrustworthy,
FedCmEntry::kEntryName);
"Blink.FedCm.Status.Disconnect",
DisconnectStatusForMetrics::kIdpNotPotentiallyTrustworthy, 1);
ExpectDisconnectStatusUKM(
DisconnectStatusForMetrics::kIdpNotPotentiallyTrustworthy,
FedCmEntry::kEntryName);
}
TEST_F(FederatedAuthRevokeRequestTest,
TEST_F(FederatedAuthDisconnectRequestTest,
NoSharingPermissionButIdpHasThirdPartyCookiesAccessAndClaimsSignin) {
base::test::ScopedFeatureList list;
list.InitAndEnableFeature(features::kFedCmExemptIdpWithThirdPartyCookies);
@ -361,15 +367,15 @@ TEST_F(FederatedAuthRevokeRequestTest,
url::Origin::Create(GURL(kRpUrl))))
.WillOnce(Return(true));
RunRevokeTest(config, RevokeStatus::kSuccess);
RunDisconnectTest(config, DisconnectStatus::kSuccess);
EXPECT_TRUE(network_manager_->has_fetched_well_known_);
EXPECT_TRUE(network_manager_->has_fetched_config_);
EXPECT_TRUE(network_manager_->has_fetched_revoke_);
EXPECT_TRUE(network_manager_->has_fetched_disconnect_);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.Revoke2",
RevokeStatusForMetrics::kSuccess, 1);
ExpectRevokeStatusUKM(RevokeStatusForMetrics::kSuccess,
FedCmEntry::kEntryName);
histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.Disconnect",
DisconnectStatusForMetrics::kSuccess, 1);
ExpectDisconnectStatusUKM(DisconnectStatusForMetrics::kSuccess,
FedCmEntry::kEntryName);
}
} // namespace content

@ -25,8 +25,8 @@
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/webid/digital_credentials/digital_credential_provider.h"
#include "content/browser/webid/fake_identity_request_dialog_controller.h"
#include "content/browser/webid/federated_auth_disconnect_request.h"
#include "content/browser/webid/federated_auth_request_page_data.h"
#include "content/browser/webid/federated_auth_revoke_request.h"
#include "content/browser/webid/federated_auth_user_info_request.h"
#include "content/browser/webid/flags.h"
#include "content/browser/webid/identity_registry.h"
@ -46,6 +46,7 @@
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h"
using base::Value;
using blink::mojom::DisconnectStatus;
using blink::mojom::FederatedAuthRequestResult;
using blink::mojom::IdentityProviderConfig;
using blink::mojom::IdentityProviderConfigPtr;
@ -54,10 +55,9 @@ using blink::mojom::IdentityProviderRequestOptions;
using blink::mojom::IdentityProviderRequestOptionsPtr;
using blink::mojom::RequestTokenStatus;
using blink::mojom::RequestUserInfoStatus;
using blink::mojom::RevokeStatus;
using FederatedApiPermissionStatus =
content::FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
using RevokeStatusForMetrics = content::FedCmRevokeStatus;
using DisconnectStatusForMetrics = content::FedCmDisconnectStatus;
using TokenStatus = content::FedCmRequestIdTokenStatus;
using SignInStateMatchStatus = content::FedCmSignInStateMatchStatus;
using TokenResponseType =
@ -499,11 +499,11 @@ FederatedAuthRequestImpl::~FederatedAuthRequestImpl() {
// naturally.
user_info_requests_.clear();
// Calls |FederatedAuthRevokeRequest|'s destructor to complete the revocation
// request. This is needed because otherwise some resources like
// Calls |FederatedAuthDisconnectRequest|'s destructor to complete the
// revocation request. This is needed because otherwise some resources like
// `fedcm_metrics_` may no longer be usable when the destructor get invoked
// naturally.
revoke_request_.reset();
disconnect_request_.reset();
// Since FederatedAuthRequestImpl is a subclass of
// DocumentService<blink::mojom::FederatedAuthRequest>, it only lives as long
@ -1192,16 +1192,16 @@ void FederatedAuthRequestImpl::OnAllConfigAndWellKnownFetched(
}
}
void FederatedAuthRequestImpl::CompleteRevokeRequest(
RevokeCallback callback,
blink::mojom::RevokeStatus status) {
if (!revoke_request_) {
NOTREACHED() << "The completed revocation request is nowhere to be found";
void FederatedAuthRequestImpl::CompleteDisconnectRequest(
DisconnectCallback callback,
blink::mojom::DisconnectStatus status) {
if (!disconnect_request_) {
NOTREACHED() << "The completed disconnect request is nowhere to be found";
return;
}
std::move(callback).Run(status);
revoke_request_.reset();
disconnect_request_.reset();
}
void FederatedAuthRequestImpl::OnClientMetadataResponseReceived(
@ -2622,13 +2622,13 @@ void FederatedAuthRequestImpl::PreventSilentAccess(
std::move(callback).Run();
}
void FederatedAuthRequestImpl::Revoke(
blink::mojom::IdentityCredentialRevokeOptionsPtr options,
RevokeCallback callback) {
if (!IsFedCmRevokeEnabled()) {
void FederatedAuthRequestImpl::Disconnect(
blink::mojom::IdentityCredentialDisconnectOptionsPtr options,
DisconnectCallback callback) {
if (!IsFedCmDisconnectEnabled()) {
// This should only happen when the request comes from a compromised
// renderer.
std::move(callback).Run(RevokeStatus::kError);
std::move(callback).Run(DisconnectStatus::kError);
return;
}
if (!fedcm_metrics_) {
@ -2642,9 +2642,10 @@ void FederatedAuthRequestImpl::Revoke(
options->config->config_url, render_frame_host().GetPageUkmSourceId(),
/*is_disabled=*/false);
}
if (revoke_request_) {
fedcm_metrics_->RecordRevokeStatus(FedCmRevokeStatus::kTooManyRequests);
std::move(callback).Run(RevokeStatus::kErrorTooManyRequests);
if (disconnect_request_) {
fedcm_metrics_->RecordDisconnectStatus(
FedCmDisconnectStatus::kTooManyRequests);
std::move(callback).Run(DisconnectStatus::kErrorTooManyRequests);
return;
}
@ -2658,14 +2659,15 @@ void FederatedAuthRequestImpl::Revoke(
auto network_manager = CreateNetworkManager();
revoke_request_ = FederatedAuthRevokeRequest::Create(
disconnect_request_ = FederatedAuthDisconnectRequest::Create(
std::move(network_manager), permission_delegate_, &render_frame_host(),
fedcm_metrics_.get(), std::move(options),
should_complete_request_immediately_);
FederatedAuthRevokeRequest* revoke_request_ptr = revoke_request_.get();
FederatedAuthDisconnectRequest* disconnect_request_ptr =
disconnect_request_.get();
revoke_request_ptr->SetCallbackAndStart(
base::BindOnce(&FederatedAuthRequestImpl::CompleteRevokeRequest,
disconnect_request_ptr->SetCallbackAndStart(
base::BindOnce(&FederatedAuthRequestImpl::CompleteDisconnectRequest,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
api_permission_delegate_);
}

@ -31,7 +31,7 @@
namespace content {
class FederatedAuthRevokeRequest;
class FederatedAuthDisconnectRequest;
class FederatedAuthUserInfoRequest;
class FederatedIdentityApiPermissionContextDelegate;
class FederatedIdentityAutoReauthnPermissionContextDelegate;
@ -89,8 +89,8 @@ class CONTENT_EXPORT FederatedAuthRequestImpl
void UnregisterIdP(const ::GURL& idp, UnregisterIdPCallback) override;
void CloseModalDialogView() override;
void PreventSilentAccess(PreventSilentAccessCallback callback) override;
void Revoke(blink::mojom::IdentityCredentialRevokeOptionsPtr options,
RevokeCallback) override;
void Disconnect(blink::mojom::IdentityCredentialDisconnectOptionsPtr options,
DisconnectCallback) override;
// FederatedIdentityPermissionContextDelegate::IdpSigninStatusObserver:
void OnIdpSigninStatusReceived(const url::Origin& idp_config_origin,
@ -348,8 +348,8 @@ class CONTENT_EXPORT FederatedAuthRequestImpl
void SignInToIdP(GURL signin_url);
void CompleteRevokeRequest(RevokeCallback callback,
blink::mojom::RevokeStatus status);
void CompleteDisconnectRequest(DisconnectCallback callback,
blink::mojom::DisconnectStatus status);
void RecordErrorMetrics(
blink::mojom::IdentityProviderRequestOptionsPtr idp,
@ -418,8 +418,8 @@ class CONTENT_EXPORT FederatedAuthRequestImpl
base::flat_set<std::unique_ptr<FederatedAuthUserInfoRequest>>
user_info_requests_;
// Pending revoke request.
std::unique_ptr<FederatedAuthRevokeRequest> revoke_request_;
// Pending disconnect request.
std::unique_ptr<FederatedAuthDisconnectRequest> disconnect_request_;
// TODO(crbug.com/1361649): Refactor these member variables introduced through
// the multi IDP prototype implementation to make them less confusing.

@ -89,7 +89,7 @@ constexpr char kClientMetadataEndpoint[] =
"https://idp.example/client_metadata";
constexpr char kMetricsEndpoint[] = "https://idp.example/metrics";
constexpr char kIdpLoginUrl[] = "https://idp.example/login_url";
constexpr char kIdpRevokeUrl[] = "https://idp.example/revoke";
constexpr char kIdpDisconnectUrl[] = "https://idp.example/disconnect";
constexpr char kPrivacyPolicyUrl[] = "https://rp.example/pp";
constexpr char kTermsOfServiceUrl[] = "https://rp.example/tos";
constexpr char kClientId[] = "client_id_123";
@ -261,7 +261,7 @@ struct MockConfig {
std::string client_metadata_endpoint;
std::string metrics_endpoint;
std::string idp_login_url;
std::string revoke_endpoint;
std::string disconnect_endpoint;
};
struct MockIdpInfo {
@ -338,7 +338,7 @@ static const MockIdpInfo kDefaultIdentityProviderInfo{
kClientMetadataEndpoint,
kMetricsEndpoint,
kIdpLoginUrl,
kIdpRevokeUrl,
kIdpDisconnectUrl,
},
kDefaultClientMetadata,
{ParseStatus::kSuccess, net::HTTP_OK},
@ -358,7 +358,7 @@ static const MockIdpInfo kProviderTwoInfo{
"https://idp2.example/client_metadata",
"https://idp2.example/metrics",
"https://idp2.example/login_url",
"https://idp2.example/revoke",
"https://idp2.example/disconnect",
},
kDefaultClientMetadata,
{ParseStatus::kSuccess, net::HTTP_OK},
@ -455,8 +455,8 @@ class TestIdpNetworkRequestManager : public MockIdpNetworkRequestManager {
GURL(config_.idp_info[provider_key].config.client_metadata_endpoint);
endpoints.metrics =
GURL(config_.idp_info[provider_key].config.metrics_endpoint);
endpoints.revoke =
GURL(config_.idp_info[provider_key].config.revoke_endpoint);
endpoints.disconnect =
GURL(config_.idp_info[provider_key].config.disconnect_endpoint);
IdentityProviderMetadata idp_metadata;
idp_metadata.config_url = provider;

@ -64,8 +64,8 @@ bool IsFedCmErrorEnabled() {
return base::FeatureList::IsEnabled(features::kFedCmError);
}
bool IsFedCmRevokeEnabled() {
return base::FeatureList::IsEnabled(features::kFedCmRevoke);
bool IsFedCmDisconnectEnabled() {
return base::FeatureList::IsEnabled(features::kFedCmDisconnect);
}
bool IsFedCmAddAccountEnabled() {

@ -47,8 +47,8 @@ bool IsFedCmDomainHintEnabled();
// Whether the Error API is enabled.
bool IsFedCmErrorEnabled();
// Whether the revoke API is enabled.
bool IsFedCmRevokeEnabled();
// Whether the disconnect API is enabled.
bool IsFedCmDisconnectEnabled();
// Whether "Add Account" is enabled.
bool IsFedCmAddAccountEnabled();

@ -68,7 +68,7 @@ constexpr char kProviderUrlListKey[] = "provider_urls";
constexpr char kIdAssertionEndpoint[] = "id_assertion_endpoint";
constexpr char kClientMetadataEndpointKey[] = "client_metadata_endpoint";
constexpr char kMetricsEndpoint[] = "metrics_endpoint";
constexpr char kRevocationEndpoint[] = "revocation_endpoint";
constexpr char kDisconnectEndpoint[] = "disconnect_endpoint";
constexpr char kSupportsAddAccountKey[] = "supports_add_account";
// Shared between the well-known files and config files
@ -128,8 +128,8 @@ constexpr char kAccessDenied[] = "access_denied";
constexpr char kTemporarilyUnavailable[] = "temporarily_unavailable";
constexpr char kServerError[] = "server_error";
// Revoke response keys.
constexpr char kRevokeAccountId[] = "account_id";
// Disconnect response keys.
constexpr char kDisconnectAccountId[] = "account_id";
// 1 MiB is an arbitrary upper bound that should account for any reasonable
// response size that is a part of this protocol.
@ -520,7 +520,8 @@ void OnConfigParsed(const GURL& provider,
endpoints.client_metadata =
ExtractEndpoint(provider, response, kClientMetadataEndpointKey);
endpoints.metrics = ExtractEndpoint(provider, response, kMetricsEndpoint);
endpoints.revoke = ExtractEndpoint(provider, response, kRevocationEndpoint);
endpoints.disconnect =
ExtractEndpoint(provider, response, kDisconnectEndpoint);
const base::Value::Dict* idp_metadata_value =
response.FindDict(kIdpBrandingKey);
@ -773,16 +774,17 @@ void OnLogoutCompleted(IdpNetworkRequestManager::LogoutCallback callback,
std::move(callback).Run();
}
void OnRevokeResponseParsed(IdpNetworkRequestManager::RevokeCallback callback,
FetchStatus fetch_status,
data_decoder::DataDecoder::ValueOrError result) {
void OnDisconnectResponseParsed(
IdpNetworkRequestManager::DisconnectCallback callback,
FetchStatus fetch_status,
data_decoder::DataDecoder::ValueOrError result) {
if (fetch_status.parse_status != ParseStatus::kSuccess) {
std::move(callback).Run(fetch_status, /*account_id=*/"");
return;
}
const base::Value::Dict& response = result->GetDict();
const std::string* account_id = response.FindString(kRevokeAccountId);
const std::string* account_id = response.FindString(kDisconnectAccountId);
if (account_id && !account_id->empty()) {
std::move(callback).Run(fetch_status, *account_id);
@ -1002,18 +1004,18 @@ void IdpNetworkRequestManager::SendLogout(const GURL& logout_url,
maxResponseSizeInKiB * 1024);
}
void IdpNetworkRequestManager::SendRevokeRequest(
const GURL& revoke_url,
void IdpNetworkRequestManager::SendDisconnectRequest(
const GURL& disconnect_url,
const std::string& account_hint,
const std::string& client_id,
RevokeCallback callback) {
DisconnectCallback callback) {
auto resource_request = CreateCredentialedResourceRequest(
revoke_url, CredentialedResourceRequestType::kOriginWithCORS);
disconnect_url, CredentialedResourceRequestType::kOriginWithCORS);
std::string url_encoded_post_data =
"client_id=" + client_id + "&account_hint=" + account_hint;
DownloadJsonAndParse(
std::move(resource_request), url_encoded_post_data,
base::BindOnce(&OnRevokeResponseParsed, std::move(callback)),
base::BindOnce(&OnDisconnectResponseParsed, std::move(callback)),
maxResponseSizeInKiB * 1024);
}

@ -110,7 +110,7 @@ class CONTENT_EXPORT IdpNetworkRequestManager {
GURL accounts;
GURL client_metadata;
GURL metrics;
GURL revoke;
GURL disconnect;
};
struct CONTENT_EXPORT WellKnown {
@ -156,7 +156,7 @@ class CONTENT_EXPORT IdpNetworkRequestManager {
kTokenEndpointInvalidResponse = 402,
};
enum class RevokeResponse {
enum class DisconnectResponse {
kSuccess,
kError,
};
@ -223,7 +223,7 @@ class CONTENT_EXPORT IdpNetworkRequestManager {
using ParseJsonCallback =
base::OnceCallback<void(FetchStatus,
data_decoder::DataDecoder::ValueOrError)>;
using RevokeCallback =
using DisconnectCallback =
base::OnceCallback<void(FetchStatus, const std::string&)>;
using TokenRequestCallback =
base::OnceCallback<void(FetchStatus, TokenResult)>;
@ -294,11 +294,11 @@ class CONTENT_EXPORT IdpNetworkRequestManager {
// Send logout request to a single target.
virtual void SendLogout(const GURL& logout_url, LogoutCallback);
// Send a revoke request to the IDP.
virtual void SendRevokeRequest(const GURL& revoke_url,
const std::string& account_hint,
const std::string& client_id,
RevokeCallback callback);
// Send a disconnect request to the IDP.
virtual void SendDisconnectRequest(const GURL& disconnect_url,
const std::string& account_hint,
const std::string& client_id,
DisconnectCallback callback);
private:
// Starts download request using `url_loader`. Calls `parse_json_callback`

@ -66,7 +66,8 @@ constexpr char kTestAccountsEndpoint[] = "https://idp.test/accounts_endpoint";
constexpr char kTestTokenEndpoint[] = "https://idp.test/token_endpoint";
constexpr char kTestClientMetadataEndpoint[] =
"https://idp.test/client_metadata_endpoint";
constexpr char kTestRevokeEndpoint[] = "https://idp.test/revocation_endpoint";
constexpr char kTestDisconnectEndpoint[] =
"https://idp.test/revocation_endpoint";
constexpr char kSingleAccountEndpointValidJson[] = R"({
"accounts" : [
@ -1568,15 +1569,15 @@ TEST_F(IdpNetworkRequestManagerTest, IdAssertionResponseWithTokenAndHttpError) {
EXPECT_FALSE(error_url_type());
}
TEST_F(IdpNetworkRequestManagerTest, RevokeRequest) {
TEST_F(IdpNetworkRequestManagerTest, DisconnectRequest) {
base::test::ScopedFeatureList list;
list.InitAndEnableFeature(features::kFedCmRevoke);
list.InitAndEnableFeature(features::kFedCmDisconnect);
bool called = false;
auto interceptor =
base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
called = true;
EXPECT_EQ(GURL(kTestRevokeEndpoint), request.url);
EXPECT_EQ(GURL(kTestDisconnectEndpoint), request.url);
EXPECT_FALSE(request.referrer.is_valid());
url::Origin rpOrigin = url::Origin::Create(GURL(kTestRpUrl));
EXPECT_EQ(GetOriginHeader(request), rpOrigin);
@ -1596,34 +1597,34 @@ TEST_F(IdpNetworkRequestManagerTest, RevokeRequest) {
});
test_url_loader_factory().SetInterceptor(interceptor);
const char test_revoke_json[] = R"({
const char test_disconnect_json[] = R"({
"account_id" : "accountId"
})";
GURL revoke_endpoint(kTestRevokeEndpoint);
AddResponse(revoke_endpoint, net::HTTP_OK, "application/json",
test_revoke_json);
GURL disconnect_endpoint(kTestDisconnectEndpoint);
AddResponse(disconnect_endpoint, net::HTTP_OK, "application/json",
test_disconnect_json);
base::RunLoop run_loop;
FetchStatus revoke_response;
absl::optional<std::string> revoke_account_id;
FetchStatus disconnect_response;
absl::optional<std::string> disconnect_account_id;
auto callback = base::BindLambdaForTesting(
[&](FetchStatus response, const std::string& account_id) {
revoke_response = response;
revoke_account_id = account_id;
disconnect_response = response;
disconnect_account_id = account_id;
run_loop.Quit();
});
std::unique_ptr<IdpNetworkRequestManager> manager = CreateTestManager();
manager->SendRevokeRequest(revoke_endpoint, "hint", "clientId",
std::move(callback));
manager->SendDisconnectRequest(disconnect_endpoint, "hint", "clientId",
std::move(callback));
run_loop.Run();
EXPECT_TRUE(called);
EXPECT_EQ(ParseStatus::kSuccess, revoke_response.parse_status);
EXPECT_EQ(net::HTTP_OK, revoke_response.response_code);
ASSERT_TRUE(revoke_account_id.has_value());
EXPECT_EQ(*revoke_account_id, "accountId");
EXPECT_EQ(ParseStatus::kSuccess, disconnect_response.parse_status);
EXPECT_EQ(net::HTTP_OK, disconnect_response.response_code);
ASSERT_TRUE(disconnect_account_id.has_value());
EXPECT_EQ(*disconnect_account_id, "accountId");
}
} // namespace

@ -79,13 +79,13 @@ void DelegatedIdpNetworkRequestManager::SendLogout(const GURL& logout_url,
delegate_->SendLogout(logout_url, std::move(callback));
}
void DelegatedIdpNetworkRequestManager::SendRevokeRequest(
const GURL& revoke_url,
void DelegatedIdpNetworkRequestManager::SendDisconnectRequest(
const GURL& disconnect_url,
const std::string& account_hint,
const std::string& client_id,
RevokeCallback callback) {
delegate_->SendRevokeRequest(revoke_url, account_hint, client_id,
std::move(callback));
DisconnectCallback callback) {
delegate_->SendDisconnectRequest(disconnect_url, account_hint, client_id,
std::move(callback));
}
} // namespace content

@ -53,10 +53,10 @@ class DelegatedIdpNetworkRequestManager : public MockIdpNetworkRequestManager {
const GURL& metrics_endpoint_url,
MetricsEndpointErrorCode error_code) override;
void SendLogout(const GURL& logout_url, LogoutCallback callback) override;
void SendRevokeRequest(const GURL& revoke_url,
const std::string& account_hint,
const std::string& client_id,
RevokeCallback callback) override;
void SendDisconnectRequest(const GURL& disconnect_url,
const std::string& account_hint,
const std::string& client_id,
DisconnectCallback callback) override;
private:
raw_ptr<IdpNetworkRequestManager, DanglingUntriaged> delegate_;

@ -239,7 +239,7 @@ void SetRuntimeFeaturesFromChromiumFeatures() {
raw_ref(blink::features::kSharedStorageAPIM118), kSetOnlyIfOverridden},
{wf::EnableFedCmMultipleIdentityProviders,
raw_ref(features::kFedCmMultipleIdentityProviders), kDefault},
{wf::EnableFedCmRevoke, raw_ref(features::kFedCmRevoke),
{wf::EnableFedCmDisconnect, raw_ref(features::kFedCmDisconnect),
kSetOnlyIfOverridden},
{wf::EnableFedCmSelectiveDisclosure,
raw_ref(features::kFedCmSelectiveDisclosure), kDefault},

@ -406,8 +406,10 @@ BASE_FEATURE(kFedCmMultipleIdentityProviders,
"FedCmMultipleIdentityProviders",
base::FEATURE_DISABLED_BY_DEFAULT);
// Enables the revoke method within the FedCM API.
BASE_FEATURE(kFedCmRevoke, "FedCmRevoke", base::FEATURE_ENABLED_BY_DEFAULT);
// Enables the disconnect method within the FedCM API.
BASE_FEATURE(kFedCmDisconnect,
"FedCmDisconnect",
base::FEATURE_ENABLED_BY_DEFAULT);
// Enables usage of the FedCM API with the Selective Disclosure API at the same
// time.

@ -101,7 +101,7 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmIdPRegistration);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmIdpSigninStatusEnabled);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmMetricsEndpoint);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmMultipleIdentityProviders);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmRevoke);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmDisconnect);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmSelectiveDisclosure);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFedCmWithoutWellKnownEnforcement);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kFencedFramesEnforceFocus);

@ -2719,10 +2719,10 @@ test("content_unittests") {
"../browser/web_package/signed_exchange_signature_header_field_unittest.cc",
"../browser/web_package/signed_exchange_signature_verifier_unittest.cc",
"../browser/web_package/signed_exchange_utils_unittest.cc",
"../browser/webid/federated_auth_disconnect_request_unittest.cc",
"../browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc",
"../browser/webid/federated_auth_request_impl_registry_unittest.cc",
"../browser/webid/federated_auth_request_impl_unittest.cc",
"../browser/webid/federated_auth_revoke_request_unittest.cc",
"../browser/webid/federated_auth_user_info_request_unittest.cc",
"../browser/webid/federated_provider_fetcher_unittest.cc",
"../browser/webid/identity_registry_unittest.cc",

@ -3547,7 +3547,7 @@ enum WebFeature {
kOBSOLETE_GestureScrollUpdate = 4221,
kOBSOLETE_GestureScrollEnd = 4222,
kArrayBufferTooBigForWebAPI = 4223,
kFedCmRevoke = 4224,
kFedCmDisconnect = 4224,
kOBSOLETE_FedCmLogout = 4225,
kOBSOLETE_FedCmLogoutRps = 4226,
kV8Navigator_DeprecatedReplaceInURN_Method = 4227,

@ -33,10 +33,10 @@ enum RequestUserInfoStatus {
kError,
};
// Represents the fetch result from a IdentityProvider.revoke() request.
// Represents the fetch result from a IdentityProvider.disconnect() request.
// It is used to determine whether a JavaScript exception should be
// thrown, and what the error message of such exception should say.
enum RevokeStatus {
enum DisconnectStatus {
kSuccess,
kErrorTooManyRequests,
kError
@ -135,11 +135,11 @@ struct IdentityProviderRequestOptions {
map<string, string> params;
};
// The information passed in an IdentityProvider.revoke() call.
struct IdentityCredentialRevokeOptions {
// The information passed in an IdentityProvider.disconnect() call.
struct IdentityCredentialDisconnectOptions {
IdentityProviderConfig config;
// The account hint for which the revocation ought to happen.
// The account hint for which the disconnect ought to happen.
string account_hint;
};
@ -228,7 +228,8 @@ interface FederatedAuthRequest {
// CredentialTypes and avoid the duplication eventually.
PreventSilentAccess() => ();
// Revokes the sharing permission for the specified |account_id| from
// Disconnects the sharing permission for the specified |account_id| from
// |provider| for the RP identified by |client_id|.
Revoke(IdentityCredentialRevokeOptions options) => (RevokeStatus status);
Disconnect(IdentityCredentialDisconnectOptions options) =>
(DisconnectStatus status);
};

@ -612,8 +612,8 @@ generated_dictionary_sources_in_modules = [
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_logout_r_ps_request.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_request_options.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_request_options.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_revoke_options.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_revoke_options.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_disconnect_options.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_disconnect_options.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_provider_config.cc",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_provider_config.h",
"$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_identity_provider_request_options.cc",

@ -28,9 +28,9 @@
#include "third_party/blink/renderer/bindings/modules/v8/v8_digital_credential_field_requirement.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_digital_credential_provider.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_digital_credential_selector.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_disconnect_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_request_options_context.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_request_options_mode.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_revoke_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_provider_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_provider_request_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_user_info.h"
@ -74,8 +74,8 @@ using blink::mojom::blink::DigitalCredentialProvider;
using blink::mojom::blink::DigitalCredentialProviderPtr;
using blink::mojom::blink::DigitalCredentialSelector;
using blink::mojom::blink::DigitalCredentialSelectorPtr;
using blink::mojom::blink::IdentityCredentialRevokeOptions;
using blink::mojom::blink::IdentityCredentialRevokeOptionsPtr;
using blink::mojom::blink::IdentityCredentialDisconnectOptions;
using blink::mojom::blink::IdentityCredentialDisconnectOptionsPtr;
using blink::mojom::blink::IdentityProvider;
using blink::mojom::blink::IdentityProviderConfig;
using blink::mojom::blink::IdentityProviderConfigPtr;
@ -1015,18 +1015,19 @@ TypeConverter<Vector<PRFValuesPtr>, blink::AuthenticationExtensionsPRFInputs>::
}
// static
IdentityCredentialRevokeOptionsPtr
TypeConverter<IdentityCredentialRevokeOptionsPtr,
blink::IdentityCredentialRevokeOptions>::
Convert(const blink::IdentityCredentialRevokeOptions& options) {
auto mojo_revoke_options = IdentityCredentialRevokeOptions::New();
IdentityCredentialDisconnectOptionsPtr
TypeConverter<IdentityCredentialDisconnectOptionsPtr,
blink::IdentityCredentialDisconnectOptions>::
Convert(const blink::IdentityCredentialDisconnectOptions& options) {
auto mojo_disconnect_options = IdentityCredentialDisconnectOptions::New();
mojo_revoke_options->config = IdentityProviderConfig::New();
mojo_revoke_options->config->config_url = blink::KURL(options.configURL());
mojo_revoke_options->config->client_id = options.clientId();
mojo_disconnect_options->config = IdentityProviderConfig::New();
mojo_disconnect_options->config->config_url =
blink::KURL(options.configURL());
mojo_disconnect_options->config->client_id = options.clientId();
mojo_revoke_options->account_hint = options.accountHint();
return mojo_revoke_options;
mojo_disconnect_options->account_hint = options.accountHint();
return mojo_disconnect_options;
}
} // namespace mojo

@ -24,7 +24,7 @@ class AuthenticationExtensionsPRFValues;
class AuthenticatorSelectionCriteria;
class CableAuthenticationData;
class Credential;
class IdentityCredentialRevokeOptions;
class IdentityCredentialDisconnectOptions;
class IdentityProviderConfig;
class IdentityProviderRequestOptions;
class IdentityUserInfo;
@ -269,10 +269,11 @@ struct TypeConverter<Vector<blink::mojom::blink::PRFValuesPtr>,
};
template <>
struct TypeConverter<blink::mojom::blink::IdentityCredentialRevokeOptionsPtr,
blink::IdentityCredentialRevokeOptions> {
static blink::mojom::blink::IdentityCredentialRevokeOptionsPtr Convert(
const blink::IdentityCredentialRevokeOptions&);
struct TypeConverter<
blink::mojom::blink::IdentityCredentialDisconnectOptionsPtr,
blink::IdentityCredentialDisconnectOptions> {
static blink::mojom::blink::IdentityCredentialDisconnectOptionsPtr Convert(
const blink::IdentityCredentialDisconnectOptions&);
};
} // namespace mojo

@ -17,8 +17,8 @@
namespace blink {
namespace {
using mojom::blink::DisconnectStatus;
using mojom::blink::RequestTokenStatus;
using mojom::blink::RevokeStatus;
constexpr char kIdentityCredentialType[] = "identity";
@ -31,10 +31,10 @@ enum class FedCmCspStatus {
kMaxValue = kFailedOrigin
};
void OnRevoke(ScriptPromiseResolver* resolver, RevokeStatus status) {
if (status != RevokeStatus::kSuccess) {
void OnDisconnect(ScriptPromiseResolver* resolver, DisconnectStatus status) {
if (status != DisconnectStatus::kSuccess) {
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNetworkError, "Error revoking account."));
DOMExceptionCode::kNetworkError, "Error disconnecting account."));
return;
}
resolver->Resolve();
@ -96,9 +96,9 @@ bool IdentityCredential::IsIdentityCredential() const {
}
// static
ScriptPromise IdentityCredential::revoke(
ScriptPromise IdentityCredential::disconnect(
ScriptState* script_state,
const blink::IdentityCredentialRevokeOptions* options,
const blink::IdentityCredentialDisconnectOptions* options,
ExceptionState& exception_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
@ -141,10 +141,11 @@ ScriptPromise IdentityCredential::revoke(
return promise;
}
mojom::blink::IdentityCredentialRevokeOptionsPtr revoke_options =
blink::mojom::blink::IdentityCredentialRevokeOptions::From(*options);
auth_request->Revoke(std::move(revoke_options),
WTF::BindOnce(&OnRevoke, WrapPersistent(resolver)));
mojom::blink::IdentityCredentialDisconnectOptionsPtr disconnect_options =
blink::mojom::blink::IdentityCredentialDisconnectOptions::From(*options);
auth_request->Disconnect(
std::move(disconnect_options),
WTF::BindOnce(&OnDisconnect, WrapPersistent(resolver)));
return promise;
}

@ -6,8 +6,8 @@
#define THIRD_PARTY_BLINK_RENDERER_MODULES_CREDENTIALMANAGEMENT_IDENTITY_CREDENTIAL_H_
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_disconnect_options.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_logout_r_ps_request.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_revoke_options.h"
#include "third_party/blink/renderer/modules/credentialmanagement/credential.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
@ -36,9 +36,10 @@ class MODULES_EXPORT IdentityCredential final : public Credential {
const String& token() const { return token_; }
const bool& isAutoSelected() const { return is_auto_selected_; }
static ScriptPromise revoke(ScriptState*,
const IdentityCredentialRevokeOptions* options,
ExceptionState&);
static ScriptPromise disconnect(
ScriptState*,
const IdentityCredentialDisconnectOptions* options,
ExceptionState&);
private:
const String token_;

@ -4,7 +4,7 @@
// https://fedidcg.github.io/FedCM/#browser-api-identity-credential-interface
dictionary IdentityCredentialRevokeOptions : IdentityProviderConfig {
dictionary IdentityCredentialDisconnectOptions : IdentityProviderConfig {
USVString accountHint;
};
@ -20,7 +20,7 @@ dictionary IdentityCredentialRevokeOptions : IdentityProviderConfig {
[RuntimeEnabled=FedCmAutoSelectedFlag]
readonly attribute boolean isAutoSelected;
// https://fedidcg.github.io/FedCM/#browser-api-revocation
[RuntimeEnabled=FedCmRevoke, CallWith=ScriptState, RaisesException, MeasureAs=FedCmRevoke] static Promise<void> revoke(optional IdentityCredentialRevokeOptions options = {});
// https://github.com/fedidcg/FedCM/pull/515
[RuntimeEnabled=FedCmDisconnect, CallWith=ScriptState, RaisesException, MeasureAs=FedCmDisconnect] static Promise<void> disconnect(optional IdentityCredentialDisconnectOptions options = {});
};

@ -1593,6 +1593,13 @@
status: "stable",
base_feature: "none",
},
{
name: "FedCmDisconnect",
depends_on: ["FedCm"],
base_feature: "none",
status: "test",
public: true,
},
{
name: "FedCmDomainHint",
depends_on: ["FedCm"],
@ -1628,13 +1635,6 @@
base_feature: "none",
public: true,
},
{
name: "FedCmRevoke",
depends_on: ["FedCm"],
base_feature: "none",
status: "test",
public: true,
},
{
name: "FedCmSelectiveDisclosure",
depends_on: ["FedCm"],

@ -97,8 +97,8 @@ crbug.com/1499775 external/wpt/credential-management/fedcm-nonce-is-optional.htt
crbug.com/1499775 external/wpt/credential-management/fedcm-not-observed-by-service-worker.https.html [ Timeout ]
crbug.com/1499775 external/wpt/credential-management/fedcm-pending-call-rejected.https.html [ Timeout ]
crbug.com/1499775 external/wpt/credential-management/fedcm-returning-account-auto-reauthn.https.html [ Timeout ]
crbug.com/1499775 external/wpt/credential-management/fedcm-revoke.sub.https.html [ Timeout ]
crbug.com/1499775 external/wpt/credential-management/fedcm-revoke-iframe.sub.https.html [ Timeout ]
crbug.com/1499775 external/wpt/credential-management/fedcm-disconnect.sub.https.html [ Timeout ]
crbug.com/1499775 external/wpt/credential-management/fedcm-disconnect-iframe.sub.https.html [ Timeout ]
crbug.com/1499775 external/wpt/credential-management/fedcm-store.https.html [ Timeout ]
crbug.com/1499775 external/wpt/credential-management/fedcm-token-returned-with-http-error.https.html [ Timeout ]
crbug.com/1499775 external/wpt/css/CSS2/floats/float-nowrap-2.html [ Failure ] # Reftest image failure

@ -7103,7 +7103,7 @@ crbug.com/1502336 [ Linux ] external/wpt/idle-detection/idle-detection-allowed-b
crbug.com/1502794 external/wpt/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html [ Failure Pass Timeout ]
# Gerdener 2023-11-17
crbug.com/1503108 [ Mac ] external/wpt/credential-management/fedcm-revoke.sub.https.html [ Timeout ]
crbug.com/1503108 [ Mac ] external/wpt/credential-management/fedcm-disconnect.sub.https.html [ Timeout ]
crbug.com/1503051 [ Mac ] virtual/keepalive-in-browser-migration/external/wpt/fetch/metadata/generated/audioworklet.https.sub.html [ Failure Pass ]
crbug.com/1503067 [ Linux ] virtual/plz-dedicated-worker-disabled/external/wpt/fetch/private-network-access/window-open.tentative.https.window.html [ Failure Pass ]
crbug.com/1503067 [ Linux ] virtual/pna-iframes-warning/external/wpt/fetch/private-network-access/window-open.tentative.https.window.html [ Failure Pass ]

@ -1,5 +1,5 @@
<!DOCTYPE html>
<title>Federated Credential Management API revoke() tests in iframes.</title>
<title>Federated Credential Management API disconnect() tests in iframes.</title>
<link rel="help" href="https://fedidcg.github.io/FedCM">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
@ -27,21 +27,21 @@ async function createIframeAndWaitForMessage(test, iframeUrl, allow = false) {
}
fedcm_test(async t => {
const message = await createIframeAndWaitForMessage(t, 'support/fedcm/revoke-iframe.html');
const message = await createIframeAndWaitForMessage(t, 'support/fedcm/disconnect-iframe.html');
assert_equals(message.result, "Pass");
}, 'Same-origin iframe does not need explicit identity-credentials-get');
fedcm_test(async t => {
const message = await createIframeAndWaitForMessage(t,
'https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/revoke-iframe.html?skip_get');
assert_equals(message.result, "Failed revoke");
'https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/disconnect-iframe.html?skip_get');
assert_equals(message.result, "Failed disconnect");
assert_equals(message.errorType, "NotAllowedError");
}, 'Cross-origin iframe fails revoke() without explicit identity-credentials-get');
}, 'Cross-origin iframe fails disconnect() without explicit identity-credentials-get');
fedcm_test(async t => {
const message = await createIframeAndWaitForMessage(t,
'https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/revoke-iframe.html',
'https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/disconnect-iframe.html',
/*allow=*/true);
assert_equals(message.result, "Pass");
}, 'Cross-origin iframe can revoke with explicit identity-credentials-get');
}, 'Cross-origin iframe can disconnect with explicit identity-credentials-get');
</script>

@ -1,5 +1,5 @@
<!DOCTYPE html>
<title>Federated Credential Management API revoke() tests.</title>
<title>Federated Credential Management API disconnect() tests.</title>
<link rel="help" href="https://fedidcg.github.io/FedCM">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
@ -12,18 +12,18 @@
import {fedcm_test,
mark_signed_in,
set_fedcm_cookie,
revoke_options,
disconnect_options,
fedcm_get_and_select_first_account,
request_options_with_mediation_required,
alt_manifest_origin,
alt_request_options_with_mediation_required,
alt_revoke_options,
alt_disconnect_options,
set_alt_fedcm_cookie} from './support/fedcm-helper.sub.js';
fedcm_test(async t => {
await mark_signed_in();
await set_fedcm_cookie();
// Get at least one connected account that can be revoked.
// Get at least one connected account that can be disconnected.
const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
// The IDP implementation will accept any account hint, so this is really testing that the user
// agent eventually stops sending the requests to the IDP.
@ -32,50 +32,53 @@ fedcm_test(async t => {
return new Promise(async resolve => {
while (true) {
try {
await IdentityCredential.revoke(revoke_options("1234"));
await IdentityCredential.disconnect(disconnect_options("1234"));
} catch(e) {
resolve();
break;
}
}
});
}, "Repeatedly calling revoke should eventually fail");
}, "Repeatedly calling disconnect should eventually fail");
fedcm_test(async t => {
const revoke = IdentityCredential.revoke(revoke_options("nonExistent"));
return promise_rejects_dom(t, 'NetworkError', revoke);
}, 'Test that revoke fails when there is no account to revoke');
const disconnect = IdentityCredential.disconnect(
disconnect_options("nonExistent"));
return promise_rejects_dom(t, 'NetworkError', disconnect);
}, 'Test that disconnect fails when there is no account to disconnect');
fedcm_test(async t => {
const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
return IdentityCredential.revoke(revoke_options("1234"));
}, 'Test that revoke succeeds when there is an account to revoke');
return IdentityCredential.disconnect(disconnect_options("1234"));
}, 'Test that disconnect succeeds when there is an account to disconnect');
fedcm_test(async t => {
const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
await IdentityCredential.revoke(revoke_options("1234"));
await IdentityCredential.disconnect(disconnect_options("1234"));
const revoke = IdentityCredential.revoke(revoke_options("1234"));
return promise_rejects_dom(t, 'NetworkError', revoke);
}, 'Test that revoking the same account twice results in failure.');
const disconnect = IdentityCredential.disconnect(disconnect_options("1234"));
return promise_rejects_dom(t, 'NetworkError', disconnect);
}, 'Test that disconnecting the same account twice results in failure.');
fedcm_test(async t => {
const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
// A connected account is guaranteed by the above, and IDP accepts any account hint, so this tests
// that the user agent allows the request to go through to the IDP.
return IdentityCredential.revoke(revoke_options("noMatch"));
}, 'Revoke passing an incorrect ID can still succeed');
return IdentityCredential.disconnect(disconnect_options("noMatch"));
}, 'Disconnect passing an incorrect ID can still succeed');
fedcm_test(async t => {
await set_alt_fedcm_cookie();
await mark_signed_in(alt_manifest_origin);
await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required());
await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
await fedcm_get_and_select_first_account(t,
request_options_with_mediation_required());
// Await the first revocation since they cannot happen in parallel. Both should succeed.
await IdentityCredential.revoke(revoke_options("1"));
return IdentityCredential.revoke(alt_revoke_options("2"));
}, 'Revocation is bound to each IDP');
// Await the first disconnect since they cannot happen in parallel. Both
// should succeed.
await IdentityCredential.disconnect(disconnect_options("1"));
return IdentityCredential.disconnect(alt_disconnect_options("2"));
}, 'Disconnect is bound to each IDP');
</script>

@ -213,7 +213,7 @@ export function fedcm_get_and_select_first_account(t, options) {
return credentialPromise;
}
export function revoke_options(accountHint, manifest_filename) {
export function disconnect_options(accountHint, manifest_filename) {
if (manifest_filename === undefined) {
manifest_filename = "manifest.py";
}
@ -226,7 +226,7 @@ credential-management/support/fedcm/${manifest_filename}`;
};
}
export function alt_revoke_options(accountHint, manifest_filename) {
export function alt_disconnect_options(accountHint, manifest_filename) {
if (manifest_filename === undefined) {
manifest_filename = "manifest.py";
}

@ -1,4 +1,4 @@
import { RequestTokenStatus, LogoutRpsStatus, RevokeStatus, FederatedAuthRequest, FederatedAuthRequestReceiver } from '/gen/third_party/blink/public/mojom/webid/federated_auth_request.mojom.m.js';
import { RequestTokenStatus, LogoutRpsStatus, DisconnectStatus, FederatedAuthRequest, FederatedAuthRequestReceiver } from '/gen/third_party/blink/public/mojom/webid/federated_auth_request.mojom.m.js';
function toMojoTokenStatus(status) {
return RequestTokenStatus["k" + status];
@ -17,7 +17,7 @@ export class MockFederatedAuthRequest {
this.selected_identity_provider_config_url_ = null;
this.status_ = RequestTokenStatus.kError;
this.logoutRpsStatus_ = LogoutRpsStatus.kError;
this.revokeStatus_ = RevokeStatus.kError;
this.disconnectStatus_ = DisconnectStatus.kError;
this.returnPending_ = false;
this.pendingPromiseResolve_ = null;
}
@ -53,13 +53,13 @@ export class MockFederatedAuthRequest {
this.logoutRpsStatus_ = validated;
}
// Causes the subsequent `FederatedCredential.revoke` to reject with this
// Causes the subsequent `FederatedCredential.disconnect` to reject with this
// status.
revokeReturn(status) {
let validated = RevokeStatus[status];
disconnectReturn(status) {
let validated = DisconnectStatus[status];
if (validated === undefined)
throw new Error("Invalid status: " + status);
this.revokeStatus_ = validated;
this.disconnectStatus_ = validated;
}
// Implements
@ -106,9 +106,9 @@ export class MockFederatedAuthRequest {
});
}
async revoke(provider, client_id, account_id) {
async disconnect(provider, client_id, account_id) {
return Promise.resolve({
status: this.revokeStatus_
status: this.disconnectStatus_
});
}
@ -135,7 +135,7 @@ export class MockFederatedAuthRequest {
this.selected_identity_provider_config_url_ = null;
this.status_ = RequestTokenStatus.kError;
this.logoutRpsStatus_ = LogoutRpsStatus.kError;
this.revokeStatus_ = RevokeStatus.kError;
this.disconnectStatus_ = DisconnectStatus.kError;
this.receiver_.$.close();
this.interceptor_.stop();

@ -2,34 +2,36 @@
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script type="module">
import {revoke_options,
import {disconnect_options,
request_options_with_mediation_required,
set_fedcm_cookie, manifest_origin} from './../fedcm-helper.sub.js';
set_fedcm_cookie, manifest_origin} from './../fedcm-helper.sub.js';
// Loading this iframe in the test will make a FedCM call on load, and
// trigger a postMessage upon completion.
//
// message {
// string result: "Pass" | "Failed get" | "Failed revoke"
// string result: "Pass" | "Failed get" | "Failed disconnect"
// string errorType: error.name
// }
async function attemptRevoke() {
async function attemptDisconnect() {
try {
await IdentityCredential.revoke(revoke_options("1234"));
await IdentityCredential.disconnect(disconnect_options("1234"));
window.top.postMessage({result: "Pass"}, "*");
} catch (error) {
window.top.postMessage({result: "Failed revoke", errorType: error.name}, "*");
window.top.postMessage({result: "Failed disconnect", errorType: error.name},
"*");
}
}
window.onload = async () => {
const params = new URLSearchParams(document.location.search);
if (params.has("skip_get")) {
attemptRevoke();
attemptDisconnect();
return;
}
// Use this variable to stop trying to select an account once the get() promise is resolved.
// Use this variable to stop trying to select an account once the get()
// promise is resolved.
let cancelHelper = false;
try {
const credentialPromise = navigator.credentials.get(request_options_with_mediation_required());
@ -46,8 +48,8 @@ window.onload = async () => {
helper();
const cred = await credentialPromise;
await set_fedcm_cookie(manifest_origin);
// Now that we have a get(), attempt to revoke permission.
attemptRevoke();
// Now that we have a get(), attempt to disconnect permission.
attemptDisconnect();
} catch (error) {
window.top.postMessage({result: "Failed get", errorType: error.name}, '*');
}

@ -13,7 +13,7 @@ def main(request, response):
"accounts_endpoint": "accounts.py",
"client_metadata_endpoint": "client_metadata.py",
"id_assertion_endpoint": "token.py",
"revocation_endpoint": "revoke.py",
"disconnect_endpoint": "disconnect.py",
"login_url": "login.html"
}
"""

@ -2,6 +2,6 @@
"accounts_endpoint": "accounts.py",
"client_metadata_endpoint": "client_metadata.py",
"id_assertion_endpoint": "/common/redirect.py?location=/credential-management/support/fedcm/token.py&status=308",
"revocation_endpoint": "revoke.py",
"disconnect_endpoint": "disconnect.py",
"login_url": "login.html"
}

@ -294,7 +294,7 @@ SET TIMEOUT: xhr/resources/init.htm
SET TIMEOUT: xhr/resources/xmlhttprequest-timeout.js
SET TIMEOUT: fenced-frame/resolve-to-config-promise.https.html
SET TIMEOUT: credential-management/support/fedcm-iframe.html
SET TIMEOUT: credential-management/support/fedcm/revoke-iframe.html
SET TIMEOUT: credential-management/support/fedcm/disconnect-iframe.html
# generate_tests implementation and sample usage
GENERATE_TESTS: resources/test/tests/functional/generate-callback.html

@ -5256,7 +5256,7 @@ interface IIRFilterNode : AudioNode
method constructor
method getFrequencyResponse
interface IdentityCredential : Credential
static method revoke
static method disconnect
attribute @@toStringTag
getter isAutoSelected
getter token

@ -24685,7 +24685,7 @@ Called by update_use_counter_feature_enum.py.-->
<int value="4221" label="OBSOLETE_GestureScrollUpdate"/>
<int value="4222" label="OBSOLETE_GestureScrollEnd"/>
<int value="4223" label="ArrayBufferTooBigForWebAPI"/>
<int value="4224" label="FedCmRevoke"/>
<int value="4224" label="FedCmDisconnect"/>
<int value="4225" label="OBSOLETE_FedCmLogout"/>
<int value="4226" label="OBSOLETE_FedCmLogoutRps"/>
<int value="4227" label="V8Navigator_DeprecatedReplaceInURN_Method"/>
@ -25408,6 +25408,29 @@ Called by update_permissions_policy_enum.py.-->
<int value="2" label="FailedOrigin"/>
</enum>
<enum name="FedCmDisconnectStatus">
<int value="0" label="Success"/>
<int value="1" label="TooManyRequests"/>
<int value="2" label="UnhandledRequest"/>
<int value="3" label="NoAccountToDisconnect"/>
<int value="4" label="DisconnectUrlIsCrossOrigin"/>
<int value="5" label="DisconnectFailedOnServer"/>
<int value="6" label="ConfigHttpNotFound"/>
<int value="7" label="ConfigNoResponse"/>
<int value="8" label="ConfigInvalidResponse"/>
<int value="9" label="DisabledInSettings"/>
<int value="10" label="DisabledInFlags"/>
<int value="11" label="WellKnownHttpNotFound"/>
<int value="12" label="WellKnownNoResponse"/>
<int value="13" label="WellKnownInvalidResponse"/>
<int value="14" label="WellKnownListEmpty"/>
<int value="15" label="ConfigNotInWellKnown"/>
<int value="16" label="WellKnownTooBig"/>
<int value="17" label="WellKnownInvalidContentType"/>
<int value="18" label="ConfigInvalidContentType"/>
<int value="19" label="IdpNotPotentiallyTrustworthy"/>
</enum>
<enum name="FedCmErrorDialogResult">
<int value="0" label="More details clicked"/>
<int value="1" label="Got it clicked without more details available"/>
@ -25564,29 +25587,6 @@ Called by update_permissions_policy_enum.py.-->
<int value="17" label="ThirdPartyCookiesBlocked"/>
</enum>
<enum name="FedCmRevokeStatus2">
<int value="0" label="Success"/>
<int value="1" label="TooManyRequests"/>
<int value="2" label="UnhandledRequest"/>
<int value="3" label="NoAccountToRevoke"/>
<int value="4" label="RevokeUrlIsCrossOrigin"/>
<int value="5" label="RevocationFailedOnServer"/>
<int value="6" label="ConfigHttpNotFound"/>
<int value="7" label="ConfigNoResponse"/>
<int value="8" label="ConfigInvalidResponse"/>
<int value="9" label="DisabledInSettings"/>
<int value="10" label="DisabledInFlags"/>
<int value="11" label="WellKnownHttpNotFound"/>
<int value="12" label="WellKnownNoResponse"/>
<int value="13" label="WellKnownInvalidResponse"/>
<int value="14" label="WellKnownListEmpty"/>
<int value="15" label="ConfigNotInWellKnown"/>
<int value="16" label="WellKnownTooBig"/>
<int value="17" label="WellKnownInvalidContentType"/>
<int value="18" label="ConfigInvalidContentType"/>
<int value="19" label="IdpNotPotentiallyTrustworthy"/>
</enum>
<enum name="FedCmRpContext">
<int value="0" label="Sign in"/>
<int value="1" label="Sign up"/>
@ -39707,6 +39707,7 @@ from previous Chrome versions.
<int value="1030608602" label="AutofillAssistantProactiveHelp:enabled"/>
<int value="1030922819" label="AlignWakeUps:enabled"/>
<int value="1031239808" label="ForceMajorVersion100InUserAgent:disabled"/>
<int value="1031278117" label="FedCmDisconnect:enabled"/>
<int value="1031281564"
label="DisablePeripheralDataAccessProtection:disabled"/>
<int value="1033148287" label="NTPShortcuts:disabled"/>
@ -41605,6 +41606,7 @@ from previous Chrome versions.
<int value="1918984253"
label="OmniboxUIExperimentBlueSearchLoopAndSearchQuery:disabled"/>
<int value="1919380242" label="ReduceAcceptLanguage:disabled"/>
<int value="1919786756" label="FedCmDisconnect:disabled"/>
<int value="1919917329" label="ImplicitRootScroller:disabled"/>
<int value="1920894670"
label="OmniboxPreserveDefaultMatchAgainstAsyncUpdate:enabled"/>

@ -1601,6 +1601,16 @@ chromium-metrics-reviews@google.com.
<summary>Records the result of CSP checks in the FedCM API.</summary>
</histogram>
<histogram name="Blink.FedCm.Status.Disconnect" enum="FedCmDisconnectStatus"
expires_after="M125">
<owner>npm@chromium.org</owner>
<owner>web-identity-eng@google.com</owner>
<summary>
Records the status of a disconnect call to the FedCM API. Recorded once per
IdentityCredential.disconnect() call, once the browser knows the result.
</summary>
</histogram>
<histogram name="Blink.FedCm.Status.IdpSigninMatch"
enum="FedCmIdpSigninMatchStatus" expires_after="2024-05-05">
<owner>cbiesinger@chromium.org</owner>
@ -1634,16 +1644,6 @@ chromium-metrics-reviews@google.com.
</summary>
</histogram>
<histogram name="Blink.FedCm.Status.Revoke2" enum="FedCmRevokeStatus2"
expires_after="M125">
<owner>npm@chromium.org</owner>
<owner>web-identity-eng@google.com</owner>
<summary>
Records the status of a revoke call to the FedCM API. Recorded once per
IdentityCredential.revoke() call, once the browser knows the result.
</summary>
</histogram>
<histogram name="Blink.FedCm.Status.SignInStateMatch"
enum="FedCmSignInStateMatchStatus" expires_after="M125">
<owner>tanzachary@chromium.org</owner>

@ -4463,6 +4463,13 @@ be describing additional metrics about the same event.
where the site of the iframe is compared with the site of the main frame.
</summary>
</metric>
<metric name="Status.Disconnect" enum="FedCmDisconnectStatus">
<summary>
Records the status of a disconnect call to the FedCM API. Recorded once
per IdentityCredential.disconnect() call, once the browser knows the
result.
</summary>
</metric>
<metric name="Status.MediationRequirement"
enum="CredentialManagerMediationRequirement">
<summary>
@ -4484,12 +4491,6 @@ be describing additional metrics about the same event.
Records the status of a revoke call to the FedCM API.
</summary>
</metric>
<metric name="Status.Revoke2" enum="FedCmRevokeStatus2">
<summary>
Records the status of a revoke call to the FedCM API. Recorded once per
IdentityCredential.revoke() call, once the browser knows the result.
</summary>
</metric>
<metric name="Status.SignInStateMatch" enum="FedCmSignInStateMatchStatus">
<summary>
Records whether user sign-in states between IDP and browser match after
@ -4605,6 +4606,13 @@ be describing additional metrics about the same event.
Records a 1 each time a mismatch dialog is shown.
</summary>
</metric>
<metric name="Status.Disconnect" enum="FedCmDisconnectStatus">
<summary>
Records the status of a disconnect call to the FedCM API. Recorded once
per IdentityCredential.disconnect() call, once the browser knows the
result.
</summary>
</metric>
<metric name="Status.MediationRequirement"
enum="CredentialManagerMediationRequirement">
<summary>
@ -4618,12 +4626,6 @@ be describing additional metrics about the same event.
Records the status of a request id token call to the FedCM API.
</summary>
</metric>
<metric name="Status.Revoke2" enum="FedCmRevokeStatus2">
<summary>
Records the status of a revoke call to the FedCM API. Recorded once per
IdentityCredential.revoke() call, once the browser knows the result.
</summary>
</metric>
<metric name="Status.SignInStateMatch" enum="FedCmSignInStateMatchStatus">
<summary>
Records whether user sign-in states between IDP and browser match after