blob: af7f0104454218f8d9649cc8f8b89809daa2276a [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/signin/easy_unlock_service.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/values.h"
#include "chrome/browser/chromeos/login/users/mock_user_manager.h"
#include "chrome/browser/chromeos/login/users/scoped_user_manager_enabler.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/signin/easy_unlock_app_manager.h"
#include "chrome/browser/signin/easy_unlock_service_factory.h"
#include "chrome/browser/signin/easy_unlock_service_regular.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_power_manager_client.h"
#include "components/signin/core/account_id/account_id.h"
#include "components/signin/core/browser/signin_manager_base.h"
#include "components/sync_preferences/testing_pref_service_syncable.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 chromeos::DBusThreadManagerSetter;
using chromeos::FakePowerManagerClient;
using chromeos::PowerManagerClient;
using chromeos::ProfileHelper;
using device::MockBluetoothAdapter;
using testing::_;
using testing::AnyNumber;
using testing::Return;
namespace {
// IDs for fake users used in tests.
const char kTestUserPrimary[] = "primary_user@nowhere.com";
const char kPrimaryGaiaId[] = "1111111111";
const char kTestUserSecondary[] = "secondary_user@nowhere.com";
const char kSecondaryGaiaId[] = "2222222222";
// 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),
app_launch_count_(0u),
reload_count_(0u),
ready_(false) {}
~TestAppManager() override {}
// The easy unlock app state.
enum State { STATE_NOT_LOADED, STATE_LOADED, STATE_DISABLED };
State state() const { return state_; }
size_t app_launch_count() const { return app_launch_count_; }
size_t reload_count() const { return reload_count_; }
// 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 LaunchSetup() override {
ASSERT_EQ(STATE_LOADED, state_);
++app_launch_count_;
}
void LoadApp() override { state_ = STATE_LOADED; }
void DisableAppIfLoaded() override {
if (state_ == STATE_LOADED)
state_ = STATE_DISABLED;
}
void ReloadApp() override {
if (state_ == STATE_LOADED)
++reload_count_;
}
bool SendUserUpdatedEvent(const std::string& user_id,
bool is_logged_in,
bool data_ready) override {
// TODO(tbarzic): Make this a bit smarter and add some test to utilize it.
return true;
}
bool SendAuthAttemptEvent() override {
ADD_FAILURE() << "Not reached.";
return false;
}
private:
// The current app state.
State state_;
// Number of times LaunchSetup was called.
size_t app_launch_count_;
// Number of times ReloadApp was called.
size_t reload_count_;
// 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 desctructed in
// EasyUnlockServiceTest::SetUp and EasyUnlockServiceTest::TearDown
// respectively.
TestAppManagerFactory* app_manager_factory = NULL;
// 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)));
service->Initialize(std::move(app_manager));
return std::move(service);
}
class EasyUnlockServiceTest : public testing::Test {
public:
EasyUnlockServiceTest()
: mock_user_manager_(new testing::NiceMock<chromeos::MockUserManager>()),
scoped_user_manager_(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 =
chromeos::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));
SetUpProfile(&profile_, AccountId::FromUserEmailGaiaId(
kTestUserPrimary, kPrimaryGaiaId));
}
void TearDown() override {
delete app_manager_factory;
app_manager_factory = NULL;
}
void SetEasyUnlockAllowedPolicy(bool allowed) {
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kEasyUnlockAllowed, base::MakeUnique<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();
}
void SetUpSecondaryProfile() {
SetUpProfile(&secondary_profile_,
AccountId::FromUserEmailGaiaId(kTestUserSecondary,
kSecondaryGaiaId));
}
private:
// Sets up a test profile with a user id.
void SetUpProfile(std::unique_ptr<TestingProfile>* profile,
const AccountId& account_id) {
ASSERT_TRUE(profile);
ASSERT_FALSE(profile->get());
TestingProfile::Builder builder;
builder.AddTestingFactory(EasyUnlockServiceFactory::GetInstance(),
&CreateEasyUnlockServiceForTest);
*profile = builder.Build();
mock_user_manager_->AddUser(account_id);
profile->get()->set_profile_name(account_id.GetUserEmail());
SigninManagerBase* signin_manager =
SigninManagerFactory::GetForProfile(profile->get());
signin_manager->SetAuthenticatedAccountInfo(account_id.GetGaiaId(),
account_id.GetUserEmail());
}
private:
// Must outlive TestingProfiles.
content::TestBrowserThreadBundle thread_bundle_;
protected:
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<TestingProfile> secondary_profile_;
chromeos::MockUserManager* mock_user_manager_;
private:
chromeos::ScopedUserManagerEnabler scoped_user_manager_;
FakePowerManagerClient* power_manager_client_;
bool is_bluetooth_adapter_present_;
scoped_refptr<testing::NiceMock<MockBluetoothAdapter>> mock_adapter_;
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));
}
TEST_F(EasyUnlockServiceTest, 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();
EXPECT_TRUE(
EasyUnlockAppInState(profile_.get(), TestAppManager::STATE_DISABLED));
power_manager_client()->SendSuspendDone();
EXPECT_TRUE(
EasyUnlockAppInState(profile_.get(), TestAppManager::STATE_LOADED));
}
TEST_F(EasyUnlockServiceTest, 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, kPrimaryGaiaId),
EasyUnlockService::Get(profile_.get())->GetAccountId());
SetUpSecondaryProfile();
EXPECT_EQ(AccountId::FromUserEmailGaiaId(kTestUserSecondary,
kSecondaryGaiaId),
EasyUnlockService::Get(secondary_profile_.get())->GetAccountId());
}
} // namespace