Ash: Store a mapping of token handles against refresh token hash

This will be used in follow-up CLs to figure out if token handles are
being checked against the latest token / LST. We have received reports
from Gaia that ChromeOS checks tokens for older LSTs.

Bug: b/297349237
Change-Id: I7c7e98600a71bc6422e6deafbb727c983cb508b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4812204
Reviewed-by: Roman Sorokin <[email protected]>
Reviewed-by: Anastasiia N <[email protected]>
Commit-Queue: Kush Sinha <[email protected]>
Reviewed-by: David Roger <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1192070}
diff --git a/chrome/browser/ash/login/session/user_session_manager.cc b/chrome/browser/ash/login/session/user_session_manager.cc
index ac4b956..b89a9ac1 100644
--- a/chrome/browser/ash/login/session/user_session_manager.cc
+++ b/chrome/browser/ash/login/session/user_session_manager.cc
@@ -25,6 +25,7 @@
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
+#include "base/hash/sha1.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
@@ -565,6 +566,13 @@
   }
 }
 
+// Returns a Base16 encoded SHA1 digest of `data`.
+std::string Sha1Digest(const std::string& data) {
+  const base::SHA1Digest hash =
+      base::SHA1HashSpan(base::as_bytes(base::make_span(data)));
+  return base::HexEncode(hash);
+}
+
 }  // namespace
 
 UserSessionManagerDelegate::~UserSessionManagerDelegate() {}
@@ -2095,6 +2103,7 @@
             profile, token_handle_util_.get(), user_context_.GetAccountId());
         token_handle_fetcher_->FillForNewUser(
             user_context_.GetAccessToken(),
+            Sha1Digest(user_context_.GetRefreshToken()),
             base::BindOnce(&UserSessionManager::OnTokenHandleObtained,
                            GetUserSessionManagerAsWeakPtr()));
       } else {
diff --git a/chrome/browser/ash/login/signin/token_handle_fetcher.cc b/chrome/browser/ash/login/signin/token_handle_fetcher.cc
index 8c39458..611a563b 100644
--- a/chrome/browser/ash/login/signin/token_handle_fetcher.cc
+++ b/chrome/browser/ash/login/signin/token_handle_fetcher.cc
@@ -6,13 +6,24 @@
 
 #include <memory>
 
+#include "base/check.h"
 #include "base/functional/bind.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/values.h"
 #include "chrome/browser/ash/login/signin/token_handle_util.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part_ash.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/ui/webui/signin/ash/signin_helper.h"
+#include "chromeos/ash/components/account_manager/account_manager_factory.h"
+#include "components/account_id/account_id.h"
+#include "components/account_manager_core/chromeos/account_manager.h"
 #include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
 #include "components/signin/public/base/consent_level.h"
 #include "components/signin/public/identity_manager/access_token_info.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
@@ -27,6 +38,10 @@
 const int kMaxRetries = 3;
 const char kAccessTokenFetchId[] = "token_handle_fetcher";
 
+// A dictionary pref that stores the mapping from (access) token handles to a
+// hash of the OAuth refresh token from which the token handle was derived.
+constexpr char kTokenHandleMap[] = "ash.token_handle_map";
+
 class TokenHandleFetcherShutdownNotifierFactory
     : public BrowserContextKeyedServiceShutdownNotifierFactory {
  public:
@@ -51,6 +66,12 @@
   ~TokenHandleFetcherShutdownNotifierFactory() override {}
 };
 
+account_manager::AccountManager* GetAccountManager(Profile* profile) {
+  return g_browser_process->platform_part()
+      ->GetAccountManagerFactory()
+      ->GetAccountManager(profile->GetPath().value());
+}
+
 }  // namespace
 
 TokenHandleFetcher::TokenHandleFetcher(Profile* profile,
@@ -66,6 +87,10 @@
 void TokenHandleFetcher::BackfillToken(TokenFetchingCallback callback) {
   callback_ = std::move(callback);
 
+  if (account_id_.GetAccountType() != AccountType::GOOGLE) {
+    return;
+  }
+
   identity_manager_ = IdentityManagerFactory::GetForProfile(profile_);
   // This class doesn't care about browser sync consent.
   if (!identity_manager_->HasAccountWithRefreshToken(
@@ -110,19 +135,34 @@
     return;
   }
 
-  FillForAccessToken(token_info.token);
+  GetAccountManager(profile_)->GetTokenHash(
+      account_manager::AccountKey(account_id_.GetGaiaId(),
+                                  account_manager::AccountType::kGaia),
+      base::BindOnce(&TokenHandleFetcher::FillForAccessToken,
+                     weak_factory_.GetWeakPtr(),
+                     /*access_token=*/token_info.token));
+}
+
+// static
+void TokenHandleFetcher::RegisterPrefs(PrefRegistrySimple* registry) {
+  registry->RegisterDictionaryPref(/*path=*/kTokenHandleMap);
 }
 
 void TokenHandleFetcher::FillForNewUser(const std::string& access_token,
+                                        const std::string& refresh_token_hash,
                                         TokenFetchingCallback callback) {
   callback_ = std::move(callback);
-  FillForAccessToken(access_token);
+  FillForAccessToken(access_token, refresh_token_hash);
 }
 
-void TokenHandleFetcher::FillForAccessToken(const std::string& access_token) {
-  if (!gaia_client_.get())
+void TokenHandleFetcher::FillForAccessToken(
+    const std::string& access_token,
+    const std::string& refresh_token_hash) {
+  refresh_token_hash_ = refresh_token_hash;
+  if (!gaia_client_.get()) {
     gaia_client_ = std::make_unique<gaia::GaiaOAuthClient>(
         profile_->GetURLLoaderFactory());
+  }
   tokeninfo_response_start_time_ = base::TimeTicks::Now();
   gaia_client_->GetTokenInfo(access_token, kMaxRetries, this);
 }
@@ -143,6 +183,7 @@
     if (handle) {
       success = true;
       token_handle_util_->StoreTokenHandle(account_id_, *handle);
+      StoreTokenHandleMapping(*handle);
     }
   }
   const base::TimeDelta duration =
@@ -151,6 +192,14 @@
   std::move(callback_).Run(account_id_, success);
 }
 
+void TokenHandleFetcher::StoreTokenHandleMapping(
+    const std::string& token_handle) {
+  PrefService* prefs = profile_->GetPrefs();
+  ScopedDictPrefUpdate update(prefs, kTokenHandleMap);
+  CHECK(!refresh_token_hash_.empty());
+  update->Set(token_handle, refresh_token_hash_);
+}
+
 void TokenHandleFetcher::OnProfileDestroyed() {
   std::move(callback_).Run(account_id_, false);
 }
diff --git a/chrome/browser/ash/login/signin/token_handle_fetcher.h b/chrome/browser/ash/login/signin/token_handle_fetcher.h
index 02961f6..650ff2d 100644
--- a/chrome/browser/ash/login/signin/token_handle_fetcher.h
+++ b/chrome/browser/ash/login/signin/token_handle_fetcher.h
@@ -16,6 +16,7 @@
 #include "google_apis/gaia/gaia_oauth_client.h"
 
 class Profile;
+class PrefRegistrySimple;
 
 namespace signin {
 class IdentityManager;
@@ -42,8 +43,11 @@
   using TokenFetchingCallback =
       base::OnceCallback<void(const AccountId&, bool success)>;
 
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
   // Fetch token handle for a user who has just signed in via Gaia online auth.
   void FillForNewUser(const std::string& access_token,
+                      const std::string& refresh_token_hash,
                       TokenFetchingCallback callback);
 
   // Fetch token handle for an existing user.
@@ -61,7 +65,9 @@
   void OnNetworkError(int response_code) override;
   void OnGetTokenInfoResponse(const base::Value::Dict& token_info) override;
 
-  void FillForAccessToken(const std::string& access_token);
+  void FillForAccessToken(const std::string& access_token,
+                          const std::string& refresh_token_hash);
+  void StoreTokenHandleMapping(const std::string& token_handle);
 
   // This is called before profile is detroyed.
   void OnProfileDestroyed();
@@ -72,11 +78,14 @@
   raw_ptr<signin::IdentityManager, ExperimentalAsh> identity_manager_ = nullptr;
 
   base::TimeTicks tokeninfo_response_start_time_ = base::TimeTicks();
+  std::string refresh_token_hash_;
   TokenFetchingCallback callback_;
   std::unique_ptr<gaia::GaiaOAuthClient> gaia_client_;
   std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher>
       access_token_fetcher_;
   base::CallbackListSubscription profile_shutdown_subscription_;
+
+  base::WeakPtrFactory<TokenHandleFetcher> weak_factory_{this};
 };
 
 }  // namespace ash
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 1b5ab228..327282b 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -378,6 +378,7 @@
 #include "chrome/browser/ash/login/security_token_session_controller.h"
 #include "chrome/browser/ash/login/session/user_session_manager.h"
 #include "chrome/browser/ash/login/signin/signin_error_notifier.h"
+#include "chrome/browser/ash/login/signin/token_handle_fetcher.h"
 #include "chrome/browser/ash/login/startup_utils.h"
 #include "chrome/browser/ash/login/users/avatar/user_image_manager.h"
 #include "chrome/browser/ash/login/users/avatar/user_image_prefs.h"
@@ -1949,6 +1950,7 @@
 #endif
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   ash::RegisterUserProfilePrefs(registry, locale);
+  ash::TokenHandleFetcher::RegisterPrefs(registry);
 #endif
 }