[go: nahoru, domu]

Reland "Reland "[UNO] Change reconcilor behavior when user not signed in to chrome""

This is a reland of commit 07e3f75861d73c22a8e176638f3ffe36e4373be3

Original change's description:
> Reland "[UNO] Change reconcilor behavior when user not signed in to chrome"
>
> This is a reland of commit 3ba4805e4d5c68485cec63c5d5f835231d43089c
>
> Original change's description:
> > [UNO] Change reconcilor behavior when user not signed in to chrome
> >
> > This CL does the following:
> > - Disables cookie updates/OAuthMultiLogin when the user is not signed in
> >   to chrome.
> > - In the above mode, accounts in the cookie are the source of truth.
> >   Refresh tokens are updated to match the accounts in the cookie. If a
> >   refresh token doesn't have an account in the gaia cookie or the
> >   account is invalid (session expired) the refresh token is removed from
> >   chrome.
> >
> > Bug: b/320279580
> > Change-Id: I26dc6e62cfba34fd6304f43e8963952bb1fed9a6
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5212147
> > Reviewed-by: Alex Ilin <alexilin@chromium.org>
> > Reviewed-by: David Roger <droger@chromium.org>
> > Commit-Queue: Monica Basta <msalama@chromium.org>
> > Cr-Commit-Position: refs/heads/main@{#1251450}
>
> Bug: b/320279580
> Change-Id: Id670fef5a715ad214e3457b1c67ebbbe28259eec
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5258540
> Mega-CQ: Monica Basta <msalama@chromium.org>
> Reviewed-by: Alex Ilin <alexilin@chromium.org>
> Reviewed-by: David Roger <droger@chromium.org>
> Commit-Queue: Monica Basta <msalama@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1256749}

Bug: b/320279580
Change-Id: I5021f8b3f00b50ea8fb06bbb5d4552c81cd267ed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5272597
Commit-Queue: Monica Basta <msalama@chromium.org>
Reviewed-by: David Roger <droger@chromium.org>
Reviewed-by: Alex Ilin <alexilin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1258416}
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_browsertest.cc b/chrome/browser/policy/cloud/user_policy_signin_service_browsertest.cc
index 10bda6e..50632f0 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_browsertest.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_browsertest.cc
@@ -235,10 +235,11 @@
 
     embedded_test_server_.StartAcceptingConnections();
 
-    account_info_ =
-        signin::MakeAccountAvailable(identity_manager(), kTestEmail);
-    signin::SetRefreshTokenForAccount(
-        identity_manager(), account_info_.account_id, kTestRefreshToken);
+    account_info_ = MakeAccountAvailable(
+        identity_manager(), signin::AccountAvailabilityOptionsBuilder()
+                                .AsPrimary(signin::ConsentLevel::kSignin)
+                                .WithRefreshToken(kTestRefreshToken)
+                                .Build(kTestEmail));
     SetupFakeGaiaResponses();
   }
 
@@ -457,9 +458,7 @@
   CreateTurnSyncOnHelper();
   WaitForPolicyHanging();
 
-  // User is not signed in, policy is not applied.
-  EXPECT_EQ(std::nullopt,
-            signin::GetPrimaryAccountConsentLevel(identity_manager()));
+  // Policy hanging, policy is not applied.
   EXPECT_FALSE(profile()->GetPrefs()->GetBoolean(prefs::kShowHomeButton));
   EXPECT_TRUE(signin_client()->IsClearPrimaryAccountAllowed(
       /*has_sync_account=*/false));
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper_browsertest.cc b/chrome/browser/ui/webui/signin/turn_sync_on_helper_browsertest.cc
index d920421..e9964d6 100644
--- a/chrome/browser/ui/webui/signin/turn_sync_on_helper_browsertest.cc
+++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper_browsertest.cc
@@ -379,8 +379,15 @@
 
   // For the scenario in https://crbug.com/1404961, the reconcilor has to be
   // triggered by the account removal.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
   ASSERT_EQ(reconcilor->GetState(),
             signin_metrics::AccountReconcilorState::kRunning);
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+  // On Dice platforms with `switches::kUnoDesktop` enabled and empty primary
+  // account, updating cookies is disabled. Therefore running the reconcilor
+  // doesn't require any network requests and might have been completed by now.
+  // The reconcilor will not remove the account from cookies but revoking
+  // refresh tokens should be sufficient to invalidate cookies.
 }
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
diff --git a/components/signin/core/browser/BUILD.gn b/components/signin/core/browser/BUILD.gn
index 8023bd5a..35913e8 100644
--- a/components/signin/core/browser/BUILD.gn
+++ b/components/signin/core/browser/BUILD.gn
@@ -165,6 +165,10 @@
       "mirror_landing_account_reconcilor_delegate_unittest.cc",
     ]
   }
+
+  if (enable_dice_support) {
+    sources += [ "dice_account_reconcilor_delegate_unittest.cc" ]
+  }
 }
 
 if (is_android) {
diff --git a/components/signin/core/browser/account_reconcilor.cc b/components/signin/core/browser/account_reconcilor.cc
index a417222..ada6c17 100644
--- a/components/signin/core/browser/account_reconcilor.cc
+++ b/components/signin/core/browser/account_reconcilor.cc
@@ -596,7 +596,15 @@
   // completely remove them from Chrome.
   // Revoking the token for the primary account is not supported (it should be
   // signed out or put to auth error state instead).
-  delegate_->RevokeSecondaryTokensBeforeReconcileIfNeeded();
+  delegate_->RevokeSecondaryTokensForReconcileIfNeeded(verified_gaia_accounts);
+  if (!delegate_->IsUpdateCookieAllowed()) {
+    // TODO(b/320279580): Record reconcilor operation if tokens were revoked to
+    // match cookies.
+    error_during_last_reconcile_ = GoogleServiceAuthError::AuthErrorNone();
+    CalculateIfMultiloginReconcileIsDone();
+    ScheduleStartReconcileIfChromeAccountsChanged();
+    return;
+  }
 
   std::vector<CoreAccountId> chrome_accounts =
       LoadValidAccountsFromTokenService();
diff --git a/components/signin/core/browser/account_reconcilor_delegate.cc b/components/signin/core/browser/account_reconcilor_delegate.cc
index 4b58001..b046242 100644
--- a/components/signin/core/browser/account_reconcilor_delegate.cc
+++ b/components/signin/core/browser/account_reconcilor_delegate.cc
@@ -22,6 +22,10 @@
   return false;
 }
 
+bool AccountReconcilorDelegate::IsUpdateCookieAllowed() const {
+  return true;
+}
+
 gaia::GaiaSource AccountReconcilorDelegate::GetGaiaApiSource() const {
   NOTREACHED() << "Reconcile is not enabled, no Gaia API calls should be made.";
   return gaia::GaiaSource::kChrome;
@@ -175,8 +179,8 @@
   return std::vector<CoreAccountId>();
 }
 
-void AccountReconcilorDelegate::RevokeSecondaryTokensBeforeReconcileIfNeeded() {
-}
+void AccountReconcilorDelegate::RevokeSecondaryTokensForReconcileIfNeeded(
+    const std::vector<gaia::ListedAccount>& gaia_accounts) {}
 
 void AccountReconcilorDelegate::OnAccountsCookieDeletedByUserAction(
     bool synced_data_deletion_in_progress) {}
diff --git a/components/signin/core/browser/account_reconcilor_delegate.h b/components/signin/core/browser/account_reconcilor_delegate.h
index 777778a..f54cca5 100644
--- a/components/signin/core/browser/account_reconcilor_delegate.h
+++ b/components/signin/core/browser/account_reconcilor_delegate.h
@@ -29,6 +29,11 @@
   // false.
   virtual bool IsReconcileEnabled() const;
 
+  // Returns true if calling OAuthMultiLogin to update cookies to match
+  // refresh tokens is allowed. Used in Dice to disable updating cookies when
+  // the user is not signed in to chrome.
+  virtual bool IsUpdateCookieAllowed() const;
+
   // Returns the value to set in the "source" parameter for Gaia API calls.
   virtual gaia::GaiaSource GetGaiaApiSource() const;
 
@@ -55,8 +60,14 @@
       const std::vector<gaia::ListedAccount>& gaia_accounts,
       bool first_execution);
 
-  // Revokes secondary accounts if needed.
-  virtual void RevokeSecondaryTokensBeforeReconcileIfNeeded();
+  // On Dice platforms:
+  // - Revokes tokens in error state except the primary account with consent
+  // level `GetConsentLevelForPrimaryAccount()`.
+  // - If `IsUpdateCookieAllowed()` returns false, it also revokes tokens not
+  // present in the gaia cookies to maintain account consistency.
+  // On other platforms, this is no-op.
+  virtual void RevokeSecondaryTokensForReconcileIfNeeded(
+      const std::vector<gaia::ListedAccount>& gaia_accounts);
 
   // Called when cookies are deleted by user action.
   // This might be a no-op or signout the profile or lead to a sync paused state
diff --git a/components/signin/core/browser/account_reconcilor_unittest.cc b/components/signin/core/browser/account_reconcilor_unittest.cc
index d26877c..961caf1 100644
--- a/components/signin/core/browser/account_reconcilor_unittest.cc
+++ b/components/signin/core/browser/account_reconcilor_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "components/signin/core/browser/account_reconcilor.h"
+
 #include <cstring>
 #include <map>
 #include <memory>
@@ -10,6 +12,7 @@
 #include <vector>
 
 #include "base/containers/contains.h"
+#include "base/feature_list.h"
 #include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
 #include "base/scoped_observation.h"
@@ -25,7 +28,6 @@
 #include "components/content_settings/core/browser/content_settings_observer.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "components/prefs/pref_service.h"
-#include "components/signin/core/browser/account_reconcilor.h"
 #include "components/signin/core/browser/mirror_account_reconcilor_delegate.h"
 #include "components/signin/public/base/account_consistency_method.h"
 #include "components/signin/public/base/consent_level.h"
@@ -1571,6 +1573,40 @@
           account_info.account_id));
 }
 
+const std::vector<AccountReconcilorTestTableParam>
+    kDiceParamsUnoPreChromeSignIn = {
+        // clang-format off
+        // See `kDiceParams` above for detailed params format.
+        {  "",     "A",    IsFirstReconcile::kBoth,  "",  "",   "A"    },
+        {  "AB",   "",     IsFirstReconcile::kBoth,  "",  "" ,  ""     },
+        {  "AB",   "A",    IsFirstReconcile::kBoth,  "",  "A",  "A"    },
+        {  "A",    "B",    IsFirstReconcile::kBoth,  "",  "" ,  "B"    },
+        {  "xA",   "A",    IsFirstReconcile::kBoth,  "",  "",   "A"    },
+        {  "xAB",  "A",    IsFirstReconcile::kBoth,  "",  "",   "A"    },
+
+        // Account marked as invalid in cookies.
+        {  "A",    "xA",   IsFirstReconcile::kBoth,  "",  "",   "xA"   },
+        {  "AB",   "AxB",  IsFirstReconcile::kBoth,  "",  "A",  "AxB"  },
+        {  "xA",   "xA",   IsFirstReconcile::kBoth,  "",  "",   "xA"   },
+        // clang-format on
+};
+class AccountReconcilorTestDiceWithUnoPreChromeSignIn
+    : public AccountReconcilorTestTable {
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_{switches::kUnoDesktop};
+};
+
+// Checks one row of the `kDiceParamsUnoPreChromeSignIn` table above.
+TEST_P(AccountReconcilorTestDiceWithUnoPreChromeSignIn, TableRowTest) {
+  SetAccountConsistency(signin::AccountConsistencyMethod::kDice);
+  RunRowTest(GetParam());
+}
+
+INSTANTIATE_TEST_SUITE_P(,
+                         AccountReconcilorTestDiceWithUnoPreChromeSignIn,
+                         ::testing::ValuesIn(GenerateTestCasesFromParams(
+                             kDiceParamsUnoPreChromeSignIn)));
+
 #endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 
 // clang-format off
diff --git a/components/signin/core/browser/dice_account_reconcilor_delegate.cc b/components/signin/core/browser/dice_account_reconcilor_delegate.cc
index 586a50f..6e05332b 100644
--- a/components/signin/core/browser/dice_account_reconcilor_delegate.cc
+++ b/components/signin/core/browser/dice_account_reconcilor_delegate.cc
@@ -17,6 +17,7 @@
 #include "components/signin/public/base/signin_metrics.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/base/signin_switches.h"
+#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
 #include "components/signin/public/identity_manager/accounts_mutator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/primary_account_mutator.h"
@@ -35,7 +36,7 @@
 }  // namespace
 #endif
 
-// Revokes tokens for all accounts in chrome_accounts but the primary account.
+// Revokes tokens for all accounts in chrome accounts but the primary account.
 void RevokeAllSecondaryTokens(
     IdentityManager* identity_manager,
     ConsentLevel consent_level,
@@ -97,6 +98,40 @@
   return true;
 }
 
+bool DiceAccountReconcilorDelegate::IsUpdateCookieAllowed() const {
+  CHECK(IsReconcileEnabled());
+  if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled(
+          switches::ExplicitBrowserSigninPhase::kExperimental)) {
+    // If the user is not signed in to chrome, cookie updates (OAuthMultiLogin)
+    // is not allowed. In this mode, cookies are the source of truth.
+    return identity_manager_->HasPrimaryAccount(
+        GetConsentLevelForPrimaryAccount());
+  }
+  return true;
+}
+
+void DiceAccountReconcilorDelegate::MatchTokensWithAccountsInCookie(
+    const std::vector<gaia::ListedAccount>& gaia_accounts) {
+  CHECK(!IsUpdateCookieAllowed());
+  const signin_metrics::SourceForRefreshTokenOperation source =
+      signin_metrics::SourceForRefreshTokenOperation::
+          kAccountReconcilor_RevokeTokensNotInCookies;
+  auto* accounts_mutator = identity_manager_->GetAccountsMutator();
+  for (const CoreAccountInfo& account_info :
+       identity_manager_->GetAccountsWithRefreshTokens()) {
+    auto it = base::ranges::find(gaia_accounts, account_info.account_id,
+                                 &gaia::ListedAccount::id);
+    if (it == gaia_accounts.end() || !it->valid) {
+      // Account not in the cookie or the account is not valid (session
+      // expired) and requires the user to reauth.
+      accounts_mutator->RemoveAccount(account_info.account_id, source);
+    }
+  }
+  // TODO(b/320279580): Record a histogram with the number of valid signed in
+  // accounts in the cookie but doesn't have a refresh token (aka Chrome
+  // account).
+}
+
 DiceAccountReconcilorDelegate::InconsistencyReason
 DiceAccountReconcilorDelegate::GetInconsistencyReason(
     const CoreAccountId& primary_account,
@@ -331,14 +366,18 @@
              : gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER;
 }
 
-void DiceAccountReconcilorDelegate::
-    RevokeSecondaryTokensBeforeReconcileIfNeeded() {
+void DiceAccountReconcilorDelegate::RevokeSecondaryTokensForReconcileIfNeeded(
+    const std::vector<gaia::ListedAccount>& gaia_accounts) {
   RevokeAllSecondaryTokens(identity_manager_,
                            GetConsentLevelForPrimaryAccount(),
                            signin_metrics::SourceForRefreshTokenOperation::
                                kAccountReconcilor_GaiaCookiesUpdated,
                            signin_metrics::ProfileSignout::kGaiaCookieUpdated,
                            /*revoke_only_if_in_error=*/true);
+  if (!IsUpdateCookieAllowed()) {
+    // Cookie update not enabled, match tokens with Gaia accounts.
+    MatchTokensWithAccountsInCookie(gaia_accounts);
+  }
 }
 
 void DiceAccountReconcilorDelegate::OnAccountsCookieDeletedByUserAction(
diff --git a/components/signin/core/browser/dice_account_reconcilor_delegate.h b/components/signin/core/browser/dice_account_reconcilor_delegate.h
index 33ac57a..fea1581 100644
--- a/components/signin/core/browser/dice_account_reconcilor_delegate.h
+++ b/components/signin/core/browser/dice_account_reconcilor_delegate.h
@@ -28,8 +28,10 @@
 
   // AccountReconcilorDelegate:
   bool IsReconcileEnabled() const override;
+  bool IsUpdateCookieAllowed() const override;
   gaia::GaiaSource GetGaiaApiSource() const override;
-  void RevokeSecondaryTokensBeforeReconcileIfNeeded() override;
+  void RevokeSecondaryTokensForReconcileIfNeeded(
+      const std::vector<gaia::ListedAccount>& gaia_accounts) override;
   void OnReconcileFinished(const CoreAccountId& first_account) override;
   void OnAccountsCookieDeletedByUserAction(
       bool synced_data_deletion_in_progress) override;
@@ -57,6 +59,12 @@
     kMaxValue = kSyncCookieNotFirst
   };
 
+  // Used when update cookies to match refresh tokens is not allowed, see
+  // `IsUpdateCookieAllowed()`. In this mode, refresh tokens are updated to
+  // match accounts in the Gaia cookies to maintain consistency.
+  void MatchTokensWithAccountsInCookie(
+      const std::vector<gaia::ListedAccount>& gaia_accounts);
+
   // Computes inconsistency reason between tokens and gaia cookies.
   InconsistencyReason GetInconsistencyReason(
       const CoreAccountId& primary_account,
diff --git a/components/signin/core/browser/dice_account_reconcilor_delegate_unittest.cc b/components/signin/core/browser/dice_account_reconcilor_delegate_unittest.cc
new file mode 100644
index 0000000..8039cd2
--- /dev/null
+++ b/components/signin/core/browser/dice_account_reconcilor_delegate_unittest.cc
@@ -0,0 +1,165 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/signin/core/browser/dice_account_reconcilor_delegate.h"
+
+#include <optional>
+
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "components/signin/public/base/consent_level.h"
+#include "components/signin/public/base/signin_client.h"
+#include "components/signin/public/base/signin_switches.h"
+#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/identity_test_environment.h"
+#include "components/signin/public/identity_manager/identity_test_utils.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace signin {
+namespace {
+constexpr char kPrimaryAccountEmail[] = "primary@gmail.com ";
+
+gaia::ListedAccount GetListedAccountFromAccountInfo(
+    const AccountInfo& account_info,
+    bool valid = true) {
+  gaia::ListedAccount gaia_account;
+  gaia_account.id = account_info.account_id;
+  gaia_account.email = account_info.email;
+  gaia_account.gaia_id = account_info.gaia;
+  gaia_account.valid = valid;
+  return gaia_account;
+}
+
+class DiceAccountReconcilorDelegateTest : public testing::Test {
+ public:
+  DiceAccountReconcilorDelegateTest()
+      : delegate_(identity_manager(),
+                  identity_test_environment_.signin_client()) {}
+
+  DiceAccountReconcilorDelegate& delegate() { return delegate_; }
+
+  IdentityTestEnvironment& identity_test_environment() {
+    return identity_test_environment_;
+  }
+
+  IdentityManager* identity_manager() {
+    return identity_test_environment().identity_manager();
+  }
+
+ private:
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  base::test::ScopedFeatureList scoped_feature_list_{switches::kUnoDesktop};
+  IdentityTestEnvironment identity_test_environment_;
+  DiceAccountReconcilorDelegate delegate_;
+};
+
+TEST_F(DiceAccountReconcilorDelegateTest, GetConsentLevelForPrimaryAccount) {
+  // Uno enabled.
+  EXPECT_EQ(delegate().GetConsentLevelForPrimaryAccount(),
+            ConsentLevel::kSignin);
+}
+
+TEST_F(DiceAccountReconcilorDelegateTest,
+       IsUpdateCookieAllowedPreChromeSignIn) {
+  EXPECT_FALSE(delegate().IsUpdateCookieAllowed());
+}
+
+TEST_F(DiceAccountReconcilorDelegateTest,
+       IsUpdateCookieAllowedPostChromeSignIn) {
+  identity_test_environment().MakePrimaryAccountAvailable(
+      kPrimaryAccountEmail, ConsentLevel::kSignin);
+  ASSERT_TRUE(identity_manager()->HasPrimaryAccount(ConsentLevel::kSignin));
+  EXPECT_TRUE(delegate().IsUpdateCookieAllowed());
+}
+
+TEST_F(DiceAccountReconcilorDelegateTest,
+       RevokeSecondaryTokensForReconcileIfNeededPreChromeSignIn) {
+  AccountInfo valid_account =
+      identity_test_environment().MakeAccountAvailable("account@gmail.com");
+
+  // Accounts with invalid refresh token should be revoked.
+  AccountInfo account_with_invalid_refresh_token =
+      identity_test_environment().MakeAccountAvailable(
+          AccountAvailabilityOptionsBuilder()
+              .WithRefreshToken(GaiaConstants::kInvalidRefreshToken)
+              .Build("invalid_refresh_token@gmail.com"));
+
+  // Accounts not in the gaia cookie should be revoked.
+  AccountInfo no_cookie_account =
+      identity_test_environment().MakeAccountAvailable("no_cookie@gmail.com");
+  AccountInfo cookie_no_refresh_token_account =
+      identity_test_environment().MakeAccountAvailable(
+          AccountAvailabilityOptionsBuilder().WithoutRefreshToken().Build(
+              "no_refresh_token_with_cookie@gmail.com"));
+
+  // Setup account with refresh token and invalid cookie account.
+  // The refresh token should be revoked
+  AccountInfo invalid_cookie_account =
+      identity_test_environment().MakeAccountAvailable(
+          "invalid_cookie@gmail.com");
+
+  // Verify the test setup.
+  EXPECT_THAT(identity_manager()->GetAccountsWithRefreshTokens(),
+              ::testing::UnorderedElementsAre(
+                  valid_account, account_with_invalid_refresh_token,
+                  no_cookie_account, invalid_cookie_account));
+  ASSERT_FALSE(delegate().IsUpdateCookieAllowed());
+
+  const std::vector<gaia::ListedAccount> gaia_signed_in_accounts{
+      GetListedAccountFromAccountInfo(valid_account),
+      GetListedAccountFromAccountInfo(cookie_no_refresh_token_account),
+      GetListedAccountFromAccountInfo(invalid_cookie_account, /*valid=*/false)};
+
+  delegate().RevokeSecondaryTokensForReconcileIfNeeded(gaia_signed_in_accounts);
+  std::vector<CoreAccountInfo> chrome_accounts =
+      identity_manager()->GetAccountsWithRefreshTokens();
+  ASSERT_EQ(chrome_accounts.size(), 1u);
+  EXPECT_THAT(chrome_accounts[0], ::testing::Eq(valid_account));
+}
+
+TEST(DiceAccountReconcilorDelegateTestUnoDisabled,
+     RevokeSecondaryTokensForReconcile) {
+  base::test::SingleThreadTaskEnvironment task_environment;
+  base::test::ScopedFeatureList scoped_feature_list;
+  IdentityTestEnvironment identity_test_environment;
+  IdentityManager* identity_manager =
+      identity_test_environment.identity_manager();
+  DiceAccountReconcilorDelegate delegate(
+      identity_manager, identity_test_environment.signin_client());
+
+  EXPECT_TRUE(delegate.IsUpdateCookieAllowed());
+
+  scoped_feature_list.InitAndDisableFeature(switches::kUnoDesktop);
+
+  AccountInfo valid_account =
+      identity_test_environment.MakeAccountAvailable("account@gmail.com");
+
+  // Accounts with invalid refresh token should be revoked.
+  AccountInfo account_with_invalid_refresh_token =
+      identity_test_environment.MakeAccountAvailable(
+          AccountAvailabilityOptionsBuilder()
+              .WithRefreshToken(GaiaConstants::kInvalidRefreshToken)
+              .Build("invalid_refresh_token@gmail.com"));
+
+  // Uno disabled, only accounts with invalid refresh tokens should be revoked.
+  AccountInfo no_cookie_account =
+      identity_test_environment.MakeAccountAvailable("no_cookie@gmail.com");
+  EXPECT_EQ(identity_manager->GetAccountsWithRefreshTokens().size(), 3u);
+
+  const std::vector<gaia::ListedAccount> gaia_signed_in_accounts{
+      GetListedAccountFromAccountInfo(valid_account),
+      GetListedAccountFromAccountInfo(account_with_invalid_refresh_token)};
+  delegate.RevokeSecondaryTokensForReconcileIfNeeded(gaia_signed_in_accounts);
+  EXPECT_THAT(
+      identity_manager->GetAccountsWithRefreshTokens(),
+      ::testing::UnorderedElementsAre(valid_account, no_cookie_account));
+}
+
+}  // namespace
+}  // namespace signin
diff --git a/components/signin/internal/identity_manager/profile_oauth2_token_service.cc b/components/signin/internal/identity_manager/profile_oauth2_token_service.cc
index dc5d3e9..aaed39d 100644
--- a/components/signin/internal/identity_manager/profile_oauth2_token_service.cc
+++ b/components/signin/internal/identity_manager/profile_oauth2_token_service.cc
@@ -12,6 +12,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/signin/internal/identity_manager/profile_oauth2_token_service_delegate.h"
 #include "components/signin/public/base/device_id_helper.h"
+#include "components/signin/public/base/signin_metrics.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "google_apis/gaia/gaia_constants.h"
 #include "google_apis/gaia/google_service_auth_error.h"
@@ -63,6 +64,9 @@
       return "LogoutTabHelper::PrimaryPageChanged";
     case SourceForRefreshTokenOperation::kForceSigninReauthWithDifferentAccount:
       return "ForceSigninReauthWithDifferentAccount";
+    case SourceForRefreshTokenOperation::
+        kAccountReconcilor_RevokeTokensNotInCookies:
+      return "AccountReconcilor::RevokeTokensNotInCookies";
   }
 }
 }  // namespace
diff --git a/components/signin/public/base/signin_metrics.h b/components/signin/public/base/signin_metrics.h
index 479cb96..a3379d9 100644
--- a/components/signin/public/base/signin_metrics.h
+++ b/components/signin/public/base/signin_metrics.h
@@ -438,8 +438,9 @@
   // kAccountReconcilor_RevokeTokensNotInCookies = 18,
   kLogoutTabHelper_PrimaryPageChanged = 19,
   kForceSigninReauthWithDifferentAccount = 20,
+  kAccountReconcilor_RevokeTokensNotInCookies = 21,
 
-  kMaxValue = kForceSigninReauthWithDifferentAccount,
+  kMaxValue = kAccountReconcilor_RevokeTokensNotInCookies,
 };
 
 // Different types of reporting. This is used as a histogram suffix.
diff --git a/tools/metrics/histograms/metadata/signin/enums.xml b/tools/metrics/histograms/metadata/signin/enums.xml
index 17c633e..ede12f47 100644
--- a/tools/metrics/histograms/metadata/signin/enums.xml
+++ b/tools/metrics/histograms/metadata/signin/enums.xml
@@ -766,6 +766,7 @@
   </int>
   <int value="19" label="LogoutTabHelper::DidFinishNavigation"/>
   <int value="20" label="ForceSigninReauthWithDifferentAccount"/>
+  <int value="21" label="AccountReconcilor::RevokeTokensNotInCookies"/>
 </enum>
 
 <enum name="SyncErrorPromptUIAction">