// 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.
// This file tests the chromeos.quickUnlockPrivate extension API.
#include "chrome/browser/extensions/api/quick_unlock_private/quick_unlock_private_api.h"
#include <memory>
#include <optional>
#include <string_view>
#include <utility>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chrome/browser/ash/login/quick_unlock/auth_token.h"
#include "chrome/browser/ash/login/quick_unlock/pin_backend.h"
#include "chrome/browser/ash/login/quick_unlock/pin_storage_prefs.h"
#include "chrome/browser/ash/login/quick_unlock/quick_unlock_utils.h"
#include "chrome/browser/ash/login/smart_lock/smart_lock_service.h"
#include "chrome/browser/ash/login/smart_lock/smart_lock_service_factory.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_api_unittest.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/cryptohome/constants.h"
#include "chromeos/ash/components/cryptohome/system_salt_getter.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_userdataauth_client.h"
#include "chromeos/ash/components/login/auth/public/cryptohome_key_constants.h"
#include "chromeos/ash/components/osauth/impl/auth_parts_impl.h"
#include "chromeos/ash/components/osauth/impl/auth_session_storage_impl.h"
#include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_manager/known_user.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/extension_function_dispatcher.h"
namespace extensions {
namespace {
namespace quick_unlock_private = api::quick_unlock_private;
using CredentialCheck = quick_unlock_private::CredentialCheck;
using CredentialProblem = quick_unlock_private::CredentialProblem;
using CredentialRequirements = quick_unlock_private::CredentialRequirements;
using QuickUnlockMode = quick_unlock_private::QuickUnlockMode;
using QuickUnlockModeList = std::vector<QuickUnlockMode>;
using CredentialList = std::vector<std::string>;
// The type of the test. Either based on Prefs or Cryptohome
enum class TestType { kPrefs, kCryptohome };
const std::string TestTypeStr(TestType type) {
switch (type) {
case TestType::kPrefs:
return "PrefBased";
case TestType::kCryptohome:
return "CryptohomeBased";
}
}
constexpr char kTestUserEmail[] = "[email protected]";
constexpr char kInvalidToken[] = "invalid";
constexpr char kValidPassword[] = "valid";
constexpr char kInvalidPassword[] = "invalid";
class FakeSmartLockService : public ash::SmartLockService {
public:
FakeSmartLockService(
Profile* profile,
ash::device_sync::FakeDeviceSyncClient* fake_device_sync_client,
ash::secure_channel::FakeSecureChannelClient* fake_secure_channel_client,
ash::multidevice_setup::FakeMultiDeviceSetupClient*
fake_multidevice_setup_client)
: ash::SmartLockService(profile,
fake_secure_channel_client,
fake_device_sync_client,
fake_multidevice_setup_client) {}
FakeSmartLockService(const FakeSmartLockService&) = delete;
FakeSmartLockService& operator=(const FakeSmartLockService&) = delete;
~FakeSmartLockService() override {}
};
std::unique_ptr<KeyedService> CreateSmartLockServiceForTest(
content::BrowserContext* context) {
static base::NoDestructor<ash::device_sync::FakeDeviceSyncClient>
fake_device_sync_client;
static base::NoDestructor<ash::secure_channel::FakeSecureChannelClient>
fake_secure_channel_client;
static base::NoDestructor<ash::multidevice_setup::FakeMultiDeviceSetupClient>
fake_multidevice_setup_client;
return std::make_unique<FakeSmartLockService>(
Profile::FromBrowserContext(context), fake_device_sync_client.get(),
fake_secure_channel_client.get(), fake_multidevice_setup_client.get());
}
void FailIfCalled(const QuickUnlockModeList& modes) {
FAIL();
}
enum ExpectedPinState {
PIN_GOOD = 1 << 0,
PIN_TOO_SHORT = 1 << 1,
PIN_TOO_LONG = 1 << 2,
PIN_WEAK_ERROR = 1 << 3,
PIN_WEAK_WARNING = 1 << 4,
PIN_CONTAINS_NONDIGIT = 1 << 5
};
} // namespace
class QuickUnlockPrivateUnitTest
: public ExtensionApiUnittest,
public ::testing::WithParamInterface<TestType> {
public:
static std::string ParamInfoToString(
testing::TestParamInfo<QuickUnlockPrivateUnitTest::ParamType> info) {
return TestTypeStr(info.param);
}
QuickUnlockPrivateUnitTest() = default;
QuickUnlockPrivateUnitTest(const QuickUnlockPrivateUnitTest&) = delete;
QuickUnlockPrivateUnitTest& operator=(const QuickUnlockPrivateUnitTest&) =
delete;
~QuickUnlockPrivateUnitTest() override = default;
protected:
void SetUp() override {
const auto param = GetParam();
ash::CryptohomeMiscClient::InitializeFake();
ash::UserDataAuthClient::InitializeFake();
auto* fake_userdataauth_client_testapi =
ash::FakeUserDataAuthClient::TestApi::Get();
fake_userdataauth_client_testapi->set_enable_auth_check(true);
if (param == TestType::kCryptohome) {
fake_userdataauth_client_testapi->set_supports_low_entropy_credentials(
true);
}
const cryptohome::AccountIdentifier account_id =
cryptohome::CreateAccountIdentifierFromAccountId(
AccountId::FromUserEmail(kTestUserEmail));
ash::Key key{kValidPassword};
key.Transform(ash::Key::KEY_TYPE_SALTED_SHA256_TOP_HALF,
ash::SystemSaltGetter::ConvertRawSaltToHexString(
ash::FakeCryptohomeMiscClient::GetStubSystemSalt()));
user_data_auth::AuthFactor auth_factor;
user_data_auth::AuthInput auth_input;
auth_factor.set_label(ash::kCryptohomeGaiaKeyLabel);
auth_factor.set_type(user_data_auth::AUTH_FACTOR_TYPE_PASSWORD);
auth_input.mutable_password_input()->set_secret(key.GetSecret());
fake_userdataauth_client_testapi->AddExistingUser(account_id);
fake_userdataauth_client_testapi->AddAuthFactor(account_id, auth_factor,
auth_input);
ash::SystemSaltGetter::Initialize();
auth_parts_ = ash::AuthPartsImpl::CreateTestInstance();
auth_parts_->SetAuthSessionStorage(
std::make_unique<ash::AuthSessionStorageImpl>(
ash::UserDataAuthClient::Get()));
ExtensionApiUnittest::SetUp();
ash::SystemSaltGetter::Get()->SetRawSaltForTesting(
{1, 2, 3, 4, 5, 6, 7, 8});
// Rebuild quick unlock state.
test_api_ = std::make_unique<ash::quick_unlock::TestApi>(
/*override_quick_unlock=*/true);
test_api_->EnablePinByPolicy(ash::quick_unlock::Purpose::kAny);
ash::quick_unlock::PinBackend::ResetForTesting();
base::RunLoop().RunUntilIdle();
modes_changed_handler_ = base::DoNothing();
// Ensure that quick unlock is turned off.
RunSetModes(QuickUnlockModeList{}, CredentialList{});
}
std::string GetDefaultProfileName() override { return kTestUserEmail; }
TestingProfile* CreateProfile(const std::string& profile_name) override {
auto pref_service =
std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
RegisterUserProfilePrefs(pref_service->registry());
test_pref_service_ = pref_service.get();
TestingProfile* profile = profile_manager()->CreateTestingProfile(
profile_name, std::move(pref_service), u"Test profile",
1 /* avatar_id */, GetTestingFactories());
OnUserProfileCreated(profile_name, profile);
// Setup a primary user.
ash::ProfileHelper::Get()->SetUserToProfileMappingForTesting(
user_manager()->GetPrimaryUser(), profile);
// Generate an auth token.
AccountId account_id = AccountId::FromUserEmail(profile_name);
auth_token_user_context_.SetAccountId(account_id);
auth_token_user_context_.SetUserIDHash(
user_manager::FakeUserManager::GetFakeUsernameHash(account_id));
auth_token_user_context_.SetSessionLifetime(
base::Time::Now() + ash::quick_unlock::AuthToken::kTokenExpiration);
if (GetParam() == TestType::kCryptohome) {
auto* fake_userdataauth_client_testapi =
ash::FakeUserDataAuthClient::TestApi::Get();
auto session_ids = fake_userdataauth_client_testapi->AddSession(
cryptohome::CreateAccountIdentifierFromAccountId(account_id),
/*authenticated=*/true);
auth_token_user_context_.SetAuthSessionIds(session_ids.first,
session_ids.second);
// Technically configuration should contain password as factor, but
// it is not checked anywhere.
auth_token_user_context_.SetAuthFactorsConfiguration(
ash::AuthFactorsConfiguration());
}
token_ = ash::AuthSessionStorage::Get()->Store(
std::make_unique<ash::UserContext>(auth_token_user_context_));
base::RunLoop().RunUntilIdle();
return profile;
}
void TearDown() override {
base::RunLoop().RunUntilIdle();
ExtensionApiUnittest::TearDown();
test_api_.reset();
ash::SystemSaltGetter::Shutdown();
ash::UserDataAuthClient::Shutdown();
ash::CryptohomeMiscClient::Shutdown();
}
TestingProfile::TestingFactories GetTestingFactories() override {
return {TestingProfile::TestingFactory{
ash::SmartLockServiceFactory::GetInstance(),
base::BindRepeating(&CreateSmartLockServiceForTest)}};
}
// If a mode change event is raised, fail the test.
void FailIfModesChanged() {
modes_changed_handler_ = base::BindRepeating(&FailIfCalled);
}
// If a mode change event is raised, expect the given |modes|.
void ExpectModesChanged(const QuickUnlockModeList& modes) {
modes_changed_handler_ =
base::BindRepeating(&QuickUnlockPrivateUnitTest::ExpectModeList,
base::Unretained(this), modes);
expect_modes_changed_ = true;
}
// Wrapper for chrome.quickUnlockPrivate.getAuthToken. Expects the function
// to succeed and returns the result.
std::optional<quick_unlock_private::TokenInfo> GetAuthToken(
const std::string& password) {
auto func = base::MakeRefCounted<QuickUnlockPrivateGetAuthTokenFunction>();
base::Value::List params;
params.Append(base::Value(password));
std::optional<base::Value> result =
RunFunction(std::move(func), std::move(params));
EXPECT_TRUE(result);
auto token_info = quick_unlock_private::TokenInfo::FromValue(*result);
EXPECT_TRUE(token_info);
return token_info;
}
// Wrapper for chrome.quickUnlockPrivate.getAuthToken with an invalid
// password. Expects the function to fail and returns the error.
std::string RunAuthTokenWithInvalidPassword() {
auto func = base::MakeRefCounted<QuickUnlockPrivateGetAuthTokenFunction>();
base::Value::List params;
params.Append(base::Value(kInvalidPassword));
return RunFunctionAndReturnError(std::move(func), std::move(params));
}
// Wrapper for chrome.quickUnlockPrivate.setLockScreenEnabled.
void SetLockScreenEnabled(const std::string& token, bool enabled) {
base::Value::List params;
params.Append(token);
params.Append(enabled);
RunFunction(
base::MakeRefCounted<QuickUnlockPrivateSetLockScreenEnabledFunction>(),
std::move(params));
}
// Wrapper for chrome.quickUnlockPrivate.setLockScreenEnabled.
std::string SetLockScreenEnabledWithInvalidToken(bool enabled) {
base::Value::List params;
params.Append(kInvalidToken);
params.Append(enabled);
return RunFunctionAndReturnError(
base::MakeRefCounted<QuickUnlockPrivateSetLockScreenEnabledFunction>(),
std::move(params));
}
// Wrapper for chrome.quickUnlockPrivate.getAvailableModes.
QuickUnlockModeList GetAvailableModes() {
// Run the function.
std::optional<base::Value> result = RunFunction(
base::MakeRefCounted<QuickUnlockPrivateGetAvailableModesFunction>(),
base::Value::List());
// Extract the results.
QuickUnlockModeList modes;
EXPECT_TRUE(result->is_list());
for (const base::Value& value : result->GetList()) {
EXPECT_TRUE(value.is_string());
modes.push_back(
quick_unlock_private::ParseQuickUnlockMode(value.GetString()));
}
return modes;
}
// Wrapper for chrome.quickUnlockPrivate.getActiveModes.
QuickUnlockModeList GetActiveModes() {
std::optional<base::Value> result = RunFunction(
base::MakeRefCounted<QuickUnlockPrivateGetActiveModesFunction>(),
base::Value::List());
QuickUnlockModeList modes;
EXPECT_TRUE(result->is_list());
for (const base::Value& value : result->GetList()) {
EXPECT_TRUE(value.is_string());
modes.push_back(
quick_unlock_private::ParseQuickUnlockMode(value.GetString()));
}
return modes;
}
bool HasFlag(int outcome, int flag) { return (outcome & flag) != 0; }
// Helper function for checking whether |IsCredentialUsableUsingPin| will
// return the right message given a pin.
void CheckPin(int expected_outcome, const std::string& pin) {
CredentialCheck result = CheckCredentialUsingPin(pin);
const std::vector<CredentialProblem> errors(result.errors);
const std::vector<CredentialProblem> warnings(result.warnings);
// A pin is considered good if it emits no errors or warnings.
EXPECT_EQ(HasFlag(expected_outcome, PIN_GOOD),
errors.empty() && warnings.empty());
EXPECT_EQ(HasFlag(expected_outcome, PIN_TOO_SHORT),
base::Contains(errors, CredentialProblem::kTooShort));
EXPECT_EQ(HasFlag(expected_outcome, PIN_TOO_LONG),
base::Contains(errors, CredentialProblem::kTooLong));
EXPECT_EQ(HasFlag(expected_outcome, PIN_WEAK_WARNING),
base::Contains(warnings, CredentialProblem::kTooWeak));
EXPECT_EQ(HasFlag(expected_outcome, PIN_WEAK_ERROR),
base::Contains(errors, CredentialProblem::kTooWeak));
EXPECT_EQ(HasFlag(expected_outcome, PIN_CONTAINS_NONDIGIT),
base::Contains(errors, CredentialProblem::kContainsNondigit));
}
CredentialCheck CheckCredentialUsingPin(const std::string& pin) {
base::Value::List params;
params.Append(ToString(QuickUnlockMode::kPin));
params.Append(pin);
std::optional<base::Value> result = RunFunction(
base::MakeRefCounted<QuickUnlockPrivateCheckCredentialFunction>(),
std::move(params));
EXPECT_TRUE(result->is_dict());
auto function_result = CredentialCheck::FromValue(result->GetDict());
EXPECT_TRUE(function_result);
return std::move(function_result).value();
}
void CheckGetCredentialRequirements(int expected_pin_min_length,
int expected_pin_max_length) {
base::Value::List params;
params.Append(ToString(QuickUnlockMode::kPin));
std::optional<base::Value> result =
RunFunction(base::MakeRefCounted<
QuickUnlockPrivateGetCredentialRequirementsFunction>(),
std::move(params));
EXPECT_TRUE(result->is_dict());
auto function_result = CredentialRequirements::FromValue(result->GetDict());
ASSERT_TRUE(function_result);
EXPECT_EQ(function_result->min_length, expected_pin_min_length);
EXPECT_EQ(function_result->max_length, expected_pin_max_length);
}
base::Value::List GetSetModesParams(const std::string& token,
const QuickUnlockModeList& modes,
const CredentialList& passwords) {
base::Value::List params;
params.Append(token);
base::Value::List serialized_modes;
for (QuickUnlockMode mode : modes)
serialized_modes.Append(quick_unlock_private::ToString(mode));
params.Append(base::Value(std::move(serialized_modes)));
base::Value::List serialized_passwords;
for (const std::string& password : passwords)
serialized_passwords.Append(password);
params.Append(base::Value(std::move(serialized_passwords)));
return params;
}
// Runs chrome.quickUnlockPrivate.setModes using a valid token. Expects the
// function to succeed.
void RunSetModes(const QuickUnlockModeList& modes,
const CredentialList& passwords) {
base::Value::List params = GetSetModesParams(token_, modes, passwords);
auto func = base::MakeRefCounted<QuickUnlockPrivateSetModesFunction>();
// Stub out event handling since we are not setting up an event router.
func->SetModesChangedEventHandlerForTesting(modes_changed_handler_);
// Run the function. Expect a non null result.
RunFunction(std::move(func), std::move(params));
// Verify that the mode change event handler was run if it was registered.
// ExpectModesChanged will set expect_modes_changed_ to true and the event
// handler will set it to false; so if the handler never runs,
// expect_modes_changed_ will still be true.
EXPECT_FALSE(expect_modes_changed_) << "Mode change event was not raised";
}
// Runs chrome.quickUnlockPrivate.setModes using an invalid token. Expects the
// function to fail and returns the error.
std::string RunSetModesWithInvalidToken() {
base::Value::List params =
GetSetModesParams(kInvalidToken, {QuickUnlockMode::kPin}, {"111111"});
auto func = base::MakeRefCounted<QuickUnlockPrivateSetModesFunction>();
// Stub out event handling since we are not setting up an event router.
func->SetModesChangedEventHandlerForTesting(modes_changed_handler_);
// Run function, expecting it to fail.
return RunFunctionAndReturnError(std::move(func), std::move(params));
}
std::string SetModesWithError(const std::string& args) {
auto func = base::MakeRefCounted<QuickUnlockPrivateSetModesFunction>();
func->SetModesChangedEventHandlerForTesting(base::DoNothing());
return api_test_utils::RunFunctionAndReturnError(func.get(), args,
profile());
}
std::string token() { return token_; }
// Returns if the pin is set in the backend.
bool IsPinSetInBackend() {
const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
base::test::TestFuture<bool> is_pin_set_future;
ash::quick_unlock::PinBackend::GetInstance()->IsSet(
account_id, is_pin_set_future.GetCallback());
return is_pin_set_future.Get();
}
// Checks whether there is a user value set for the PIN auto submit
// preference.
bool HasUserValueForPinAutosubmitPref() {
const bool has_user_val =
test_pref_service_->GetUserPrefValue(
::prefs::kPinUnlockAutosubmitEnabled) != nullptr;
return has_user_val;
}
bool GetAutosubmitPrefVal() {
return test_pref_service_->GetBoolean(::prefs::kPinUnlockAutosubmitEnabled);
}
int GetExposedPinLength() {
const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
return user_manager::KnownUser(g_browser_process->local_state())
.GetUserPinLength(account_id);
}
void ClearExposedPinLength() {
const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
user_manager::KnownUser(g_browser_process->local_state())
.SetUserPinLength(account_id, 0);
}
bool IsBackfillNeeded() {
const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
return user_manager::KnownUser(g_browser_process->local_state())
.PinAutosubmitIsBackfillNeeded(account_id);
}
void SetBackfillNotNeeded() {
const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
user_manager::KnownUser(g_browser_process->local_state())
.PinAutosubmitSetBackfillNotNeeded(account_id);
}
void SetBackfillNeededForTests() {
const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
user_manager::KnownUser(g_browser_process->local_state())
.PinAutosubmitSetBackfillNeededForTests(account_id);
}
void OnUpdateUserPods() {
const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
ash::quick_unlock::PinBackend::GetInstance()->GetExposedPinLength(
account_id);
}
void SetPin(const std::string& pin) {
RunSetModes(QuickUnlockModeList{QuickUnlockMode::kPin}, {pin});
}
void SetPinForBackfillTests(const std::string& pin) {
// No PIN set. By default IsBackfillNeeded should return true.
ASSERT_EQ(IsBackfillNeeded(), true);
// Set PIN. Backfill must be marked as 'not needed' internally by the API.
SetPin(pin);
ASSERT_EQ(IsBackfillNeeded(), false);
ASSERT_EQ(HasUserValueForPinAutosubmitPref(), false);
// Set 'backfill needed' and clear the exposed length to simulate a PIN that
// was set before the PIN auto submit feature existed.
SetBackfillNeededForTests();
ClearExposedPinLength();
ASSERT_EQ(IsBackfillNeeded(), true);
ASSERT_EQ(GetExposedPinLength(), 0);
}
void ClearPin() { RunSetModes(QuickUnlockModeList{}, CredentialList{}); }
// Run an authentication attempt with the plain-text |password|.
bool TryAuthenticate(const std::string& password) {
const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
auto user_context = std::make_unique<ash::UserContext>(
user_manager::UserType::kRegular, account_id);
user_context->SetIsUsingPin(true);
base::test::TestFuture<std::unique_ptr<ash::UserContext>,
std::optional<ash::AuthenticationError>>
auth_future;
ash::quick_unlock::PinBackend::GetInstance()->TryAuthenticate(
std::move(user_context), ash::Key(password),
ash::quick_unlock::Purpose::kAny, auth_future.GetCallback());
return !auth_future.Get<std::optional<ash::AuthenticationError>>()
.has_value();
}
bool SetPinAutosubmitEnabled(const std::string& pin, const bool enabled) {
const AccountId account_id = AccountId::FromUserEmail(kTestUserEmail);
base::test::TestFuture<bool> set_pin_future;
ash::quick_unlock::PinBackend::GetInstance()->SetPinAutoSubmitEnabled(
account_id, pin, enabled, set_pin_future.GetCallback());
return set_pin_future.Get();
}
void DisablePinByPolicy() {
test_api_.reset();
test_api_ = std::make_unique<ash::quick_unlock::TestApi>(
/*override_quick_unlock=*/true);
}
raw_ptr<sync_preferences::TestingPrefServiceSyncable, DanglingUntriaged>
test_pref_service_;
private:
// Runs the given |func| with the given |params|.
std::optional<base::Value> RunFunction(scoped_refptr<ExtensionFunction> func,
base::Value::List params) {
base::RunLoop().RunUntilIdle();
std::optional<base::Value> result =
api_test_utils::RunFunctionWithDelegateAndReturnSingleResult(
std::move(func), std::move(params),
std::make_unique<ExtensionFunctionDispatcher>(profile()),
api_test_utils::FunctionMode::kNone);
base::RunLoop().RunUntilIdle();
return result;
}
// Runs |func| with |params|. Expects and returns an error result.
std::string RunFunctionAndReturnError(scoped_refptr<ExtensionFunction> func,
base::Value::List params) {
base::RunLoop().RunUntilIdle();
auto dispatcher = std::make_unique<ExtensionFunctionDispatcher>(profile());
api_test_utils::RunFunction(func.get(), std::move(params),
std::move(dispatcher),
api_test_utils::FunctionMode::kNone);
EXPECT_TRUE(func->GetResultListForTest()->empty());
base::RunLoop().RunUntilIdle();
return func->GetError();
}
// Verifies a mode change event is raised and that |expected| is now the
// active set of quick unlock modes.
void ExpectModeList(const QuickUnlockModeList& expected,
const QuickUnlockModeList& actual) {
EXPECT_EQ(expected, actual);
expect_modes_changed_ = false;
}
std::unique_ptr<ash::AuthPartsImpl> auth_parts_;
QuickUnlockPrivateSetModesFunction::ModesChangedEventHandler
modes_changed_handler_;
bool expect_modes_changed_ = false;
ash::UserContext auth_token_user_context_;
std::string token_;
std::unique_ptr<ash::quick_unlock::TestApi> test_api_;
};
// Verifies that GetAuthTokenValid succeeds when a valid password is provided.
TEST_P(QuickUnlockPrivateUnitTest, GetAuthTokenValid) {
std::optional<quick_unlock_private::TokenInfo> token_info =
GetAuthToken(kValidPassword);
EXPECT_TRUE(ash::AuthSessionStorage::Get()->IsValid(token_info->token));
EXPECT_EQ(token_info->lifetime_seconds,
cryptohome::kAuthsessionInitialLifetime.InSeconds());
}
// Verifies that GetAuthTokenValid fails when an invalid password is provided.
TEST_P(QuickUnlockPrivateUnitTest, GetAuthTokenInvalid) {
std::string error = RunAuthTokenWithInvalidPassword();
EXPECT_FALSE(error.empty());
}
// Verifies that setting lock screen enabled modifies the setting.
TEST_P(QuickUnlockPrivateUnitTest, SetLockScreenEnabled) {
PrefService* pref_service = profile()->GetPrefs();
bool lock_screen_enabled =
pref_service->GetBoolean(ash::prefs::kEnableAutoScreenLock);
SetLockScreenEnabled(token(), !lock_screen_enabled);
EXPECT_EQ(!lock_screen_enabled,
pref_service->GetBoolean(ash::prefs::kEnableAutoScreenLock));
}
// Verifies that setting lock screen enabled fails to modify the setting with
// an invalid token.
TEST_P(QuickUnlockPrivateUnitTest, SetLockScreenEnabledFailsWithInvalidToken) {
PrefService* pref_service = profile()->GetPrefs();
bool lock_screen_enabled =
pref_service->GetBoolean(ash::prefs::kEnableAutoScreenLock);
std::string error =
SetLockScreenEnabledWithInvalidToken(!lock_screen_enabled);
EXPECT_FALSE(error.empty());
EXPECT_EQ(lock_screen_enabled,
pref_service->GetBoolean(ash::prefs::kEnableAutoScreenLock));
}
// Verifies that this returns PIN for GetAvailableModes, unless it is blocked by
// policy.
TEST_P(QuickUnlockPrivateUnitTest, GetAvailableModes) {
EXPECT_EQ(GetAvailableModes(), QuickUnlockModeList{QuickUnlockMode::kPin});
// Reset the flags set in Setup.
DisablePinByPolicy();
EXPECT_TRUE(GetAvailableModes().empty());
}
// Verfies that trying to set modes with a valid PIN failes when PIN is blocked
// by policy.
TEST_P(QuickUnlockPrivateUnitTest, SetModesForPinFailsWhenPinDisabledByPolicy) {
// Reset the flags set in Setup.
DisablePinByPolicy();
EXPECT_FALSE(SetModesWithError("[\"valid\", [\"PIN\"], [\"111\"]]").empty());
}
// Verifies that SetModes succeeds with a valid token.
TEST_P(QuickUnlockPrivateUnitTest, SetModes) {
// Verify there is no active mode.
EXPECT_EQ(GetActiveModes(), QuickUnlockModeList{});
RunSetModes(QuickUnlockModeList{QuickUnlockMode::kPin}, {"111111"});
EXPECT_EQ(GetActiveModes(), QuickUnlockModeList{QuickUnlockMode::kPin});
}
// Verifies that an invalid password cannot be used to update the mode list.
TEST_P(QuickUnlockPrivateUnitTest, SetModesFailsWithInvalidPassword) {
// Verify there is no active mode.
EXPECT_EQ(GetActiveModes(), QuickUnlockModeList{});
// Try to enable PIN, but use an invalid password. Verify that no event is
// raised and GetActiveModes still returns an empty set.
FailIfModesChanged();
std::string error = RunSetModesWithInvalidToken();
EXPECT_FALSE(error.empty());
EXPECT_EQ(GetActiveModes(), QuickUnlockModeList{});
}
// Verifies that the quickUnlockPrivate.onActiveModesChanged is only raised when
// the active set of modes changes.
TEST_P(QuickUnlockPrivateUnitTest, ModeChangeEventOnlyRaisedWhenModesChange) {
// Make sure quick unlock is turned off, and then verify that turning it off
// again does not trigger an event.
RunSetModes(QuickUnlockModeList{}, CredentialList{});
FailIfModesChanged();
RunSetModes(QuickUnlockModeList{}, CredentialList{});
// Turn on PIN unlock, and then verify turning it on again and also changing
// the password does not trigger an event.
ExpectModesChanged(QuickUnlockModeList{QuickUnlockMode::kPin});
RunSetModes(QuickUnlockModeList{QuickUnlockMode::kPin}, {"111111"});
FailIfModesChanged();
RunSetModes(QuickUnlockModeList{QuickUnlockMode::kPin}, {"222222"});
RunSetModes(QuickUnlockModeList{QuickUnlockMode::kPin}, {""});
}
// Ensures that quick unlock can be enabled and disabled by checking the result
// of quickUnlockPrivate.GetActiveModes and PinStoragePrefs::IsPinSet.
TEST_P(QuickUnlockPrivateUnitTest, SetModesAndGetActiveModes) {
// Update mode to PIN raises an event and updates GetActiveModes.
ExpectModesChanged(QuickUnlockModeList{QuickUnlockMode::kPin});
RunSetModes(QuickUnlockModeList{QuickUnlockMode::kPin}, {"111111"});
EXPECT_EQ(GetActiveModes(), QuickUnlockModeList{QuickUnlockMode::kPin});
EXPECT_TRUE(IsPinSetInBackend());
// SetModes can be used to turn off a quick unlock mode.
ExpectModesChanged(QuickUnlockModeList{});
RunSetModes(QuickUnlockModeList{}, CredentialList{});
EXPECT_EQ(GetActiveModes(), QuickUnlockModeList{});
EXPECT_FALSE(IsPinSetInBackend());
}
// Verifies that enabling PIN quick unlock actually talks to the PIN subsystem.
TEST_P(QuickUnlockPrivateUnitTest, VerifyAuthenticationAgainstPIN) {
RunSetModes(QuickUnlockModeList{}, CredentialList{});
EXPECT_FALSE(IsPinSetInBackend());
RunSetModes(QuickUnlockModeList{QuickUnlockMode::kPin}, {"111111"});
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_FALSE(TryAuthenticate("000000"));
EXPECT_TRUE(TryAuthenticate("111111"));
EXPECT_FALSE(TryAuthenticate("000000"));
}
// Verifies that the number of modes and the number of passwords given must be
// the same.
TEST_P(QuickUnlockPrivateUnitTest, ThrowErrorOnMismatchedParameterCount) {
EXPECT_FALSE(SetModesWithError("[\"valid\", [\"PIN\"], []]").empty());
EXPECT_FALSE(SetModesWithError("[\"valid\", [], [\"11\"]]").empty());
}
// Validates PIN error checking in conjuction with policy-related prefs.
TEST_P(QuickUnlockPrivateUnitTest, CheckCredentialProblemReporting) {
PrefService* pref_service = profile()->GetPrefs();
// Verify the pin checks work with the default preferences which are minimum
// length of 6, maximum length of 0 (no maximum) and no easy to guess check.
CheckPin(PIN_GOOD, "111112");
CheckPin(PIN_GOOD, "1111112");
CheckPin(PIN_GOOD, "1111111111111112");
CheckPin(PIN_WEAK_WARNING, "111111");
CheckPin(PIN_TOO_SHORT, "1");
CheckPin(PIN_TOO_SHORT, "11");
CheckPin(PIN_TOO_SHORT | PIN_WEAK_WARNING, "111");
CheckPin(PIN_TOO_SHORT | PIN_CONTAINS_NONDIGIT, "a");
CheckPin(PIN_CONTAINS_NONDIGIT, "aaaaab");
CheckPin(PIN_CONTAINS_NONDIGIT | PIN_WEAK_WARNING, "aaaaaa");
CheckPin(PIN_CONTAINS_NONDIGIT | PIN_WEAK_WARNING, "abcdef");
// Verify that now if the minimum length is set to 3, PINs of length 3 are
// accepted.
pref_service->SetInteger(ash::prefs::kPinUnlockMinimumLength, 3);
CheckPin(PIN_WEAK_WARNING, "111");
// Verify setting a nonzero maximum length that is less than the minimum
// length results in the pin only accepting PINs of length minimum length.
pref_service->SetInteger(ash::prefs::kPinUnlockMaximumLength, 2);
pref_service->SetInteger(ash::prefs::kPinUnlockMinimumLength, 4);
CheckPin(PIN_GOOD, "1112");
CheckPin(PIN_TOO_SHORT, "112");
CheckPin(PIN_TOO_LONG, "11112");
// Verify that now if the maximum length is set to 5, PINs longer than 5 are
// considered too long and cannot be used.
pref_service->SetInteger(ash::prefs::kPinUnlockMaximumLength, 5);
CheckPin(PIN_TOO_LONG | PIN_WEAK_WARNING, "111111");
CheckPin(PIN_TOO_LONG | PIN_WEAK_WARNING, "1111111");
// Verify that if both the minimum length and maximum length is set to 4, only
// 4 digit PINs can be used.
pref_service->SetInteger(ash::prefs::kPinUnlockMinimumLength, 4);
pref_service->SetInteger(ash::prefs::kPinUnlockMaximumLength, 4);
CheckPin(PIN_TOO_SHORT, "122");
CheckPin(PIN_TOO_LONG, "12222");
CheckPin(PIN_GOOD, "1222");
// Set the PINs minimum/maximum lengths back to their defaults.
pref_service->SetInteger(ash::prefs::kPinUnlockMinimumLength, 4);
pref_service->SetInteger(ash::prefs::kPinUnlockMaximumLength, 0);
// Verify that PINs that are weak are flagged as such. See
// IsPinDifficultEnough in quick_unlock_private_api.cc for the description of
// a weak pin.
pref_service->SetBoolean(ash::prefs::kPinUnlockWeakPinsAllowed, false);
// Good.
CheckPin(PIN_GOOD, "1112");
CheckPin(PIN_GOOD, "7890");
CheckPin(PIN_GOOD, "0987");
// Same digits.
CheckPin(PIN_WEAK_ERROR, "1111");
// Increasing.
CheckPin(PIN_WEAK_ERROR, "0123");
CheckPin(PIN_WEAK_ERROR, "3456789");
// Decreasing.
CheckPin(PIN_WEAK_ERROR, "3210");
CheckPin(PIN_WEAK_ERROR, "987654");
// Too common.
CheckPin(PIN_WEAK_ERROR, "1212");
// Verify that if a PIN has more than one error, both are returned.
CheckPin(PIN_TOO_SHORT | PIN_WEAK_ERROR, "111");
CheckPin(PIN_TOO_SHORT | PIN_WEAK_ERROR, "234");
}
TEST_P(QuickUnlockPrivateUnitTest, GetCredentialRequirements) {
PrefService* pref_service = profile()->GetPrefs();
// Verify that trying out PINs under the minimum/maximum lengths will send the
// minimum/maximum lengths as additional information for display purposes.
pref_service->SetInteger(ash::prefs::kPinUnlockMinimumLength, 6);
pref_service->SetInteger(ash::prefs::kPinUnlockMaximumLength, 8);
CheckGetCredentialRequirements(6, 8);
// Verify that by setting a maximum length to be nonzero and smaller than the
// minimum length, the resulting maxium length will be equal to the minimum
// length pref.
pref_service->SetInteger(ash::prefs::kPinUnlockMaximumLength, 4);
CheckGetCredentialRequirements(6, 6);
// Verify that the values received from policy are sanitized.
pref_service->SetInteger(ash::prefs::kPinUnlockMinimumLength, -3);
pref_service->SetInteger(ash::prefs::kPinUnlockMaximumLength, -3);
CheckGetCredentialRequirements(1, 0);
}
// Enabling a PIN will by default enable auto submit, unless it is
// recommended/forced by policy to be disabled.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitLongestPossiblePin) {
SetPin("123456789012");
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_TRUE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 12);
}
// When recommended to be disabled, PIN auto submit will not be enabled when
// setting a PIN.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitRecommendedDisabled) {
test_pref_service_->SetRecommendedPref(::prefs::kPinUnlockAutosubmitEnabled,
std::make_unique<base::Value>(false));
SetPin("123456");
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_FALSE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 0);
}
// When forced to be disabled, PIN auto submit will not be enabled when
// setting a PIN.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitForcedDisabled) {
test_pref_service_->SetManagedPref(::prefs::kPinUnlockAutosubmitEnabled,
std::make_unique<base::Value>(false));
SetPin("123456");
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_FALSE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 0);
// Not possible to enable auto submit through Settings. The dialog
// that makes this call cannot even be opened because its a mandatory pref.
EXPECT_FALSE(SetPinAutosubmitEnabled("123456", true /*enabled*/));
EXPECT_FALSE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 0);
}
// Setting a PIN that is longer than 12 digits does not enable auto submit.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitLongPinIsNotExposed) {
SetPin("1234567890123"); // 13 digits
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_FALSE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 0);
}
// When auto submit is enabled, it remains enabled when the PIN is changed
// and the exposed length is updated.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitOnSetAndUpdate) {
SetPin("123456");
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_TRUE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 6);
SetPin("12345678");
EXPECT_TRUE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 8);
}
// When auto submit is disabled, it remains disabled when the PIN is changed
// and the exposed length remains zero.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitBehaviorWhenDisabled) {
SetPin("123456");
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_TRUE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 6);
// Disable auto submit
EXPECT_TRUE(SetPinAutosubmitEnabled("", false /*enabled*/));
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_TRUE(HasUserValueForPinAutosubmitPref());
EXPECT_FALSE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 0);
// Change to a different PIN
SetPin("12345678");
EXPECT_FALSE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 0);
EXPECT_TRUE(HasUserValueForPinAutosubmitPref());
}
// Disabling PIN removes the user set value for auto submit and clears
// the exposed length.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitOnPinDisabled) {
SetPin("123456");
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_TRUE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 6);
// Disable PIN
ClearPin();
EXPECT_FALSE(IsPinSetInBackend());
EXPECT_FALSE(HasUserValueForPinAutosubmitPref());
EXPECT_EQ(GetExposedPinLength(), 0);
}
// If the user has no control over the preference, the pin length is collected
// upon a successful authentication attempt.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitCollectLengthOnAuthSuccess) {
// Start with MANDATORY FALSE to prevent auto enabling when setting a PIN.
test_pref_service_->SetManagedPref(::prefs::kPinUnlockAutosubmitEnabled,
std::make_unique<base::Value>(false));
SetPin("123456");
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_FALSE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 0);
// Autosubmit disabled, length unknown. Change to MANDATORY TRUE
test_pref_service_->SetManagedPref(::prefs::kPinUnlockAutosubmitEnabled,
std::make_unique<base::Value>(true));
EXPECT_TRUE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 0);
// Try to authenticate with the wrong pin. Length won't be exposed.
EXPECT_FALSE(TryAuthenticate("1234567"));
EXPECT_EQ(GetExposedPinLength(), 0);
// Authenticate with the correct pin. Length is exposed.
EXPECT_TRUE(TryAuthenticate("123456"));
EXPECT_EQ(GetExposedPinLength(), 6);
}
// If the user had PIN auto submit enabled and it was forced disabled via
// policy, the exposed length will be removed when the user pods are updated.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitClearLengthOnUiUpdate) {
SetPin("123456");
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_TRUE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 6);
// Switch to MANDATORY FALSE.
test_pref_service_->SetManagedPref(::prefs::kPinUnlockAutosubmitEnabled,
std::make_unique<base::Value>(false));
// Called during user pod update.
OnUpdateUserPods();
// Exposed length must have been cleared.
EXPECT_TRUE(IsPinSetInBackend());
EXPECT_FALSE(GetAutosubmitPrefVal());
EXPECT_EQ(GetExposedPinLength(), 0);
}
// Tests that the backfill operation sets a user value for the auto submit pref
// for users who have set a PIN in a version of Chrome OS that did not support
// auto submit.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitBackfillDefaultPinLength) {
base::HistogramTester histogram_tester;
SetPinForBackfillTests("123456");
// A successful authentication attempt will backfill the user value.
EXPECT_TRUE(TryAuthenticate("123456"));
EXPECT_EQ(GetExposedPinLength(), 6);
EXPECT_TRUE(HasUserValueForPinAutosubmitPref());
EXPECT_TRUE(GetAutosubmitPrefVal());
EXPECT_FALSE(IsBackfillNeeded());
histogram_tester.ExpectUniqueSample(
"Ash.Login.PinAutosubmit.Backfill",
ash::quick_unlock::PinBackend::BackfillEvent::kEnabled, 1);
}
// No backfill operation if the PIN is longer than 6 digits.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitBackfillNonDefaultPinLength) {
base::HistogramTester histogram_tester;
SetPinForBackfillTests("1234567");
// A successful authentication attempt will backfill the user value to false.
EXPECT_TRUE(TryAuthenticate("1234567"));
EXPECT_EQ(GetExposedPinLength(), 0);
EXPECT_TRUE(HasUserValueForPinAutosubmitPref());
EXPECT_FALSE(GetAutosubmitPrefVal());
EXPECT_FALSE(IsBackfillNeeded());
histogram_tester.ExpectUniqueSample(
"Ash.Login.PinAutosubmit.Backfill",
ash::quick_unlock::PinBackend::BackfillEvent::kDisabledDueToPinLength, 1);
}
// Tests that the backfill operation sets a user value for the auto submit pref
// to false for enterprise users even with a default length of 6.
TEST_P(QuickUnlockPrivateUnitTest, PinAutosubmitBackfillEnterprise) {
base::HistogramTester histogram_tester;
// Enterprise users have auto submit disabled by default.
test_pref_service_->SetManagedPref(::prefs::kPinUnlockAutosubmitEnabled,
std::make_unique<base::Value>(false));
SetPinForBackfillTests("123456");
// A successful authentication attempt will backfill the user value to false.
EXPECT_TRUE(TryAuthenticate("123456"));
EXPECT_EQ(GetExposedPinLength(), 0);
EXPECT_TRUE(HasUserValueForPinAutosubmitPref());
EXPECT_FALSE(GetAutosubmitPrefVal());
EXPECT_FALSE(IsBackfillNeeded());
histogram_tester.ExpectUniqueSample(
"Ash.Login.PinAutosubmit.Backfill",
ash::quick_unlock::PinBackend::BackfillEvent::kDisabledDueToPolicy, 1);
}
INSTANTIATE_TEST_SUITE_P(StorageProviders,
QuickUnlockPrivateUnitTest,
testing::Values(TestType::kPrefs,
TestType::kCryptohome),
QuickUnlockPrivateUnitTest::ParamInfoToString);
} // namespace extensions