chromium/chrome/browser/ash/app_mode/kiosk_crash_restore_browsertest.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string>
#include <utility>

#include "ash/constants/ash_switches.h"
#include "base/base64.h"
#include "base/callback_list.h"
#include "base/check.h"
#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/test_future.h"
#include "base/values.h"
#include "chrome/browser/ash/app_mode/app_launch_utils.h"
#include "chrome/browser/ash/app_mode/kiosk_app_launch_error.h"
#include "chrome/browser/ash/app_mode/kiosk_app_types.h"
#include "chrome/browser/ash/crosapi/browser_data_migrator.h"
#include "chrome/browser/ash/login/app_mode/test/kiosk_apps_mixin.h"
#include "chrome/browser/ash/login/app_mode/test/kiosk_test_helpers.h"
#include "chrome/browser/ash/login/test/embedded_test_server_setup_mixin.h"
#include "chrome/browser/ash/login/test/local_state_mixin.h"
#include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
#include "chrome/browser/ash/policy/core/device_policy_builder.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/termination_notification.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
#include "components/ownership/mock_owner_key_util.h"
#include "components/policy/core/common/device_local_account_type.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/browser_test.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

PrefService& local_state() {
  return CHECK_DEREF(g_browser_process->local_state());
}

// Allows waiting for the Chrome session to be terminated.
// Starts checking for session termination as soon as the instance is created.
class SessionTerminationWaiter {
 public:
  SessionTerminationWaiter() = default;
  SessionTerminationWaiter(const SessionTerminationWaiter&) = delete;
  SessionTerminationWaiter& operator=(const SessionTerminationWaiter&) = delete;
  ~SessionTerminationWaiter() = default;

  bool Wait() { return terminate_waiter_.Wait(); }

 private:
  base::test::TestFuture<void> terminate_waiter_;
  base::CallbackListSubscription subscription =
      browser_shutdown::AddAppTerminatingCallback(
          terminate_waiter_.GetCallback());
};

}  // namespace

class KioskCrashRestoreTest : public MixinBasedInProcessBrowserTest,
                              public LocalStateMixin::Delegate {
 public:
  KioskCrashRestoreTest()
      : owner_key_util_(new ownership::MockOwnerKeyUtil()) {}
  KioskCrashRestoreTest(const KioskCrashRestoreTest&) = delete;
  KioskCrashRestoreTest& operator=(const KioskCrashRestoreTest&) = delete;

  // LocalStateMixin::Delegate:
  void SetUpLocalState() override { SetUpExistingKioskApp(); }

  void SetUpInProcessBrowserTestFixture() override {
    MixinBasedInProcessBrowserTest::SetUpInProcessBrowserTestFixture();
    // Override device policy.
    OwnerSettingsServiceAshFactory::GetInstance()->SetOwnerKeyUtilForTesting(
        owner_key_util_);
    owner_key_util_->SetPublicKeyFromPrivateKey(
        *device_policy_.GetSigningKey());

    // SessionManagerClient will be destroyed in ChromeBrowserMain.
    SessionManagerClient::InitializeFakeInMemory();
    FakeSessionManagerClient::Get()->set_device_policy(
        device_policy_.GetBlob());
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
    const AccountId account_id = AccountId::FromUserEmail(GetTestAppUserId());
    const cryptohome::AccountIdentifier cryptohome_id =
        cryptohome::CreateAccountIdentifierFromAccountId(account_id);

    command_line->AppendSwitchASCII(switches::kLoginUser,
                                    cryptohome_id.account_id());
    command_line->AppendSwitchASCII(
        switches::kLoginProfile,
        UserDataAuthClient::GetStubSanitizedUsername(cryptohome_id));
  }

  void SetUpOnMainThread() override {
    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
    host_resolver()->AddRule("*", "127.0.0.1");
  }

  virtual const std::string GetTestAppUserId() const = 0;

 private:
  void SetUpExistingKioskApp() {
    // Create policy data that contains the test app as an existing kiosk app.
    KioskAppsMixin::AppendKioskAccount(&device_policy_.payload());
    KioskAppsMixin::AppendWebKioskAccount(&device_policy_.payload());
    device_policy_.Build();

    // Prepare the policy data to store in device policy cache.
    enterprise_management::PolicyData policy_data;
    CHECK(device_policy_.payload().SerializeToString(
        policy_data.mutable_policy_value()));
    const std::string policy_data_string = policy_data.SerializeAsString();

    // Store policy data and existing device local accounts in local state.
    local_state().SetString(prefs::kDeviceSettingsCache,
                            base::Base64Encode(policy_data_string));

    base::Value::List accounts;
    accounts.Append(GetTestAppUserId());
    local_state().SetList("PublicAccounts", std::move(accounts));
  }

  policy::DevicePolicyBuilder device_policy_;
  scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util_;

  EmbeddedTestServerSetupMixin embedded_test_server_{&mixin_host_,
                                                     embedded_test_server()};
  KioskAppsMixin kiosk_apps_{&mixin_host_, embedded_test_server()};
  LocalStateMixin local_state_mixin_{&mixin_host_, this};
};

class ChromeKioskCrashRestoreTest : public KioskCrashRestoreTest {
 public:
  const std::string GetTestAppUserId() const override {
    return policy::GenerateDeviceLocalAccountUserId(
        KioskAppsMixin::kEnterpriseKioskAccountId,
        policy::DeviceLocalAccountType::kKioskApp);
  }
};

IN_PROC_BROWSER_TEST_F(ChromeKioskCrashRestoreTest,
                       ShouldFailToRelaunchMissingCrashedChromeApp) {
  // If app is not installed when restoring from crash, the kiosk launch is
  // expected to fail, as in that case the crash occured during the app
  // initialization - before the app was actually launched.
  EXPECT_EQ(KioskAppLaunchError::Error::kUnableToLaunch,
            KioskAppLaunchError::Get());
}

class WebKioskCrashRestoreTest : public KioskCrashRestoreTest {
 public:
  const std::string GetTestAppUserId() const override {
    return policy::GenerateDeviceLocalAccountUserId(
        KioskAppsMixin::kEnterpriseWebKioskAccountId,
        policy::DeviceLocalAccountType::kWebKioskApp);
  }
};

IN_PROC_BROWSER_TEST_F(WebKioskCrashRestoreTest, ShouldRelaunchCrashedWebApp) {
  // Wait for the kiosk app to launch (through the crash recovery flow).
  KioskSessionInitializedWaiter().Wait();
  // Check there was no launch error.
  EXPECT_EQ(KioskAppLaunchError::Error::kNone, KioskAppLaunchError::Get());
}

class KioskWebAppAfterMigration : public WebKioskCrashRestoreTest {
 public:
  void SetUpLocalState() override {
    WebKioskCrashRestoreTest::SetUpLocalState();
    BrowserDataMigratorImpl::SetFirstLaunchAfterMigrationForTesting(
        &local_state());
  }

  AccountId GetTestAppAccountId() {
    return AccountId::FromUserEmail(GetTestAppUserId());
  }

  SessionTerminationWaiter session_termination_waiter_;
};

IN_PROC_BROWSER_TEST_F(KioskWebAppAfterMigration,
                       ShouldStoreAppIdAndTerminateSession) {
  EXPECT_EQ(GetOneTimeAutoLaunchKioskAppId(local_state()),
            KioskAppId::ForWebApp(GetTestAppAccountId()));

  EXPECT_TRUE(session_termination_waiter_.Wait());
}

class ChromeKioskAfterMigration : public ChromeKioskCrashRestoreTest {
 public:
  void SetUpLocalState() override {
    ChromeKioskCrashRestoreTest ::SetUpLocalState();
    BrowserDataMigratorImpl::SetFirstLaunchAfterMigrationForTesting(
        &local_state());
  }

  AccountId GetTestAppAccountId() {
    return AccountId::FromUserEmail(GetTestAppUserId());
  }

  SessionTerminationWaiter session_termination_waiter_;
};

IN_PROC_BROWSER_TEST_F(ChromeKioskAfterMigration,
                       ShouldStoreAppIdAndTerminateSession) {
  EXPECT_EQ(GetOneTimeAutoLaunchKioskAppId(local_state()),
            KioskAppId::ForChromeApp(KioskAppsMixin::kTestChromeAppId,
                                     GetTestAppAccountId()));

  EXPECT_TRUE(session_termination_waiter_.Wait());
}

}  // namespace ash