blob: 8561464ef69bec17970becd6b44fe5431ff14170 [file] [log] [blame]
// Copyright 2017 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 "components/cryptauth/software_feature_manager_impl.h"
#include "base/bind.h"
#include "base/macros.h"
#include "chromeos/components/multidevice/remote_device_ref.h"
#include "chromeos/components/multidevice/remote_device_test_util.h"
#include "chromeos/components/multidevice/software_feature.h"
#include "chromeos/services/device_sync/proto/enum_util.h"
#include "components/cryptauth/mock_cryptauth_client.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Invoke;
namespace cryptauth {
namespace {
enum class Result { kSuccess, kErrorSettingFeature, kErrorFindingEligible };
// Arbitrarily choose different error types which match to Result types.
const NetworkRequestError kErrorSettingFeatureNetworkRequestError =
NetworkRequestError::kOffline;
const NetworkRequestError kErrorFindingEligibleNetworkRequestError =
NetworkRequestError::kEndpointNotFound;
const char kBetterTogetherHostCallbackBluetoothAddress[] =
"BETTER_TOGETHER_HOST";
const char kBetterTogetherClientCallbackBluetoothAddress[] =
"BETTER_TOGETHER_CLIENT";
std::vector<cryptauth::ExternalDeviceInfo>
CreateExternalDeviceInfosForRemoteDevices(
const chromeos::multidevice::RemoteDeviceRefList remote_devices) {
std::vector<cryptauth::ExternalDeviceInfo> device_infos;
for (const auto& remote_device : remote_devices) {
// Add an ExternalDeviceInfo with the same public key as the
// chromeos::multidevice::RemoteDevice.
cryptauth::ExternalDeviceInfo info;
info.set_public_key(remote_device.public_key());
device_infos.push_back(info);
}
return device_infos;
}
} // namespace
class CryptAuthSoftwareFeatureManagerImplTest
: public testing::Test,
public MockCryptAuthClientFactory::Observer {
public:
CryptAuthSoftwareFeatureManagerImplTest()
: all_test_external_device_infos_(
CreateExternalDeviceInfosForRemoteDevices(
chromeos::multidevice::CreateRemoteDeviceRefListForTest(5))),
test_eligible_external_devices_infos_(
{all_test_external_device_infos_[0],
all_test_external_device_infos_[1],
all_test_external_device_infos_[2]}),
test_ineligible_external_devices_infos_(
{all_test_external_device_infos_[3],
all_test_external_device_infos_[4]}) {}
void SetUp() override {
mock_cryptauth_client_factory_ =
std::make_unique<MockCryptAuthClientFactory>(
MockCryptAuthClientFactory::MockType::MAKE_NICE_MOCKS);
mock_cryptauth_client_factory_->AddObserver(this);
software_feature_manager_ =
SoftwareFeatureManagerImpl::Factory::NewInstance(
mock_cryptauth_client_factory_.get());
}
void TearDown() override {
mock_cryptauth_client_factory_->RemoveObserver(this);
}
void OnCryptAuthClientCreated(MockCryptAuthClient* client) override {
ON_CALL(*client, ToggleEasyUnlock(_, _, _))
.WillByDefault(Invoke(
this,
&CryptAuthSoftwareFeatureManagerImplTest::MockToggleEasyUnlock));
ON_CALL(*client, FindEligibleUnlockDevices(_, _, _))
.WillByDefault(Invoke(this, &CryptAuthSoftwareFeatureManagerImplTest::
MockFindEligibleUnlockDevices));
}
// Mock CryptAuthClient::ToggleEasyUnlock() implementation.
void MockToggleEasyUnlock(
const ToggleEasyUnlockRequest& request,
const CryptAuthClient::ToggleEasyUnlockCallback& callback,
const CryptAuthClient::ErrorCallback& error_callback) {
last_toggle_request_ = request;
toggle_easy_unlock_callback_ = callback;
error_callback_ = error_callback;
error_code_ = kErrorSettingFeatureNetworkRequestError;
}
// Mock CryptAuthClient::FindEligibleUnlockDevices() implementation.
void MockFindEligibleUnlockDevices(
const FindEligibleUnlockDevicesRequest& request,
const CryptAuthClient::FindEligibleUnlockDevicesCallback& callback,
const CryptAuthClient::ErrorCallback& error_callback) {
last_find_request_ = request;
find_eligible_unlock_devices_callback_ = callback;
error_callback_ = error_callback;
error_code_ = kErrorFindingEligibleNetworkRequestError;
}
FindEligibleUnlockDevicesResponse CreateFindEligibleUnlockDevicesResponse() {
FindEligibleUnlockDevicesResponse find_eligible_unlock_devices_response;
for (const auto& device_info : test_eligible_external_devices_infos_) {
find_eligible_unlock_devices_response.add_eligible_devices()->CopyFrom(
device_info);
}
for (const auto& device_info : test_ineligible_external_devices_infos_) {
find_eligible_unlock_devices_response.add_ineligible_devices()
->mutable_device()
->CopyFrom(device_info);
}
return find_eligible_unlock_devices_response;
}
void VerifyDeviceEligibility() {
// Ensure that resulting devices are not empty. Otherwise, following for
// loop checks will succeed on empty resulting devices.
EXPECT_TRUE(result_eligible_devices_.size() > 0);
EXPECT_TRUE(result_ineligible_devices_.size() > 0);
for (const auto& device_info : result_eligible_devices_) {
EXPECT_TRUE(
std::find_if(
test_eligible_external_devices_infos_.begin(),
test_eligible_external_devices_infos_.end(),
[&device_info](const cryptauth::ExternalDeviceInfo& device) {
return device.public_key() == device_info.public_key();
}) != test_eligible_external_devices_infos_.end());
}
for (const auto& ineligible_device : result_ineligible_devices_) {
EXPECT_TRUE(
std::find_if(test_ineligible_external_devices_infos_.begin(),
test_ineligible_external_devices_infos_.end(),
[&ineligible_device](
const cryptauth::ExternalDeviceInfo& device) {
return device.public_key() ==
ineligible_device.device().public_key();
}) != test_ineligible_external_devices_infos_.end());
}
result_eligible_devices_.clear();
result_ineligible_devices_.clear();
}
void SetSoftwareFeatureState(chromeos::multidevice::SoftwareFeature feature,
const ExternalDeviceInfo& device_info,
bool enabled,
bool is_exclusive = false) {
software_feature_manager_->SetSoftwareFeatureState(
device_info.public_key(), feature, enabled,
base::Bind(
&CryptAuthSoftwareFeatureManagerImplTest::OnSoftwareFeatureStateSet,
base::Unretained(this)),
base::Bind(&CryptAuthSoftwareFeatureManagerImplTest::OnError,
base::Unretained(this)),
is_exclusive);
}
void FindEligibleDevices(chromeos::multidevice::SoftwareFeature feature) {
software_feature_manager_->FindEligibleDevices(
feature,
base::Bind(
&CryptAuthSoftwareFeatureManagerImplTest::OnEligibleDevicesFound,
base::Unretained(this)),
base::Bind(&CryptAuthSoftwareFeatureManagerImplTest::OnError,
base::Unretained(this)));
}
void OnSoftwareFeatureStateSet() { result_ = Result::kSuccess; }
void OnEligibleDevicesFound(
const std::vector<ExternalDeviceInfo>& eligible_devices,
const std::vector<IneligibleDevice>& ineligible_devices) {
result_ = Result::kSuccess;
result_eligible_devices_ = eligible_devices;
result_ineligible_devices_ = ineligible_devices;
}
void OnError(NetworkRequestError error) {
if (error == kErrorSettingFeatureNetworkRequestError)
result_ = Result::kErrorSettingFeature;
else if (error == kErrorFindingEligibleNetworkRequestError)
result_ = Result::kErrorFindingEligible;
else
NOTREACHED();
}
void InvokeSetSoftwareFeatureCallback() {
CryptAuthClient::ToggleEasyUnlockCallback success_callback =
toggle_easy_unlock_callback_;
ASSERT_TRUE(!success_callback.is_null());
toggle_easy_unlock_callback_.Reset();
success_callback.Run(ToggleEasyUnlockResponse());
}
void InvokeFindEligibleDevicesCallback(
const FindEligibleUnlockDevicesResponse& retrieved_devices_response) {
CryptAuthClient::FindEligibleUnlockDevicesCallback success_callback =
find_eligible_unlock_devices_callback_;
ASSERT_TRUE(!success_callback.is_null());
find_eligible_unlock_devices_callback_.Reset();
success_callback.Run(retrieved_devices_response);
}
void InvokeErrorCallback() {
CryptAuthClient::ErrorCallback error_callback = error_callback_;
ASSERT_TRUE(!error_callback.is_null());
error_callback_.Reset();
error_callback.Run(*error_code_);
}
Result GetResultAndReset() {
EXPECT_TRUE(result_);
Result result = *result_;
result_.reset();
return result;
}
const std::vector<cryptauth::ExternalDeviceInfo>
all_test_external_device_infos_;
const std::vector<ExternalDeviceInfo> test_eligible_external_devices_infos_;
const std::vector<ExternalDeviceInfo> test_ineligible_external_devices_infos_;
std::unique_ptr<MockCryptAuthClientFactory> mock_cryptauth_client_factory_;
std::unique_ptr<cryptauth::SoftwareFeatureManager> software_feature_manager_;
CryptAuthClient::ErrorCallback error_callback_;
// Set when a CryptAuthClient function returns. If empty, no callback has been
// invoked.
base::Optional<Result> result_;
// The code passed to the error callback; varies depending on what
// CryptAuthClient function is invoked.
base::Optional<NetworkRequestError> error_code_;
// For SetSoftwareFeatureState() tests.
ToggleEasyUnlockRequest last_toggle_request_;
CryptAuthClient::ToggleEasyUnlockCallback toggle_easy_unlock_callback_;
// For FindEligibleDevices() tests.
FindEligibleUnlockDevicesRequest last_find_request_;
CryptAuthClient::FindEligibleUnlockDevicesCallback
find_eligible_unlock_devices_callback_;
std::vector<ExternalDeviceInfo> result_eligible_devices_;
std::vector<IneligibleDevice> result_ineligible_devices_;
private:
DISALLOW_COPY_AND_ASSIGN(CryptAuthSoftwareFeatureManagerImplTest);
};
TEST_F(CryptAuthSoftwareFeatureManagerImplTest, TestOrderUponMultipleRequests) {
SetSoftwareFeatureState(
chromeos::multidevice::SoftwareFeature::kBetterTogetherHost,
test_eligible_external_devices_infos_[0], true /* enable */);
FindEligibleDevices(
chromeos::multidevice::SoftwareFeature::kBetterTogetherHost);
SetSoftwareFeatureState(
chromeos::multidevice::SoftwareFeature::kBetterTogetherClient,
test_eligible_external_devices_infos_[1], false /* enable */);
FindEligibleDevices(
chromeos::multidevice::SoftwareFeature::kBetterTogetherClient);
EXPECT_EQ(SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_HOST),
last_toggle_request_.feature());
EXPECT_EQ(true, last_toggle_request_.enable());
EXPECT_EQ(false, last_toggle_request_.is_exclusive());
InvokeSetSoftwareFeatureCallback();
EXPECT_EQ(Result::kSuccess, GetResultAndReset());
EXPECT_EQ(SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_HOST),
last_find_request_.feature());
EXPECT_EQ(kBetterTogetherHostCallbackBluetoothAddress,
last_find_request_.callback_bluetooth_address());
InvokeFindEligibleDevicesCallback(CreateFindEligibleUnlockDevicesResponse());
EXPECT_EQ(Result::kSuccess, GetResultAndReset());
VerifyDeviceEligibility();
EXPECT_EQ(
SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_CLIENT),
last_toggle_request_.feature());
EXPECT_EQ(false, last_toggle_request_.enable());
EXPECT_EQ(false, last_toggle_request_.is_exclusive());
InvokeSetSoftwareFeatureCallback();
EXPECT_EQ(Result::kSuccess, GetResultAndReset());
EXPECT_EQ(
SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_CLIENT),
last_find_request_.feature());
EXPECT_EQ(kBetterTogetherClientCallbackBluetoothAddress,
last_find_request_.callback_bluetooth_address());
InvokeFindEligibleDevicesCallback(CreateFindEligibleUnlockDevicesResponse());
EXPECT_EQ(Result::kSuccess, GetResultAndReset());
VerifyDeviceEligibility();
}
TEST_F(CryptAuthSoftwareFeatureManagerImplTest,
TestMultipleSetUnlocksRequests) {
SetSoftwareFeatureState(
chromeos::multidevice::SoftwareFeature::kBetterTogetherHost,
test_eligible_external_devices_infos_[0], true /* enable */);
SetSoftwareFeatureState(
chromeos::multidevice::SoftwareFeature::kBetterTogetherClient,
test_eligible_external_devices_infos_[1], false /* enable */);
SetSoftwareFeatureState(
chromeos::multidevice::SoftwareFeature::kBetterTogetherHost,
test_eligible_external_devices_infos_[2], true /* enable */);
EXPECT_EQ(SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_HOST),
last_toggle_request_.feature());
EXPECT_EQ(true, last_toggle_request_.enable());
EXPECT_EQ(false, last_toggle_request_.is_exclusive());
InvokeErrorCallback();
EXPECT_EQ(Result::kErrorSettingFeature, GetResultAndReset());
EXPECT_EQ(
SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_CLIENT),
last_toggle_request_.feature());
EXPECT_EQ(false, last_toggle_request_.enable());
EXPECT_EQ(false, last_toggle_request_.is_exclusive());
InvokeSetSoftwareFeatureCallback();
EXPECT_EQ(Result::kSuccess, GetResultAndReset());
EXPECT_EQ(SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_HOST),
last_toggle_request_.feature());
EXPECT_EQ(true, last_toggle_request_.enable());
EXPECT_EQ(false, last_toggle_request_.is_exclusive());
InvokeSetSoftwareFeatureCallback();
EXPECT_EQ(Result::kSuccess, GetResultAndReset());
}
TEST_F(CryptAuthSoftwareFeatureManagerImplTest,
TestMultipleFindEligibleForUnlockDevicesRequests) {
FindEligibleDevices(
chromeos::multidevice::SoftwareFeature::kBetterTogetherHost);
FindEligibleDevices(
chromeos::multidevice::SoftwareFeature::kBetterTogetherClient);
FindEligibleDevices(
chromeos::multidevice::SoftwareFeature::kBetterTogetherHost);
EXPECT_EQ(SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_HOST),
last_find_request_.feature());
EXPECT_EQ(kBetterTogetherHostCallbackBluetoothAddress,
last_find_request_.callback_bluetooth_address());
InvokeFindEligibleDevicesCallback(CreateFindEligibleUnlockDevicesResponse());
EXPECT_EQ(Result::kSuccess, GetResultAndReset());
VerifyDeviceEligibility();
EXPECT_EQ(
SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_CLIENT),
last_find_request_.feature());
EXPECT_EQ(kBetterTogetherClientCallbackBluetoothAddress,
last_find_request_.callback_bluetooth_address());
InvokeErrorCallback();
EXPECT_EQ(Result::kErrorFindingEligible, GetResultAndReset());
EXPECT_EQ(SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_HOST),
last_find_request_.feature());
EXPECT_EQ(kBetterTogetherHostCallbackBluetoothAddress,
last_find_request_.callback_bluetooth_address());
InvokeFindEligibleDevicesCallback(CreateFindEligibleUnlockDevicesResponse());
EXPECT_EQ(Result::kSuccess, GetResultAndReset());
VerifyDeviceEligibility();
}
TEST_F(CryptAuthSoftwareFeatureManagerImplTest, TestOrderViaMultipleErrors) {
SetSoftwareFeatureState(
chromeos::multidevice::SoftwareFeature::kBetterTogetherHost,
test_eligible_external_devices_infos_[0], true /* enable */);
FindEligibleDevices(
chromeos::multidevice::SoftwareFeature::kBetterTogetherHost);
EXPECT_EQ(SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_HOST),
last_toggle_request_.feature());
InvokeErrorCallback();
EXPECT_EQ(Result::kErrorSettingFeature, GetResultAndReset());
EXPECT_EQ(SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_HOST),
last_find_request_.feature());
EXPECT_EQ(kBetterTogetherHostCallbackBluetoothAddress,
last_find_request_.callback_bluetooth_address());
InvokeErrorCallback();
EXPECT_EQ(Result::kErrorFindingEligible, GetResultAndReset());
}
TEST_F(CryptAuthSoftwareFeatureManagerImplTest, TestIsExclusive) {
SetSoftwareFeatureState(
chromeos::multidevice::SoftwareFeature::kBetterTogetherHost,
test_eligible_external_devices_infos_[0], true /* enable */,
true /* is_exclusive */);
EXPECT_EQ(SoftwareFeatureEnumToString(SoftwareFeature::BETTER_TOGETHER_HOST),
last_toggle_request_.feature());
EXPECT_EQ(true, last_toggle_request_.enable());
EXPECT_EQ(true, last_toggle_request_.is_exclusive());
InvokeErrorCallback();
EXPECT_EQ(Result::kErrorSettingFeature, GetResultAndReset());
}
TEST_F(CryptAuthSoftwareFeatureManagerImplTest, TestEasyUnlockSpecialCase) {
SetSoftwareFeatureState(
chromeos::multidevice::SoftwareFeature::kSmartLockHost,
test_eligible_external_devices_infos_[0], false /* enable */);
EXPECT_EQ(SoftwareFeatureEnumToString(SoftwareFeature::EASY_UNLOCK_HOST),
last_toggle_request_.feature());
EXPECT_EQ(false, last_toggle_request_.enable());
// apply_to_all() should be false when disabling EasyUnlock host capabilities.
EXPECT_EQ(true, last_toggle_request_.apply_to_all());
EXPECT_FALSE(last_toggle_request_.has_public_key());
InvokeErrorCallback();
EXPECT_EQ(Result::kErrorSettingFeature, GetResultAndReset());
}
} // namespace cryptauth