blob: 35af0a31c85c00feae623ff2d41ee00fc7b96b23 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/policy/cloud/user_policy_signin_service.h"
#include <memory>
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/signin_features.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/webui/signin/dice_turn_sync_on_helper.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/policy_switches.h"
#include "components/policy/core/common/policy_test_utils.h"
#include "components/policy/policy_constants.h"
#include "components/policy/test_support/local_policy_test_server.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/pref_test_utils.h"
#include "components/signin/public/identity_manager/accounts_mutator.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/sync/driver/sync_driver_switches.h"
#include "content/public/test/browser_test.h"
#include "google_apis/gaia/fake_gaia.h"
#include "google_apis/gaia/gaia_switches.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/base/url_util.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"
class UserPolicySigninServiceTest;
class SigninUIError;
namespace {
const char kTestEmail[] = "enterprise@example.com";
const char kTestRefreshToken[] =
"test_refresh_token_for_enterprise@example.com";
// Dummy delegate forwarding all the calls the test fixture.
// Owned by the DiceTurnOnSyncHelper.
class TestDiceTurnSyncOnHelperDelegate : public DiceTurnSyncOnHelper::Delegate {
public:
explicit TestDiceTurnSyncOnHelperDelegate(
UserPolicySigninServiceTest* test_fixture);
~TestDiceTurnSyncOnHelperDelegate() override;
private:
// DiceTurnSyncOnHelper::Delegate:
void ShowLoginError(const SigninUIError& error) override;
void ShowMergeSyncDataConfirmation(
const std::string& previous_email,
const std::string& new_email,
DiceTurnSyncOnHelper::SigninChoiceCallback callback) override;
void ShowEnterpriseAccountConfirmation(
const AccountInfo& account_info,
DiceTurnSyncOnHelper::SigninChoiceCallback callback) override;
void ShowSyncConfirmation(
base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)>
callback) override;
void ShowSyncDisabledConfirmation(
bool is_managed_account,
base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)>
callback) override;
void ShowSyncSettings() override;
void SwitchToProfile(Profile* new_profile) override;
raw_ptr<UserPolicySigninServiceTest> test_fixture_;
};
std::string GetTestPolicy() {
const char kTestPolicy[] = R"(
{
"%s": {
"mandatory": {
"ShowHomeButton": true
}
},
"managed_users": [ "*" ],
"policy_user": "%s",
"current_key_index": 0,
"policy_invalidation_topic": "test_policy_topic"
})";
return base::StringPrintf(
kTestPolicy, policy::dm_protocol::kChromeUserPolicyType, kTestEmail);
}
} // namespace
class UserPolicySigninServiceTest : public InProcessBrowserTest {
public:
UserPolicySigninServiceTest()
: embedded_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
// HandleUserInfoRequest must be registered first, to take priority over
// FakeGaia.
embedded_test_server_.RegisterRequestHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest, "/oauth2/v1/userinfo",
base::BindRepeating(&UserPolicySigninServiceTest::HandleUserInfoRequest,
base::Unretained(this))));
embedded_test_server_.RegisterRequestHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest, "/devicemanagement",
base::BindRepeating(
&UserPolicySigninServiceTest::HandleDeviceManagementRequest,
base::Unretained(this))));
embedded_test_server_.RegisterRequestHandler(base::BindRepeating(
&FakeGaia::HandleRequest, base::Unretained(&fake_gaia_)));
}
~UserPolicySigninServiceTest() override {
// Ensure that no helper leaked.
DCHECK_EQ(dice_helper_created_count_, dice_helper_deleted_count_);
}
Profile* profile() { return browser()->profile(); }
const CoreAccountId& account_id() { return account_info_.account_id; }
policy::PolicyService* GetPolicyService() {
return profile()->GetProfilePolicyConnector()->policy_service();
}
DiceTurnSyncOnHelper* CreateDiceTurnOnSyncHelper() {
// DiceTurnSyncOnHelper deletes itself. At the end of the test, there is a
// check that these objects did not leak.
++dice_helper_created_count_;
return new DiceTurnSyncOnHelper(
profile(), signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER,
signin_metrics::PromoAction::PROMO_ACTION_WITH_DEFAULT,
signin_metrics::Reason::kReauthentication, account_info_.account_id,
DiceTurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT,
std::make_unique<TestDiceTurnSyncOnHelperDelegate>(this),
base::DoNothing());
}
// This will never return if the Sync confirmation was already shown. Make
// sure to call this before the sync confirmation.
void WaitForSyncConfirmation() {
base::RunLoop loop;
sync_confirmation_shown_closure_ = loop.QuitClosure();
loop.Run();
}
// This will never return if the policy request was already handled. Make sure
// to call this before the policy request is made.
void WaitForPolicyHanging() {
base::RunLoop loop;
policy_hanging_closure_ = loop.QuitClosure();
loop.Run();
}
void ConfirmSync(
LoginUIService::SyncConfirmationUIClosedResult confirmation_result) {
std::move(sync_confirmation_callback_).Run(confirmation_result);
}
// DiceTurnSyncOnHelperDelegate calls:
void OnShowEnterpriseAccountConfirmation(
const AccountInfo& account_info,
DiceTurnSyncOnHelper::SigninChoiceCallback callback) {
std::move(callback).Run(DiceTurnSyncOnHelper::SIGNIN_CHOICE_CONTINUE);
}
void OnShowSyncConfirmation(
base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)>
callback) {
sync_confirmation_callback_ = std::move(callback);
if (sync_confirmation_shown_closure_)
std::move(sync_confirmation_shown_closure_).Run();
}
void OnShowSyncDisabledConfirmation(
base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)>
callback) {
sync_confirmation_callback_ = std::move(callback);
if (sync_confirmation_shown_closure_)
std::move(sync_confirmation_shown_closure_).Run();
}
void OnDiceTurnSyncOnHelperDeleted() { ++dice_helper_deleted_count_; }
void set_policy_hanging(bool hanging) { policy_hanging_ = hanging; }
private:
// InProcessBrowserTest:
void SetUp() override {
ASSERT_TRUE(embedded_test_server_.InitializeAndListen());
InProcessBrowserTest::SetUp();
}
void SetUpInProcessBrowserTestFixture() override {
InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
// Configure policy server.
ASSERT_NO_FATAL_FAILURE(SetUpPolicyServer());
ASSERT_TRUE(policy_server_->Start());
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
// Configure embedded test server.
const GURL& base_url = embedded_test_server_.base_url();
command_line->AppendSwitchASCII(
policy::switches::kDeviceManagementUrl,
base_url.Resolve("/devicemanagement").spec());
command_line->AppendSwitchASCII(switches::kGaiaUrl, base_url.spec());
command_line->AppendSwitchASCII(switches::kLsoUrl, base_url.spec());
command_line->AppendSwitchASCII(switches::kGoogleApisUrl, base_url.spec());
policy::ChromeBrowserPolicyConnector::EnableCommandLineSupportForTesting();
fake_gaia_.Initialize();
// Configure Sync server.
command_line->AppendSwitch(switches::kDisableSync);
// Set retry delay to prevent timeouts.
policy::DeviceManagementService::SetRetryDelayForTesting(0);
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(PolicyServiceIsEmpty(g_browser_process->policy_service()))
<< "Pre-existing policies in this machine will make this test fail.";
embedded_test_server_.StartAcceptingConnections();
account_info_ = signin::MakeAccountAvailable(
IdentityManagerFactory::GetForProfile(profile()), kTestEmail);
signin::SetRefreshTokenForAccount(
IdentityManagerFactory::GetForProfile(profile()),
account_info_.account_id, kTestRefreshToken);
SetupFakeGaiaResponses();
}
void SetServerPolicy(const std::string& policy) {
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(base::WriteFile(policy_file_path(), policy));
}
base::FilePath policy_file_path() const {
return temp_dir_.GetPath().AppendASCII("policy.json");
}
void SetUpPolicyServer() {
base::ScopedAllowBlockingForTesting allow_blocking;
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_NO_FATAL_FAILURE(SetServerPolicy(GetTestPolicy()));
policy_server_ =
std::make_unique<policy::LocalPolicyTestServer>(policy_file_path());
}
void SetupFakeGaiaResponses() {
FakeGaia::AccessTokenInfo access_token_info;
access_token_info.token = "test_access_token";
access_token_info.any_scope = true;
access_token_info.audience =
GaiaUrls::GetInstance()->oauth2_chrome_client_id();
access_token_info.email = kTestEmail;
fake_gaia_.IssueOAuthToken(kTestRefreshToken, access_token_info);
}
std::unique_ptr<net::test_server::HttpResponse> HandleUserInfoRequest(
const net::test_server::HttpRequest& r) {
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_content(R"(
{
"email": "enterprise@example.com",
"verified_email": true,
"hd": "example.com"
})");
return http_response;
}
// This simply redirects to the policy server, unless |policy_hanging_| is
// true.
std::unique_ptr<net::test_server::HttpResponse> HandleDeviceManagementRequest(
const net::test_server::HttpRequest& r) {
std::string request_type;
net::GetValueForKeyInQuery(r.GetURL(), policy::dm_protocol::kParamRequest,
&request_type);
if (request_type == policy::dm_protocol::kValueRequestPolicy &&
policy_hanging_) {
if (policy_hanging_closure_)
std::move(policy_hanging_closure_).Run();
return std::make_unique<net::test_server::HungResponse>();
}
// Redirect to the policy server.
GURL::Replacements replace_query;
std::string query = r.GetURL().query();
replace_query.SetQueryStr(query);
std::string dest =
policy_server_->GetServiceURL().ReplaceComponents(replace_query).spec();
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
http_response->AddCustomHeader("Location", dest);
return http_response;
}
base::ScopedTempDir temp_dir_;
net::EmbeddedTestServer embedded_test_server_;
FakeGaia fake_gaia_;
std::unique_ptr<policy::LocalPolicyTestServer> policy_server_;
CoreAccountInfo account_info_;
base::OnceClosure sync_confirmation_shown_closure_;
base::OnceClosure policy_hanging_closure_;
base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)>
sync_confirmation_callback_;
bool policy_hanging_ = false;
int dice_helper_created_count_ = 0;
int dice_helper_deleted_count_ = 0;
};
TestDiceTurnSyncOnHelperDelegate::TestDiceTurnSyncOnHelperDelegate(
UserPolicySigninServiceTest* test_fixture)
: test_fixture_(test_fixture) {}
TestDiceTurnSyncOnHelperDelegate::~TestDiceTurnSyncOnHelperDelegate() {
test_fixture_->OnDiceTurnSyncOnHelperDeleted();
}
void TestDiceTurnSyncOnHelperDelegate::ShowLoginError(
const SigninUIError& error) {
NOTREACHED();
}
void TestDiceTurnSyncOnHelperDelegate::ShowMergeSyncDataConfirmation(
const std::string& previous_email,
const std::string& new_email,
DiceTurnSyncOnHelper::SigninChoiceCallback callback) {
NOTREACHED();
}
void TestDiceTurnSyncOnHelperDelegate::ShowEnterpriseAccountConfirmation(
const AccountInfo& account_info,
DiceTurnSyncOnHelper::SigninChoiceCallback callback) {
test_fixture_->OnShowEnterpriseAccountConfirmation(account_info,
std::move(callback));
}
void TestDiceTurnSyncOnHelperDelegate::ShowSyncConfirmation(
base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)>
callback) {
test_fixture_->OnShowSyncConfirmation(std::move(callback));
}
void TestDiceTurnSyncOnHelperDelegate::ShowSyncDisabledConfirmation(
bool is_managed_account,
base::OnceCallback<void(LoginUIService::SyncConfirmationUIClosedResult)>
callback) {
test_fixture_->OnShowSyncDisabledConfirmation(std::move(callback));
}
void TestDiceTurnSyncOnHelperDelegate::ShowSyncSettings() {
NOTREACHED();
}
void TestDiceTurnSyncOnHelperDelegate::SwitchToProfile(Profile* new_profile) {
NOTREACHED();
}
IN_PROC_BROWSER_TEST_F(UserPolicySigninServiceTest, BasicSignin) {
EXPECT_FALSE(profile()->GetPrefs()->GetBoolean(prefs::kShowHomeButton));
// Signin and show sync confirmation dialog.
CreateDiceTurnOnSyncHelper();
WaitForSyncConfirmation();
// Policies are applied even before the user confirms.
EXPECT_TRUE(
IdentityManagerFactory::GetForProfile(profile())->HasPrimaryAccount(
signin::ConsentLevel::kSync));
WaitForPrefValue(profile()->GetPrefs(), prefs::kShowHomeButton,
base::Value(true));
// Confirm the signin.
ConfirmSync(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
// Policy is still applied.
EXPECT_TRUE(profile()->GetPrefs()->GetBoolean(prefs::kShowHomeButton));
}
IN_PROC_BROWSER_TEST_F(UserPolicySigninServiceTest, UndoSignin) {
EXPECT_FALSE(profile()->GetPrefs()->GetBoolean(prefs::kShowHomeButton));
// Signin and show sync confirmation dialog.
CreateDiceTurnOnSyncHelper();
WaitForSyncConfirmation();
// Policies are applied even before the user confirms.
EXPECT_TRUE(
IdentityManagerFactory::GetForProfile(profile())->HasPrimaryAccount(
signin::ConsentLevel::kSync));
WaitForPrefValue(profile()->GetPrefs(), prefs::kShowHomeButton,
base::Value(true));
// Cancel sync.
ConfirmSync(LoginUIService::ABORT_SYNC);
// Policy is reverted.
WaitForPrefValue(profile()->GetPrefs(), prefs::kShowHomeButton,
base::Value(false));
}
// Regression test for https://crbug.com/1061459
// Start a new signing flow while the existing one is hanging on a policy
// request.
IN_PROC_BROWSER_TEST_F(UserPolicySigninServiceTest, ConcurrentSignin) {
EXPECT_FALSE(profile()->GetPrefs()->GetBoolean(prefs::kShowHomeButton));
set_policy_hanging(true);
CreateDiceTurnOnSyncHelper();
WaitForPolicyHanging();
// User is not signed in, policy is not applied.
EXPECT_FALSE(
IdentityManagerFactory::GetForProfile(profile())->HasPrimaryAccount(
signin::ConsentLevel::kSync));
EXPECT_FALSE(profile()->GetPrefs()->GetBoolean(prefs::kShowHomeButton));
// Restart a new signin flow and allow it to complete.
CreateDiceTurnOnSyncHelper();
set_policy_hanging(false);
WaitForSyncConfirmation();
// Policies are applied even before the user confirms.
EXPECT_TRUE(
IdentityManagerFactory::GetForProfile(profile())->HasPrimaryAccount(
signin::ConsentLevel::kSync));
WaitForPrefValue(profile()->GetPrefs(), prefs::kShowHomeButton,
base::Value(true));
// Confirm the signin.
ConfirmSync(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
// Policy is still applied.
EXPECT_TRUE(profile()->GetPrefs()->GetBoolean(prefs::kShowHomeButton));
}
class UserPolicySigninServiceSyncNotRequiredTest
: public UserPolicySigninServiceTest {
public:
UserPolicySigninServiceSyncNotRequiredTest() {
DiceTurnSyncOnHelper::SetShowSyncEnabledUiForTesting(true);
feature_list.InitAndEnableFeature(kAccountPoliciesLoadedWithoutSync);
}
~UserPolicySigninServiceSyncNotRequiredTest() override {
DiceTurnSyncOnHelper::SetShowSyncEnabledUiForTesting(false);
}
private:
base::test::ScopedFeatureList feature_list;
};
// crbug.com/1230268 not working on Lacros.
// TODO(crbug.com/1254962): flaky on Mac builders
#if BUILDFLAG(IS_CHROMEOS_LACROS) || defined(OS_MAC)
#define MAYBE_AcceptManagementDeclineSync DISABLED_AcceptManagementDeclineSync
#else
#define MAYBE_AcceptManagementDeclineSync AcceptManagementDeclineSync
#endif
IN_PROC_BROWSER_TEST_F(UserPolicySigninServiceSyncNotRequiredTest,
MAYBE_AcceptManagementDeclineSync) {
EXPECT_FALSE(profile()->GetPrefs()->GetBoolean(prefs::kShowHomeButton));
// Signin and show sync confirmation dialog.
CreateDiceTurnOnSyncHelper();
WaitForSyncConfirmation();
// Policies are applied even before the user confirms.
EXPECT_TRUE(
IdentityManagerFactory::GetForProfile(profile())->HasPrimaryAccount(
signin::ConsentLevel::kSync));
WaitForPrefValue(profile()->GetPrefs(), prefs::kShowHomeButton,
base::Value(true));
// Cancel sync.
ConfirmSync(LoginUIService::ABORT_SYNC);
EXPECT_TRUE(
IdentityManagerFactory::GetForProfile(profile())->HasPrimaryAccount(
signin::ConsentLevel::kSignin));
EXPECT_FALSE(
IdentityManagerFactory::GetForProfile(profile())->HasPrimaryAccount(
signin::ConsentLevel::kSync));
EXPECT_TRUE(
chrome::enterprise_util::UserAcceptedAccountManagement(profile()));
EXPECT_TRUE(chrome::enterprise_util::ProfileCanBeManaged(profile()));
// Policy is still applied.
EXPECT_TRUE(profile()->GetPrefs()->GetBoolean(prefs::kShowHomeButton));
// Signout
auto* accounts_mutator =
IdentityManagerFactory::GetForProfile(profile())->GetAccountsMutator();
accounts_mutator->RemoveAccount(
account_id(), signin_metrics::SourceForRefreshTokenOperation::
kDiceResponseHandler_Signout);
EXPECT_FALSE(
IdentityManagerFactory::GetForProfile(profile())->HasPrimaryAccount(
signin::ConsentLevel::kSignin));
EXPECT_FALSE(
chrome::enterprise_util::UserAcceptedAccountManagement(profile()));
EXPECT_FALSE(chrome::enterprise_util::ProfileCanBeManaged(profile()));
}