blob: f79c4f9c14fe9d31d0c009a9fb7d3d2ea984c00c [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 <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/auto_reset.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_writer.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/apps/platform_apps/shortcut_manager.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/cookie_settings_factory.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/extensions/api/identity/web_auth_flow.h"
#include "chrome/browser/policy/cloud/user_policy_signin_service_internal.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/account_consistency_mode_manager.h"
#include "chrome/browser/signin/account_reconcilor_factory.h"
#include "chrome/browser/signin/chrome_device_id_helper.h"
#include "chrome/browser/signin/chrome_signin_client_test_util.h"
#include "chrome/browser/signin/chrome_signin_helper.h"
#include "chrome/browser/signin/dice_response_handler.h"
#include "chrome/browser/signin/dice_response_handler_factory.h"
#include "chrome/browser/signin/dice_web_signin_interceptor.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/sync/user_event_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/simple_message_box_internal.h"
#include "chrome/browser/ui/webui/signin/login_ui_service.h"
#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
#include "chrome/browser/ui/webui/signin/login_ui_test_utils.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/user_education/interactive_feature_promo_test.h"
#include "components/autofill/core/browser/data_manager/personal_data_manager.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/embedder_support/user_agent_utils.h"
#include "components/feature_engagement/public/feature_list.h"
#include "components/prefs/pref_service.h"
#include "components/search/ntp_features.h"
#include "components/signin/core/browser/account_reconcilor.h"
#include "components/signin/core/browser/dice_header_helper.h"
#include "components/signin/core/browser/signin_header_helper.h"
#include "components/signin/public/base/account_consistency_method.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/base/signin_buildflags.h"
#include "components/signin/public/base/signin_client.h"
#include "components/signin/public/base/signin_metrics.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_prefs.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_capabilities_test_mutator.h"
#include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/signin/public/identity_manager/primary_account_mutator.h"
#include "components/signin/public/identity_manager/signin_constants.h"
#include "components/sync/base/features.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_prefs.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "components/sync_user_events/user_event_service.h"
#include "components/user_education/views/help_bubble_view.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_launcher.h"
#include "content/public/test/test_navigation_observer.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/gaia_switches.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
#include "crypto/scoped_mock_unexportable_key_provider.h"
#endif // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
using net::test_server::BasicHttpResponse;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;
using signin::AccountConsistencyMethod;
using signin::constants::kNoHostedDomainFound;
namespace {
constexpr int kAccountReconcilorDelayMs = 10;
enum SignoutType {
kSignoutTypeFirst = 0,
kAllAccounts = 0, // Sign out from all accounts.
kMainAccount = 1, // Sign out from main account only.
kSecondaryAccount = 2, // Sign out from secondary account only.
kSignoutTypeLast
};
const char kAuthorizationCode[] = "authorization_code";
const char kBoundTokenRegistrationJwt[] = "bound_token_registration_jwt";
const char kDiceResponseHeader[] = "X-Chrome-ID-Consistency-Response";
const char kChromeSyncEndpointURL[] = "/signin/chrome/sync";
const char kEnableSyncURL[] = "/enable_sync";
const char kGoogleSignoutResponseHeader[] = "Google-Accounts-SignOut";
const char kMainGmailEmail[] = "main_email@gmail.com";
const char kMainManagedEmail[] = "main_email@managed.com";
const char kNoDiceRequestHeader[] = "NoDiceHeader";
const char kOAuth2TokenExchangeURL[] = "/oauth2/v4/token";
const char kOAuth2TokenRevokeURL[] = "/o/oauth2/revoke";
const char kSecondaryEmail[] = "secondary_email@example.com";
const char kSigninURL[] = "/signin";
const char kSigninWithOutageInDiceURL[] = "/signin/outage";
const char kSignoutURL[] = "/signout";
// Test response that does not complete synchronously. It must be unblocked by
// calling the completion closure.
class BlockedHttpResponse : public net::test_server::BasicHttpResponse {
public:
explicit BlockedHttpResponse(
base::OnceCallback<void(base::OnceClosure)> callback)
: callback_(std::move(callback)) {}
void SendResponse(
base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) override {
// Called on the IO thread to unblock the response.
base::OnceClosure unblock_response =
base::BindOnce(&BlockedHttpResponse::SendResponseInternal,
weak_factory_.GetWeakPtr(), delegate);
// Bind the callback to the current sequence to ensure invoking `Run()` from
// any thread will run the callback on the current sequence.
base::OnceClosure unblock_from_any_thread =
base::BindPostTaskToCurrentDefault(std::move(unblock_response));
// Pass |unblock_any_thread| to the caller on the UI thread.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_),
std::move(unblock_from_any_thread)));
}
private:
void SendResponseInternal(
base::WeakPtr<net::test_server::HttpResponseDelegate> delegate) {
if (delegate) {
BasicHttpResponse::SendResponse(delegate);
}
}
base::OnceCallback<void(base::OnceClosure)> callback_;
base::WeakPtrFactory<BlockedHttpResponse> weak_factory_{this};
};
void AddCanShowHistorySyncOptInsWithoutMinorModeCapability(
signin::IdentityManager* identity_manager) {
CoreAccountInfo core_account_info =
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
AccountInfo account_info =
identity_manager->FindExtendedAccountInfo(core_account_info);
// Triggers immediate drawing of sync-consent button. Without that, screens
// would be delayed to give chances for capabilities to load and then
// present minor-safe screen; but the sync button is present on the screen
// for the duration of that load (just invisible and not clickable), which
// is difficult to be expressed in those tests without examining CSS.
AccountCapabilitiesTestMutator mutator(&account_info.capabilities);
mutator.set_can_show_history_sync_opt_ins_without_minor_mode_restrictions(
true);
signin::UpdateAccountInfoForAccount(identity_manager, account_info);
}
} // namespace
namespace FakeGaia {
// Handler for the signin page on the embedded test server.
// The response has the content of the Dice request header in its body, and has
// the Dice response header.
// Handles both the "Chrome Sync" endpoint and the old endpoint.
std::unique_ptr<HttpResponse> HandleSigninURL(
const std::string& main_email,
const base::RepeatingCallback<void(const std::string&)>& callback,
const HttpRequest& request) {
if (!net::test_server::ShouldHandle(request, kSigninURL) &&
!net::test_server::ShouldHandle(request, kChromeSyncEndpointURL) &&
!net::test_server::ShouldHandle(request, kSigninWithOutageInDiceURL)) {
return nullptr;
}
// Extract Dice request header.
std::string header_value = kNoDiceRequestHeader;
auto it = request.headers.find(signin::kDiceRequestHeader);
if (it != request.headers.end()) {
header_value = it->second;
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(callback, header_value));
// Add the SIGNIN dice header.
std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
if (header_value != kNoDiceRequestHeader) {
if (net::test_server::ShouldHandle(request, kSigninWithOutageInDiceURL)) {
http_response->AddCustomHeader(
kDiceResponseHeader,
base::StringPrintf("action=SIGNIN,authuser=1,id=%s,email=%s,"
"no_authorization_code=true",
signin::GetTestGaiaIdForEmail(main_email).c_str(),
main_email.c_str()));
} else {
http_response->AddCustomHeader(
kDiceResponseHeader,
base::StringPrintf(
"action=SIGNIN,authuser=1,id=%s,email=%s,authorization_code=%s,"
"eligible_for_token_binding=ES256 RS256",
signin::GetTestGaiaIdForEmail(main_email).c_str(),
main_email.c_str(), kAuthorizationCode));
}
}
// When hitting the Chrome Sync endpoint, redirect to kEnableSyncURL, which
// adds the ENABLE_SYNC dice header.
if (net::test_server::ShouldHandle(request, kChromeSyncEndpointURL)) {
http_response->set_code(net::HTTP_FOUND); // 302 redirect.
http_response->AddCustomHeader("location", kEnableSyncURL);
}
http_response->AddCustomHeader("Cache-Control", "no-store");
return std::move(http_response);
}
// Handler for the Gaia endpoint adding the ENABLE_SYNC dice header.
std::unique_ptr<HttpResponse> HandleEnableSyncURL(
const std::string& main_email,
const base::RepeatingCallback<void(base::OnceClosure)>& callback,
const HttpRequest& request) {
if (!net::test_server::ShouldHandle(request, kEnableSyncURL)) {
return nullptr;
}
std::unique_ptr<BlockedHttpResponse> http_response =
std::make_unique<BlockedHttpResponse>(callback);
http_response->AddCustomHeader(
kDiceResponseHeader,
base::StringPrintf("action=ENABLE_SYNC,authuser=1,id=%s,email=%s",
signin::GetTestGaiaIdForEmail(main_email).c_str(),
main_email.c_str()));
http_response->AddCustomHeader("Cache-Control", "no-store");
return std::move(http_response);
}
// Handler for the signout page on the embedded test server.
// Responds with a Google-Accounts-SignOut header for the main account, the
// secondary account, or both (depending on the SignoutType, which is encoded in
// the query string).
std::unique_ptr<HttpResponse> HandleSignoutURL(const std::string& main_email,
const HttpRequest& request) {
if (!net::test_server::ShouldHandle(request, kSignoutURL)) {
return nullptr;
}
// Build signout header.
int query_value;
EXPECT_TRUE(base::StringToInt(request.GetURL().query(), &query_value));
SignoutType signout_type = static_cast<SignoutType>(query_value);
EXPECT_GE(signout_type, kSignoutTypeFirst);
EXPECT_LT(signout_type, kSignoutTypeLast);
std::string signout_header_value;
if (signout_type == kAllAccounts || signout_type == kMainAccount) {
std::string main_gaia_id = signin::GetTestGaiaIdForEmail(main_email);
signout_header_value =
base::StringPrintf("email=\"%s\", obfuscatedid=\"%s\", sessionindex=1",
main_email.c_str(), main_gaia_id.c_str());
}
if (signout_type == kAllAccounts || signout_type == kSecondaryAccount) {
if (!signout_header_value.empty()) {
signout_header_value += ", ";
}
std::string secondary_gaia_id =
signin::GetTestGaiaIdForEmail(kSecondaryEmail);
signout_header_value +=
base::StringPrintf("email=\"%s\", obfuscatedid=\"%s\", sessionindex=2",
kSecondaryEmail, secondary_gaia_id.c_str());
}
std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
http_response->AddCustomHeader(kGoogleSignoutResponseHeader,
signout_header_value);
http_response->AddCustomHeader("Cache-Control", "no-store");
return std::move(http_response);
}
// Handler for OAuth2 token exchange.
// Checks that the request is well formatted and returns a refresh token in a
// JSON dictionary.
std::unique_ptr<HttpResponse> HandleOAuth2TokenExchangeURL(
const base::RepeatingCallback<void(base::OnceClosure)>& callback,
const HttpRequest& request) {
if (!net::test_server::ShouldHandle(request, kOAuth2TokenExchangeURL)) {
return nullptr;
}
// Check that the authorization code is somewhere in the request body.
if (!request.has_content) {
return nullptr;
}
if (request.content.find(kAuthorizationCode) == std::string::npos) {
return nullptr;
}
std::unique_ptr<BlockedHttpResponse> http_response =
std::make_unique<BlockedHttpResponse>(callback);
base::Value::Dict response = base::Value::Dict()
.Set("access_token", "access_token")
.Set("refresh_token", "new_refresh_token")
.Set("expires_in", 9999);
// If the request contains binding registration token, include successful
// binding result in the response and verify that the client passed the
// version information in the headers.
if (request.content.find(kBoundTokenRegistrationJwt) != std::string::npos) {
response.Set("refresh_token_type", "bound_to_key");
std::optional<std::string> version_header_value;
if (auto it = request.headers.find("Sec-CH-UA-Full-Version-List");
it != request.headers.end()) {
version_header_value = it->second;
}
EXPECT_EQ(version_header_value, embedder_support::GetUserAgentMetadata()
.SerializeBrandFullVersionList());
}
http_response->set_content(*base::WriteJson(response));
http_response->set_content_type("text/plain");
http_response->AddCustomHeader("Cache-Control", "no-store");
return std::move(http_response);
}
// Handler for OAuth2 token revocation.
std::unique_ptr<HttpResponse> HandleOAuth2TokenRevokeURL(
const base::RepeatingClosure& callback,
const HttpRequest& request) {
if (!net::test_server::ShouldHandle(request, kOAuth2TokenRevokeURL)) {
return nullptr;
}
content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, callback);
std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
http_response->AddCustomHeader("Cache-Control", "no-store");
return std::move(http_response);
}
// Handler for ServiceLogin on the embedded test server.
// Calls the callback with the dice request header, or kNoDiceRequestHeader if
// there is no Dice header.
std::unique_ptr<HttpResponse> HandleChromeSigninEmbeddedURL(
const base::RepeatingCallback<void(const std::string&)>& callback,
const HttpRequest& request) {
if (!net::test_server::ShouldHandle(request,
"/embedded/setup/chrome/usermenu")) {
return nullptr;
}
std::string dice_request_header(kNoDiceRequestHeader);
auto it = request.headers.find(signin::kDiceRequestHeader);
if (it != request.headers.end()) {
dice_request_header = it->second;
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(callback, dice_request_header));
std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse);
http_response->AddCustomHeader("Cache-Control", "no-store");
return std::move(http_response);
}
} // namespace FakeGaia
class DiceBrowserTest : public InProcessBrowserTest,
public AccountReconcilor::Observer,
public signin::IdentityManager::Observer {
public:
DiceBrowserTest(const DiceBrowserTest&) = delete;
DiceBrowserTest& operator=(const DiceBrowserTest&) = delete;
protected:
~DiceBrowserTest() override = default;
explicit DiceBrowserTest(const std::string& main_email = kMainGmailEmail)
: main_email_(main_email),
https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
enable_sync_requested_(false),
token_requested_(false),
refresh_token_available_(false),
token_revoked_notification_count_(0),
token_revoked_count_(0),
reconcilor_blocked_count_(0),
reconcilor_unblocked_count_(0),
reconcilor_started_count_(0) {
// TODO(b/320650075): Adapt tests for
// `switches::kExplicitBrowserSigninUIOnDesktop` is enabled.
feature_list_.InitAndDisableFeature(
switches::kExplicitBrowserSigninUIOnDesktop);
https_server_.RegisterDefaultHandler(base::BindRepeating(
&FakeGaia::HandleSigninURL, main_email_,
base::BindRepeating(&DiceBrowserTest::OnSigninRequest,
base::Unretained(this))));
https_server_.RegisterDefaultHandler(base::BindRepeating(
&FakeGaia::HandleEnableSyncURL, main_email_,
base::BindRepeating(&DiceBrowserTest::OnEnableSyncRequest,
base::Unretained(this))));
https_server_.RegisterDefaultHandler(
base::BindRepeating(&FakeGaia::HandleSignoutURL, main_email_));
https_server_.RegisterDefaultHandler(base::BindRepeating(
&FakeGaia::HandleOAuth2TokenExchangeURL,
base::BindRepeating(&DiceBrowserTest::OnTokenExchangeRequest,
base::Unretained(this))));
https_server_.RegisterDefaultHandler(base::BindRepeating(
&FakeGaia::HandleOAuth2TokenRevokeURL,
base::BindRepeating(&DiceBrowserTest::OnTokenRevocationRequest,
base::Unretained(this))));
https_server_.RegisterDefaultHandler(base::BindRepeating(
&FakeGaia::HandleChromeSigninEmbeddedURL,
base::BindRepeating(&DiceBrowserTest::OnChromeSigninEmbeddedRequest,
base::Unretained(this))));
signin::SetDiceAccountReconcilorBlockDelayForTesting(
kAccountReconcilorDelayMs);
}
// Navigates to the given path on the test server.
void NavigateToURL(const std::string& path) {
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), https_server_.GetURL(path)));
}
// Returns the identity manager.
signin::IdentityManager* GetIdentityManager() {
return IdentityManagerFactory::GetForProfile(browser()->profile());
}
// Returns the account ID associated with |main_email_| and its associated
// gaia ID.
CoreAccountId GetMainAccountID() {
return GetIdentityManager()->PickAccountIdForAccount(
signin::GetTestGaiaIdForEmail(main_email_), main_email_);
}
// Returns the account ID associated with kSecondaryEmail and its associated
// gaia ID.
CoreAccountId GetSecondaryAccountID() {
return GetIdentityManager()->PickAccountIdForAccount(
signin::GetTestGaiaIdForEmail(kSecondaryEmail), kSecondaryEmail);
}
std::string GetDeviceId() {
return GetSigninScopedDeviceIdForProfile(browser()->profile());
}
// Signin with a main account and add token for a secondary account.
void SetupSignedInAccounts(
signin::ConsentLevel primary_account_consent_level) {
// Signin main account.
AccountInfo primary_account_info = signin::MakePrimaryAccountAvailable(
GetIdentityManager(), main_email_, primary_account_consent_level);
ASSERT_TRUE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
ASSERT_FALSE(
GetIdentityManager()->HasAccountWithRefreshTokenInPersistentErrorState(
GetMainAccountID()));
ASSERT_EQ(GetMainAccountID(), GetIdentityManager()->GetPrimaryAccountId(
primary_account_consent_level));
// Add a token for a secondary account.
AccountInfo secondary_account_info =
signin::MakeAccountAvailable(GetIdentityManager(), kSecondaryEmail);
ASSERT_TRUE(GetIdentityManager()->HasAccountWithRefreshToken(
secondary_account_info.account_id));
ASSERT_FALSE(
GetIdentityManager()->HasAccountWithRefreshTokenInPersistentErrorState(
secondary_account_info.account_id));
}
// Navigate to a Gaia URL setting the Google-Accounts-SignOut header.
void SignOutWithDice(SignoutType signout_type) {
NavigateToURL(base::StringPrintf("%s?%i", kSignoutURL, signout_type));
EXPECT_EQ(1, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(1);
base::RunLoop().RunUntilIdle();
}
// InProcessBrowserTest:
void SetUp() override {
ASSERT_TRUE(https_server_.InitializeAndListen());
InProcessBrowserTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
const GURL& base_url = https_server_.base_url();
command_line->AppendSwitchASCII(switches::kGaiaUrl, base_url.spec());
command_line->AppendSwitchASCII(switches::kGoogleApisUrl, base_url.spec());
command_line->AppendSwitchASCII(switches::kLsoUrl, base_url.spec());
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
https_server_.StartAcceptingConnections();
identity_manager_observation_.Observe(GetIdentityManager());
// Wait for the token service to be ready.
if (!GetIdentityManager()->AreRefreshTokensLoaded()) {
WaitForClosure(&tokens_loaded_quit_closure_);
}
ASSERT_TRUE(GetIdentityManager()->AreRefreshTokensLoaded());
AccountReconcilor* reconcilor =
AccountReconcilorFactory::GetForProfile(browser()->profile());
// Reconcilor starts as soon as the token service finishes loading its
// credentials. Abort the reconcilor here to make sure tests start in a
// stable state.
reconcilor->AbortReconcile();
reconcilor->SetState(signin_metrics::AccountReconcilorState::kOk);
account_reconcilor_observation_.Observe(reconcilor);
}
void TearDownOnMainThread() override {
identity_manager_observation_.Reset();
account_reconcilor_observation_.Reset();
}
// Calls |closure| if it is not null and resets it after.
void RunClosureIfValid(base::OnceClosure closure) {
if (closure) {
std::move(closure).Run();
}
}
// Creates and runs a RunLoop until |closure| is called.
void WaitForClosure(base::OnceClosure* closure) {
base::RunLoop run_loop;
*closure = run_loop.QuitClosure();
run_loop.Run();
}
// FakeGaia callbacks:
void OnSigninRequest(const std::string& dice_request_header) {
EXPECT_EQ(dice_request_header != kNoDiceRequestHeader,
IsReconcilorBlocked());
dice_request_header_ = dice_request_header;
RunClosureIfValid(std::move(signin_requested_quit_closure_));
}
void OnChromeSigninEmbeddedRequest(const std::string& dice_request_header) {
dice_request_header_ = dice_request_header;
RunClosureIfValid(std::move(chrome_signin_embedded_quit_closure_));
}
void OnEnableSyncRequest(base::OnceClosure unblock_response_closure) {
EXPECT_TRUE(IsReconcilorBlocked());
enable_sync_requested_ = true;
RunClosureIfValid(std::move(enable_sync_requested_quit_closure_));
unblock_enable_sync_response_closure_ = std::move(unblock_response_closure);
}
void OnTokenExchangeRequest(base::OnceClosure unblock_response_closure) {
// The token must be exchanged only once.
EXPECT_FALSE(token_requested_);
EXPECT_TRUE(IsReconcilorBlocked());
token_requested_ = true;
RunClosureIfValid(std::move(token_requested_quit_closure_));
unblock_token_exchange_response_closure_ =
std::move(unblock_response_closure);
}
void OnTokenRevocationRequest() {
++token_revoked_count_;
RunClosureIfValid(std::move(token_revoked_quit_closure_));
}
// AccountReconcilor::Observer:
void OnBlockReconcile() override { ++reconcilor_blocked_count_; }
void OnUnblockReconcile() override {
++reconcilor_unblocked_count_;
RunClosureIfValid(std::move(unblock_count_quit_closure_));
}
void OnStateChanged(signin_metrics::AccountReconcilorState state) override {
if (state == signin_metrics::AccountReconcilorState::kRunning) {
++reconcilor_started_count_;
}
}
// signin::IdentityManager::Observer
void OnPrimaryAccountChanged(
const signin::PrimaryAccountChangeEvent& event) override {
if (event.GetEventTypeFor(signin::ConsentLevel::kSignin) ==
signin::PrimaryAccountChangeEvent::Type::kSet) {
RunClosureIfValid(std::move(on_primary_account_set_quit_closure_));
}
}
void OnRefreshTokenUpdatedForAccount(
const CoreAccountInfo& account_info) override {
if (account_info.account_id == GetMainAccountID()) {
refresh_token_available_ = true;
RunClosureIfValid(std::move(refresh_token_available_quit_closure_));
}
}
void OnRefreshTokenRemovedForAccount(
const CoreAccountId& account_id) override {
++token_revoked_notification_count_;
}
void OnRefreshTokensLoaded() override {
RunClosureIfValid(std::move(tokens_loaded_quit_closure_));
}
// Returns true if the account reconcilor is currently blocked.
bool IsReconcilorBlocked() {
EXPECT_GE(reconcilor_blocked_count_, reconcilor_unblocked_count_);
EXPECT_LE(reconcilor_blocked_count_, reconcilor_unblocked_count_ + 1);
return (reconcilor_unblocked_count_ + 1) == reconcilor_blocked_count_;
}
// Waits until |reconcilor_unblocked_count_| reaches |count|.
void WaitForReconcilorUnblockedCount(int count) {
if (reconcilor_unblocked_count_ == count) {
return;
}
ASSERT_EQ(count - 1, reconcilor_unblocked_count_);
// Wait for the timeout after the request is complete.
WaitForClosure(&unblock_count_quit_closure_);
EXPECT_EQ(count, reconcilor_unblocked_count_);
}
// Waits until the user consented at the `kSignin` level.
void WaitForSigninSucceeded() {
if (GetIdentityManager()
->GetPrimaryAccountId(signin::ConsentLevel::kSignin)
.empty()) {
WaitForClosure(&on_primary_account_set_quit_closure_);
}
AddCanShowHistorySyncOptInsWithoutMinorModeCapability(GetIdentityManager());
}
// Waits for the ENABLE_SYNC request to hit the server, and unblocks the
// response. If this is not called, ENABLE_SYNC will not be sent by the
// server.
// Note: this does not wait for the response to reach Chrome.
void SendEnableSyncResponse() {
if (!enable_sync_requested_) {
WaitForClosure(&enable_sync_requested_quit_closure_);
}
DCHECK(unblock_enable_sync_response_closure_);
std::move(unblock_enable_sync_response_closure_).Run();
}
// Waits until the token request is sent to the server, the response is
// received and the refresh token is available. If this is not called, the
// refresh token will not be sent by the server.
void SendRefreshTokenResponse() {
// Wait for the request hitting the server.
if (!token_requested_) {
WaitForClosure(&token_requested_quit_closure_);
}
EXPECT_TRUE(token_requested_);
// Unblock the server response.
DCHECK(unblock_token_exchange_response_closure_);
std::move(unblock_token_exchange_response_closure_).Run();
// Wait for the response coming back.
if (!refresh_token_available_) {
WaitForClosure(&refresh_token_available_quit_closure_);
}
EXPECT_TRUE(refresh_token_available_);
}
void WaitForTokenRevokedCount(int count) {
EXPECT_LE(token_revoked_count_, count);
while (token_revoked_count_ < count) {
WaitForClosure(&token_revoked_quit_closure_);
}
EXPECT_EQ(count, token_revoked_count_);
}
void CloseBrowser() {
identity_manager_observation_.Reset();
account_reconcilor_observation_.Reset();
CloseBrowserSynchronously(browser());
}
const std::string main_email_;
net::EmbeddedTestServer https_server_;
bool enable_sync_requested_;
bool token_requested_;
bool refresh_token_available_;
int token_revoked_notification_count_;
int token_revoked_count_;
int reconcilor_blocked_count_;
int reconcilor_unblocked_count_;
int reconcilor_started_count_;
std::string dice_request_header_;
base::ScopedObservation<signin::IdentityManager,
signin::IdentityManager::Observer>
identity_manager_observation_{this};
base::ScopedObservation<AccountReconcilor, AccountReconcilor::Observer>
account_reconcilor_observation_{this};
// Unblocks the server responses.
base::OnceClosure unblock_token_exchange_response_closure_;
base::OnceClosure unblock_enable_sync_response_closure_;
// Used for waiting asynchronous events.
base::OnceClosure enable_sync_requested_quit_closure_;
base::OnceClosure token_requested_quit_closure_;
base::OnceClosure token_revoked_quit_closure_;
base::OnceClosure refresh_token_available_quit_closure_;
base::OnceClosure chrome_signin_embedded_quit_closure_;
base::OnceClosure unblock_count_quit_closure_;
base::OnceClosure tokens_loaded_quit_closure_;
base::OnceClosure on_primary_account_set_quit_closure_;
base::OnceClosure signin_requested_quit_closure_;
private:
base::test::ScopedFeatureList feature_list_;
};
// Checks that signin on Gaia triggers the fetch for a refresh token.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, Signin) {
EXPECT_EQ(0, reconcilor_started_count_);
// Navigate to Gaia and sign in.
NavigateToURL(kSigninURL);
// Check that the Dice request header was sent.
std::string client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
EXPECT_EQ(base::StringPrintf("version=%s,client_id=%s,device_id=%s,"
"signin_mode=all_accounts,"
"signout_mode=show_confirmation",
signin::kDiceProtocolVersion, client_id.c_str(),
GetDeviceId().c_str()),
dice_request_header_);
base::HistogramTester histogram_tester;
// Check that the token was requested and added to the token service.
SendRefreshTokenResponse();
EXPECT_TRUE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
// Sync should not be enabled.
EXPECT_TRUE(GetIdentityManager()
->GetPrimaryAccountId(signin::ConsentLevel::kSync)
.empty());
// Make sure we are recording this value for the Control group of the
// `switches::kExplicitBrowserSigninUIOnDesktop` experiment.
ASSERT_FALSE(switches::IsExplicitBrowserSigninUIOnDesktopEnabled());
histogram_tester.ExpectUniqueSample(
"Signin.Intercept.Heuristic.ShouldShowChromeSigninBubbleWithReason",
ShouldShowChromeSigninBubbleWithReason::kShouldShow, 1);
EXPECT_EQ(1, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(1);
EXPECT_EQ(1, reconcilor_started_count_);
}
#if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
class DiceBrowserTestWithBoundSessionCredentialsEnabled
: public DiceBrowserTest {
private:
base::test::ScopedFeatureList scoped_feature_list_{
switches::kEnableChromeRefreshTokenBinding};
crypto::ScopedMockUnexportableKeyProvider mock_key_provider_;
};
// Checks that signin on Gaia triggers the fetch for a refresh token.
IN_PROC_BROWSER_TEST_F(DiceBrowserTestWithBoundSessionCredentialsEnabled,
SigninWithTokenBinding) {
// Navigate to Gaia and sign in.
NavigateToURL(kSigninURL);
// Check that the bound token was requested and added to the token service.
SendRefreshTokenResponse();
EXPECT_TRUE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
EXPECT_FALSE(
GetIdentityManager()
->GetWrappedBindingKeyOfRefreshTokenForAccount(GetMainAccountID())
.empty());
}
#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.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, SupportOAuthOutageInDice) {
DiceResponseHandler* dice_response_handler =
DiceResponseHandlerFactory::GetForProfile(browser()->profile());
scoped_refptr<base::TestMockTimeTaskRunner> task_runner =
new base::TestMockTimeTaskRunner();
dice_response_handler->SetTaskRunner(task_runner);
NavigateToURL(kSigninWithOutageInDiceURL);
// Check that the Dice request header was sent.
std::string client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
EXPECT_EQ(base::StringPrintf("version=%s,client_id=%s,device_id=%s,"
"signin_mode=all_accounts,"
"signout_mode=show_confirmation",
signin::kDiceProtocolVersion, client_id.c_str(),
GetDeviceId().c_str()),
dice_request_header_);
// Check that the reconcilor was blocked and not unblocked before timeout.
EXPECT_EQ(1, reconcilor_blocked_count_);
EXPECT_EQ(0, reconcilor_unblocked_count_);
task_runner->FastForwardBy(
base::Hours(kLockAccountReconcilorTimeoutHours / 2));
EXPECT_EQ(0, reconcilor_unblocked_count_);
task_runner->FastForwardBy(
base::Hours((kLockAccountReconcilorTimeoutHours + 1) / 2));
// Wait until reconcilor is unblocked.
WaitForReconcilorUnblockedCount(1);
}
// Checks that re-auth on Gaia triggers the fetch for a refresh token.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, Reauth) {
EXPECT_EQ(0, reconcilor_started_count_);
// Start from a signed-in state.
SetupSignedInAccounts(signin::ConsentLevel::kSync);
EXPECT_EQ(1, reconcilor_started_count_);
// Navigate to Gaia and sign in again with the main account.
NavigateToURL(kSigninURL);
// Check that the Dice request header was sent.
std::string client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
EXPECT_EQ(base::StringPrintf("version=%s,client_id=%s,device_id=%s,"
"signin_mode=all_accounts,"
"signout_mode=show_confirmation",
signin::kDiceProtocolVersion, client_id.c_str(),
GetDeviceId().c_str()),
dice_request_header_);
// Check that the token was requested and added to the token service.
SendRefreshTokenResponse();
EXPECT_EQ(GetMainAccountID(), GetIdentityManager()->GetPrimaryAccountId(
signin::ConsentLevel::kSync));
// Old token must not be revoked (see http://crbug.com/865189).
EXPECT_EQ(0, token_revoked_notification_count_);
EXPECT_EQ(1, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(1);
EXPECT_EQ(2, reconcilor_started_count_);
}
// Checks that the Dice signout flow works and deletes all tokens.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, SignoutMainAccount) {
// Start from a signed-in state.
SetupSignedInAccounts(signin::ConsentLevel::kSync);
// Signout from main account.
SignOutWithDice(kMainAccount);
// Check that the user is in error state.
EXPECT_EQ(GetMainAccountID(), GetIdentityManager()->GetPrimaryAccountId(
signin::ConsentLevel::kSync));
EXPECT_TRUE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
EXPECT_TRUE(
GetIdentityManager()->HasAccountWithRefreshTokenInPersistentErrorState(
GetMainAccountID()));
EXPECT_TRUE(GetIdentityManager()->HasAccountWithRefreshToken(
GetSecondaryAccountID()));
// Token for main account is revoked on server but not notified in the client.
EXPECT_EQ(0, token_revoked_notification_count_);
WaitForTokenRevokedCount(1);
EXPECT_EQ(1, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(1);
}
// Checks that signing out from a secondary account does not delete the main
// token.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, SignoutSecondaryAccount) {
// Start from a signed-in state.
SetupSignedInAccounts(signin::ConsentLevel::kSync);
// Signout from secondary account.
SignOutWithDice(kSecondaryAccount);
// Check that the user is still signed in from main account, but secondary
// token is deleted.
EXPECT_EQ(GetMainAccountID(), GetIdentityManager()->GetPrimaryAccountId(
signin::ConsentLevel::kSync));
EXPECT_TRUE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
EXPECT_FALSE(GetIdentityManager()->HasAccountWithRefreshToken(
GetSecondaryAccountID()));
EXPECT_EQ(1, token_revoked_notification_count_);
WaitForTokenRevokedCount(1);
EXPECT_EQ(1, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(1);
}
// Checks that the Dice signout flow works and deletes all tokens.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, SignoutAllAccounts) {
// Start from a signed-in state.
SetupSignedInAccounts(signin::ConsentLevel::kSync);
// Signout from all accounts.
SignOutWithDice(kAllAccounts);
// Check that the user is in error state.
EXPECT_EQ(GetMainAccountID(), GetIdentityManager()->GetPrimaryAccountId(
signin::ConsentLevel::kSync));
EXPECT_TRUE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
EXPECT_TRUE(
GetIdentityManager()->HasAccountWithRefreshTokenInPersistentErrorState(
GetMainAccountID()));
EXPECT_FALSE(GetIdentityManager()->HasAccountWithRefreshToken(
GetSecondaryAccountID()));
// Token for main account is revoked on server but not notified in the client.
EXPECT_EQ(1, token_revoked_notification_count_);
WaitForTokenRevokedCount(2);
EXPECT_EQ(1, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(1);
}
// Checks that the Dice signout flow works and deletes all tokens.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, RevokeSyncAccountInAuthErrorState) {
// Start from a signed-in state.
SetupSignedInAccounts(signin::ConsentLevel::kSync);
// Signout from main account.
SignOutWithDice(kMainAccount);
// Check that the user is in error state.
ASSERT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSync));
ASSERT_TRUE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
ASSERT_TRUE(
GetIdentityManager()->HasAccountWithRefreshTokenInPersistentErrorState(
GetMainAccountID()));
// Revoking the sync consent should clear the primary account as it is in
// an permanent auth error state.
RevokeSyncConsent(GetIdentityManager());
// Updating the primary is done asynchronously. Wait for the update to happen.
WaitForPrimaryAccount(GetIdentityManager(), signin::ConsentLevel::kSignin,
CoreAccountId());
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
}
// Checks that Dice request header is not set from request from WebUI.
// See https://crbug.com/428396
#if BUILDFLAG(IS_WIN)
#define MAYBE_NoDiceFromWebUI DISABLED_NoDiceFromWebUI
#else
#define MAYBE_NoDiceFromWebUI NoDiceFromWebUI
#endif
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, MAYBE_NoDiceFromWebUI) {
// Navigate to Gaia and from the native tab, which uses an extension.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GURL("chrome:chrome-signin?reason=5")));
// Check that the request had no Dice request header.
if (dice_request_header_.empty()) {
WaitForClosure(&chrome_signin_embedded_quit_closure_);
}
EXPECT_EQ(kNoDiceRequestHeader, dice_request_header_);
EXPECT_EQ(0, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(0);
}
// Tests that Sync is enabled if the ENABLE_SYNC response is received after the
// refresh token.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, EnableSyncAfterToken) {
base::HistogramTester histogram_tester;
EXPECT_EQ(0, reconcilor_started_count_);
// Signin using the Chrome Sync endpoint.
signin_metrics::AccessPoint access_point =
signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS;
browser()->signin_view_controller()->ShowDiceEnableSyncTab(
access_point,
signin_metrics::PromoAction::PROMO_ACTION_NEW_ACCOUNT_NO_EXISTING_ACCOUNT,
/*email_hint=*/std::string());
// Receive token.
EXPECT_FALSE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
SendRefreshTokenResponse();
EXPECT_TRUE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
// Receive ENABLE_SYNC.
SendEnableSyncResponse();
// Check that the Dice request header was sent, with signout confirmation.
std::string client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
EXPECT_EQ(base::StringPrintf("version=%s,client_id=%s,device_id=%s,"
"signin_mode=all_accounts,"
"signout_mode=show_confirmation",
signin::kDiceProtocolVersion, client_id.c_str(),
GetDeviceId().c_str()),
dice_request_header_);
content::WebContents* tab_contents =
browser()->tab_strip_model()->GetActiveWebContents();
base::RunLoop ntp_run_loop;
content::DidFinishNavigationObserver ntp_url_observer(
tab_contents,
base::BindLambdaForTesting(
[&ntp_run_loop](content::NavigationHandle* navigation_handle) {
const GURL& url = navigation_handle->GetURL();
// Some test flags (e.g. ForceWebRequestProxyForTest) can change
// whether the reported NTP URL is chrome://newtab or
// chrome://new-tab-page.
if (url == GURL(chrome::kChromeUINewTabPageURL) ||
url == GURL(chrome::kChromeUINewTabURL)) {
ntp_run_loop.Quit();
}
}));
WaitForSigninSucceeded();
EXPECT_EQ(GetMainAccountID(), GetIdentityManager()->GetPrimaryAccountId(
signin::ConsentLevel::kSignin));
histogram_tester.ExpectUniqueSample("Signin.SignIn.Completed", access_point,
1);
EXPECT_EQ(1, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(1);
EXPECT_EQ(1, reconcilor_started_count_);
// Check that the tab was navigated to the NTP.
ntp_run_loop.Run();
// Dismiss the Sync confirmation UI.
EXPECT_TRUE(login_ui_test_utils::ConfirmSyncConfirmationDialog(browser()));
}
// Tests that the account is signed in if the ENABLE_SYNC response is received
// before the refresh token, and the Sync opt-in is offered.
// https://crbug.com/1082858
#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && !defined(NDEBUG)
#define MAYBE_EnableSyncBeforeToken DISABLED_EnableSyncBeforeToken
#else
#define MAYBE_EnableSyncBeforeToken EnableSyncBeforeToken
#endif
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, MAYBE_EnableSyncBeforeToken) {
EXPECT_EQ(0, reconcilor_started_count_);
ui_test_utils::UrlLoadObserver enable_sync_url_observer(
https_server_.GetURL(kEnableSyncURL));
// Signin using the Chrome Sync endpoint.
browser()->signin_view_controller()->ShowSignin(
signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS);
// Receive ENABLE_SYNC.
SendEnableSyncResponse();
// Wait for the page to be fully loaded.
enable_sync_url_observer.Wait();
// Receive token.
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
EXPECT_FALSE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
SendRefreshTokenResponse();
ui_test_utils::UrlLoadObserver ntp_url_observer(
(GURL(chrome::kChromeUINewTabURL)));
EXPECT_EQ(1, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(1);
EXPECT_EQ(1, reconcilor_started_count_);
// Check that the tab was navigated to the NTP.
ntp_url_observer.Wait();
EXPECT_TRUE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
EXPECT_EQ(GetMainAccountID(), GetIdentityManager()->GetPrimaryAccountId(
signin::ConsentLevel::kSignin));
AddCanShowHistorySyncOptInsWithoutMinorModeCapability(GetIdentityManager());
// Check that the Dice request header was sent, with signout confirmation.
std::string client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
EXPECT_EQ(base::StringPrintf("version=%s,client_id=%s,device_id=%s,"
"signin_mode=all_accounts,"
"signout_mode=show_confirmation",
signin::kDiceProtocolVersion, client_id.c_str(),
GetDeviceId().c_str()),
dice_request_header_);
// Wait for the Sync confirmation UI and click through.
EXPECT_TRUE(login_ui_test_utils::ConfirmSyncConfirmationDialog(browser()));
EXPECT_EQ(signin::ConsentLevel::kSync,
signin::GetPrimaryAccountConsentLevel(GetIdentityManager()));
}
// Verifies that Chrome doesn't crash on browser window close when the sync
// confirmation dialog is waiting for its size.
// Regression test for https://crbug.com/1304055.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest,
CloseBrowserWhileInitializingSyncConfirmation) {
// Signin using the Chrome Sync endpoint.
browser()->signin_view_controller()->ShowSignin(
signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS);
content::TestNavigationObserver sync_confirmation_url_observer(
GURL("chrome://sync-confirmation?style=0"));
sync_confirmation_url_observer.StartWatchingNewWebContents();
// Receive token.
SendRefreshTokenResponse();
// Receive ENABLE_SYNC.
SendEnableSyncResponse();
WaitForSigninSucceeded();
EXPECT_EQ(GetMainAccountID(), GetIdentityManager()->GetPrimaryAccountId(
signin::ConsentLevel::kSignin));
// Wait until the sync confirmation webUI is created but not fully loaded
// yet. The native dialog is not displayed yet since it waits until the webUI
// passes the dialog height back to native.
sync_confirmation_url_observer.WaitForNavigationFinished();
// This should not crash.
CloseBrowser();
}
// Tests that turning off Dice via preferences works when singed out.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, PRE_TurnOffDice_SignedOut) {
ASSERT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
ASSERT_TRUE(AccountConsistencyModeManager::IsDiceEnabledForProfile(
browser()->profile()));
// Turn off Dice for this profile.
browser()->profile()->GetPrefs()->SetBoolean(
prefs::kSigninAllowedOnNextStartup, false);
}
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, TurnOffDice_SignedOut) {
// Check that Dice is disabled.
EXPECT_FALSE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kSigninAllowed));
EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kSigninAllowedOnNextStartup));
EXPECT_FALSE(AccountConsistencyModeManager::IsDiceEnabledForProfile(
browser()->profile()));
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
// Navigate to Gaia and sign in.
NavigateToURL(kSigninURL);
// Check that the Dice request header was not sent.
EXPECT_EQ(kNoDiceRequestHeader, dice_request_header_);
EXPECT_EQ(0, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(0);
}
// Tests that turning off Dice via preferences works when signed in without sync
// consent.
//
// Regression test for crbug/1254325
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, PRE_TurnOffDice_NotOptedIntoSync) {
SetupSignedInAccounts(signin::ConsentLevel::kSignin);
ASSERT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
ASSERT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSync));
ASSERT_TRUE(AccountConsistencyModeManager::IsDiceEnabledForProfile(
browser()->profile()));
// Turn off Dice for this profile.
browser()->profile()->GetPrefs()->SetBoolean(
prefs::kSigninAllowedOnNextStartup, false);
}
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, TurnOffDice_NotOptedIntoSync) {
// Check that Dice is disabled.
EXPECT_FALSE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kSigninAllowed));
EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kSigninAllowedOnNextStartup));
EXPECT_FALSE(AccountConsistencyModeManager::IsDiceEnabledForProfile(
browser()->profile()));
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
EXPECT_FALSE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
EXPECT_TRUE(GetIdentityManager()->GetAccountsWithRefreshTokens().empty());
// Navigate to Gaia and sign in.
NavigateToURL(kSigninURL);
// Check that the Dice request header was not sent.
EXPECT_EQ(kNoDiceRequestHeader, dice_request_header_);
EXPECT_EQ(0, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(0);
}
// Tests that turning off Dice via preferences works when signed in with sync
// consent
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, PRE_TurnOffDice_OptedIntoSync) {
// Sign the profile in and turn sync on.
SetupSignedInAccounts(signin::ConsentLevel::kSync);
syncer::SyncPrefs(browser()->profile()->GetPrefs())
.SetInitialSyncFeatureSetupComplete();
ASSERT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSync));
ASSERT_TRUE(AccountConsistencyModeManager::IsDiceEnabledForProfile(
browser()->profile()));
// Turn off Dice for this profile.
browser()->profile()->GetPrefs()->SetBoolean(
prefs::kSigninAllowedOnNextStartup, false);
}
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, TurnOffDice_OptedIntoSync) {
EXPECT_FALSE(
browser()->profile()->GetPrefs()->GetBoolean(prefs::kSigninAllowed));
EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kSigninAllowedOnNextStartup));
EXPECT_FALSE(AccountConsistencyModeManager::IsDiceEnabledForProfile(
browser()->profile()));
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSync));
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
EXPECT_FALSE(
GetIdentityManager()->HasAccountWithRefreshToken(GetMainAccountID()));
EXPECT_TRUE(GetIdentityManager()->GetAccountsWithRefreshTokens().empty());
// Navigate to Gaia and sign in.
NavigateToURL(kSigninURL);
// Check that the Dice request header was not sent.
EXPECT_EQ(kNoDiceRequestHeader, dice_request_header_);
EXPECT_EQ(0, reconcilor_blocked_count_);
WaitForReconcilorUnblockedCount(0);
}
// Checks that Dice is disabled in incognito mode.
IN_PROC_BROWSER_TEST_F(DiceBrowserTest, Incognito) {
Browser* incognito_browser = Browser::Create(Browser::CreateParams(
browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true),
true));
// Check that Dice is disabled.
EXPECT_FALSE(AccountConsistencyModeManager::IsDiceEnabledForProfile(
incognito_browser->profile()));
}
class DiceExplicitSigninRollbackBrowserTest : public InProcessBrowserTest {
public:
struct AccountStorageStatus {
bool autofill_sync_toggle_available = false;
syncer::UserSelectableTypeSet user_selectable_type_set;
};
// InProcessBrowserTest:
void SetUpInProcessBrowserTestFixture() override {
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
client_helper_.SetUp();
}
DiceExplicitSigninRollbackBrowserTest() {
std::vector<base::test::FeatureRef> features = {
syncer::kSyncEnableContactInfoDataTypeInTransportMode,
switches::kExplicitBrowserSigninUIOnDesktop};
std::vector<base::test::FeatureRef> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
if (content::IsPreTest()) {
// PRE_ test has explicit signin.
enabled_features = std::move(features);
} else {
// Non-PRE_ test has implicit signin.
disabled_features = std::move(features);
}
feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
signin::IdentityManager* GetIdentityManager() {
return IdentityManagerFactory::GetForProfile(browser()->profile());
}
AccountStorageStatus GetAccountStorageStatus() {
syncer::SyncUserSettings* settings =
SyncServiceFactory::GetForProfile(browser()->profile())
->GetUserSettings();
return {.autofill_sync_toggle_available =
autofill::PersonalDataManagerFactory::GetForBrowserContext(
browser()->profile())
->address_data_manager()
.IsAutofillSyncToggleAvailable(),
.user_selectable_type_set = settings->GetSelectedTypes()};
}
network::TestURLLoaderFactory* test_url_loader_factory() {
return client_helper_.test_url_loader_factory();
}
private:
ChromeSigninClientWithURLLoaderHelper client_helper_;
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninRollbackBrowserTest, PRE_Rollback) {
signin::AccountAvailabilityOptionsBuilder builder;
signin::MakeAccountAvailable(
GetIdentityManager(),
builder.AsPrimary(signin::ConsentLevel::kSignin)
.WithAccessPoint(signin_metrics::AccessPoint::
ACCESS_POINT_CHROME_SIGNIN_INTERCEPT_BUBBLE)
.Build(kMainGmailEmail));
ASSERT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
signin::ConsentLevel::kSignin);
ASSERT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kExplicitBrowserSignin));
// Butter for autofill is enabled.
AccountStorageStatus account_storage_status = GetAccountStorageStatus();
EXPECT_TRUE(account_storage_status.autofill_sync_toggle_available);
EXPECT_TRUE(account_storage_status.user_selectable_type_set.HasAll(
{syncer::UserSelectableType::kAutofill,
syncer::UserSelectableType::kPasswords}));
// Cookie migration is complete.
EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kCookieClearOnExitMigrationNoticeComplete));
}
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninRollbackBrowserTest, Rollback) {
Profile* profile = browser()->profile();
// The user is now signed in implicitly.
signin::WaitForRefreshTokensLoaded(GetIdentityManager());
ASSERT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
signin::ConsentLevel::kSignin);
ASSERT_TRUE(gaia::AreEmailsSame(
GetIdentityManager()
->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
.email,
kMainGmailEmail));
ASSERT_FALSE(profile->GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
// Account storage is disabled.
AccountStorageStatus account_storage_status = GetAccountStorageStatus();
EXPECT_FALSE(account_storage_status.user_selectable_type_set.HasAny(
{syncer::UserSelectableType::kAutofill,
syncer::UserSelectableType::kPasswords}));
// Cookie migration is required.
EXPECT_FALSE(profile->GetPrefs()->GetBoolean(
prefs::kCookieClearOnExitMigrationNoticeComplete));
// And cannot be completed by changing cookie settings.
content_settings::CookieSettings* settings =
CookieSettingsFactory::GetForProfile(profile).get();
settings->SetDefaultCookieSetting(CONTENT_SETTING_ALLOW);
EXPECT_FALSE(profile->GetPrefs()->GetBoolean(
prefs::kCookieClearOnExitMigrationNoticeComplete));
}
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninRollbackBrowserTest,
PRE_RollbackSigninPending) {
signin::MakeAccountAvailable(
GetIdentityManager(),
signin::AccountAvailabilityOptionsBuilder()
.AsPrimary(signin::ConsentLevel::kSignin)
.WithAccessPoint(signin_metrics::AccessPoint::
ACCESS_POINT_CHROME_SIGNIN_INTERCEPT_BUBBLE)
.Build(kMainGmailEmail));
ASSERT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
signin::ConsentLevel::kSignin);
ASSERT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kExplicitBrowserSignin));
// Induce SigninPending
signin::SetInvalidRefreshTokenForPrimaryAccount(GetIdentityManager());
// Still signed in.
EXPECT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
signin::ConsentLevel::kSignin);
// But account is in error.
EXPECT_TRUE(
GetIdentityManager()->HasAccountWithRefreshTokenInPersistentErrorState(
GetIdentityManager()->GetPrimaryAccountId(
signin::ConsentLevel::kSignin)));
}
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninRollbackBrowserTest,
RollbackSigninPending) {
// The account is signed out after tokens are loaded.
signin::WaitForRefreshTokensLoaded(GetIdentityManager());
// After rollback, a signin pending state should transition to signed out.
EXPECT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
std::nullopt);
}
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninRollbackBrowserTest,
PRE_RollbackWebSigninOnly) {
// Signin on the web only.
AccountInfo account_info = signin::MakeAccountAvailable(
GetIdentityManager(),
signin::AccountAvailabilityOptionsBuilder(test_url_loader_factory())
.WithAccessPoint(signin_metrics::AccessPoint::ACCESS_POINT_WEB_SIGNIN)
.WithCookie()
.Build(kMainGmailEmail));
ASSERT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
std::nullopt);
ASSERT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kExplicitBrowserSignin));
// Signed in on the web.
EXPECT_TRUE(GetIdentityManager()->HasAccountWithRefreshToken(
account_info.account_id));
signin::AccountsInCookieJarInfo cookie_jar =
GetIdentityManager()->GetAccountsInCookieJar();
ASSERT_TRUE(cookie_jar.AreAccountsFresh());
ASSERT_EQ(cookie_jar.GetSignedInAccounts().size(), 1u);
EXPECT_TRUE(gaia::AreEmailsSame(cookie_jar.GetSignedInAccounts()[0].email,
kMainGmailEmail));
}
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninRollbackBrowserTest,
RollbackWebSigninOnly) {
signin::WaitForRefreshTokensLoaded(GetIdentityManager());
signin::TriggerListAccount(GetIdentityManager(), test_url_loader_factory());
// After rollback, Chrome would be implicitly signed in.
EXPECT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
signin::ConsentLevel::kSignin);
CoreAccountInfo primary_account = GetIdentityManager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin);
EXPECT_TRUE(gaia::AreEmailsSame(primary_account.email, kMainGmailEmail));
EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kExplicitBrowserSignin));
// Signed in on the web as well.
EXPECT_TRUE(GetIdentityManager()->HasAccountWithRefreshToken(
primary_account.account_id));
signin::AccountsInCookieJarInfo cookie_jar =
GetIdentityManager()->GetAccountsInCookieJar();
ASSERT_TRUE(cookie_jar.AreAccountsFresh());
ASSERT_EQ(cookie_jar.GetSignedInAccounts().size(), 1u);
EXPECT_TRUE(gaia::AreEmailsSame(cookie_jar.GetSignedInAccounts()[0].email,
kMainGmailEmail));
}
class DiceExplicitSigninBrowserTest : public InProcessBrowserTest {
public:
struct AccountStorageStatus {
bool autofill_sync_toggle_available = false;
syncer::UserSelectableTypeSet user_selectable_type_set;
};
DiceExplicitSigninBrowserTest() {
std::vector<base::test::FeatureRef> enabled_features = {
syncer::kSyncEnableContactInfoDataTypeInTransportMode,
};
std::vector<base::test::FeatureRef> disabled_features = {};
if (content::IsPreTest()) {
disabled_features.push_back(switches::kExplicitBrowserSigninUIOnDesktop);
} else {
enabled_features.push_back(switches::kExplicitBrowserSigninUIOnDesktop);
}
feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
signin::IdentityManager* GetIdentityManager() {
return IdentityManagerFactory::GetForProfile(browser()->profile());
}
AccountStorageStatus GetAccountStorageStatus() {
syncer::SyncUserSettings* settings =
SyncServiceFactory::GetForProfile(browser()->profile())
->GetUserSettings();
return {.autofill_sync_toggle_available =
autofill::PersonalDataManagerFactory::GetForBrowserContext(
browser()->profile())
->address_data_manager()
.IsAutofillSyncToggleAvailable(),
.user_selectable_type_set = settings->GetSelectedTypes()};
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninBrowserTest, PRE_Migration) {
signin::AccountAvailabilityOptionsBuilder builder;
signin::MakeAccountAvailable(
GetIdentityManager(),
builder
.AsPrimary(signin::ConsentLevel::kSignin)
// `ACCESS_POINT_WEB_SIGNIN` is not explicit before the migration.
.WithAccessPoint(signin_metrics::AccessPoint::ACCESS_POINT_WEB_SIGNIN)
.Build(kMainGmailEmail));
ASSERT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
signin::ConsentLevel::kSignin);
ASSERT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kExplicitBrowserSignin));
AccountStorageStatus account_storage_status = GetAccountStorageStatus();
EXPECT_FALSE(account_storage_status.autofill_sync_toggle_available);
EXPECT_FALSE(account_storage_status.user_selectable_type_set.HasAny(
{syncer::UserSelectableType::kAutofill,
syncer::UserSelectableType::kPasswords}));
}
// Checks that a user who signed in with Dice before
// `switches::kExplicitBrowserSigninUIOnDesktop` was enabled does not get the
// account storage enabled silently. Account storage is enabled after the user
// signs out and signs in again through an explicit flow.
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninBrowserTest, Migration) {
Profile* profile = browser()->profile();
// The user is still signed in implicitly.
ASSERT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
signin::ConsentLevel::kSignin);
ASSERT_TRUE(gaia::AreEmailsSame(
GetIdentityManager()
->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
.email,
kMainGmailEmail));
ASSERT_FALSE(profile->GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
// Account storage was not enabled yet.
AccountStorageStatus account_storage_status = GetAccountStorageStatus();
EXPECT_FALSE(account_storage_status.user_selectable_type_set.HasAny(
{syncer::UserSelectableType::kAutofill,
syncer::UserSelectableType::kPasswords}));
// Signout, and then signin again explicitly.
signin::ClearPrimaryAccount(GetIdentityManager());
AccountInfo primary_account_info = signin::MakePrimaryAccountAvailable(
GetIdentityManager(), kMainGmailEmail, signin::ConsentLevel::kSignin);
EXPECT_TRUE(profile->GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
// Account storage is now enabled.
account_storage_status = GetAccountStorageStatus();
EXPECT_TRUE(account_storage_status.user_selectable_type_set.HasAll(
{syncer::UserSelectableType::kAutofill,
syncer::UserSelectableType::kPasswords}));
// Cookie migration is done.
EXPECT_TRUE(profile->GetPrefs()->GetBoolean(
prefs::kCookieClearOnExitMigrationNoticeComplete));
}
// Checks that migration handles Cookie clear on exit and sync toggles.
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninBrowserTest,
PRE_MigrationWithSettings) {
Profile* profile = browser()->profile();
signin::AccountAvailabilityOptionsBuilder builder;
signin::MakeAccountAvailable(
GetIdentityManager(),
builder.AsPrimary(signin::ConsentLevel::kSync).Build(kMainGmailEmail));
ASSERT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
signin::ConsentLevel::kSync);
ASSERT_FALSE(profile->GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
// Set cookie clear on exit, and set addresses and password sync to OFF.
content_settings::CookieSettings* settings =
CookieSettingsFactory::GetForProfile(profile).get();
settings->SetDefaultCookieSetting(CONTENT_SETTING_SESSION_ONLY);
syncer::SyncPrefs(profile->GetPrefs())
.SetSelectedTypesForSyncingUser(
/*keep_everything_synced=*/false,
/*registered_types=*/syncer::UserSelectableTypeSet::All(),
/*selected_types=*/{});
AccountStorageStatus account_storage_status = GetAccountStorageStatus();
EXPECT_FALSE(account_storage_status.autofill_sync_toggle_available);
EXPECT_FALSE(account_storage_status.user_selectable_type_set.HasAny(
{syncer::UserSelectableType::kAutofill,
syncer::UserSelectableType::kPasswords}));
}
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninBrowserTest, MigrationWithSettings) {
Profile* profile = browser()->profile();
// The user is still signed in implicitly.
ASSERT_EQ(signin::GetPrimaryAccountConsentLevel(GetIdentityManager()),
signin::ConsentLevel::kSync);
ASSERT_TRUE(gaia::AreEmailsSame(
GetIdentityManager()
->GetPrimaryAccountInfo(signin::ConsentLevel::kSync)
.email,
kMainGmailEmail));
ASSERT_FALSE(profile->GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
// Account storage was not enabled yet.
AccountStorageStatus account_storage_status = GetAccountStorageStatus();
EXPECT_FALSE(account_storage_status.user_selectable_type_set.HasAny(
{syncer::UserSelectableType::kAutofill,
syncer::UserSelectableType::kPasswords}));
// Signout, and then signin again explicitly.
signin::ClearPrimaryAccount(GetIdentityManager());
AccountInfo primary_account_info = signin::MakePrimaryAccountAvailable(
GetIdentityManager(), kMainGmailEmail, signin::ConsentLevel::kSignin);
EXPECT_TRUE(profile->GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
// Account storage is not enabled, because Sync settings were carried over.
account_storage_status = GetAccountStorageStatus();
EXPECT_FALSE(account_storage_status.user_selectable_type_set.HasAny(
{syncer::UserSelectableType::kAutofill,
syncer::UserSelectableType::kPasswords}));
// Cookie migration is not done, because there is clear on exit.
content_settings::CookieSettings* settings =
CookieSettingsFactory::GetForProfile(profile).get();
EXPECT_EQ(CONTENT_SETTING_SESSION_ONLY, settings->GetDefaultCookieSetting());
EXPECT_FALSE(profile->GetPrefs()->GetBoolean(
prefs::kCookieClearOnExitMigrationNoticeComplete));
// Allow cookies to trigger the migration.
settings->SetDefaultCookieSetting(CONTENT_SETTING_ALLOW);
EXPECT_TRUE(profile->GetPrefs()->GetBoolean(
prefs::kCookieClearOnExitMigrationNoticeComplete));
}
// Signin implicitlty, Dice Signin.
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninBrowserTest,
PRE_DiceUserMigratedClearsCookie) {
signin::MakeAccountAvailable(
GetIdentityManager(),
signin::AccountAvailabilityOptionsBuilder()
.AsPrimary(signin::ConsentLevel::kSignin)
// `ACCESS_POINT_WEB_SIGNIN` is not explicit before the migration.
.WithAccessPoint(signin_metrics::AccessPoint::ACCESS_POINT_WEB_SIGNIN)
.Build(kMainGmailEmail));
// Set the SAPISID cookie so that its deletion can be detected later.
// Set a max-age so that it's persisted on disk.
std::string gaia_cookie = base::StrCat(
{GaiaConstants::kGaiaSigninCookieName, "=foo; secure; max-age=1000"});
ASSERT_TRUE(content::SetCookie(browser()->profile(),
GURL("https://google.com/"), gaia_cookie));
ASSERT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
ASSERT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
prefs::kExplicitBrowserSignin));
}
// Dice Signin with `switches::kExplicitBrowserSigninUIOnDesktop` enabled.
IN_PROC_BROWSER_TEST_F(DiceExplicitSigninBrowserTest,
DiceUserMigratedClearsCookie) {
Profile* profile = browser()->profile();
// The user is still signed in implicitly.
ASSERT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
ASSERT_TRUE(gaia::AreEmailsSame(
GetIdentityManager()
->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
.email,
kMainGmailEmail));
ASSERT_FALSE(profile->GetPrefs()->GetBoolean(prefs::kExplicitBrowserSignin));
content::DeleteCookies(profile, network::mojom::CookieDeletionFilter());
// User should be signed out.
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
}
class DiceBrowserTestWithExplicitSignin : public DiceBrowserTest {
public:
// Sets the user choice for Chrome Signin on `main_email_`.
void SetChromeSigninChoice(ChromeSigninUserChoice choice) {
SigninPrefs(*browser()->profile()->GetPrefs())
.SetChromeSigninInterceptionUserChoice(
signin::GetTestGaiaIdForEmail(main_email_), choice);
}
// Signs in `main_email_`.
void SimulateWebSigninMainAccount() {
NavigateToURL(kSigninURL);
SendRefreshTokenResponse();
WaitForReconcilorUnblockedCount(1);
}
private:
base::test::ScopedFeatureList scoped_feature_list_{
switches::kExplicitBrowserSigninUIOnDesktop};
};
IN_PROC_BROWSER_TEST_F(DiceBrowserTestWithExplicitSignin,
SigninWithChoiceRemembered_NoChoiceDefault) {
// Sign in with no prior user action -- same as
// `ChromeSigninUserChoice::kNoChoice`.
SimulateWebSigninMainAccount();
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
ASSERT_EQ(GetIdentityManager()->GetAccountsWithRefreshTokens().size(), 1u);
EXPECT_TRUE(gaia::AreEmailsSame(
GetIdentityManager()->GetAccountsWithRefreshTokens()[0].email,
main_email_));
}
IN_PROC_BROWSER_TEST_F(DiceBrowserTestWithExplicitSignin,
SigninWithChoiceRemembered_NoChoice) {
// Simulates no previous choice yet.
SetChromeSigninChoice(ChromeSigninUserChoice::kNoChoice);
SimulateWebSigninMainAccount();
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
ASSERT_EQ(GetIdentityManager()->GetAccountsWithRefreshTokens().size(), 1u);
EXPECT_TRUE(gaia::AreEmailsSame(
GetIdentityManager()->GetAccountsWithRefreshTokens()[0].email,
main_email_));
}
IN_PROC_BROWSER_TEST_F(DiceBrowserTestWithExplicitSignin,
SigninWithChoiceRemembered_DoNotSignin) {
// Simulates a previous choice done with do not sign in.
SetChromeSigninChoice(ChromeSigninUserChoice::kDoNotSignin);
SimulateWebSigninMainAccount();
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
ASSERT_EQ(GetIdentityManager()->GetAccountsWithRefreshTokens().size(), 1u);
EXPECT_TRUE(gaia::AreEmailsSame(
GetIdentityManager()->GetAccountsWithRefreshTokens()[0].email,
main_email_));
}
IN_PROC_BROWSER_TEST_F(DiceBrowserTestWithExplicitSignin,
SigninWithChoiceRemembered_AlwaysAsk) {
// Simulates a previous choice done with always ask, expecting the Chrome
// Signin bubble to show.
SetChromeSigninChoice(ChromeSigninUserChoice::kAlwaysAsk);
SimulateWebSigninMainAccount();
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
}
IN_PROC_BROWSER_TEST_F(DiceBrowserTestWithExplicitSignin,
SigninWithChoiceRemembered_Signin) {
base::HistogramTester histogram_tester;
PrefService* prefs = browser()->profile()->GetPrefs();
ASSERT_FALSE(prefs->GetBoolean(prefs::kExplicitBrowserSignin));
// Simulates a previous choice done with Always sign in.
SetChromeSigninChoice(ChromeSigninUserChoice::kSignin);
SimulateWebSigninMainAccount();
EXPECT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
histogram_tester.ExpectUniqueSample(
"Signin.SignIn.Completed",
signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_CHOICE_REMEMBERED, 1);
// Should still count as an explicit sign in since the choice was explicit
// set.
EXPECT_TRUE(prefs->GetBoolean(prefs::kExplicitBrowserSignin));
}
class DiceBrowserTestWithChromeSigninIPH
: public InteractiveFeaturePromoTestT<DiceBrowserTestWithExplicitSignin> {
public:
DiceBrowserTestWithChromeSigninIPH()
: InteractiveFeaturePromoTestT(UseDefaultTrackerAllowingPromos(
{feature_engagement::
kIPHExplicitBrowserSigninPreferenceRememberedFeature})) {}
void SimulateExtendedAccountInfoFetched() {
CoreAccountInfo core_account_info =
GetIdentityManager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin);
AccountInfo account_info =
GetIdentityManager()->FindExtendedAccountInfo(core_account_info);
account_info.full_name = "First Last";
account_info.given_name = "First";
account_info.hosted_domain = kNoHostedDomainFound;
account_info.picture_url = "https://example.com";
signin::UpdateAccountInfoForAccount(GetIdentityManager(), account_info);
}
void CloseIPH() {
RunTestSequence(
PressButton(user_education::HelpBubbleView::kCloseButtonIdForTesting),
WaitForHide(
user_education::HelpBubbleView::kHelpBubbleElementIdForTesting),
CheckResult(
[this]() {
return browser()->window()->IsFeaturePromoActive(
feature_engagement::
kIPHExplicitBrowserSigninPreferenceRememberedFeature);
},
false));
}
void SignoutAndResetState() {
signin::ClearPrimaryAccount(GetIdentityManager());
// Reset internal state to sign in again.
token_requested_ = false;
refresh_token_available_ = false;
reconcilor_unblocked_count_ = 0;
reconcilor_blocked_count_ = 0;
EXPECT_FALSE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
}
};
IN_PROC_BROWSER_TEST_F(DiceBrowserTestWithChromeSigninIPH,
SigninRememberedIPH) {
// The IPH can be shown after 14 days. Use 15 in the test to avoid any
// precision problem.
base::TimeDelta kIPHReshowDelay = base::Days(15);
// Simulates a previous choice done with Always sign in.
SetChromeSigninChoice(ChromeSigninUserChoice::kSignin);
base::HistogramTester histogram_tester;
SimulateWebSigninMainAccount();
EXPECT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
histogram_tester.ExpectUniqueSample(
"Signin.SignIn.Completed",
signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_CHOICE_REMEMBERED, 1);
CoreAccountInfo core_account_info =
GetIdentityManager()->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin);
AccountInfo account_info =
GetIdentityManager()->FindExtendedAccountInfo(core_account_info);
// IPH not showing yet, waiting for the name.
ASSERT_TRUE(account_info.given_name.empty());
EXPECT_FALSE(browser()->window()->IsFeaturePromoActive(
feature_engagement::
kIPHExplicitBrowserSigninPreferenceRememberedFeature));
// IPH shown after receiving the name.
SimulateExtendedAccountInfoFetched();
EXPECT_TRUE(browser()->window()->IsFeaturePromoActive(
feature_engagement::
kIPHExplicitBrowserSigninPreferenceRememberedFeature));
// Sign-in once more, the IPH is not shown again.
CloseIPH();
SignoutAndResetState();
SimulateWebSigninMainAccount();
EXPECT_TRUE(
GetIdentityManager()->HasPrimaryAccount(signin::ConsentLevel::kSignin));
histogram_tester.ExpectUniqueSample(
"Signin.SignIn.Completed",
signin_metrics::AccessPoint::ACCESS_POINT_SIGNIN_CHOICE_REMEMBERED, 2);
SimulateExtendedAccountInfoFetched();
EXPECT_FALSE(browser()->window()->IsFeaturePromoActive(
feature_engagement::
kIPHExplicitBrowserSigninPreferenceRememberedFeature));
// The IPH can be reshown two weeks after the signout.
RunTestSequence(AdvanceTime(kIPHReshowDelay));
SignoutAndResetState();
SimulateWebSigninMainAccount();
SimulateExtendedAccountInfoFetched();
// IPH does not reshow yet, because the delay was before the signout event.
EXPECT_FALSE(browser()->window()->IsFeaturePromoActive(
feature_engagement::
kIPHExplicitBrowserSigninPreferenceRememberedFeature));
SignoutAndResetState();
// Wait 2 weeks after the signout event (by overriding the last signout date).
SigninPrefs(*browser()->profile()->GetPrefs())
.SetChromeLastSignoutTime(core_account_info.gaia,
base::Time::Now() - kIPHReshowDelay);
SimulateWebSigninMainAccount();
SimulateExtendedAccountInfoFetched();
// IPH can now show again.
EXPECT_TRUE(browser()->window()->IsFeaturePromoActive(
feature_engagement::
kIPHExplicitBrowserSigninPreferenceRememberedFeature));
}
// This test is not specifically related to DICE, but it extends
// |DiceBrowserTest| for convenience.
class DiceManageAccountBrowserTest : public DiceBrowserTest {
public:
DiceManageAccountBrowserTest()
: DiceBrowserTest(kMainManagedEmail),
// Skip showing the error message box to avoid freezing the main thread.
skip_message_box_auto_reset_(
&chrome::internal::g_should_skip_message_box_for_test,
true),
// Force the policy component to prohibit clearing the primary account
// even when the policy core component is not initialized.
prohibit_sigout_auto_reset_(
&policy::internal::g_force_prohibit_signout_for_tests,
true) {}
void SetUp() override {
#if BUILDFLAG(IS_WIN)
// Shortcut deletion delays tests shutdown on Win-7 and results in time out.
// See crbug.com/1073451.
AppShortcutManager::SuppressShortcutsForTesting();
#endif
DiceBrowserTest::SetUp();
}
protected:
base::AutoReset<bool> skip_message_box_auto_reset_;
base::AutoReset<bool> prohibit_sigout_auto_reset_;
unsigned int number_of_profiles_added_ = 0;
};
// Tests that prohiting sign-in on startup for a managed profile clears the
// profile directory on next start-up.
IN_PROC_BROWSER_TEST_F(DiceManageAccountBrowserTest,
PRE_ClearManagedProfileOnStartup) {
// Ensure that there are not deleted profiles before running this test.
PrefService* local_state = g_browser_process->local_state();
DCHECK(local_state);
const base::Value::List& deleted_profiles =
local_state->GetList(prefs::kProfilesDeleted);
ASSERT_TRUE(deleted_profiles.empty());
// Sign the profile in.
SetupSignedInAccounts(signin::ConsentLevel::kSync);
// Prohibit sign-in on next start-up.
browser()->profile()->GetPrefs()->SetBoolean(
prefs::kSigninAllowedOnNextStartup, false);
}
IN_PROC_BROWSER_TEST_F(DiceManageAccountBrowserTest,
ClearManagedProfileOnStartup) {
// Initial profile should have been deleted as sign-in and sign out were no
// longer allowed.
PrefService* local_state = g_browser_process->local_state();
DCHECK(local_state);
const base::Value::List& deleted_profiles =
local_state->GetList(prefs::kProfilesDeleted);
EXPECT_EQ(1U, deleted_profiles.size());
content::RunAllTasksUntilIdle();
// Verify that there is an active profile.
Profile* initial_profile = browser()->profile();
EXPECT_EQ(1U, g_browser_process->profile_manager()->GetNumberOfProfiles());
EXPECT_EQ(g_browser_process->profile_manager()->GetLastUsedProfile(),
initial_profile);
}