|  | // 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/device_capability_manager_impl.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <chrono> | 
|  | #include <memory> | 
|  | #include <thread> | 
|  | #include <unordered_set> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/macros.h" | 
|  | #include "components/cryptauth/mock_cryptauth_client.h" | 
|  | #include "components/cryptauth/remote_device.h" | 
|  | #include "components/cryptauth/remote_device_test_util.h" | 
|  | #include "testing/gmock/include/gmock/gmock.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | using testing::_; | 
|  | using testing::Invoke; | 
|  |  | 
|  | namespace cryptauth { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kSuccessResult[] = "success"; | 
|  | const char kErrorOnToggleEasyUnlockResult[] = "toggleEasyUnlockError"; | 
|  | const char kErrorFindEligibleUnlockDevices[] = "findEligibleUnlockDeviceError"; | 
|  | const char kErrorFindEligibleForPromotion[] = "findEligibleForPromotionError"; | 
|  | const std::string kTestErrorCodeTogglingIneligibleDevice = | 
|  | "togglingIneligibleDevice."; | 
|  |  | 
|  | std::vector<cryptauth::ExternalDeviceInfo> | 
|  | CreateExternalDeviceInfosForRemoteDevices( | 
|  | const std::vector<cryptauth::RemoteDevice> 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 RemoteDevice. | 
|  | cryptauth::ExternalDeviceInfo info; | 
|  | info.set_public_key(remote_device.public_key); | 
|  | device_infos.push_back(info); | 
|  | } | 
|  | return device_infos; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class DeviceCapabilityManagerImplTest | 
|  | : public testing::Test, | 
|  | public MockCryptAuthClientFactory::Observer { | 
|  | public: | 
|  | DeviceCapabilityManagerImplTest() | 
|  | : all_test_external_device_infos_( | 
|  | CreateExternalDeviceInfosForRemoteDevices( | 
|  | cryptauth::GenerateTestRemoteDevices(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); | 
|  | device_capability_manager_ = std::make_unique<DeviceCapabilityManagerImpl>( | 
|  | mock_cryptauth_client_factory_.get()); | 
|  | } | 
|  |  | 
|  | void OnCryptAuthClientCreated(MockCryptAuthClient* client) override { | 
|  | ON_CALL(*client, ToggleEasyUnlock(_, _, _)) | 
|  | .WillByDefault(Invoke( | 
|  | this, &DeviceCapabilityManagerImplTest::MockToggleEasyUnlock)); | 
|  | ON_CALL(*client, FindEligibleUnlockDevices(_, _, _)) | 
|  | .WillByDefault(Invoke( | 
|  | this, | 
|  | &DeviceCapabilityManagerImplTest::MockFindEligibleUnlockDevices)); | 
|  | ON_CALL(*client, FindEligibleForPromotion(_, _, _)) | 
|  | .WillByDefault(Invoke( | 
|  | this, | 
|  | &DeviceCapabilityManagerImplTest::MockFindEligibleForPromotion)); | 
|  | } | 
|  |  | 
|  | // Mock CryptAuthClient::ToggleEasyUnlock() implementation. | 
|  | void MockToggleEasyUnlock( | 
|  | const ToggleEasyUnlockRequest& request, | 
|  | const CryptAuthClient::ToggleEasyUnlockCallback& callback, | 
|  | const CryptAuthClient::ErrorCallback& error_callback) { | 
|  | toggle_easy_unlock_callback_ = callback; | 
|  | error_callback_ = error_callback; | 
|  | error_code_ = kErrorOnToggleEasyUnlockResult; | 
|  | } | 
|  |  | 
|  | // Mock CryptAuthClient::FindEligibleUnlockDevices() implementation. | 
|  | void MockFindEligibleUnlockDevices( | 
|  | const FindEligibleUnlockDevicesRequest& request, | 
|  | const CryptAuthClient::FindEligibleUnlockDevicesCallback& callback, | 
|  | const CryptAuthClient::ErrorCallback& error_callback) { | 
|  | find_eligible_unlock_devices_callback_ = callback; | 
|  | error_callback_ = error_callback; | 
|  | error_code_ = kErrorFindEligibleUnlockDevices; | 
|  | } | 
|  |  | 
|  | // Mock CryptAuthClient::FindEligibleForPromotion() implementation. | 
|  | void MockFindEligibleForPromotion( | 
|  | const FindEligibleForPromotionRequest& request, | 
|  | const CryptAuthClient::FindEligibleForPromotionCallback& callback, | 
|  | const CryptAuthClient::ErrorCallback& error_callback) { | 
|  | find_eligible_for_promotion_callback_ = callback; | 
|  | error_callback_ = error_callback; | 
|  | error_code_ = kErrorFindEligibleForPromotion; | 
|  | } | 
|  |  | 
|  | 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 SetCapabilityEnabled(DeviceCapabilityManagerImpl::Capability capability, | 
|  | const ExternalDeviceInfo& device_info, | 
|  | bool enable) { | 
|  | device_capability_manager_->SetCapabilityEnabled( | 
|  | device_info.public_key(), capability, enable, | 
|  | base::Bind(&DeviceCapabilityManagerImplTest:: | 
|  | TestSuccessSetCapabilityKeyUnlockCallback, | 
|  | base::Unretained(this)), | 
|  | base::Bind(&DeviceCapabilityManagerImplTest::TestErrorCallback, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void FindEligibleDevicesForCapability( | 
|  | DeviceCapabilityManagerImpl::Capability capability) { | 
|  | device_capability_manager_->FindEligibleDevicesForCapability( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | base::Bind(&DeviceCapabilityManagerImplTest:: | 
|  | TestSuccessFindEligibleUnlockDevicesCallback, | 
|  | base::Unretained(this)), | 
|  | base::Bind(&DeviceCapabilityManagerImplTest::TestErrorCallback, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void IsPromotableDevice(DeviceCapabilityManagerImpl::Capability capability, | 
|  | const std::string& public_key) { | 
|  | device_capability_manager_->IsCapabilityPromotable( | 
|  | public_key, capability, | 
|  | base::Bind(&DeviceCapabilityManagerImplTest:: | 
|  | TestSuccessFindEligibleForPromotionDeviceCallback, | 
|  | base::Unretained(this)), | 
|  | base::Bind(&DeviceCapabilityManagerImplTest::TestErrorCallback, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void TestSuccessSetCapabilityKeyUnlockCallback() { result_ = kSuccessResult; } | 
|  |  | 
|  | void TestSuccessFindEligibleUnlockDevicesCallback( | 
|  | const std::vector<ExternalDeviceInfo>& eligible_devices, | 
|  | const std::vector<IneligibleDevice>& ineligible_devices) { | 
|  | result_ = kSuccessResult; | 
|  | result_eligible_devices_ = eligible_devices; | 
|  | result_ineligible_devices_ = ineligible_devices; | 
|  | } | 
|  |  | 
|  | void TestSuccessFindEligibleForPromotionDeviceCallback(bool eligible) { | 
|  | result_ = kSuccessResult; | 
|  | result_eligible_for_promotion_ = eligible; | 
|  | } | 
|  |  | 
|  | void TestErrorCallback(const std::string& error_message) { | 
|  | result_ = error_message; | 
|  | } | 
|  |  | 
|  | void InvokeSetCapabilityKeyUnlockCallback() { | 
|  | CryptAuthClient::ToggleEasyUnlockCallback success_callback = | 
|  | toggle_easy_unlock_callback_; | 
|  | ASSERT_TRUE(!success_callback.is_null()); | 
|  | toggle_easy_unlock_callback_.Reset(); | 
|  | success_callback.Run(ToggleEasyUnlockResponse()); | 
|  | } | 
|  |  | 
|  | void InvokeFindEligibleUnlockDevicesCallback( | 
|  | 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 InvokeFindEligibleForPromotionCallback(bool eligible_for_promotion) { | 
|  | FindEligibleForPromotionResponse response; | 
|  | response.set_may_show_promo(eligible_for_promotion); | 
|  | CryptAuthClient::FindEligibleForPromotionCallback success_callback = | 
|  | find_eligible_for_promotion_callback_; | 
|  | ASSERT_TRUE(!success_callback.is_null()); | 
|  | find_eligible_for_promotion_callback_.Reset(); | 
|  | success_callback.Run(response); | 
|  | } | 
|  |  | 
|  | void InvokeErrorCallback() { | 
|  | CryptAuthClient::ErrorCallback error_callback = error_callback_; | 
|  | ASSERT_TRUE(!error_callback.is_null()); | 
|  | error_callback_.Reset(); | 
|  | error_callback.Run(error_code_); | 
|  | } | 
|  |  | 
|  | std::string GetResultAndReset() { | 
|  | std::string result; | 
|  | result.swap(result_); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | bool GetEligibleForPromotionAndReset() { | 
|  | bool result_eligible_for_promotion = result_eligible_for_promotion_; | 
|  | result_eligible_for_promotion_ = false; | 
|  | return result_eligible_for_promotion; | 
|  | } | 
|  |  | 
|  | 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::DeviceCapabilityManagerImpl> | 
|  | device_capability_manager_; | 
|  | CryptAuthClient::ToggleEasyUnlockCallback toggle_easy_unlock_callback_; | 
|  | CryptAuthClient::FindEligibleUnlockDevicesCallback | 
|  | find_eligible_unlock_devices_callback_; | 
|  | CryptAuthClient::FindEligibleForPromotionCallback | 
|  | find_eligible_for_promotion_callback_; | 
|  | CryptAuthClient::ErrorCallback error_callback_; | 
|  |  | 
|  | // Used by all tests that involve CryptauthClient network calls internally. | 
|  | // Network call statuses are mocked out in place by |result_| and | 
|  | // |error_code_| to keep track of the order in which DevicaCapabilityManager | 
|  | // functions are called. | 
|  | std::string result_; | 
|  | std::string error_code_; | 
|  | // For FindEligibleUnlockDevice tests. | 
|  | std::vector<ExternalDeviceInfo> result_eligible_devices_; | 
|  | std::vector<IneligibleDevice> result_ineligible_devices_; | 
|  | // For FindEligibleForPromotion tests. | 
|  | bool result_eligible_for_promotion_; | 
|  |  | 
|  | private: | 
|  | DISALLOW_COPY_AND_ASSIGN(DeviceCapabilityManagerImplTest); | 
|  | }; | 
|  |  | 
|  | TEST_F(DeviceCapabilityManagerImplTest, TestOrderUponMultipleRequests) { | 
|  | SetCapabilityEnabled( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[0], true /* enable */); | 
|  | FindEligibleDevicesForCapability( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY); | 
|  | IsPromotableDevice( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[0].public_key()); | 
|  | SetCapabilityEnabled( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[1], true /* enable */); | 
|  | FindEligibleDevicesForCapability( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY); | 
|  | IsPromotableDevice( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[1].public_key()); | 
|  |  | 
|  | InvokeSetCapabilityKeyUnlockCallback(); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  |  | 
|  | InvokeFindEligibleUnlockDevicesCallback( | 
|  | CreateFindEligibleUnlockDevicesResponse()); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  | VerifyDeviceEligibility(); | 
|  |  | 
|  | InvokeFindEligibleForPromotionCallback(true /* eligible */); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  | EXPECT_TRUE(GetEligibleForPromotionAndReset()); | 
|  |  | 
|  | InvokeSetCapabilityKeyUnlockCallback(); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  |  | 
|  | InvokeFindEligibleUnlockDevicesCallback( | 
|  | CreateFindEligibleUnlockDevicesResponse()); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  | VerifyDeviceEligibility(); | 
|  |  | 
|  | InvokeFindEligibleForPromotionCallback(true /* eligible */); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  | EXPECT_TRUE(GetEligibleForPromotionAndReset()); | 
|  | } | 
|  |  | 
|  | TEST_F(DeviceCapabilityManagerImplTest, TestMultipleSetUnlocksRequests) { | 
|  | SetCapabilityEnabled( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[0], true /* enable */); | 
|  | SetCapabilityEnabled( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[1], true /* enable */); | 
|  | SetCapabilityEnabled( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[2], true /* enable */); | 
|  |  | 
|  | InvokeErrorCallback(); | 
|  | EXPECT_EQ(kErrorOnToggleEasyUnlockResult, GetResultAndReset()); | 
|  |  | 
|  | InvokeSetCapabilityKeyUnlockCallback(); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  |  | 
|  | InvokeSetCapabilityKeyUnlockCallback(); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  | } | 
|  |  | 
|  | TEST_F(DeviceCapabilityManagerImplTest, | 
|  | TestMultipleFindEligibleForUnlockDevicesRequests) { | 
|  | FindEligibleDevicesForCapability( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY); | 
|  | FindEligibleDevicesForCapability( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY); | 
|  | FindEligibleDevicesForCapability( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY); | 
|  |  | 
|  | InvokeFindEligibleUnlockDevicesCallback( | 
|  | CreateFindEligibleUnlockDevicesResponse()); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  | VerifyDeviceEligibility(); | 
|  |  | 
|  | InvokeErrorCallback(); | 
|  | EXPECT_EQ(kErrorFindEligibleUnlockDevices, GetResultAndReset()); | 
|  |  | 
|  | InvokeFindEligibleUnlockDevicesCallback( | 
|  | CreateFindEligibleUnlockDevicesResponse()); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  | VerifyDeviceEligibility(); | 
|  | } | 
|  |  | 
|  | TEST_F(DeviceCapabilityManagerImplTest, TestMultipleIsPromotableRequests) { | 
|  | IsPromotableDevice( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[0].public_key()); | 
|  | IsPromotableDevice( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[1].public_key()); | 
|  | IsPromotableDevice( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[2].public_key()); | 
|  |  | 
|  | InvokeFindEligibleForPromotionCallback(true /*eligible*/); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  | EXPECT_TRUE(GetEligibleForPromotionAndReset()); | 
|  |  | 
|  | InvokeFindEligibleForPromotionCallback(true /*eligible*/); | 
|  | EXPECT_EQ(kSuccessResult, GetResultAndReset()); | 
|  | EXPECT_TRUE(GetEligibleForPromotionAndReset()); | 
|  |  | 
|  | InvokeErrorCallback(); | 
|  | EXPECT_EQ(kErrorFindEligibleForPromotion, GetResultAndReset()); | 
|  | } | 
|  |  | 
|  | TEST_F(DeviceCapabilityManagerImplTest, TestOrderViaMultipleErrors) { | 
|  | SetCapabilityEnabled( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[0], true /* enable */); | 
|  | FindEligibleDevicesForCapability( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY); | 
|  | IsPromotableDevice( | 
|  | DeviceCapabilityManagerImpl::Capability::CAPABILITY_UNLOCK_KEY, | 
|  | test_eligible_external_devices_infos_[0].public_key()); | 
|  |  | 
|  | InvokeErrorCallback(); | 
|  | EXPECT_EQ(kErrorOnToggleEasyUnlockResult, GetResultAndReset()); | 
|  |  | 
|  | InvokeErrorCallback(); | 
|  | EXPECT_EQ(kErrorFindEligibleUnlockDevices, GetResultAndReset()); | 
|  |  | 
|  | InvokeErrorCallback(); | 
|  | EXPECT_EQ(kErrorFindEligibleForPromotion, GetResultAndReset()); | 
|  | } | 
|  |  | 
|  | }  // namespace cryptauth |