blob: 3f1d15181ec0b199e77122405afa29d734d9fe71 [file] [log] [blame]
// Copyright 2020 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/ash/login/screens/terms_of_service_screen.h"
#include "ash/public/cpp/login_screen_test_api.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/ash/login/existing_user_controller.h"
#include "chrome/browser/ash/login/oobe_screen.h"
#include "chrome/browser/ash/login/screen_manager.h"
#include "chrome/browser/ash/login/session/user_session_manager_test_api.h"
#include "chrome/browser/ash/login/test/cryptohome_mixin.h"
#include "chrome/browser/ash/login/test/device_state_mixin.h"
#include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
#include "chrome/browser/ash/login/test/embedded_test_server_setup_mixin.h"
#include "chrome/browser/ash/login/test/js_checker.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/ash/login/test/oobe_base_test.h"
#include "chrome/browser/ash/login/test/oobe_screen_waiter.h"
#include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
#include "chrome/browser/ash/login/test/user_policy_mixin.h"
#include "chrome/browser/ash/login/ui/login_display_host.h"
#include "chrome/browser/ash/login/ui/webui_login_view.h"
#include "chrome/browser/ash/login/wizard_controller.h"
#include "chrome/browser/ash/policy/core/device_local_account.h"
#include "chrome/browser/ash/policy/core/device_policy_builder.h"
#include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/webui/ash/login/family_link_notice_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/gaia_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/oobe_ui.h"
#include "chrome/browser/ui/webui/ash/login/sync_consent_screen_handler.h"
#include "chrome/browser/ui/webui/ash/login/terms_of_service_screen_handler.h"
#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
#include "components/policy/core/common/cloud/test/policy_builder.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/known_user.h"
#include "content/public/test/browser_test.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace ash {
namespace {
namespace em = ::enterprise_management;
using ::net::test_server::BasicHttpResponse;
using ::net::test_server::HttpRequest;
using ::net::test_server::HttpResponse;
const char kAccountId[] = "dla@example.com";
const char kDisplayName[] = "display name";
const char kManagedUser[] = "user@example.com";
const char kManagedGaiaID[] = "33333";
const char kTosText[] = "By using this test you agree to fix future bugs";
absl::optional<std::string> ReadFileToOptionalString(
const base::FilePath& file_path) {
base::ScopedAllowBlockingForTesting allow_blocking;
std::string content;
if (base::ReadFileToString(file_path, &content))
return absl::make_optional<std::string>(content);
return absl::nullopt;
}
std::string TestServerBaseUrl(net::EmbeddedTestServer* server) {
return std::string(
base::TrimString(server->base_url().DeprecatedGetOriginAsURL().spec(),
"/", base::TrimPositions::TRIM_TRAILING));
}
// Returns a successful `BasicHttpResponse` with `content`.
std::unique_ptr<BasicHttpResponse> BuildHttpResponse(
const std::string& content) {
std::unique_ptr<BasicHttpResponse> http_response =
std::make_unique<BasicHttpResponse>();
http_response->set_code(net::HTTP_OK);
http_response->set_content_type("text/plain");
http_response->set_content(content);
return http_response;
}
std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
return BuildHttpResponse(kTosText);
}
class PublicSessionTosScreenTest : public OobeBaseTest {
public:
PublicSessionTosScreenTest() = default;
~PublicSessionTosScreenTest() override = default;
void SetUpOnMainThread() override {
OobeBaseTest::SetUpOnMainThread();
// Prevent browser start in user session so that we do not need to wait
// for its initialization.
test::UserSessionManagerTestApi(UserSessionManager::GetInstance())
.SetShouldLaunchBrowserInTests(false);
}
void RegisterAdditionalRequestHandlers() override {
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(&HandleRequest));
}
void SetUpInProcessBrowserTestFixture() override {
OobeBaseTest::SetUpInProcessBrowserTestFixture();
SessionManagerClient::InitializeFakeInMemory();
InitializePolicy();
}
void InitializePolicy() {
device_policy()->policy_data().set_public_key_version(1);
policy::DeviceLocalAccountTestHelper::SetupDeviceLocalAccount(
&device_local_account_policy_, kAccountId, kDisplayName);
UploadDeviceLocalAccountPolicy();
}
void BuildDeviceLocalAccountPolicy() {
device_local_account_policy_.SetDefaultSigningKey();
device_local_account_policy_.Build();
}
void UploadDeviceLocalAccountPolicy() {
BuildDeviceLocalAccountPolicy();
policy_test_server_mixin_.UpdatePolicy(
policy::dm_protocol::kChromePublicAccountPolicyType, kAccountId,
device_local_account_policy_.payload().SerializeAsString());
}
void UploadAndInstallDeviceLocalAccountPolicy() {
UploadDeviceLocalAccountPolicy();
session_manager_client()->set_device_local_account_policy(
kAccountId, device_local_account_policy_.GetBlob());
}
void AddPublicSessionToDevicePolicy() {
em::ChromeDeviceSettingsProto& proto(device_policy()->payload());
policy::DeviceLocalAccountTestHelper::AddPublicSession(&proto, kAccountId);
RefreshDevicePolicy();
policy_test_server_mixin_.UpdateDevicePolicy(proto);
}
void WaitForDisplayName() {
policy::DictionaryLocalStateValueWaiter("UserDisplayName", kDisplayName,
account_id_.GetUserEmail())
.Wait();
}
void WaitForPolicy() {
// Wait for the display name becoming available as that indicates
// device-local account policy is fully loaded, which is a prerequisite for
// successful login.
WaitForDisplayName();
}
void StartLogin() {
ASSERT_TRUE(LoginScreenTestApi::ExpandPublicSessionPod(account_id_));
LoginScreenTestApi::ClickPublicExpandedSubmitButton();
}
void StartPublicSession() {
UploadAndInstallDeviceLocalAccountPolicy();
AddPublicSessionToDevicePolicy();
WaitForPolicy();
StartLogin();
}
void SetUpTermsOfServiceUrlPolicy() {
device_local_account_policy_.payload()
.mutable_termsofserviceurl()
->set_value(TestServerBaseUrl(embedded_test_server()));
}
void SetUpExitCallback() {
TermsOfServiceScreen* screen = static_cast<TermsOfServiceScreen*>(
WizardController::default_controller()->screen_manager()->GetScreen(
TermsOfServiceScreenView::kScreenId));
original_callback_ = screen->get_exit_callback_for_testing();
screen->set_exit_callback_for_testing(base::BindRepeating(
&PublicSessionTosScreenTest::HandleScreenExit, base::Unretained(this)));
}
void WaitForScreenShown() {
OobeScreenWaiter(TermsOfServiceScreenView::kScreenId).Wait();
EXPECT_TRUE(LoginScreenTestApi::IsOobeDialogVisible());
}
void WaitForScreenExit() {
if (result_.has_value()) {
original_callback_.Run(result_.value());
return;
}
base::RunLoop run_loop;
screen_exit_callback_ = run_loop.QuitClosure();
run_loop.Run();
ASSERT_TRUE(result_.has_value());
original_callback_.Run(result_.value());
}
FakeSessionManagerClient* session_manager_client() {
return FakeSessionManagerClient::Get();
}
absl::optional<TermsOfServiceScreen::Result> result_;
base::HistogramTester histogram_tester_;
private:
void RefreshDevicePolicy() { policy_helper()->RefreshDevicePolicy(); }
policy::DevicePolicyBuilder* device_policy() {
return policy_helper()->device_policy();
}
policy::DevicePolicyCrosTestHelper* policy_helper() {
return &policy_helper_;
}
void HandleScreenExit(TermsOfServiceScreen::Result result) {
result_ = result;
if (screen_exit_callback_)
screen_exit_callback_.Run();
}
base::RepeatingClosure screen_exit_callback_;
TermsOfServiceScreen::ScreenExitCallback original_callback_;
policy::UserPolicyBuilder device_local_account_policy_;
const AccountId account_id_ =
AccountId::FromUserEmail(GenerateDeviceLocalAccountUserId(
kAccountId,
policy::DeviceLocalAccount::TYPE_PUBLIC_SESSION));
policy::DevicePolicyCrosTestHelper policy_helper_;
EmbeddedPolicyTestServerMixin policy_test_server_mixin_{&mixin_host_};
DeviceStateMixin device_state_{
&mixin_host_, DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
};
IN_PROC_BROWSER_TEST_F(PublicSessionTosScreenTest, Skipped) {
StartPublicSession();
test::WaitForPrimaryUserSessionStart();
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Accepted", 0);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Declined", 0);
histogram_tester_.ExpectTotalCount("OOBE.StepCompletionTime.Tos", 0);
}
IN_PROC_BROWSER_TEST_F(PublicSessionTosScreenTest, Accepted) {
SetUpTermsOfServiceUrlPolicy();
StartPublicSession();
WaitForScreenShown();
SetUpExitCallback();
test::OobeJS()
.CreateVisibilityWaiter(true, {"terms-of-service", "acceptButton"})
->Wait();
test::OobeJS().TapOnPath({"terms-of-service", "acceptButton"});
WaitForScreenExit();
EXPECT_EQ(result_.value(), TermsOfServiceScreen::Result::ACCEPTED);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Accepted", 1);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Declined", 0);
histogram_tester_.ExpectTotalCount("OOBE.StepCompletionTime.Tos", 1);
test::WaitForPrimaryUserSessionStart();
}
IN_PROC_BROWSER_TEST_F(PublicSessionTosScreenTest, Declined) {
SetUpTermsOfServiceUrlPolicy();
StartPublicSession();
WaitForScreenShown();
SetUpExitCallback();
test::OobeJS().TapOnPath({"terms-of-service", "backButton"});
WaitForScreenExit();
EXPECT_EQ(result_.value(), TermsOfServiceScreen::Result::DECLINED);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Accepted", 0);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Declined", 1);
histogram_tester_.ExpectTotalCount("OOBE.StepCompletionTime.Tos", 1);
EXPECT_TRUE(session_manager_client()->session_stopped());
EXPECT_TRUE(LoginScreenTestApi::IsPublicSessionExpanded());
}
class ManagedUserTosScreenTest : public OobeBaseTest {
public:
ManagedUserTosScreenTest() = default;
~ManagedUserTosScreenTest() override = default;
void RegisterAdditionalRequestHandlers() override {
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(&HandleRequest));
}
void StartManagedUserSession() {
user_policy_mixin_.RequestPolicyUpdate();
auto user_context =
login_manager_mixin_.CreateDefaultUserContext(managed_user_);
ExistingUserController* controller =
ExistingUserController::current_controller();
if (!controller) {
ADD_FAILURE();
return;
}
controller->Login(user_context, SigninSpecifics());
}
void SetUpTermsOfServiceUrlPolicy() {
std::unique_ptr<ScopedUserPolicyUpdate> scoped_user_policy_update =
user_policy_mixin_.RequestPolicyUpdate();
scoped_user_policy_update->policy_payload()
->mutable_termsofserviceurl()
->set_value(TestServerBaseUrl(embedded_test_server()));
}
TermsOfServiceScreen* GetTosScreen() {
return static_cast<TermsOfServiceScreen*>(
WizardController::default_controller()->screen_manager()->GetScreen(
TermsOfServiceScreenView::kScreenId));
}
void SetUpExitCallback() {
auto* screen = GetTosScreen();
original_callback_ = screen->get_exit_callback_for_testing();
screen->set_exit_callback_for_testing(base::BindRepeating(
&ManagedUserTosScreenTest::HandleScreenExit, base::Unretained(this)));
}
void WaitForScreenShown() {
OobeScreenWaiter(TermsOfServiceScreenView::kScreenId).Wait();
EXPECT_TRUE(LoginScreenTestApi::IsOobeDialogVisible());
}
void WaitForScreenExit() {
if (result_.has_value()) {
original_callback_.Run(result_.value());
return;
}
base::RunLoop run_loop;
screen_exit_callback_ = run_loop.QuitClosure();
run_loop.Run();
ASSERT_TRUE(result_.has_value());
original_callback_.Run(result_.value());
}
FakeSessionManagerClient* session_manager_client() {
return FakeSessionManagerClient::Get();
}
bool TosFileExists() {
base::ScopedAllowBlockingForTesting allow_blocking;
return base::PathExists(TermsOfServiceScreen::GetTosFilePath());
}
bool SavedTosMatchString(const std::string& tos) {
auto saved_tos =
ReadFileToOptionalString(TermsOfServiceScreen::GetTosFilePath());
return saved_tos.has_value() && saved_tos.value() == tos;
}
absl::optional<TermsOfServiceScreen::Result> result_;
base::HistogramTester histogram_tester_;
protected:
const LoginManagerMixin::TestUserInfo managed_user_{
AccountId::FromUserEmailGaiaId(kManagedUser, kManagedGaiaID)};
private:
void HandleScreenExit(TermsOfServiceScreen::Result result) {
result_ = result;
if (screen_exit_callback_)
screen_exit_callback_.Run();
}
base::RepeatingClosure screen_exit_callback_;
TermsOfServiceScreen::ScreenExitCallback original_callback_;
DeviceStateMixin device_state_{
&mixin_host_, DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
UserPolicyMixin user_policy_mixin_{&mixin_host_, managed_user_.account_id};
CryptohomeMixin cryptohome_mixin_{&mixin_host_};
LoginManagerMixin login_manager_mixin_{&mixin_host_,
{managed_user_},
nullptr,
&cryptohome_mixin_};
};
IN_PROC_BROWSER_TEST_F(ManagedUserTosScreenTest, Skipped) {
EXPECT_FALSE(TosFileExists());
StartManagedUserSession();
EXPECT_FALSE(TosFileExists());
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Accepted", 0);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Declined", 0);
histogram_tester_.ExpectTotalCount("OOBE.StepCompletionTime.Tos", 0);
test::WaitForPrimaryUserSessionStart();
}
IN_PROC_BROWSER_TEST_F(ManagedUserTosScreenTest, Accepted) {
SetUpTermsOfServiceUrlPolicy();
StartManagedUserSession();
WaitForScreenShown();
SetUpExitCallback();
test::OobeJS()
.CreateVisibilityWaiter(true, {"terms-of-service", "acceptButton"})
->Wait();
test::OobeJS().TapOnPath({"terms-of-service", "acceptButton"});
WaitForScreenExit();
EXPECT_EQ(result_.value(), TermsOfServiceScreen::Result::ACCEPTED);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Accepted", 1);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Declined", 0);
histogram_tester_.ExpectTotalCount("OOBE.StepCompletionTime.Tos", 1);
test::WaitForPrimaryUserSessionStart();
}
IN_PROC_BROWSER_TEST_F(ManagedUserTosScreenTest, Declined) {
SetUpTermsOfServiceUrlPolicy();
StartManagedUserSession();
WaitForScreenShown();
SetUpExitCallback();
test::OobeJS().TapOnPath({"terms-of-service", "backButton"});
WaitForScreenExit();
EXPECT_EQ(result_.value(), TermsOfServiceScreen::Result::DECLINED);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Accepted", 0);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Declined", 1);
histogram_tester_.ExpectTotalCount("OOBE.StepCompletionTime.Tos", 1);
EXPECT_TRUE(session_manager_client()->session_stopped());
}
IN_PROC_BROWSER_TEST_F(ManagedUserTosScreenTest, TosSaved) {
SetUpTermsOfServiceUrlPolicy();
EXPECT_FALSE(TosFileExists());
base::RunLoop run_loop;
TermsOfServiceScreen::SetTosSavedCallbackForTesting(run_loop.QuitClosure());
StartManagedUserSession();
WaitForScreenShown();
run_loop.Run();
EXPECT_TRUE(TosFileExists());
EXPECT_TRUE(SavedTosMatchString(std::string(kTosText)));
}
enum class PendingScreen { kEmpty, kTermsOfService, kSyncConsent };
OobeScreenId PendingScreenToId(PendingScreen pending_screen) {
switch (pending_screen) {
case PendingScreen::kEmpty:
return OOBE_SCREEN_UNKNOWN;
case PendingScreen::kTermsOfService:
return TermsOfServiceScreenView::kScreenId;
case PendingScreen::kSyncConsent:
return SyncConsentScreenView::kScreenId;
}
}
class ManagedUserTosOnboardingResumeTest
: public ManagedUserTosScreenTest,
public LocalStateMixin::Delegate,
public ::testing::WithParamInterface<PendingScreen> {
public:
ManagedUserTosOnboardingResumeTest() { pending_screen_param_ = GetParam(); }
void SetUpLocalState() override {
auto pending_screen_param = GetParam();
if (pending_screen_param_ == PendingScreen::kEmpty) {
return;
}
user_manager::KnownUser(g_browser_process->local_state())
.SetPendingOnboardingScreen(
managed_user_.account_id,
PendingScreenToId(pending_screen_param).name);
}
void EnsurePendingScreenIsEmpty() {
EXPECT_TRUE(user_manager::KnownUser(g_browser_process->local_state())
.GetPendingOnboardingScreen(managed_user_.account_id)
.empty());
}
protected:
PendingScreen pending_screen_param_;
private:
LocalStateMixin local_state_mixin_{&mixin_host_, this};
};
IN_PROC_BROWSER_TEST_P(ManagedUserTosOnboardingResumeTest, ResumeOnboarding) {
SetUpTermsOfServiceUrlPolicy();
StartManagedUserSession();
WaitForScreenShown();
SetUpExitCallback();
test::OobeJS().TapOnPath({"terms-of-service", "acceptButton"});
WaitForScreenExit();
EXPECT_EQ(result_.value(), TermsOfServiceScreen::Result::ACCEPTED);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Accepted", 1);
histogram_tester_.ExpectTotalCount(
"OOBE.StepCompletionTimeByExitReason.Terms-of-service.Declined", 0);
histogram_tester_.ExpectTotalCount("OOBE.StepCompletionTime.Tos", 1);
switch (pending_screen_param_) {
case PendingScreen::kEmpty:
EnsurePendingScreenIsEmpty();
EXPECT_EQ(WizardController::default_controller()
->get_screen_after_managed_tos_for_testing(),
OOBE_SCREEN_UNKNOWN);
test::WaitForPrimaryUserSessionStart();
break;
case PendingScreen::kTermsOfService:
EXPECT_EQ(WizardController::default_controller()
->get_screen_after_managed_tos_for_testing(),
FamilyLinkNoticeView::kScreenId);
break;
case PendingScreen::kSyncConsent:
EXPECT_EQ(WizardController::default_controller()
->get_screen_after_managed_tos_for_testing(),
SyncConsentScreenView::kScreenId);
break;
}
}
// Sets different pending screens to test resumable flow.
INSTANTIATE_TEST_SUITE_P(All,
ManagedUserTosOnboardingResumeTest,
testing::Values(PendingScreen::kEmpty,
PendingScreen::kTermsOfService,
PendingScreen::kSyncConsent));
} // namespace
} // namespace ash