blob: 2d668845b570af4e7326b05917a10df6cbc6c2e0 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/signin/dice_response_handler.h"
#include <memory>
#include <string_view>
#include <utility>
#include "base/check.h"
#include "base/containers/to_vector.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/test/gmock_move_support.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/about_signin_internals.h"
#include "components/signin/core/browser/account_reconcilor.h"
#include "components/signin/core/browser/dice_account_reconcilor_delegate.h"
#include "components/signin/core/browser/signin_error_controller.h"
#include "components/signin/core/browser/signin_header_helper.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/base/signin_buildflags.h"
#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/base/test_signin_client.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "crypto/signature_verifier.h"
#include "google_apis/gaia/core_account_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/signin/bound_session_credentials/registration_token_helper.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/unexportable_keys/fake_unexportable_key_service.h"
#include "components/unexportable_keys/unexportable_key_id.h"
#include "components/unexportable_keys/unexportable_key_service.h"
#include "components/unexportable_keys/unexportable_key_task_manager.h"
#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
using signin::DiceAction;
using signin::DiceResponseParams;
using testing::_;
using testing::Invoke;
using testing::Return;
using testing::StrictMock;
using testing::Unused;
namespace {
constexpr char kAuthorizationCode[] = "authorization_code";
constexpr char kEmail[] = "test@email.com";
constexpr int kSessionIndex = 42;
constexpr char kEligibleForTokenBinding[] = "ES256 RS256";
constexpr crypto::SignatureVerifier::SignatureAlgorithm
kAcceptableAlgorithms[] = {crypto::SignatureVerifier::ECDSA_SHA256,
crypto::SignatureVerifier::RSA_PKCS1_SHA256};
constexpr char kTokenBindingOutcomeHistogram[] =
"Signin.DiceTokenBindingOutcome";
DiceResponseParams::AccountInfo GetDiceResponseParamsAccountInfo(
const std::string& email) {
DiceResponseParams::AccountInfo account_info;
account_info.gaia_id = signin::GetTestGaiaIdForEmail(email);
account_info.email = kEmail;
account_info.session_index = kSessionIndex;
return account_info;
}
// TestSigninClient implementation that intercepts the GaiaAuthConsumer and
// replaces it by a dummy one.
class DiceTestSigninClient : public TestSigninClient, public GaiaAuthConsumer {
public:
explicit DiceTestSigninClient(PrefService* pref_service)
: TestSigninClient(pref_service), consumer_(nullptr) {}
DiceTestSigninClient(const DiceTestSigninClient&) = delete;
DiceTestSigninClient& operator=(const DiceTestSigninClient&) = delete;
~DiceTestSigninClient() override = default;
std::unique_ptr<GaiaAuthFetcher> CreateGaiaAuthFetcher(
GaiaAuthConsumer* consumer,
gaia::GaiaSource source) override {
DCHECK(!consumer_ || (consumer_ == consumer));
consumer_ = consumer;
// Pass |this| as a dummy consumer to CreateGaiaAuthFetcher().
// Since DiceTestSigninClient does not overrides any consumer method,
// everything will be dropped on the floor.
return TestSigninClient::CreateGaiaAuthFetcher(this, source);
}
// We want to reset |consumer_| here before the test interacts with the last
// consumer. Interacting with the last consumer (simulating success of the
// fetcher) namely sometimes immediately triggers another fetch with another
// consumer. If |consumer_| is non-null, we would hit the DCHECK.
GaiaAuthConsumer* GetAndClearConsumer() {
GaiaAuthConsumer* last_consumer = consumer_;
consumer_ = nullptr;
return last_consumer;
}
private:
raw_ptr<GaiaAuthConsumer> consumer_;
};
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
class MockRegistrationTokenHelper : public RegistrationTokenHelper {
public:
MockRegistrationTokenHelper()
: RegistrationTokenHelper(
fake_unexportable_key_service_,
std::vector<crypto::SignatureVerifier::SignatureAlgorithm>{}) {}
~MockRegistrationTokenHelper() override = default;
MOCK_METHOD(void,
GenerateForSessionBinding,
(std::string_view challenge,
const GURL& registration_url,
base::OnceCallback<void(std::optional<Result>)> callback),
(override));
MOCK_METHOD(void,
GenerateForTokenBinding,
(std::string_view client_id,
std::string_view auth_code,
const GURL& registration_url,
base::OnceCallback<void(std::optional<Result>)> callback),
(override));
private:
unexportable_keys::FakeUnexportableKeyService fake_unexportable_key_service_;
};
#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
class DiceResponseHandlerTest : public testing::Test,
public AccountReconcilor::Observer {
public:
// Called after the refresh token was fetched and added in the token service.
void HandleTokenExchangeSuccess(CoreAccountId account_id,
bool is_new_account) {
token_exchange_account_id_ = account_id;
token_exchange_is_new_account_ = is_new_account;
}
// Called after the refresh token was fetched and added in the token service.
void EnableSync(const CoreAccountInfo& account_info) {
enable_sync_account_info_ = account_info;
}
void HandleTokenExchangeFailure(const std::string& email,
const GoogleServiceAuthError& error) {
auth_error_email_ = email;
auth_error_ = error;
}
protected:
DiceResponseHandlerTest()
: task_environment_(
base::test::SingleThreadTaskEnvironment::MainThreadType::IO,
base::test::SingleThreadTaskEnvironment::TimeSource::
MOCK_TIME), // URLRequestContext requires IO.
signin_client_(&pref_service_),
identity_test_env_(/*test_url_loader_factory=*/nullptr,
&pref_service_,
&signin_client_),
signin_error_controller_(
SigninErrorController::AccountMode::PRIMARY_ACCOUNT,
identity_test_env_.identity_manager()) {
EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
AboutSigninInternals::RegisterPrefs(pref_service_.registry());
auto account_reconcilor_delegate =
std::make_unique<signin::DiceAccountReconcilorDelegate>(
identity_manager(), &signin_client_);
account_reconcilor_ = std::make_unique<AccountReconcilor>(
identity_test_env_.identity_manager(), &signin_client_,
std::move(account_reconcilor_delegate));
account_reconcilor_->AddObserver(this);
about_signin_internals_ = std::make_unique<AboutSigninInternals>(
identity_test_env_.identity_manager(), &signin_error_controller_,
signin::AccountConsistencyMethod::kDice, &signin_client_,
account_reconcilor_.get());
dice_response_handler_ = std::make_unique<DiceResponseHandler>(
&signin_client_, identity_test_env_.identity_manager(),
account_reconcilor_.get(), about_signin_internals_.get(),
/*registration_token_helper_factory=*/
DiceResponseHandler::RegistrationTokenHelperFactory());
}
~DiceResponseHandlerTest() override {
account_reconcilor_->RemoveObserver(this);
account_reconcilor_->Shutdown();
about_signin_internals_->Shutdown();
signin_error_controller_.Shutdown();
}
DiceResponseParams MakeDiceParams(DiceAction action) {
DiceResponseParams dice_params;
dice_params.user_intention = action;
DiceResponseParams::AccountInfo account_info =
GetDiceResponseParamsAccountInfo(kEmail);
switch (action) {
case DiceAction::SIGNIN:
dice_params.signin_info =
std::make_unique<DiceResponseParams::SigninInfo>();
dice_params.signin_info->account_info = account_info;
dice_params.signin_info->authorization_code = kAuthorizationCode;
dice_params.signin_info->supported_algorithms_for_token_binding =
kEligibleForTokenBinding;
break;
case DiceAction::ENABLE_SYNC:
dice_params.enable_sync_info =
std::make_unique<DiceResponseParams::EnableSyncInfo>();
dice_params.enable_sync_info->account_info = account_info;
break;
case DiceAction::SIGNOUT:
dice_params.signout_info =
std::make_unique<DiceResponseParams::SignoutInfo>();
dice_params.signout_info->account_infos.push_back(account_info);
break;
case DiceAction::NONE:
NOTREACHED();
}
return dice_params;
}
sync_preferences::TestingPrefServiceSyncable& pref_service() {
return pref_service_;
}
void RunSignoutTest(
const DiceResponseParams& dice_params,
const std::vector<CoreAccountId>& secondary_with_valid_refresh_tokens,
const CoreAccountId& primary_account,
bool invalid_primary_account);
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
void EnableRegistrationTokenHelperFactory() {
dice_response_handler_->SetRegistrationTokenHelperFactoryForTesting(
mock_registration_token_helper_factory_.Get());
}
void ExpectRegistrationTokenHelperCreated(
const std::vector<std::string>& expected_authorization_codes,
const RegistrationTokenHelper::KeyInitParam& expected_key_init_param) {
EXPECT_CALL(mock_registration_token_helper_factory_,
Run(expected_key_init_param))
.WillOnce(
Return(BuildRegistrationTokenHelper(expected_authorization_codes)));
}
std::unique_ptr<RegistrationTokenHelper> BuildRegistrationTokenHelper(
const std::vector<std::string>& expected_authorization_codes) {
auto helper = std::make_unique<StrictMock<MockRegistrationTokenHelper>>();
for (const auto& authorization_code : expected_authorization_codes) {
EXPECT_CALL(*helper, GenerateForTokenBinding(_, authorization_code, _, _))
.WillOnce(
MoveArg<3>(&binding_registration_callbacks_[authorization_code]));
}
return helper;
}
void SimulateRegistrationTokenHelperResult(
const std::string& authorization_code,
std::optional<RegistrationTokenHelper::Result> result) {
auto node = binding_registration_callbacks_.extract(authorization_code);
ASSERT_FALSE(node.empty());
std::move(node.mapped()).Run(std::move(result));
}
#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
// AccountReconcilor::Observer:
void OnBlockReconcile() override { ++reconcilor_blocked_count_; }
void OnUnblockReconcile() override { ++reconcilor_unblocked_count_; }
signin::IdentityManager* identity_manager() {
return identity_test_env_.identity_manager();
}
base::test::SingleThreadTaskEnvironment task_environment_;
base::ScopedTempDir temp_dir_;
sync_preferences::TestingPrefServiceSyncable pref_service_;
DiceTestSigninClient signin_client_;
signin::IdentityTestEnvironment identity_test_env_;
SigninErrorController signin_error_controller_;
std::unique_ptr<AboutSigninInternals> about_signin_internals_;
std::unique_ptr<AccountReconcilor> account_reconcilor_;
std::unique_ptr<DiceResponseHandler> dice_response_handler_;
int reconcilor_blocked_count_ = 0;
int reconcilor_unblocked_count_ = 0;
CoreAccountId token_exchange_account_id_;
bool token_exchange_is_new_account_ = false;
CoreAccountInfo enable_sync_account_info_;
GoogleServiceAuthError auth_error_;
std::string auth_error_email_;
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
base::test::ScopedFeatureList feature_list_{
switches::kEnableChromeRefreshTokenBinding};
std::map<
std::string,
base::OnceCallback<void(std::optional<RegistrationTokenHelper::Result>)>>
binding_registration_callbacks_;
StrictMock<
base::MockCallback<DiceResponseHandler::RegistrationTokenHelperFactory>>
mock_registration_token_helper_factory_;
base::HistogramTester histogram_tester_;
#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
};
class TestProcessDiceHeaderDelegate : public ProcessDiceHeaderDelegate {
public:
explicit TestProcessDiceHeaderDelegate(DiceResponseHandlerTest* owner)
: owner_(owner) {}
~TestProcessDiceHeaderDelegate() override = default;
// Called after the refresh token was fetched and added in the token service.
void HandleTokenExchangeSuccess(CoreAccountId account_id,
bool is_new_account) override {
owner_->HandleTokenExchangeSuccess(account_id, is_new_account);
}
// Called after the refresh token was fetched and added in the token service.
void EnableSync(const CoreAccountInfo& account_info) override {
owner_->EnableSync(account_info);
}
void HandleTokenExchangeFailure(
const std::string& email,
const GoogleServiceAuthError& error) override {
owner_->HandleTokenExchangeFailure(email, error);
}
signin_metrics::AccessPoint GetAccessPoint() override {
return signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS;
}
void OnDiceSigninHeaderReceived() override {}
private:
raw_ptr<DiceResponseHandlerTest> owner_;
};
void DiceResponseHandlerTest::RunSignoutTest(
const DiceResponseParams& dice_params,
const std::vector<CoreAccountId>& secondary_with_valid_refresh_tokens,
const CoreAccountId& primary_account,
bool invalid_primary_account) {
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Only the token corresponding the the Dice parameter has been removed, and
// the user is still signed in.
bool has_primary_account = !primary_account.empty();
size_t expected_accounts_with_refresh_tokens =
secondary_with_valid_refresh_tokens.size() +
(has_primary_account ? 1U : 0U);
EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(),
expected_accounts_with_refresh_tokens);
for (const CoreAccountId& account_id : secondary_with_valid_refresh_tokens) {
SCOPED_TRACE(account_id.ToString());
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_FALSE(
identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
account_id));
}
CHECK(!invalid_primary_account || has_primary_account);
if (has_primary_account) {
EXPECT_EQ(
identity_manager()->GetPrimaryAccountId(signin::ConsentLevel::kSignin),
primary_account);
EXPECT_EQ(
identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
primary_account),
invalid_primary_account);
} else if (identity_manager()->HasPrimaryAccount(
signin::ConsentLevel::kSignin)) {
// In the unittest `RemoveAccount()` will not lead to the primary account
// being removed. Check there is no refresh token instead.
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(
identity_manager()->GetPrimaryAccountId(
signin::ConsentLevel::kSignin)));
}
if (invalid_primary_account) {
auto error = identity_manager()->GetErrorStateOfRefreshTokenForAccount(
primary_account);
EXPECT_EQ(error.state(), GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
EXPECT_EQ(error.GetInvalidGaiaCredentialsReason(),
GoogleServiceAuthError::InvalidGaiaCredentialsReason::
CREDENTIALS_REJECTED_BY_CLIENT);
}
}
class SigninDiceResponseHandlerTestPreconnect
: public DiceResponseHandlerTest,
public ::testing::WithParamInterface<bool> {
public:
SigninDiceResponseHandlerTestPreconnect() {
feature_list_.InitWithFeatureState(
switches::kPreconnectAccountCapabilitiesPostSignin,
PreconnectEnabled());
}
bool PreconnectEnabled() { return GetParam(); }
private:
base::test::ScopedFeatureList feature_list_;
};
// Checks that a SIGNIN action triggers a token exchange request.
TEST_P(SigninDiceResponseHandlerTestPreconnect, Signin) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(0, reconcilor_unblocked_count_);
// Simulate GaiaAuthFetcher success.
consumer->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/true, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_TRUE(auth_error_email_.empty());
EXPECT_EQ(GoogleServiceAuthError::NONE, auth_error_.state());
// Check HandleTokenExchangeSuccess parameters.
EXPECT_EQ(token_exchange_account_id_, account_id);
EXPECT_TRUE(token_exchange_is_new_account_);
// Check that the reconcilor was blocked and unblocked exactly once.
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(1, reconcilor_unblocked_count_);
// Check that the AccountInfo::is_under_advanced_protection is set.
AccountInfo extended_account_info =
identity_manager()->FindExtendedAccountInfoByAccountId(account_id);
EXPECT_TRUE(extended_account_info.is_under_advanced_protection);
// Check that the AccessPoint was propagated from the delegate.
EXPECT_EQ(extended_account_info.access_point,
signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS);
EXPECT_EQ(
identity_test_env_.GetNumCallsToPrepareForFetchingAccountCapabilities(),
PreconnectEnabled() ? 1 : 0);
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
histogram_tester_.ExpectUniqueSample(
kTokenBindingOutcomeHistogram,
DiceResponseHandler::TokenBindingOutcome::kNotBoundNotSupported,
/*expected_bucket_count=*/1);
#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
}
INSTANTIATE_TEST_SUITE_P(PreconnectEnabled,
SigninDiceResponseHandlerTestPreconnect,
::testing::Bool());
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
// Checks that a SIGNIN action triggers a token exchange request.
TEST_F(DiceResponseHandlerTest, SigninWithBoundToken) {
EnableRegistrationTokenHelperFactory();
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
const std::string authorization_code =
dice_params.signin_info->authorization_code;
ExpectRegistrationTokenHelperCreated({authorization_code},
base::ToVector(kAcceptableAlgorithms));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Token fetch should be blocked on the binding registration token generation.
ASSERT_THAT(signin_client_.GetAndClearConsumer(), testing::IsNull());
// Simulate successful token generation.
const std::vector<uint8_t> kWrappedKey = {1, 2, 3};
SimulateRegistrationTokenHelperResult(
authorization_code,
RegistrationTokenHelper::Result(unexportable_keys::UnexportableKeyId(),
kWrappedKey, "test_registration_token"));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
// Simulate GaiaAuthFetcher success.
consumer->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/true));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_EQ(identity_manager()->GetWrappedBindingKeyOfRefreshTokenForAccount(
account_id),
kWrappedKey);
EXPECT_TRUE(auth_error_email_.empty());
EXPECT_EQ(GoogleServiceAuthError::NONE, auth_error_.state());
histogram_tester_.ExpectUniqueSample(
kTokenBindingOutcomeHistogram,
DiceResponseHandler::TokenBindingOutcome::kBound,
/*expected_bucket_count=*/1);
}
// Checks that no token binding attempt is made when an account is ineligible
// for token binding.
TEST_F(DiceResponseHandlerTest, SigninIneligibleForTokenBinding) {
EnableRegistrationTokenHelperFactory();
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
dice_params.signin_info->supported_algorithms_for_token_binding.clear();
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created immediately.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
// Simulate GaiaAuthFetcher success.
consumer->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/true, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service and it is
// unbound.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_TRUE(identity_manager()
->GetWrappedBindingKeyOfRefreshTokenForAccount(account_id)
.empty());
EXPECT_TRUE(auth_error_email_.empty());
EXPECT_EQ(GoogleServiceAuthError::NONE, auth_error_.state());
histogram_tester_.ExpectUniqueSample(
kTokenBindingOutcomeHistogram,
DiceResponseHandler::TokenBindingOutcome::kNotBoundNotEligible,
/*expected_bucket_count=*/1);
}
// Checks that Chrome will discard the binding key if the server didn't accept
// the binding key.
TEST_F(DiceResponseHandlerTest, SigninServerRejectedBinding) {
EnableRegistrationTokenHelperFactory();
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
const std::string authorization_code =
dice_params.signin_info->authorization_code;
ExpectRegistrationTokenHelperCreated({authorization_code},
base::ToVector(kAcceptableAlgorithms));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Token fetch should be blocked on the binding registration token generation.
ASSERT_THAT(signin_client_.GetAndClearConsumer(), testing::IsNull());
// Simulate successful token generation.
const std::vector<uint8_t> kWrappedKey = {1, 2, 3};
SimulateRegistrationTokenHelperResult(
authorization_code,
RegistrationTokenHelper::Result(unexportable_keys::UnexportableKeyId(),
kWrappedKey, "test_registration_token"));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
// Simulate GaiaAuthFetcher success with an unbound token.
consumer->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_TRUE(identity_manager()
->GetWrappedBindingKeyOfRefreshTokenForAccount(account_id)
.empty());
EXPECT_TRUE(auth_error_email_.empty());
EXPECT_EQ(GoogleServiceAuthError::NONE, auth_error_.state());
histogram_tester_.ExpectUniqueSample(
kTokenBindingOutcomeHistogram,
DiceResponseHandler::TokenBindingOutcome::kNotBoundServerRejectedKey,
/*expected_bucket_count=*/1);
}
TEST_F(DiceResponseHandlerTest, ReuseBindingKeyOtherTokenIsBound) {
EnableRegistrationTokenHelperFactory();
const std::vector<uint8_t> kWrappedKey = {1, 2, 3};
identity_test_env_.MakeAccountAvailable(
signin::AccountAvailabilityOptionsBuilder()
.WithRefreshTokenBindingKey(kWrappedKey)
.Build("other@email.com"));
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
const std::string authorization_code =
dice_params.signin_info->authorization_code;
ExpectRegistrationTokenHelperCreated({authorization_code}, kWrappedKey);
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Verify that the next step can complete with the reused token.
// Token fetch should be blocked on the binding registration token generation.
ASSERT_THAT(signin_client_.GetAndClearConsumer(), testing::IsNull());
// Simulate successful token generation.
SimulateRegistrationTokenHelperResult(
authorization_code,
RegistrationTokenHelper::Result(unexportable_keys::UnexportableKeyId(),
kWrappedKey, "test_registration_token"));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
// Simulate GaiaAuthFetcher success.
consumer->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/true));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_EQ(identity_manager()->GetWrappedBindingKeyOfRefreshTokenForAccount(
account_id),
kWrappedKey);
}
TEST_F(DiceResponseHandlerTest, ReuseBindingKeyOneTokenBoundOneNonBound) {
EnableRegistrationTokenHelperFactory();
const std::vector<uint8_t> kWrappedKey = {1, 2, 3};
identity_test_env_.MakeAccountAvailable("nonbound@gmail.com");
identity_test_env_.MakeAccountAvailable(
signin::AccountAvailabilityOptionsBuilder()
.WithRefreshTokenBindingKey(kWrappedKey)
.Build("bound@email.com"));
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
ExpectRegistrationTokenHelperCreated(
{dice_params.signin_info->authorization_code}, kWrappedKey);
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
}
TEST_F(DiceResponseHandlerTest, NewBindingKeyOtherTokenIsNotBound) {
EnableRegistrationTokenHelperFactory();
identity_test_env_.MakeAccountAvailable("other@email.com");
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
ExpectRegistrationTokenHelperCreated(
{dice_params.signin_info->authorization_code},
base::ToVector(kAcceptableAlgorithms));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
}
TEST_F(DiceResponseHandlerTest, TwoFetchersReuseRegistrationTokenHelper) {
EnableRegistrationTokenHelperFactory();
auto account_id = [&](const DiceResponseParams& dice_params) {
const auto& account_info = dice_params.signin_info->account_info;
return identity_manager()->PickAccountIdForAccount(account_info.gaia_id,
account_info.email);
};
auto authorization_code = [&](const DiceResponseParams& dice_params) {
return dice_params.signin_info->authorization_code;
};
DiceResponseParams dice_params_1 = MakeDiceParams(DiceAction::SIGNIN);
DiceResponseParams dice_params_2 = MakeDiceParams(DiceAction::SIGNIN);
dice_params_2.signin_info->account_info =
GetDiceResponseParamsAccountInfo("other@email.com");
dice_params_2.signin_info->authorization_code = "other_authorization_code";
ExpectRegistrationTokenHelperCreated(
{authorization_code(dice_params_1), authorization_code(dice_params_2)},
base::ToVector(kAcceptableAlgorithms));
dice_response_handler_->ProcessDiceHeader(
dice_params_1, std::make_unique<TestProcessDiceHeaderDelegate>(this));
dice_response_handler_->ProcessDiceHeader(
dice_params_2, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Token fetch should be blocked on the binding registration token generation.
ASSERT_THAT(signin_client_.GetAndClearConsumer(), testing::IsNull());
// Simulate successful token generation and check that GaiaAuthFetchers have
// been created.
const std::vector<uint8_t> kWrappedKey = {1, 2, 3};
SimulateRegistrationTokenHelperResult(
authorization_code(dice_params_2),
RegistrationTokenHelper::Result(unexportable_keys::UnexportableKeyId(),
kWrappedKey, "test_registration_token"));
GaiaAuthConsumer* consumer_2 = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer_2, testing::NotNull());
SimulateRegistrationTokenHelperResult(
authorization_code(dice_params_1),
RegistrationTokenHelper::Result(unexportable_keys::UnexportableKeyId(),
kWrappedKey, "other_registration_token"));
GaiaAuthConsumer* consumer_1 = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer_1, testing::NotNull());
// Simulate GaiaAuthFetchers successes and check that tokens have been
// inserted in the token service.
consumer_1->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/true));
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
account_id(dice_params_1)));
EXPECT_EQ(identity_manager()->GetWrappedBindingKeyOfRefreshTokenForAccount(
account_id(dice_params_1)),
kWrappedKey);
consumer_2->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/true));
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
account_id(dice_params_2)));
EXPECT_EQ(identity_manager()->GetWrappedBindingKeyOfRefreshTokenForAccount(
account_id(dice_params_2)),
kWrappedKey);
histogram_tester_.ExpectUniqueSample(
kTokenBindingOutcomeHistogram,
DiceResponseHandler::TokenBindingOutcome::kBound,
/*expected_bucket_count=*/2);
}
TEST_F(DiceResponseHandlerTest, TwoFetchersOneEligible) {
EnableRegistrationTokenHelperFactory();
auto authorization_code = [&](const DiceResponseParams& dice_params) {
return dice_params.signin_info->authorization_code;
};
DiceResponseParams eligible_dice_params_ = MakeDiceParams(DiceAction::SIGNIN);
DiceResponseParams ineligible_dice_params =
MakeDiceParams(DiceAction::SIGNIN);
ineligible_dice_params.signin_info->account_info =
GetDiceResponseParamsAccountInfo("other@email.com");
ineligible_dice_params.signin_info->authorization_code =
"other_authorization_code";
ineligible_dice_params.signin_info->supported_algorithms_for_token_binding
.clear();
ExpectRegistrationTokenHelperCreated(
{authorization_code(eligible_dice_params_)},
base::ToVector(kAcceptableAlgorithms));
dice_response_handler_->ProcessDiceHeader(
eligible_dice_params_,
std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Token fetch should be blocked on the binding registration token generation.
ASSERT_THAT(signin_client_.GetAndClearConsumer(), testing::IsNull());
dice_response_handler_->ProcessDiceHeader(
ineligible_dice_params,
std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Token fetch should start immediately for ineligible account.
ASSERT_THAT(signin_client_.GetAndClearConsumer(), testing::NotNull());
// Simulate successful token generation and check that GaiaAuthFetcher has
// been created.
const std::vector<uint8_t> kWrappedKey = {1, 2, 3};
SimulateRegistrationTokenHelperResult(
authorization_code(eligible_dice_params_),
RegistrationTokenHelper::Result(unexportable_keys::UnexportableKeyId(),
kWrappedKey, "test_registration_token"));
ASSERT_THAT(signin_client_.GetAndClearConsumer(), testing::NotNull());
}
TEST_F(DiceResponseHandlerTest,
NewRegistrationTokenHelperCreatedForConsecutiveFetchers) {
EnableRegistrationTokenHelperFactory();
auto account_id = [&](const DiceResponseParams& dice_params) {
const auto& account_info = dice_params.signin_info->account_info;
return identity_manager()->PickAccountIdForAccount(account_info.gaia_id,
account_info.email);
};
auto authorization_code = [&](const DiceResponseParams& dice_params) {
return dice_params.signin_info->authorization_code;
};
DiceResponseParams dice_params_1 = MakeDiceParams(DiceAction::SIGNIN);
ExpectRegistrationTokenHelperCreated({authorization_code(dice_params_1)},
base::ToVector(kAcceptableAlgorithms));
dice_response_handler_->ProcessDiceHeader(
dice_params_1, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Token fetch should be blocked on the binding registration token generation.
ASSERT_THAT(signin_client_.GetAndClearConsumer(), testing::IsNull());
const std::vector<uint8_t> kWrappedKey = {1, 2, 3};
SimulateRegistrationTokenHelperResult(
authorization_code(dice_params_1),
RegistrationTokenHelper::Result(unexportable_keys::UnexportableKeyId(),
kWrappedKey, "test_registration_token"));
GaiaAuthConsumer* consumer_1 = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer_1, testing::NotNull());
// Simulate GaiaAuthFetcher success with the binding key being rejected.
consumer_1->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/false));
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
account_id(dice_params_1)));
EXPECT_TRUE(identity_manager()
->GetWrappedBindingKeyOfRefreshTokenForAccount(
account_id(dice_params_1))
.empty());
// Next request should create a new RegistrationTokenHelper with a new binding
// key as none of the existing tokens are bound.
DiceResponseParams dice_params_2 = MakeDiceParams(DiceAction::SIGNIN);
dice_params_2.signin_info->account_info =
GetDiceResponseParamsAccountInfo("other@email.com");
dice_params_2.signin_info->authorization_code = "other_authorization_code";
ExpectRegistrationTokenHelperCreated({authorization_code(dice_params_2)},
base::ToVector(kAcceptableAlgorithms));
dice_response_handler_->ProcessDiceHeader(
dice_params_2, std::make_unique<TestProcessDiceHeaderDelegate>(this));
}
TEST_F(DiceResponseHandlerTest, SigninWithFailedBoundTokenAttempt) {
EnableRegistrationTokenHelperFactory();
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
const std::string authorization_code =
dice_params.signin_info->authorization_code;
ExpectRegistrationTokenHelperCreated({authorization_code},
base::ToVector(kAcceptableAlgorithms));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Token fetch should be blocked on the binding registration token generation.
ASSERT_THAT(signin_client_.GetAndClearConsumer(), testing::IsNull());
// Simulate failed token generation.
SimulateRegistrationTokenHelperResult(authorization_code, std::nullopt);
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
// Simulate GaiaAuthFetcher success.
consumer->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_TRUE(identity_manager()
->GetWrappedBindingKeyOfRefreshTokenForAccount(account_id)
.empty());
EXPECT_TRUE(auth_error_email_.empty());
EXPECT_EQ(GoogleServiceAuthError::NONE, auth_error_.state());
histogram_tester_.ExpectUniqueSample(
kTokenBindingOutcomeHistogram,
DiceResponseHandler::TokenBindingOutcome::
kNotBoundRegistrationTokenGenerationFailed,
/*expected_bucket_count=*/1);
}
#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
// Checks that the account reconcilor is blocked when where was OAuth
// outage in Dice, and unblocked after the timeout.
TEST_F(DiceResponseHandlerTest, SupportOAuthOutageInDice) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
dice_params.signin_info->authorization_code.clear();
dice_params.signin_info->no_authorization_code = true;
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that the reconcilor was blocked and not unblocked before timeout.
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(0, reconcilor_unblocked_count_);
task_environment_.FastForwardBy(
base::Hours(kLockAccountReconcilorTimeoutHours + 1));
// Check that the reconcilor was unblocked.
EXPECT_EQ(1, reconcilor_unblocked_count_);
EXPECT_EQ(1, reconcilor_blocked_count_);
}
// Check that after receiving two headers with no authorization code,
// timeout still restarts.
TEST_F(DiceResponseHandlerTest, CheckTimersDuringOutageinDice) {
ASSERT_GT(kLockAccountReconcilorTimeoutHours, 3);
// Create params for the first header with no authorization code.
DiceResponseParams dice_params_1 = MakeDiceParams(DiceAction::SIGNIN);
dice_params_1.signin_info->authorization_code.clear();
dice_params_1.signin_info->no_authorization_code = true;
dice_response_handler_->ProcessDiceHeader(
dice_params_1, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that the reconcilor was blocked and not unblocked before timeout.
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(0, reconcilor_unblocked_count_);
// Wait half of the timeout.
task_environment_.FastForwardBy(
base::Hours(kLockAccountReconcilorTimeoutHours / 2));
// Create params for the second header with no authorization code.
DiceResponseParams dice_params_2 = MakeDiceParams(DiceAction::SIGNIN);
dice_params_2.signin_info->authorization_code.clear();
dice_params_2.signin_info->no_authorization_code = true;
dice_response_handler_->ProcessDiceHeader(
dice_params_2, std::make_unique<TestProcessDiceHeaderDelegate>(this));
task_environment_.FastForwardBy(
base::Hours((kLockAccountReconcilorTimeoutHours + 1) / 2 + 1));
// Check that the reconcilor was not unblocked after the first timeout
// passed, timer should be restarted after getting the second header.
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(0, reconcilor_unblocked_count_);
task_environment_.FastForwardBy(
base::Hours((kLockAccountReconcilorTimeoutHours + 1) / 2));
// Check that the reconcilor was unblocked.
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(1, reconcilor_unblocked_count_);
}
// Check that signin works normally (the token is fetched and added to chrome)
// on valid headers after getting a no_authorization_code header.
TEST_F(DiceResponseHandlerTest, CheckSigninAfterOutageInDice) {
// Create params for the header with no authorization code.
DiceResponseParams dice_params_1 = MakeDiceParams(DiceAction::SIGNIN);
dice_params_1.signin_info->authorization_code.clear();
dice_params_1.signin_info->no_authorization_code = true;
dice_response_handler_->ProcessDiceHeader(
dice_params_1, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Create params for the valid header with an authorization code.
DiceResponseParams dice_params_2 = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info_2 = dice_params_2.signin_info->account_info;
CoreAccountId account_id_2 = identity_manager()->PickAccountIdForAccount(
account_info_2.gaia_id, account_info_2.email);
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id_2));
dice_response_handler_->ProcessDiceHeader(
dice_params_2, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that the reconcilor was blocked and not unblocked before timeout.
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(0, reconcilor_unblocked_count_);
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
// Simulate GaiaAuthFetcher success.
consumer->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/true, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id_2));
EXPECT_TRUE(auth_error_email_.empty());
EXPECT_EQ(GoogleServiceAuthError::NONE, auth_error_.state());
// Check HandleTokenExchangeSuccess parameters.
EXPECT_EQ(token_exchange_account_id_, account_id_2);
EXPECT_TRUE(token_exchange_is_new_account_);
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(0, reconcilor_unblocked_count_);
// Check that the AccountInfo::is_under_advanced_protection is set.
EXPECT_TRUE(identity_manager()
->FindExtendedAccountInfoByAccountId(account_id_2)
.is_under_advanced_protection);
task_environment_.FastForwardBy(
base::Hours(kLockAccountReconcilorTimeoutHours + 1));
// Check that the reconcilor was unblocked.
EXPECT_EQ(1, reconcilor_unblocked_count_);
EXPECT_EQ(1, reconcilor_blocked_count_);
}
// Checks that a SIGNIN action triggers a token exchange request when the
// account is in authentication error.
TEST_F(DiceResponseHandlerTest, Reauth) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
AccountInfo account_info = identity_test_env_.MakePrimaryAccountAvailable(
dice_params.signin_info->account_info.email, signin::ConsentLevel::kSync);
dice_params.signin_info->account_info.gaia_id = account_info.gaia;
CoreAccountId account_id = account_info.account_id;
identity_test_env_.UpdatePersistentErrorOfRefreshTokenForAccount(
account_id,
GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_TRUE(
identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
account_id));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(0, reconcilor_unblocked_count_);
// Simulate GaiaAuthFetcher success.
consumer->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/true, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_FALSE(
identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
account_id));
// Check HandleTokenExchangeSuccess parameters.
EXPECT_EQ(token_exchange_account_id_, account_id);
EXPECT_FALSE(token_exchange_is_new_account_);
}
// Checks that a GaiaAuthFetcher failure is handled correctly.
TEST_F(DiceResponseHandlerTest, SigninFailure) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
EXPECT_EQ(
1u, dice_response_handler_->GetPendingDiceTokenFetchersCountForTesting());
// Simulate GaiaAuthFetcher failure.
GoogleServiceAuthError::State error_state =
GoogleServiceAuthError::SERVICE_UNAVAILABLE;
consumer->OnClientOAuthFailure(GoogleServiceAuthError(error_state));
EXPECT_EQ(
0u, dice_response_handler_->GetPendingDiceTokenFetchersCountForTesting());
// Check that the token has not been inserted in the token service.
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_EQ(account_info.email, auth_error_email_);
EXPECT_EQ(error_state, auth_error_.state());
}
// Checks that a second token for the same account is not requested when a
// request is already in flight.
TEST_F(DiceResponseHandlerTest, SigninRepeatedWithSameAccount) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
ASSERT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer_1 = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer_1, testing::NotNull());
// Start a second request for the same account.
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that there is no new request.
GaiaAuthConsumer* consumer_2 = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer_2, testing::IsNull());
// Simulate GaiaAuthFetcher success for the first request.
consumer_1->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
EXPECT_FALSE(identity_manager()
->FindExtendedAccountInfoByAccountId(account_id)
.is_under_advanced_protection);
}
// Checks that two SIGNIN requests can happen concurrently.
TEST_F(DiceResponseHandlerTest, SigninWithTwoAccounts) {
DiceResponseParams dice_params_1 = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info_1 = dice_params_1.signin_info->account_info;
DiceResponseParams dice_params_2 = MakeDiceParams(DiceAction::SIGNIN);
dice_params_2.signin_info->account_info.email = "other_email";
dice_params_2.signin_info->account_info.gaia_id = "other_gaia_id";
const auto& account_info_2 = dice_params_2.signin_info->account_info;
CoreAccountId account_id_1 = identity_manager()->PickAccountIdForAccount(
account_info_1.gaia_id, account_info_1.email);
CoreAccountId account_id_2 = identity_manager()->PickAccountIdForAccount(
account_info_2.gaia_id, account_info_2.email);
ASSERT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id_1));
ASSERT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id_2));
// Start first request.
dice_response_handler_->ProcessDiceHeader(
dice_params_1, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer_1 = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer_1, testing::NotNull());
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(0, reconcilor_unblocked_count_);
// Start second request.
dice_response_handler_->ProcessDiceHeader(
dice_params_2, std::make_unique<TestProcessDiceHeaderDelegate>(this));
GaiaAuthConsumer* consumer_2 = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer_2, testing::NotNull());
// Simulate GaiaAuthFetcher success for the first request.
consumer_1->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/true, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id_1));
EXPECT_TRUE(identity_manager()
->FindExtendedAccountInfoByAccountId(account_id_1)
.is_under_advanced_protection);
// Simulate GaiaAuthFetcher success for the second request.
consumer_2->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id_2));
EXPECT_FALSE(identity_manager()
->FindExtendedAccountInfoByAccountId(account_id_2)
.is_under_advanced_protection);
// Check that the reconcilor was blocked and unblocked exactly once.
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(1, reconcilor_unblocked_count_);
}
// Checks that a ENABLE_SYNC action received after the refresh token is added
// to the token service, triggers a call to enable sync on the delegate.
TEST_F(DiceResponseHandlerTest, SigninEnableSyncAfterRefreshTokenFetched) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
ASSERT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
// Simulate GaiaAuthFetcher success.
consumer->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
// Check HandleTokenExchangeSuccess parameters.
EXPECT_EQ(token_exchange_account_id_, account_id);
EXPECT_TRUE(token_exchange_is_new_account_);
// Check that delegate was not called to enable sync.
EXPECT_TRUE(enable_sync_account_info_.IsEmpty());
// Enable sync.
dice_response_handler_->ProcessDiceHeader(
MakeDiceParams(DiceAction::ENABLE_SYNC),
std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that delegate was called to enable sync.
EXPECT_EQ(account_info.gaia_id, enable_sync_account_info_.gaia);
EXPECT_EQ(account_info.email, enable_sync_account_info_.email);
}
// Checks that a ENABLE_SYNC action received before the refresh token is added
// to the token service, is schedules a call to enable sync on the delegate
// once the refresh token is received.
TEST_F(DiceResponseHandlerTest, SigninEnableSyncBeforeRefreshTokenFetched) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
ASSERT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
// Enable sync.
dice_response_handler_->ProcessDiceHeader(
MakeDiceParams(DiceAction::ENABLE_SYNC),
std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that delegate was not called to enable sync.
EXPECT_TRUE(enable_sync_account_info_.IsEmpty());
// Simulate GaiaAuthFetcher success.
consumer->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/false));
// Check that the token has been inserted in the token service.
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id));
// Check HandleTokenExchangeSuccess parameters.
EXPECT_EQ(token_exchange_account_id_, account_id);
EXPECT_TRUE(token_exchange_is_new_account_);
// Check that delegate was called to enable sync.
EXPECT_EQ(account_info.gaia_id, enable_sync_account_info_.gaia);
EXPECT_EQ(account_info.email, enable_sync_account_info_.email);
}
TEST_F(DiceResponseHandlerTest, Timeout) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
ASSERT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
EXPECT_EQ(
1u, dice_response_handler_->GetPendingDiceTokenFetchersCountForTesting());
// Force a timeout.
task_environment_.FastForwardBy(
base::Seconds(kDiceTokenFetchTimeoutSeconds + 1));
EXPECT_EQ(
0u, dice_response_handler_->GetPendingDiceTokenFetchersCountForTesting());
// Check that the token has not been inserted in the token service.
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
// Check that the reconcilor was blocked and unblocked exactly once.
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(1, reconcilor_unblocked_count_);
}
// Checks that there is no crash if the DiceResponseHandler is deleted before
// the timeout expires. Tests the scenario from https://crbug.com/1290214
TEST_F(DiceResponseHandlerTest, DeleteBeforeTimeout) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNIN);
const auto& account_info = dice_params.signin_info->account_info;
CoreAccountId account_id = identity_manager()->PickAccountIdForAccount(
account_info.gaia_id, account_info.email);
ASSERT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
dice_response_handler_->ProcessDiceHeader(
dice_params, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created.
GaiaAuthConsumer* consumer = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer, testing::NotNull());
EXPECT_EQ(
1u, dice_response_handler_->GetPendingDiceTokenFetchersCountForTesting());
// Delete the handler.
dice_response_handler_.reset();
// Force a timeout, this should not crash.
task_environment_.FastForwardBy(
base::Seconds(kDiceTokenFetchTimeoutSeconds + 1));
// Check that the token has not been inserted in the token service.
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id));
// Check that the reconcilor was blocked and unblocked exactly once.
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(1, reconcilor_unblocked_count_);
}
TEST_F(DiceResponseHandlerTest, SignoutSyncPrimaryAccount) {
// Setup.
// Configure Dice params.
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNOUT);
const char kSecondarySignedOutEmail[] = "secondary_signed_out@gmail.com";
dice_params.signout_info->account_infos.push_back(
GetDiceResponseParamsAccountInfo(kSecondarySignedOutEmail));
const std::string dice_primary_account_email =
dice_params.signout_info->account_infos[0].email;
// Configure Chrome.
AccountInfo primary_account = identity_test_env_.MakePrimaryAccountAvailable(
dice_primary_account_email, signin::ConsentLevel::kSync);
AccountInfo secondary_signed_out =
identity_test_env_.MakeAccountAvailable(kSecondarySignedOutEmail);
AccountInfo secondary_not_signed_out =
identity_test_env_.MakeAccountAvailable("other@gmail.com");
EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 3U);
EXPECT_TRUE(
identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSync));
// Receive signout response including sync and secondary account.
RunSignoutTest(dice_params, {secondary_not_signed_out.account_id},
primary_account.account_id, /*invalid_primary_account=*/true);
}
TEST_F(DiceResponseHandlerTest, SignoutSigninPrimaryAccount) {
// Setup.
// Configure Dice params.
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNOUT);
const char kSecondarySignedOutEmail[] = "secondary_signed_out@gmail.com";
dice_params.signout_info->account_infos.push_back(
GetDiceResponseParamsAccountInfo(kSecondarySignedOutEmail));
const std::string dice_primary_account_email =
dice_params.signout_info->account_infos[0].email;
// Configure Chrome.
AccountInfo primary_account = identity_test_env_.MakePrimaryAccountAvailable(
dice_primary_account_email, signin::ConsentLevel::kSignin);
AccountInfo secondary_signed_out =
identity_test_env_.MakeAccountAvailable(kSecondarySignedOutEmail);
AccountInfo secondary_not_signed_out =
identity_test_env_.MakeAccountAvailable("other@gmail.com");
EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 3U);
EXPECT_TRUE(
identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
// Receive signout response including primary and secondary account.
if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled()) {
RunSignoutTest(dice_params, {secondary_not_signed_out.account_id},
primary_account.account_id,
/*invalid_primary_account=*/true);
} else {
RunSignoutTest(dice_params, {secondary_not_signed_out.account_id},
/*primary_account=*/CoreAccountId(),
/*invalid_primary_account=*/false);
}
}
TEST_F(DiceResponseHandlerTest, SignoutSecondaryAccount) {
const char kPrimaryAccount[] = "main@gmail.com";
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNOUT);
const std::string secondary_account_email =
dice_params.signout_info->account_infos[0].email;
// User is signed in to Chrome, and has some refresh token for a secondary
// account.
AccountInfo primary_account_info =
identity_test_env_.MakePrimaryAccountAvailable(
kPrimaryAccount, signin::ConsentLevel::kSync);
AccountInfo secondary_account_info =
identity_test_env_.MakeAccountAvailable(secondary_account_email);
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
secondary_account_info.account_id));
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
primary_account_info.account_id));
EXPECT_TRUE(
identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSync));
// Receive signout response for the secondary account.
RunSignoutTest(dice_params, {}, primary_account_info.account_id,
/*invalid_primary_account=*/false);
}
TEST_F(DiceResponseHandlerTest, SignoutWebOnly) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNOUT);
const auto& dice_account_info = dice_params.signout_info->account_infos[0];
// User is NOT signed in to Chrome, and has some refresh tokens for two
// accounts.
AccountInfo account_info =
identity_test_env_.MakeAccountAvailable(dice_account_info.email);
AccountInfo secondary_account_info =
identity_test_env_.MakeAccountAvailable("other@gmail.com");
EXPECT_TRUE(
identity_manager()->HasAccountWithRefreshToken(account_info.account_id));
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
secondary_account_info.account_id));
EXPECT_FALSE(
identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSync));
// Receive signout response.
RunSignoutTest(dice_params, {secondary_account_info.account_id},
/*primary_account=*/CoreAccountId(),
/*invalid_primary_account=*/false);
}
// Checks that signin in progress is canceled by a signout.
TEST_F(DiceResponseHandlerTest, SigninSignoutSameAccount) {
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNOUT);
const auto& dice_account_info = dice_params.signout_info->account_infos[0];
// User is signed in to Chrome.
AccountInfo account_info = identity_test_env_.MakePrimaryAccountAvailable(
dice_account_info.email, signin::ConsentLevel::kSync);
EXPECT_TRUE(
identity_manager()->HasAccountWithRefreshToken(account_info.account_id));
EXPECT_FALSE(
identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
account_info.account_id));
// Start Dice signin (reauth).
DiceResponseParams dice_params_2 = MakeDiceParams(DiceAction::SIGNIN);
dice_response_handler_->ProcessDiceHeader(
dice_params_2, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that a GaiaAuthFetcher has been created and is pending.
ASSERT_THAT(signin_client_.GetAndClearConsumer(), testing::NotNull());
EXPECT_EQ(
1u, dice_response_handler_->GetPendingDiceTokenFetchersCountForTesting());
// Signout while signin is in flight.
RunSignoutTest(dice_params, {}, account_info.account_id,
/*invalid_primary_account=*/true);
// Check that the token fetcher has been canceled and the token is invalid.
EXPECT_EQ(
0u, dice_response_handler_->GetPendingDiceTokenFetchersCountForTesting());
}
// Checks that signin in progress is not canceled by a signout for a different
// account.
TEST_F(DiceResponseHandlerTest, SigninSignoutDifferentAccount) {
// User starts signin in the web with two accounts.
DiceResponseParams signout_params_1 = MakeDiceParams(DiceAction::SIGNOUT);
DiceResponseParams signin_params_1 = MakeDiceParams(DiceAction::SIGNIN);
DiceResponseParams signin_params_2 = MakeDiceParams(DiceAction::SIGNIN);
signin_params_2.signin_info->account_info.email = "other_email";
signin_params_2.signin_info->account_info.gaia_id = "other_gaia_id";
const auto& signin_account_info_1 = signin_params_1.signin_info->account_info;
const auto& signin_account_info_2 = signin_params_2.signin_info->account_info;
CoreAccountId account_id_1 = identity_manager()->PickAccountIdForAccount(
signin_account_info_1.gaia_id, signin_account_info_1.email);
CoreAccountId account_id_2 = identity_manager()->PickAccountIdForAccount(
signin_account_info_2.gaia_id, signin_account_info_2.email);
dice_response_handler_->ProcessDiceHeader(
signin_params_1, std::make_unique<TestProcessDiceHeaderDelegate>(this));
GaiaAuthConsumer* consumer_1 = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer_1, testing::NotNull());
dice_response_handler_->ProcessDiceHeader(
signin_params_2, std::make_unique<TestProcessDiceHeaderDelegate>(this));
GaiaAuthConsumer* consumer_2 = signin_client_.GetAndClearConsumer();
ASSERT_THAT(consumer_2, testing::NotNull());
EXPECT_EQ(
2u, dice_response_handler_->GetPendingDiceTokenFetchersCountForTesting());
ASSERT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id_1));
ASSERT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id_2));
ASSERT_FALSE(
identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
account_id_1));
ASSERT_FALSE(
identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
account_id_2));
// Signout from one of the accounts while signin is in flight.
dice_response_handler_->ProcessDiceHeader(
signout_params_1, std::make_unique<TestProcessDiceHeaderDelegate>(this));
// Check that one of the fetchers is cancelled.
EXPECT_EQ(
1u, dice_response_handler_->GetPendingDiceTokenFetchersCountForTesting());
// Allow the remaining fetcher to complete.
consumer_2->OnClientOAuthSuccess(GaiaAuthConsumer::ClientOAuthResult(
"refresh_token", "access_token", 10, /*is_child_account=*/false,
/*is_under_advanced_protection=*/false, /*is_bound_to_key=*/false));
EXPECT_EQ(
0u, dice_response_handler_->GetPendingDiceTokenFetchersCountForTesting());
// Check that the right token is available.
EXPECT_FALSE(identity_manager()->HasAccountWithRefreshToken(account_id_1));
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(account_id_2));
EXPECT_FALSE(
identity_manager()->HasAccountWithRefreshTokenInPersistentErrorState(
account_id_2));
}
TEST_F(DiceResponseHandlerTest,
SignoutPrimaryNonSyncAccountWithSignoutRestrictions) {
signin_client_.set_is_clear_primary_account_allowed_for_testing(
SigninClient::SignoutDecision::CLEAR_PRIMARY_ACCOUNT_DISALLOWED);
const char kSecondaryEmail[] = "other@gmail.com";
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNOUT);
dice_params.signout_info->account_infos.push_back(
GetDiceResponseParamsAccountInfo(kSecondaryEmail));
const auto& dice_account_info = dice_params.signout_info->account_infos[0];
AccountInfo primary_account = identity_test_env_.MakePrimaryAccountAvailable(
dice_account_info.email, signin::ConsentLevel::kSignin);
AccountInfo secondary_account_info =
identity_test_env_.MakeAccountAvailable(kSecondaryEmail);
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
primary_account.account_id));
EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
secondary_account_info.account_id));
EXPECT_FALSE(
identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSync));
EXPECT_TRUE(
identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
// Receive signout response.
RunSignoutTest(dice_params, {}, primary_account.account_id,
/*invalid_primary_account=*/true);
// Check that the reconcilor was not blocked.
EXPECT_EQ(0, reconcilor_blocked_count_);
EXPECT_EQ(0, reconcilor_unblocked_count_);
}
class ExplicitBrowserSigninDiceResponseHandlerSignoutTest
: public DiceResponseHandlerTest {
public:
ExplicitBrowserSigninDiceResponseHandlerSignoutTest() {
feature_list_.InitAndEnableFeature(
switches::kExplicitBrowserSigninUIOnDesktop);
}
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(ExplicitBrowserSigninDiceResponseHandlerSignoutTest,
SignoutSigninPrimaryAccount) {
// Setup.
// Configure Dice params.
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNOUT);
const char kSecondarySignedOutEmail[] = "secondary_signed_out@gmail.com";
dice_params.signout_info->account_infos.push_back(
GetDiceResponseParamsAccountInfo(kSecondarySignedOutEmail));
const std::string dice_primary_account_email =
dice_params.signout_info->account_infos[0].email;
// Configure Chrome.
AccountInfo primary_account = identity_test_env_.MakePrimaryAccountAvailable(
dice_primary_account_email, signin::ConsentLevel::kSignin);
identity_test_env_.MakeAccountAvailable(kSecondarySignedOutEmail);
AccountInfo secondary_not_signed_out =
identity_test_env_.MakeAccountAvailable("other@gmail.com");
EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 3U);
EXPECT_TRUE(
identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
// Receive signout response including primary and secondary account.
RunSignoutTest(dice_params, {secondary_not_signed_out.account_id},
primary_account.account_id,
/*invalid_primary_account=*/true);
}
TEST_F(ExplicitBrowserSigninDiceResponseHandlerSignoutTest,
SignoutImplicitPrimaryAccountSignin) {
// Setup.
// Configure Dice params.
DiceResponseParams dice_params = MakeDiceParams(DiceAction::SIGNOUT);
const char kSecondarySignedOutEmail[] = "secondary_signed_out@gmail.com";
dice_params.signout_info->account_infos.push_back(
GetDiceResponseParamsAccountInfo(kSecondarySignedOutEmail));
const std::string dice_primary_account_email =
dice_params.signout_info->account_infos[0].email;
// Configure Chrome.
AccountInfo primary_account = identity_test_env_.MakePrimaryAccountAvailable(
dice_primary_account_email, signin::ConsentLevel::kSignin);
// Mark as implicit sign in.
pref_service().SetBoolean(prefs::kExplicitBrowserSignin, false);
identity_test_env_.MakeAccountAvailable(kSecondarySignedOutEmail);
AccountInfo secondary_not_signed_out =
identity_test_env_.MakeAccountAvailable("other@gmail.com");
EXPECT_EQ(identity_manager()->GetAccountsWithRefreshTokens().size(), 3U);
EXPECT_TRUE(
identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
// Receive signout response including primary and secondary account.
RunSignoutTest(dice_params, {secondary_not_signed_out.account_id},
/*primary_account=*/CoreAccountId(),
/*invalid_primary_account=*/false);
}
} // namespace