// 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 "components/cryptauth/mock_cryptauth_client.h" #include "components/cryptauth/remote_device_ref.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 { 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; std::vector CreateExternalDeviceInfosForRemoteDevices( const cryptauth::RemoteDeviceRefList remote_devices) { std::vector 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 CryptAuthSoftwareFeatureManagerImplTest : public testing::Test, public MockCryptAuthClientFactory::Observer { public: CryptAuthSoftwareFeatureManagerImplTest() : all_test_external_device_infos_( CreateExternalDeviceInfosForRemoteDevices( cryptauth::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::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(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(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& eligible_devices, const std::vector& 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 all_test_external_device_infos_; const std::vector test_eligible_external_devices_infos_; const std::vector test_ineligible_external_devices_infos_; std::unique_ptr mock_cryptauth_client_factory_; std::unique_ptr software_feature_manager_; CryptAuthClient::ErrorCallback error_callback_; // Set when a CryptAuthClient function returns. If empty, no callback has been // invoked. base::Optional result_; // The code passed to the error callback; varies depending on what // CryptAuthClient function is invoked. base::Optional 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 result_eligible_devices_; std::vector result_ineligible_devices_; private: DISALLOW_COPY_AND_ASSIGN(CryptAuthSoftwareFeatureManagerImplTest); }; TEST_F(CryptAuthSoftwareFeatureManagerImplTest, TestOrderUponMultipleRequests) { SetSoftwareFeatureState(SoftwareFeature::BETTER_TOGETHER_HOST, test_eligible_external_devices_infos_[0], true /* enable */); FindEligibleDevices(SoftwareFeature::BETTER_TOGETHER_HOST); SetSoftwareFeatureState(SoftwareFeature::BETTER_TOGETHER_CLIENT, test_eligible_external_devices_infos_[1], false /* enable */); FindEligibleDevices(SoftwareFeature::BETTER_TOGETHER_CLIENT); EXPECT_EQ(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(SoftwareFeature::BETTER_TOGETHER_HOST, last_find_request_.feature()); InvokeFindEligibleDevicesCallback(CreateFindEligibleUnlockDevicesResponse()); EXPECT_EQ(Result::kSuccess, GetResultAndReset()); VerifyDeviceEligibility(); EXPECT_EQ(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(SoftwareFeature::BETTER_TOGETHER_CLIENT, last_find_request_.feature()); InvokeFindEligibleDevicesCallback(CreateFindEligibleUnlockDevicesResponse()); EXPECT_EQ(Result::kSuccess, GetResultAndReset()); VerifyDeviceEligibility(); } TEST_F(CryptAuthSoftwareFeatureManagerImplTest, TestMultipleSetUnlocksRequests) { SetSoftwareFeatureState(SoftwareFeature::BETTER_TOGETHER_HOST, test_eligible_external_devices_infos_[0], true /* enable */); SetSoftwareFeatureState(SoftwareFeature::BETTER_TOGETHER_CLIENT, test_eligible_external_devices_infos_[1], false /* enable */); SetSoftwareFeatureState(SoftwareFeature::BETTER_TOGETHER_HOST, test_eligible_external_devices_infos_[2], true /* enable */); EXPECT_EQ(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(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(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(SoftwareFeature::BETTER_TOGETHER_HOST); FindEligibleDevices(SoftwareFeature::BETTER_TOGETHER_CLIENT); FindEligibleDevices(SoftwareFeature::BETTER_TOGETHER_HOST); EXPECT_EQ(SoftwareFeature::BETTER_TOGETHER_HOST, last_find_request_.feature()); InvokeFindEligibleDevicesCallback(CreateFindEligibleUnlockDevicesResponse()); EXPECT_EQ(Result::kSuccess, GetResultAndReset()); VerifyDeviceEligibility(); EXPECT_EQ(SoftwareFeature::BETTER_TOGETHER_CLIENT, last_find_request_.feature()); InvokeErrorCallback(); EXPECT_EQ(Result::kErrorFindingEligible, GetResultAndReset()); EXPECT_EQ(SoftwareFeature::BETTER_TOGETHER_HOST, last_find_request_.feature()); InvokeFindEligibleDevicesCallback(CreateFindEligibleUnlockDevicesResponse()); EXPECT_EQ(Result::kSuccess, GetResultAndReset()); VerifyDeviceEligibility(); } TEST_F(CryptAuthSoftwareFeatureManagerImplTest, TestOrderViaMultipleErrors) { SetSoftwareFeatureState(SoftwareFeature::BETTER_TOGETHER_HOST, test_eligible_external_devices_infos_[0], true /* enable */); FindEligibleDevices(SoftwareFeature::BETTER_TOGETHER_HOST); EXPECT_EQ(SoftwareFeature::BETTER_TOGETHER_HOST, last_toggle_request_.feature()); InvokeErrorCallback(); EXPECT_EQ(Result::kErrorSettingFeature, GetResultAndReset()); EXPECT_EQ(SoftwareFeature::BETTER_TOGETHER_HOST, last_find_request_.feature()); InvokeErrorCallback(); EXPECT_EQ(Result::kErrorFindingEligible, GetResultAndReset()); } TEST_F(CryptAuthSoftwareFeatureManagerImplTest, TestIsExclusive) { SetSoftwareFeatureState(SoftwareFeature::BETTER_TOGETHER_HOST, test_eligible_external_devices_infos_[0], true /* enable */, true /* is_exclusive */); EXPECT_EQ(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(SoftwareFeature::EASY_UNLOCK_HOST, test_eligible_external_devices_infos_[0], false /* enable */); EXPECT_EQ(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