blob: b7c9ab5516715726f48252a36cea34748e7ef9f8 [file] [log] [blame]
// Copyright 2015 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/chromeos/login/easy_unlock/easy_unlock_service.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/run_loop.h"
#include "base/values.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_app_manager.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_notification_controller.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_factory.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_service_regular.h"
#include "chrome/browser/chromeos/login/users/mock_user_manager.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_power_manager_client.h"
#include "chromeos/dbus/power_manager/suspend.pb.h"
#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "components/account_id/account_id.h"
#include "components/signin/core/browser/account_info.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "testing/gmock/include/gmock/gmock.h"
using device::MockBluetoothAdapter;
using testing::_;
using testing::AnyNumber;
using testing::Return;
namespace chromeos {
namespace {
// Emails for fake users used in tests.
const char kTestUserPrimary[] = "primary_user@nowhere.com";
const char kTestUserSecondary[] = "secondary_user@nowhere.com";
class MockEasyUnlockNotificationController
: public EasyUnlockNotificationController {
public:
MockEasyUnlockNotificationController()
: EasyUnlockNotificationController(nullptr) {}
~MockEasyUnlockNotificationController() override {}
// EasyUnlockNotificationController:
MOCK_METHOD0(ShowChromebookAddedNotification, void());
MOCK_METHOD0(ShowPairingChangeNotification, void());
MOCK_METHOD1(ShowPairingChangeAppliedNotification, void(const std::string&));
private:
DISALLOW_COPY_AND_ASSIGN(MockEasyUnlockNotificationController);
};
// App manager to be used in EasyUnlockService tests.
// This effectivelly abstracts the extension system from the tests.
class TestAppManager : public EasyUnlockAppManager {
public:
TestAppManager()
: state_(STATE_NOT_LOADED),
ready_(false) {}
~TestAppManager() override {}
// The easy unlock app state.
enum State { STATE_NOT_LOADED, STATE_LOADED, STATE_DISABLED };
State state() const { return state_; }
// Marks the manager as ready and runs |ready_callback_| if there is one set.
void SetReady() {
ready_ = true;
if (!ready_callback_.is_null()) {
ready_callback_.Run();
ready_callback_ = base::Closure();
}
}
void EnsureReady(const base::Closure& ready_callback) override {
ASSERT_TRUE(ready_callback_.is_null());
if (ready_) {
ready_callback.Run();
return;
}
ready_callback_ = ready_callback;
}
void LoadApp() override { state_ = STATE_LOADED; }
void DisableAppIfLoaded() override {
if (state_ == STATE_LOADED)
state_ = STATE_DISABLED;
}
bool SendAuthAttemptEvent() override {
ADD_FAILURE() << "Not reached.";
return false;
}
private:
// The current app state.
State state_;
// Whether the manager is ready. Set using |SetReady|.
bool ready_;
// If |EnsureReady| was called before |SetReady|, cached callback that will be
// called when manager becomes ready.
base::Closure ready_callback_;
DISALLOW_COPY_AND_ASSIGN(TestAppManager);
};
// Helper factory that tracks AppManagers passed to EasyUnlockServices per
// browser context owning a EasyUnlockService. Used to allow tests access to the
// TestAppManagers passed to the created services.
class TestAppManagerFactory {
public:
TestAppManagerFactory() {}
~TestAppManagerFactory() {}
// Creates a TestAppManager for the provided browser context. If a
// TestAppManager was already created for the context, returns NULL.
std::unique_ptr<TestAppManager> Create(content::BrowserContext* context) {
if (Find(context))
return std::unique_ptr<TestAppManager>();
std::unique_ptr<TestAppManager> app_manager(new TestAppManager());
mapping_[context] = app_manager.get();
return app_manager;
}
// Finds a TestAppManager created for |context|. Returns NULL if no
// TestAppManagers have been created for the context.
TestAppManager* Find(content::BrowserContext* context) {
std::map<content::BrowserContext*, TestAppManager*>::iterator it =
mapping_.find(context);
if (it == mapping_.end())
return NULL;
return it->second;
}
private:
// Mapping from browser contexts to test AppManagers. The AppManagers are not
// owned by this.
std::map<content::BrowserContext*, TestAppManager*> mapping_;
DISALLOW_COPY_AND_ASSIGN(TestAppManagerFactory);
};
// Global TestAppManager factory. It should be created and destroyed in
// EasyUnlockServiceTest::SetUp and EasyUnlockServiceTest::TearDown,
// respectively.
TestAppManagerFactory* app_manager_factory = nullptr;
device_sync::FakeDeviceSyncClient* GetDefaultDeviceSyncClient() {
static base::NoDestructor<device_sync::FakeDeviceSyncClient> fake_client;
return fake_client.get();
}
multidevice_setup::FakeMultiDeviceSetupClient*
GetDefaultMultiDeviceSetupClient() {
static base::NoDestructor<multidevice_setup::FakeMultiDeviceSetupClient>
fake_client;
return fake_client.get();
}
// EasyUnlockService factory function injected into testing profiles.
// It creates an EasyUnlockService with test AppManager.
std::unique_ptr<KeyedService> CreateEasyUnlockServiceForTest(
content::BrowserContext* context) {
EXPECT_TRUE(app_manager_factory);
if (!app_manager_factory)
return nullptr;
std::unique_ptr<EasyUnlockAppManager> app_manager =
app_manager_factory->Create(context);
EXPECT_TRUE(app_manager.get());
if (!app_manager.get())
return nullptr;
std::unique_ptr<EasyUnlockServiceRegular> service(
new EasyUnlockServiceRegular(
Profile::FromBrowserContext(context),
nullptr /* secure_channel_client */,
std::make_unique<MockEasyUnlockNotificationController>(),
GetDefaultDeviceSyncClient(), GetDefaultMultiDeviceSetupClient()));
service->Initialize(std::move(app_manager));
return std::move(service);
}
} // namespace
class EasyUnlockServiceTest : public testing::Test {
protected:
EasyUnlockServiceTest()
: mock_user_manager_(new testing::NiceMock<MockUserManager>()),
scoped_user_manager_(base::WrapUnique(mock_user_manager_)),
is_bluetooth_adapter_present_(true) {}
~EasyUnlockServiceTest() override {}
void SetUp() override {
app_manager_factory = new TestAppManagerFactory();
mock_adapter_ = new testing::NiceMock<MockBluetoothAdapter>();
device::BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
EXPECT_CALL(*mock_adapter_, IsPresent())
.WillRepeatedly(testing::Invoke(
this, &EasyUnlockServiceTest::is_bluetooth_adapter_present));
std::unique_ptr<DBusThreadManagerSetter> dbus_setter =
DBusThreadManager::GetSetterForTesting();
power_manager_client_ = new FakePowerManagerClient;
dbus_setter->SetPowerManagerClient(
std::unique_ptr<PowerManagerClient>(power_manager_client_));
ON_CALL(*mock_user_manager_, Shutdown()).WillByDefault(Return());
ON_CALL(*mock_user_manager_, IsLoggedInAsUserWithGaiaAccount())
.WillByDefault(Return(true));
ON_CALL(*mock_user_manager_, IsCurrentUserNonCryptohomeDataEphemeral())
.WillByDefault(Return(false));
TestingBrowserProcess::GetGlobal()->SetLocalState(&local_pref_service_);
RegisterLocalState(local_pref_service_.registry());
profile_ = SetUpProfile(kTestUserPrimary, &profile_gaia_id_);
}
void TearDown() override {
TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
delete app_manager_factory;
app_manager_factory = nullptr;
}
void SetEasyUnlockAllowedPolicy(bool allowed) {
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kEasyUnlockAllowed, std::make_unique<base::Value>(allowed));
}
void set_is_bluetooth_adapter_present(bool is_present) {
is_bluetooth_adapter_present_ = is_present;
}
bool is_bluetooth_adapter_present() const {
return is_bluetooth_adapter_present_;
}
FakePowerManagerClient* power_manager_client() {
return power_manager_client_;
}
// Checks whether AppManager passed to EasyUnlockservice for |profile| has
// Easy Unlock app loaded.
bool EasyUnlockAppInState(Profile* profile, TestAppManager::State state) {
EXPECT_TRUE(app_manager_factory);
if (!app_manager_factory)
return false;
TestAppManager* app_manager = app_manager_factory->Find(profile);
EXPECT_TRUE(app_manager);
return app_manager && app_manager->state() == state;
}
void SetAppManagerReady(content::BrowserContext* context) {
ASSERT_TRUE(app_manager_factory);
TestAppManager* app_manager = app_manager_factory->Find(context);
ASSERT_TRUE(app_manager);
app_manager->SetReady();
}
// Sets up a test profile using the provided |email|. Will generate a unique
// gaia id and output to |gaia_id|. Returns the created TestingProfile.
std::unique_ptr<TestingProfile> SetUpProfile(const std::string& email,
std::string* gaia_id) {
std::unique_ptr<TestingProfile> profile =
IdentityTestEnvironmentProfileAdaptor::
CreateProfileForIdentityTestEnvironment(
{{EasyUnlockServiceFactory::GetInstance(),
base::BindRepeating(&CreateEasyUnlockServiceForTest)}});
// Note: This can simply be a local variable as the test does not need to
// interact with IdentityTestEnvironment outside of this method. If that
// ever changes, there will need to be distinct instance variables for the
// environments associated with |profile_| and |secondary_profile_|.
IdentityTestEnvironmentProfileAdaptor identity_test_env_adaptor(
profile.get());
AccountInfo account_info =
identity_test_env_adaptor.identity_test_env()->SetPrimaryAccount(email);
*gaia_id = account_info.gaia;
mock_user_manager_->AddUser(
AccountId::FromUserEmailGaiaId(email, *gaia_id));
profile.get()->set_profile_name(email);
return profile;
}
void SetUpSecondaryProfile() {
secondary_profile_ =
SetUpProfile(kTestUserSecondary, &secondary_profile_gaia_id_);
}
// Must outlive TestingProfiles.
content::TestBrowserThreadBundle thread_bundle_;
std::unique_ptr<TestingProfile> profile_;
std::string profile_gaia_id_;
std::unique_ptr<TestingProfile> secondary_profile_;
std::string secondary_profile_gaia_id_;
MockUserManager* mock_user_manager_;
private:
user_manager::ScopedUserManager scoped_user_manager_;
FakePowerManagerClient* power_manager_client_;
bool is_bluetooth_adapter_present_;
scoped_refptr<testing::NiceMock<MockBluetoothAdapter>> mock_adapter_;
// PrefService which contains the browser process' local storage.
TestingPrefServiceSimple local_pref_service_;
DISALLOW_COPY_AND_ASSIGN(EasyUnlockServiceTest);
};
TEST_F(EasyUnlockServiceTest, NoBluetoothNoService) {
set_is_bluetooth_adapter_present(false);
// This should start easy unlock service initialization.
SetAppManagerReady(profile_.get());
EasyUnlockService* service = EasyUnlockService::Get(profile_.get());
ASSERT_TRUE(service);
EXPECT_FALSE(service->IsAllowed());
EXPECT_TRUE(
EasyUnlockAppInState(profile_.get(), TestAppManager::STATE_NOT_LOADED));
}
// TODO(https://crbug.com/893878): Fix disabled test.
TEST_F(EasyUnlockServiceTest, DISABLED_DisabledOnSuspend) {
// This should start easy unlock service initialization.
SetAppManagerReady(profile_.get());
EasyUnlockService* service = EasyUnlockService::Get(profile_.get());
ASSERT_TRUE(service);
EXPECT_TRUE(service->IsAllowed());
EXPECT_TRUE(
EasyUnlockAppInState(profile_.get(), TestAppManager::STATE_LOADED));
power_manager_client()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_OTHER);
EXPECT_TRUE(
EasyUnlockAppInState(profile_.get(), TestAppManager::STATE_DISABLED));
power_manager_client()->SendSuspendDone();
EXPECT_TRUE(
EasyUnlockAppInState(profile_.get(), TestAppManager::STATE_LOADED));
}
// TODO(https://crbug.com/893878): Fix disabled test.
TEST_F(EasyUnlockServiceTest, DISABLED_NotAllowedForSecondaryProfile) {
SetAppManagerReady(profile_.get());
EasyUnlockService* primary_service = EasyUnlockService::Get(profile_.get());
ASSERT_TRUE(primary_service);
// A sanity check for the test to confirm that the primary profile service
// is allowed under these conditions..
ASSERT_TRUE(primary_service->IsAllowed());
SetUpSecondaryProfile();
SetAppManagerReady(secondary_profile_.get());
EasyUnlockService* secondary_service =
EasyUnlockService::Get(secondary_profile_.get());
ASSERT_TRUE(secondary_service);
EXPECT_FALSE(secondary_service->IsAllowed());
EXPECT_TRUE(EasyUnlockAppInState(secondary_profile_.get(),
TestAppManager::STATE_NOT_LOADED));
}
TEST_F(EasyUnlockServiceTest, NotAllowedForEphemeralAccounts) {
ON_CALL(*mock_user_manager_, IsCurrentUserNonCryptohomeDataEphemeral())
.WillByDefault(Return(true));
SetAppManagerReady(profile_.get());
EXPECT_FALSE(EasyUnlockService::Get(profile_.get())->IsAllowed());
EXPECT_TRUE(
EasyUnlockAppInState(profile_.get(), TestAppManager::STATE_NOT_LOADED));
}
TEST_F(EasyUnlockServiceTest, GetAccountId) {
EXPECT_EQ(AccountId::FromUserEmailGaiaId(kTestUserPrimary, profile_gaia_id_),
EasyUnlockService::Get(profile_.get())->GetAccountId());
SetUpSecondaryProfile();
EXPECT_EQ(AccountId::FromUserEmailGaiaId(kTestUserSecondary,
secondary_profile_gaia_id_),
EasyUnlockService::Get(secondary_profile_.get())->GetAccountId());
}
} // namespace chromeos