0

Add binary format to /ListAccounts responses.

This is a follow-up cl for crbug.com/357948173. We would like to add
handling of binary response to /ListAccounts. The implementation is
based on prototype crrev.com/5783859 and implements the new
functionality behind a feature flag.

Bug: 361268659
TEST=google_apis_unittests

Change-Id: I1e35077c1d51f5400eee4883f2231ac5ea699fc4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6054254
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Commit-Queue: Maciek Slusarczyk <mslus@chromium.org>
Reviewed-by: Alex Ilin <alexilin@chromium.org>
Reviewed-by: David Roger <droger@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1401364}
This commit is contained in:
Maciek Slusarczyk
2025-01-02 03:46:30 -08:00
committed by Chromium LUCI CQ
parent 9495326013
commit d792e6e969
13 changed files with 474 additions and 12 deletions

@ -37,6 +37,7 @@
#include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h"
#include "google_apis/credentials_mode.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/gaia_features.h"
#include "google_apis/gaia/gaia_id.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/google_service_auth_error.h"
@ -440,17 +441,27 @@ GaiaCookieManagerService::GaiaCookieManagerService(
std::string gaia_cookie_last_list_accounts_data =
signin_client_->GetPrefs()->GetString(
prefs::kGaiaCookieLastListAccountsData);
std::string gaia_cookie_last_list_accounts_binary_data =
signin_client_->GetPrefs()->GetString(
prefs::kGaiaCookieLastListAccountsBinaryData);
// Parse ListAccounts data from prefs. In case both jspb and protobuf encoded
// data are present prefer the jspb one.
bool parse_success = false;
if (!gaia_cookie_last_list_accounts_data.empty()) {
if (!gaia::ParseListAccountsData(gaia_cookie_last_list_accounts_data,
&accounts_)) {
DLOG(WARNING) << "GaiaCookieManagerService::ListAccounts: Failed to "
"parse list accounts data from pref.";
accounts_.clear();
return;
}
InitializeListedAccountsIds();
parse_success = gaia::ParseListAccountsData(
gaia_cookie_last_list_accounts_data, &accounts_);
} else if (!gaia_cookie_last_list_accounts_binary_data.empty()) {
parse_success = gaia::ParseBinaryListAccountsData(
gaia_cookie_last_list_accounts_binary_data, &accounts_);
}
if (!parse_success) {
DLOG(WARNING) << "GaiaCookieManagerService::ListAccounts: Failed to "
"parse list accounts data from pref.";
accounts_.clear();
return;
}
InitializeListedAccountsIds();
}
GaiaCookieManagerService::~GaiaCookieManagerService() {
@ -463,6 +474,8 @@ GaiaCookieManagerService::~GaiaCookieManagerService() {
void GaiaCookieManagerService::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterStringPref(prefs::kGaiaCookieLastListAccountsData,
std::string());
registry->RegisterStringPref(prefs::kGaiaCookieLastListAccountsBinaryData,
std::string());
}
void GaiaCookieManagerService::InitCookieListener() {
@ -691,10 +704,16 @@ void GaiaCookieManagerService::OnListAccountsSuccess(const std::string& data) {
GaiaCookieRequestType::LIST_ACCOUNTS);
fetcher_backoff_.InformOfRequest(true);
if (!gaia::ParseListAccountsData(data, &accounts_)) {
bool parse_success = base::FeatureList::IsEnabled(
gaia::features::kListAccountsUsesBinaryFormat)
? gaia::ParseBinaryListAccountsData(data, &accounts_)
: gaia::ParseListAccountsData(data, &accounts_);
if (!parse_success) {
accounts_.clear();
signin_client_->GetPrefs()->ClearPref(
prefs::kGaiaCookieLastListAccountsData);
signin_client_->GetPrefs()->ClearPref(
prefs::kGaiaCookieLastListAccountsBinaryData);
GoogleServiceAuthError error =
GoogleServiceAuthError::FromUnexpectedServiceResponse(
"Error parsing ListAccounts response");
@ -702,8 +721,18 @@ void GaiaCookieManagerService::OnListAccountsSuccess(const std::string& data) {
return;
}
signin_client_->GetPrefs()->SetString(prefs::kGaiaCookieLastListAccountsData,
data);
if (base::FeatureList::IsEnabled(
gaia::features::kListAccountsUsesBinaryFormat)) {
signin_client_->GetPrefs()->SetString(
prefs::kGaiaCookieLastListAccountsBinaryData, data);
signin_client_->GetPrefs()->ClearPref(
prefs::kGaiaCookieLastListAccountsData);
} else {
signin_client_->GetPrefs()->SetString(
prefs::kGaiaCookieLastListAccountsData, data);
signin_client_->GetPrefs()->ClearPref(
prefs::kGaiaCookieLastListAccountsBinaryData);
}
RecordListAccountsFailure(GoogleServiceAuthError::NONE);
InitializeListedAccountsIds();

@ -18,6 +18,7 @@
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_mock_time_task_runner.h"
#include "build/buildflag.h"
@ -31,6 +32,7 @@
#include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h"
#include "google_apis/gaia/core_account_id.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/gaia_features.h"
#include "google_apis/gaia/gaia_id.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/cookies/canonical_cookie.h"
@ -950,6 +952,67 @@ TEST_F(GaiaCookieManagerServiceTest, GaiaCookieLastListAccountsDataSaved) {
}
}
TEST_F(GaiaCookieManagerServiceTest, ListAccountsEncodingMigration) {
gaia::ListedAccount account;
account.gaia_id = GaiaId("8");
account.id = CoreAccountId::FromGaiaId(account.gaia_id);
account.email = "a@b.com";
account.raw_email = "a@b.com";
signin::AccountsInCookieJarInfo cookies_expected_fresh(true, {account});
signin::AccountsInCookieJarInfo cookies_expected_stale(false, {account});
// ListAccounts data as jspb.
std::string data_jspb =
"[\"f\", [[\"b\", 0, \"n\", \"a@b.com\", \"p\", 0, 0, 0, 0, 1, \"8\"]]]";
// ListAccounts data as base64-encoded protobuf.
std::string data_binary = "Cg4aB2FAYi5jb21IAVIBOA==";
{
InstrumentedGaiaCookieManagerService helper(
account_tracker_service(), token_service(), signin_client());
MockObserver observer(&helper);
EXPECT_EQ(helper.ListAccounts(), kCookiesEmptyStale);
// Default behaviour: return and store ListAccounts data encoded in jspb.
SimulateListAccountsSuccess(&helper, data_jspb);
ASSERT_EQ(helper.ListAccounts(), cookies_expected_fresh);
EXPECT_EQ(signin_client()->GetPrefs()->GetString(
prefs::kGaiaCookieLastListAccountsData),
data_jspb);
EXPECT_TRUE(signin_client()
->GetPrefs()
->GetString(prefs::kGaiaCookieLastListAccountsBinaryData)
.empty());
}
{
// Enable the feature before reading prefs.
base::test::ScopedFeatureList feature_list(
gaia::features::kListAccountsUsesBinaryFormat);
InstrumentedGaiaCookieManagerService helper(
account_tracker_service(), token_service(), signin_client());
MockObserver observer(&helper);
// Verify that the jspb pref was read correctly.
EXPECT_EQ(helper.ListAccounts(), cookies_expected_stale);
// Set and verify binary ListAccounts.
SimulateListAccountsSuccess(&helper, data_binary);
ASSERT_EQ(helper.ListAccounts(), cookies_expected_fresh);
EXPECT_TRUE(signin_client()
->GetPrefs()
->GetString(prefs::kGaiaCookieLastListAccountsData)
.empty());
EXPECT_EQ(signin_client()->GetPrefs()->GetString(
prefs::kGaiaCookieLastListAccountsBinaryData),
data_binary);
}
{
InstrumentedGaiaCookieManagerService helper(
account_tracker_service(), token_service(), signin_client());
// Verify that the binary pref was read correctly.
EXPECT_EQ(helper.ListAccounts(), cookies_expected_stale);
}
}
TEST_F(GaiaCookieManagerServiceTest, ExternalCcResultFetcher) {
InstrumentedGaiaCookieManagerService helper(account_tracker_service(),
token_service(), signin_client());

@ -108,10 +108,16 @@ const char kSignedInWithCredentialProvider[] =
// Boolean which stores if the user is allowed to signin to chrome.
const char kSigninAllowed[] = "signin.allowed";
// Contains last |ListAccounts| data which corresponds to Gaia cookies.
// Contains last |ListAccounts| data which corresponds to Gaia cookies encoded
// in jspb.
const char kGaiaCookieLastListAccountsData[] =
"gaia_cookie.last_list_accounts_data";
// Contains last |ListAccounts| data which corresponds to Gaia cookies in
// base64-encoded protobuf.
const char kGaiaCookieLastListAccountsBinaryData[] =
"gaia_cookie.last_list_accounts_binary_data";
// The timestamp when History Sync was last declined (in the opt-in screen or
// in the settings).
// This value is reset when the user opts in to History Sync.

@ -60,6 +60,8 @@ extern const char kSigninAllowed[];
COMPONENT_EXPORT(SIGNIN_SWITCHES)
extern const char kGaiaCookieLastListAccountsData[];
COMPONENT_EXPORT(SIGNIN_SWITCHES)
extern const char kGaiaCookieLastListAccountsBinaryData[];
COMPONENT_EXPORT(SIGNIN_SWITCHES)
extern const char kSigninAllowedOnNextStartup[];
COMPONENT_EXPORT(SIGNIN_SWITCHES)
extern const char kSigninInterceptionIDPCookiesUrl[];

@ -133,6 +133,8 @@ component("google_apis") {
"gaia/gaia_config.h",
"gaia/gaia_constants.cc",
"gaia/gaia_constants.h",
"gaia/gaia_features.cc",
"gaia/gaia_features.h",
"gaia/gaia_id.cc",
"gaia/gaia_id.h",
"gaia/gaia_oauth_client.cc",
@ -210,6 +212,7 @@ component("google_apis") {
proto_library("proto") {
sources = [
"gaia/bound_oauth_token.proto",
"gaia/list_accounts_response.proto",
"gaia/oauth2_mint_token_consent_result.proto",
]
}
@ -286,6 +289,7 @@ test("google_apis_unittests") {
"//google_apis/common:common_unittests",
"//testing/gmock",
"//testing/gtest",
"//third_party/protobuf:protobuf_lite",
]
if (is_ios) {

@ -9,6 +9,7 @@
#include <memory>
#include <string_view>
#include "base/base64.h"
#include "base/base64url.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
@ -21,6 +22,7 @@
#include "google_apis/gaia/bound_oauth_token.pb.h"
#include "google_apis/gaia/gaia_id.h"
#include "google_apis/gaia/gaia_urls.h"
#include "google_apis/gaia/list_accounts_response.pb.h"
#include "google_apis/gaia/oauth2_mint_token_consent_result.pb.h"
#include "url/gurl.h"
#include "url/origin.h"
@ -203,6 +205,54 @@ bool ParseListAccountsData(std::string_view data,
return true;
}
bool ParseBinaryListAccountsData(const std::string& data,
std::vector<ListedAccount>* accounts) {
// Clear and rebuild our accounts list if one is given.
if (accounts) {
accounts->clear();
}
// The input is expected to be base64-encoded.
std::string decoded_data;
if (!base::Base64Decode(data, &decoded_data,
base::Base64DecodePolicy::kForgiving)) {
VLOG(1) << "Failed to decode ListAccounts data as a Base64 String";
return false;
}
// Parse our binary proto response.
ListAccountsResponse parsed_result;
if (!parsed_result.ParseFromString(decoded_data)) {
VLOG(1) << "malformed ListAccountsResponse";
return false;
}
// Build a vector of accounts from the cookie. Order is important: the first
// account in the list is the primary account.
for (const auto& account : parsed_result.account()) {
if (account.display_email().empty() || account.obfuscated_id().empty()) {
continue;
}
ListedAccount listed_account;
listed_account.email = CanonicalizeEmail(account.display_email());
listed_account.gaia_id = GaiaId(account.obfuscated_id());
listed_account.valid = (
// Assume the account is valid if unspecified for backcompat.
account.has_valid_session() ? account.valid_session() : true);
listed_account.signed_out =
(account.has_signed_out() ? account.signed_out() : false);
listed_account.verified =
(account.has_is_verified() ? account.is_verified() : true);
listed_account.raw_email = account.display_email();
if (accounts) {
accounts->push_back(std::move(listed_account));
}
}
return true;
}
bool ParseOAuth2MintTokenConsentResult(std::string_view consent_result,
bool* approved,
GaiaId* gaia_id) {

@ -88,6 +88,16 @@ bool IsGoogleRobotAccountEmail(std::string_view email);
// a GAIA origin and will in that case return false.
COMPONENT_EXPORT(GOOGLE_APIS) bool HasGaiaSchemeHostPort(const GURL& url);
// Parses binary proto data returned by /ListAccounts call into the given
// ListedAccounts. An email addresses is considered valid if a passive login
// would succeed (i.e. the user does not need to reauthenticate).
// If there was a parse error, this method returns false.
// If |accounts| is null, the corresponding accounts returned from /ListAccounts
// will be ignored.
COMPONENT_EXPORT(GOOGLE_APIS)
bool ParseBinaryListAccountsData(const std::string& data,
std::vector<ListedAccount>* accounts);
// Parses JSON data returned by /ListAccounts call, returning a vector of
// email/valid pairs. An email addresses is considered valid if a passive
// login would succeed (i.e. the user does not need to reauthenticate).

@ -4,9 +4,11 @@
#include "google_apis/gaia/gaia_auth_util.h"
#include "base/base64.h"
#include "base/base64url.h"
#include "google_apis/gaia/gaia_auth_test_util.h"
#include "google_apis/gaia/gaia_id.h"
#include "google_apis/gaia/list_accounts_response.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
@ -47,6 +49,12 @@ ListedAccount NonverifiedListedAccount(const std::string& raw_email,
return result;
}
std::string CreateBinaryListAccountsData(const ListAccountsResponse& response) {
std::string serialized_response;
response.SerializeToString(&serialized_response);
return base::Base64Encode(serialized_response);
}
} // namespace
using ::testing::ElementsAre;
@ -314,6 +322,202 @@ TEST(GaiaAuthUtilTest, ParseListAccountsAcceptsNull) {
nullptr));
}
TEST(GaiaAuthUtilTest, ParseBinaryListAccountsData) {
std::vector<ListedAccount> accounts;
ListAccountsResponse response;
std::string serialized_empty_response;
response.SerializeToString(&serialized_empty_response);
std::string encoded_empty_response =
base::Base64Encode(serialized_empty_response);
ASSERT_TRUE(ParseBinaryListAccountsData(encoded_empty_response, &accounts));
ASSERT_EQ(0u, accounts.size());
Account* account1 = response.add_account();
account1->set_display_email("u@g.c");
account1->set_valid_session(true);
account1->set_obfuscated_id("45");
std::string encoded_one_account_response =
CreateBinaryListAccountsData(response);
ASSERT_TRUE(
ParseBinaryListAccountsData(encoded_one_account_response, &accounts));
ASSERT_EQ(1u, accounts.size());
ASSERT_EQ("u@g.c", accounts[0].email);
ASSERT_TRUE(accounts[0].valid);
Account* account2 = response.add_account();
account2->set_display_email("u2@g.c");
account2->set_valid_session(true);
account2->set_obfuscated_id("6");
std::string encoded_two_accounts_response =
CreateBinaryListAccountsData(response);
ASSERT_TRUE(
ParseBinaryListAccountsData(encoded_two_accounts_response, &accounts));
ASSERT_EQ(2u, accounts.size());
ASSERT_EQ("u@g.c", accounts[0].email);
ASSERT_TRUE(accounts[0].valid);
ASSERT_EQ("u2@g.c", accounts[1].email);
ASSERT_TRUE(accounts[1].valid);
account1->set_display_email("U1@g.c");
account1->set_obfuscated_id("45");
account2->set_display_email("u.2@g.c");
account2->set_obfuscated_id("46");
std::string encoded_noncanonical_account_response =
CreateBinaryListAccountsData(response);
ASSERT_TRUE(ParseBinaryListAccountsData(encoded_noncanonical_account_response,
&accounts));
ASSERT_EQ(2u, accounts.size());
ASSERT_EQ(CanonicalizeEmail("U1@g.c"), accounts[0].email);
ASSERT_TRUE(accounts[0].valid);
ASSERT_EQ(CanonicalizeEmail("u.2@g.c"), accounts[1].email);
ASSERT_TRUE(accounts[1].valid);
}
TEST(GaiaAuthUtilTest, ParseBinaryListAccountsDataValidSession) {
std::vector<ListedAccount> accounts;
ListAccountsResponse response;
Account* account1 = response.add_account();
account1->set_display_email("u@g.c");
account1->set_valid_session(true);
account1->set_obfuscated_id("45");
// Valid session is true means: return account.
std::string encoded_one_account_response =
CreateBinaryListAccountsData(response);
ASSERT_TRUE(
ParseBinaryListAccountsData(encoded_one_account_response, &accounts));
ASSERT_EQ(1u, accounts.size());
ASSERT_EQ("u@g.c", accounts[0].email);
ASSERT_TRUE(accounts[0].valid);
// Valid session is false means: return account with valid bit false.
account1->set_valid_session(false);
std::string encoded_invalid_account_response =
CreateBinaryListAccountsData(response);
ASSERT_TRUE(
ParseBinaryListAccountsData(encoded_invalid_account_response, &accounts));
ASSERT_EQ(1u, accounts.size());
ASSERT_EQ("u@g.c", accounts[0].email);
ASSERT_FALSE(accounts[0].valid);
}
TEST(GaiaAuthUtilTest, ParseBinaryListAccountsDataGaiaId) {
std::vector<ListedAccount> accounts;
ListAccountsResponse response;
Account* account1 = response.add_account();
account1->set_display_email("u@g.c");
account1->set_valid_session(true);
// Missing gaia id: do not return account.
std::string encoded_no_gaia_id_response =
CreateBinaryListAccountsData(response);
ASSERT_TRUE(
ParseBinaryListAccountsData(encoded_no_gaia_id_response, &accounts));
ASSERT_EQ(0u, accounts.size());
account1->set_obfuscated_id("9863");
// Valid gaia session and gaia_id: return gaia session
std::string encoded_with_gaia_session_response =
CreateBinaryListAccountsData(response);
ASSERT_TRUE(ParseBinaryListAccountsData(encoded_with_gaia_session_response,
&accounts));
ASSERT_EQ(1u, accounts.size());
ASSERT_EQ("u@g.c", accounts[0].email);
ASSERT_TRUE(accounts[0].valid);
ASSERT_EQ("9863", accounts[0].gaia_id);
}
TEST(GaiaAuthUtilTest, ParseBinaryListAccountsWithSignedOutAccounts) {
std::vector<ListedAccount> accounts;
ListAccountsResponse response;
Account* account1 = response.add_account();
account1->set_display_email("u@g.c");
account1->set_valid_session(true);
account1->set_obfuscated_id("45");
Account* account2 = response.add_account();
account2->set_display_email("u.2@g.c");
account2->set_valid_session(true);
account2->set_obfuscated_id("46");
account2->set_signed_out(true);
std::string encoded_with_signed_out_session_response =
CreateBinaryListAccountsData(response);
ASSERT_TRUE(ParseBinaryListAccountsData(
encoded_with_signed_out_session_response, &accounts));
ASSERT_EQ(2u, accounts.size());
ASSERT_EQ("u@g.c", accounts[0].email);
ASSERT_FALSE(accounts[0].signed_out);
ASSERT_EQ("u.2@g.c", accounts[1].email);
ASSERT_TRUE(accounts[1].signed_out);
}
TEST(GaiaAuthUtilTest, ParseBinaryListAccountsVerifiedAccounts) {
std::vector<ListedAccount> accounts;
ListAccountsResponse response;
Account* account1 = response.add_account();
account1->set_display_email("a@g.c");
account1->set_valid_session(true);
account1->set_obfuscated_id("45");
Account* account2 = response.add_account();
account2->set_display_email("b@g.c");
account2->set_valid_session(true);
account2->set_obfuscated_id("46");
account2->set_signed_out(false);
Account* account3 = response.add_account();
account3->set_display_email("c@g.c");
account3->set_valid_session(true);
account3->set_obfuscated_id("47");
account3->set_signed_out(true);
std::string encoded_three_accounts_response =
CreateBinaryListAccountsData(response);
ASSERT_TRUE(
ParseBinaryListAccountsData(encoded_three_accounts_response, &accounts));
ASSERT_EQ(3u, accounts.size());
ASSERT_EQ("a@g.c", accounts[0].email);
EXPECT_TRUE(accounts[0].verified); // Accounts are not verified by default.
EXPECT_FALSE(accounts[0].signed_out); // Accounts are not signed out by
// default.
ASSERT_EQ("b@g.c", accounts[1].email);
EXPECT_TRUE(accounts[1].verified);
EXPECT_FALSE(accounts[1].signed_out);
ASSERT_EQ("c@g.c", accounts[2].email);
EXPECT_TRUE(accounts[2].verified);
EXPECT_TRUE(accounts[2].signed_out);
}
TEST(GaiaAuthUtilTest, ParseBinaryListAccountsWithInvalidInput) {
std::vector<ListedAccount> accounts;
ListAccountsResponse response;
// Try to parse a malformed input string.
std::string malformed_base64_response = "AAAAAAAAAAAAA";
ASSERT_FALSE(
ParseBinaryListAccountsData(malformed_base64_response, &accounts));
Account* account1 = response.add_account();
account1->set_display_email("a@g.c");
account1->set_valid_session(true);
account1->set_obfuscated_id("45");
std::string serialized_response;
response.SerializeToString(&serialized_response);
// Try to parse serialized but not base64-encoded response.
ASSERT_FALSE(ParseBinaryListAccountsData(serialized_response, &accounts));
}
TEST(GaiaAuthUtilTest, ParseConsentResultApproved) {
const char kApprovedConsent[] = "CAESCUVOQ1JZUFRFRBoMZmFrZV9nYWlhX2lk";
EXPECT_EQ(kApprovedConsent,

@ -0,0 +1,15 @@
// 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 "google_apis/gaia/gaia_features.h"
namespace gaia::features {
// Enables binary format parsing in the /ListAccounts Gaia call. The endpoint
// response depends on the presence of laf=b64bin parameter in the called url.
BASE_FEATURE(kListAccountsUsesBinaryFormat,
"ListAccountsUsesBinaryFormat",
base::FEATURE_DISABLED_BY_DEFAULT);
} // namespace gaia::features

@ -0,0 +1,18 @@
// 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.
#ifndef GOOGLE_APIS_GAIA_GAIA_FEATURES_H_
#define GOOGLE_APIS_GAIA_GAIA_FEATURES_H_
#include "base/component_export.h"
#include "base/feature_list.h"
namespace gaia::features {
COMPONENT_EXPORT(GOOGLE_APIS)
BASE_DECLARE_FEATURE(kListAccountsUsesBinaryFormat);
} // namespace gaia::features
#endif // GOOGLE_APIS_GAIA_GAIA_FEATURES_H_

@ -14,6 +14,7 @@
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "google_apis/gaia/gaia_config.h"
#include "google_apis/gaia/gaia_features.h"
#include "google_apis/gaia/gaia_switches.h"
#include "google_apis/google_api_keys.h"
#include "url/url_canon.h"
@ -327,6 +328,11 @@ GURL GaiaUrls::ListAccountsURLWithSource(const std::string& source) {
return list_accounts_url_;
} else {
std::string query = list_accounts_url_.query();
if (base::FeatureList::IsEnabled(
gaia::features::kListAccountsUsesBinaryFormat)) {
return list_accounts_url_.Resolve(base::StringPrintf(
"?gpsia=1&source=%s&laf=b64bin&%s", source.c_str(), query.c_str()));
}
return list_accounts_url_.Resolve(base::StringPrintf(
"?gpsia=1&source=%s&%s", source.c_str(), query.c_str()));
}

@ -13,9 +13,11 @@
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "google_apis/gaia/gaia_config.h"
#include "google_apis/gaia/gaia_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
@ -383,6 +385,20 @@ TEST_F(GaiaUrlsTest, InitializeFromConfig_AllBaseUrls) {
"https://www.exampleapis.com/reauth/v1beta/users/");
}
TEST_F(GaiaUrlsTest, InitializeDefault_ListAccountsFormat) {
base::test::ScopedFeatureList feature_list;
// Default behaviour - kListAccountsUsesBinaryFormat disabled.
EXPECT_EQ(gaia_urls()->ListAccountsURLWithSource("fake_source").spec(),
"https://accounts.google.com/"
"ListAccounts?gpsia=1&source=fake_source&json=standard");
feature_list.InitAndEnableFeature(
gaia::features::kListAccountsUsesBinaryFormat);
EXPECT_EQ(gaia_urls()->ListAccountsURLWithSource("fake_source").spec(),
"https://accounts.google.com/"
"ListAccounts?gpsia=1&source=fake_source&laf=b64bin&json=standard");
}
TEST_F(GaiaUrlsTest, InitializeFromConfigContents) {
base::test::ScopedCommandLine command_line;
command_line.GetProcessCommandLine()->AppendSwitchASCII(

@ -0,0 +1,39 @@
// Copyright (c) 2024 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
syntax = "proto2";
package gaia;
option optimize_for = LITE_RUNTIME;
// The state of a Gaia account.
//
// This is a projection of the type returned by Gaia's /ListAccounts, which only
// has the fields that Chrome will use.
message Account {
// The display email for the account.
optional string display_email = 3;
// If the session for this account is still 'valid' (not expired,
// the account is not disabled, etc.). Only present for person accounts when
// called with cookies as credentials.
optional bool valid_session = 9;
// Obfuscated Gaia ID.
optional string obfuscated_id = 10;
// Whether this account is only in the cookie and is actually signed out.
optional bool signed_out = 14;
// Whether this account is verified.
optional bool is_verified = 15;
// Internal Fields.
reserved 1, 2, 4, 5, 6, 7, 8, 11, 12, 13;
}
message ListAccountsResponse {
repeated Account account = 1;
}