| // Copyright 2019 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/sharing/sharing_device_registration.h" |
| |
| #include <map> |
| #include <memory> |
| #include <string> |
| |
| #include "base/run_loop.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/sharing/shared_clipboard/feature_flags.h" |
| #include "chrome/browser/sharing/sharing_constants.h" |
| #include "chrome/browser/sharing/sharing_device_registration_result.h" |
| #include "chrome/browser/sharing/sharing_sync_preference.h" |
| #include "chrome/browser/sharing/vapid_key_manager.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/gcm_driver/instance_id/instance_id_driver.h" |
| #include "components/prefs/pref_registry.h" |
| #include "components/prefs/pref_service_factory.h" |
| #include "components/sync_device_info/device_info.h" |
| #include "components/sync_device_info/fake_device_info_sync_service.h" |
| #include "components/sync_preferences/pref_service_mock_factory.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "google_apis/gcm/engine/account_mapping.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using namespace gcm; |
| using namespace instance_id; |
| using namespace testing; |
| |
| namespace { |
| const char kAppID[] = "test_app_id"; |
| const char kFCMToken[] = "test_fcm_token"; |
| const char kFCMToken2[] = "test_fcm_token_2"; |
| const char kDevicep256dh[] = "test_p256_dh"; |
| const char kDevicep256dh2[] = "test_p256_dh_2"; |
| const char kDeviceAuthSecret[] = "test_auth_secret"; |
| const char kDeviceAuthSecret2[] = "test_auth_secret_2"; |
| |
| class MockInstanceIDDriver : public InstanceIDDriver { |
| public: |
| MockInstanceIDDriver() : InstanceIDDriver(/*gcm_driver=*/nullptr) {} |
| ~MockInstanceIDDriver() override = default; |
| |
| MOCK_METHOD1(GetInstanceID, InstanceID*(const std::string& app_id)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockInstanceIDDriver); |
| }; |
| |
| class FakeInstanceID : public InstanceID { |
| public: |
| FakeInstanceID() : InstanceID(kAppID, /*gcm_driver = */ nullptr) {} |
| ~FakeInstanceID() override = default; |
| |
| void GetID(const GetIDCallback& callback) override { NOTIMPLEMENTED(); } |
| |
| void GetCreationTime(const GetCreationTimeCallback& callback) override { |
| NOTIMPLEMENTED(); |
| } |
| |
| void GetToken(const std::string& authorized_entity, |
| const std::string& scope, |
| const std::map<std::string, std::string>& options, |
| std::set<Flags> flags, |
| GetTokenCallback callback) override { |
| std::move(callback).Run(fcm_token_, result_); |
| } |
| |
| void ValidateToken(const std::string& authorized_entity, |
| const std::string& scope, |
| const std::string& token, |
| const ValidateTokenCallback& callback) override { |
| NOTIMPLEMENTED(); |
| } |
| |
| void DeleteToken(const std::string& authorized_entity, |
| const std::string& scope, |
| DeleteTokenCallback callback) override { |
| std::move(callback).Run(result_); |
| } |
| |
| void DeleteTokenImpl(const std::string& authorized_entity, |
| const std::string& scope, |
| DeleteTokenCallback callback) override { |
| NOTIMPLEMENTED(); |
| } |
| |
| void DeleteIDImpl(DeleteIDCallback callback) override { NOTIMPLEMENTED(); } |
| |
| void SetFCMResult(InstanceID::Result result) { result_ = result; } |
| |
| void SetFCMToken(std::string fcm_token) { fcm_token_ = std::move(fcm_token); } |
| |
| void GetEncryptionInfo(const std::string& authorized_entity, |
| GetEncryptionInfoCallback callback) override { |
| std::move(callback).Run(p256dh_, auth_secret_); |
| } |
| |
| void SetEncryptionInfo(const std::string& p256dh, |
| const std::string& auth_secret) { |
| p256dh_ = p256dh; |
| auth_secret_ = auth_secret; |
| } |
| |
| private: |
| InstanceID::Result result_; |
| std::string fcm_token_; |
| std::string p256dh_ = kDevicep256dh; |
| std::string auth_secret_ = kDeviceAuthSecret; |
| }; |
| |
| class SharingDeviceRegistrationTest : public testing::Test { |
| public: |
| SharingDeviceRegistrationTest() |
| : sync_prefs_(&prefs_, &fake_device_info_sync_service_), |
| vapid_key_manager_(&sync_prefs_), |
| sharing_device_registration_(pref_service_.get(), |
| &sync_prefs_, |
| &mock_instance_id_driver_, |
| &vapid_key_manager_) { |
| SharingSyncPreference::RegisterProfilePrefs(prefs_.registry()); |
| } |
| |
| static std::unique_ptr<PrefService> CreatePrefServiceAndRegisterPrefs() { |
| scoped_refptr<user_prefs::PrefRegistrySyncable> registry( |
| new user_prefs::PrefRegistrySyncable()); |
| registry->RegisterBooleanPref(prefs::kSharedClipboardEnabled, true); |
| PrefServiceFactory factory; |
| factory.set_user_prefs(base::MakeRefCounted<TestingPrefStore>()); |
| return factory.Create(registry); |
| } |
| |
| void SetUp() { |
| ON_CALL(mock_instance_id_driver_, GetInstanceID(_)) |
| .WillByDefault(testing::Return(&fake_instance_id_)); |
| } |
| |
| void SetSharedClipboardPolicy(bool val) { |
| pref_service_->SetBoolean(prefs::kSharedClipboardEnabled, val); |
| } |
| |
| void EnableSharedClipboardReceiverFlag() { |
| scoped_feature_list_.InitAndEnableFeature(kSharedClipboardReceiver); |
| } |
| |
| void RegisterDeviceSync() { |
| base::RunLoop run_loop; |
| sharing_device_registration_.RegisterDevice( |
| base::BindLambdaForTesting([&](SharingDeviceRegistrationResult r) { |
| result_ = r; |
| local_sharing_info_ = sync_prefs_.GetLocalSharingInfo(); |
| synced_sharing_info_ = sync_prefs_.GetSharingInfo( |
| fake_device_info_sync_service_.GetLocalDeviceInfoProvider() |
| ->GetLocalDeviceInfo() |
| ->guid()); |
| fcm_registration_ = sync_prefs_.GetFCMRegistration(); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| void UnregisterDeviceSync() { |
| base::RunLoop run_loop; |
| sharing_device_registration_.UnregisterDevice( |
| base::BindLambdaForTesting([&](SharingDeviceRegistrationResult r) { |
| result_ = r; |
| local_sharing_info_ = sync_prefs_.GetLocalSharingInfo(); |
| synced_sharing_info_ = sync_prefs_.GetSharingInfo( |
| fake_device_info_sync_service_.GetLocalDeviceInfoProvider() |
| ->GetLocalDeviceInfo() |
| ->guid()); |
| fcm_registration_ = sync_prefs_.GetFCMRegistration(); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| void SetInstanceIDFCMResult(InstanceID::Result result) { |
| fake_instance_id_.SetFCMResult(result); |
| } |
| |
| void SetInstanceIDFCMToken(std::string fcm_token) { |
| fake_instance_id_.SetFCMToken(std::move(fcm_token)); |
| } |
| |
| std::set<sync_pb::SharingSpecificFields::EnabledFeatures> |
| GetExpectedEnabledFeatures() { |
| // IsClickToCallSupported() involves JNI call which is hard to test. |
| if (sharing_device_registration_.IsClickToCallSupported()) { |
| return {sync_pb::SharingSpecificFields::CLICK_TO_CALL, |
| sync_pb::SharingSpecificFields::SHARED_CLIPBOARD}; |
| } |
| |
| // Shared clipboard should always be supported. |
| return {sync_pb::SharingSpecificFields::SHARED_CLIPBOARD}; |
| } |
| |
| protected: |
| content::BrowserTaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| sync_preferences::TestingPrefServiceSyncable prefs_; |
| NiceMock<MockInstanceIDDriver> mock_instance_id_driver_; |
| syncer::FakeDeviceInfoSyncService fake_device_info_sync_service_; |
| FakeInstanceID fake_instance_id_; |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| std::unique_ptr<PrefService> pref_service_ = |
| CreatePrefServiceAndRegisterPrefs(); |
| SharingSyncPreference sync_prefs_; |
| VapidKeyManager vapid_key_manager_; |
| SharingDeviceRegistration sharing_device_registration_; |
| |
| // callback results |
| base::Optional<syncer::DeviceInfo::SharingInfo> local_sharing_info_; |
| base::Optional<syncer::DeviceInfo::SharingInfo> synced_sharing_info_; |
| base::Optional<SharingSyncPreference::FCMRegistration> fcm_registration_; |
| SharingDeviceRegistrationResult result_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(SharingDeviceRegistrationTest, IsSharedClipboardSupported_True) { |
| SetSharedClipboardPolicy(true); |
| EnableSharedClipboardReceiverFlag(); |
| |
| EXPECT_TRUE(sharing_device_registration_.IsSharedClipboardSupported()); |
| } |
| |
| TEST_F(SharingDeviceRegistrationTest, IsSharedClipboardSupported_False) { |
| SetSharedClipboardPolicy(false); |
| EnableSharedClipboardReceiverFlag(); |
| |
| EXPECT_FALSE(sharing_device_registration_.IsSharedClipboardSupported()); |
| } |
| |
| TEST_F(SharingDeviceRegistrationTest, RegisterDeviceTest_Success) { |
| SetInstanceIDFCMResult(InstanceID::Result::SUCCESS); |
| SetInstanceIDFCMToken(kFCMToken); |
| fake_device_info_sync_service_.GetDeviceInfoTracker()->Add( |
| fake_device_info_sync_service_.GetLocalDeviceInfoProvider() |
| ->GetLocalDeviceInfo()); |
| |
| RegisterDeviceSync(); |
| |
| std::set<sync_pb::SharingSpecificFields::EnabledFeatures> enabled_features = |
| GetExpectedEnabledFeatures(); |
| syncer::DeviceInfo::SharingInfo expected_sharing_info( |
| kFCMToken, kDevicep256dh, kDeviceAuthSecret, enabled_features); |
| |
| EXPECT_EQ(SharingDeviceRegistrationResult::kSuccess, result_); |
| EXPECT_EQ(expected_sharing_info, local_sharing_info_); |
| EXPECT_EQ(expected_sharing_info, synced_sharing_info_); |
| EXPECT_TRUE(fcm_registration_); |
| |
| // Remove VAPID key to force a re-register, which will return a different FCM |
| // token. |
| prefs_.RemoveUserPref("sharing.vapid_key"); |
| SetInstanceIDFCMToken(kFCMToken2); |
| |
| RegisterDeviceSync(); |
| |
| // Device should be re-registered with the new FCM token. |
| syncer::DeviceInfo::SharingInfo expected_synced_sharing_info_2( |
| kFCMToken2, kDevicep256dh, kDeviceAuthSecret, enabled_features); |
| |
| EXPECT_EQ(SharingDeviceRegistrationResult::kSuccess, result_); |
| EXPECT_EQ(expected_synced_sharing_info_2, local_sharing_info_); |
| EXPECT_EQ(expected_synced_sharing_info_2, synced_sharing_info_); |
| EXPECT_TRUE(fcm_registration_); |
| } |
| |
| TEST_F(SharingDeviceRegistrationTest, RegisterDeviceTest_VapidKeysUnchanged) { |
| SetInstanceIDFCMToken(kFCMToken); |
| SetInstanceIDFCMResult(InstanceID::Result::SUCCESS); |
| fake_device_info_sync_service_.GetDeviceInfoTracker()->Add( |
| fake_device_info_sync_service_.GetLocalDeviceInfoProvider() |
| ->GetLocalDeviceInfo()); |
| |
| RegisterDeviceSync(); |
| |
| EXPECT_EQ(SharingDeviceRegistrationResult::kSuccess, result_); |
| |
| // Instance ID now returns a new token, however it shouldn't be invoked. |
| SetInstanceIDFCMToken(kFCMToken2); |
| // GCMDriver now returns new encryption info. |
| fake_instance_id_.SetEncryptionInfo(kDevicep256dh2, kDeviceAuthSecret2); |
| |
| // Register device again without changing VAPID keys. |
| RegisterDeviceSync(); |
| |
| // Encryption info is updated with new value but FCM token is not updated |
| std::set<sync_pb::SharingSpecificFields::EnabledFeatures> enabled_features = |
| GetExpectedEnabledFeatures(); |
| syncer::DeviceInfo::SharingInfo expected_sharing_info( |
| kFCMToken, kDevicep256dh2, kDeviceAuthSecret2, enabled_features); |
| |
| EXPECT_EQ(SharingDeviceRegistrationResult::kSuccess, result_); |
| EXPECT_EQ(expected_sharing_info, local_sharing_info_); |
| EXPECT_EQ(expected_sharing_info, synced_sharing_info_); |
| EXPECT_TRUE(fcm_registration_); |
| } |
| |
| TEST_F(SharingDeviceRegistrationTest, RegisterDeviceTest_Expired) { |
| SetInstanceIDFCMResult(InstanceID::Result::SUCCESS); |
| fake_device_info_sync_service_.GetDeviceInfoTracker()->Add( |
| fake_device_info_sync_service_.GetLocalDeviceInfoProvider() |
| ->GetLocalDeviceInfo()); |
| |
| // First register the device. |
| RegisterDeviceSync(); |
| EXPECT_EQ(SharingDeviceRegistrationResult::kSuccess, result_); |
| |
| // Advance time so registration is expired. |
| task_environment_.FastForwardBy(kRegistrationExpiration); |
| |
| // Register the device again, Instance.GetToken will be attempted once more, |
| // which will return a different FCM token. |
| SetInstanceIDFCMToken(kFCMToken2); |
| RegisterDeviceSync(); |
| |
| // Device should be registered with the new FCM token. |
| std::set<sync_pb::SharingSpecificFields::EnabledFeatures> enabled_features = |
| GetExpectedEnabledFeatures(); |
| syncer::DeviceInfo::SharingInfo expected_sharing_info( |
| kFCMToken2, kDevicep256dh, kDeviceAuthSecret, enabled_features); |
| |
| EXPECT_EQ(SharingDeviceRegistrationResult::kSuccess, result_); |
| EXPECT_EQ(expected_sharing_info, local_sharing_info_); |
| EXPECT_EQ(expected_sharing_info, synced_sharing_info_); |
| EXPECT_TRUE(fcm_registration_); |
| } |
| |
| TEST_F(SharingDeviceRegistrationTest, RegisterDeviceTest_NetworkError) { |
| SetInstanceIDFCMResult(InstanceID::Result::NETWORK_ERROR); |
| |
| RegisterDeviceSync(); |
| |
| EXPECT_EQ(SharingDeviceRegistrationResult::kFcmTransientError, result_); |
| EXPECT_FALSE(local_sharing_info_); |
| EXPECT_FALSE(synced_sharing_info_); |
| EXPECT_FALSE(fcm_registration_); |
| } |
| |
| TEST_F(SharingDeviceRegistrationTest, RegisterDeviceTest_FatalError) { |
| SetInstanceIDFCMResult(InstanceID::Result::DISABLED); |
| |
| RegisterDeviceSync(); |
| |
| EXPECT_EQ(SharingDeviceRegistrationResult::kFcmFatalError, result_); |
| EXPECT_FALSE(local_sharing_info_); |
| EXPECT_FALSE(synced_sharing_info_); |
| EXPECT_FALSE(fcm_registration_); |
| } |
| |
| TEST_F(SharingDeviceRegistrationTest, UnregisterDeviceTest_Success) { |
| SetInstanceIDFCMResult(InstanceID::Result::SUCCESS); |
| fake_device_info_sync_service_.GetDeviceInfoTracker()->Add( |
| fake_device_info_sync_service_.GetLocalDeviceInfoProvider() |
| ->GetLocalDeviceInfo()); |
| |
| // First register the device. |
| RegisterDeviceSync(); |
| EXPECT_EQ(SharingDeviceRegistrationResult::kSuccess, result_); |
| EXPECT_TRUE(local_sharing_info_); |
| EXPECT_TRUE(synced_sharing_info_); |
| EXPECT_TRUE(fcm_registration_); |
| |
| // Then unregister the device. |
| UnregisterDeviceSync(); |
| EXPECT_EQ(SharingDeviceRegistrationResult::kSuccess, result_); |
| EXPECT_FALSE(local_sharing_info_); |
| EXPECT_FALSE(synced_sharing_info_); |
| EXPECT_FALSE(fcm_registration_); |
| |
| // Further unregister does nothing and returns kDeviceNotRegistered. |
| UnregisterDeviceSync(); |
| EXPECT_EQ(SharingDeviceRegistrationResult::kDeviceNotRegistered, result_); |
| |
| // Register the device again, Instance.GetToken will be attempted once more, |
| // which will return a different FCM token. |
| SetInstanceIDFCMToken(kFCMToken2); |
| RegisterDeviceSync(); |
| |
| // Device should be registered with the new FCM token. |
| std::set<sync_pb::SharingSpecificFields::EnabledFeatures> enabled_features = |
| GetExpectedEnabledFeatures(); |
| syncer::DeviceInfo::SharingInfo expected_sharing_info( |
| kFCMToken2, kDevicep256dh, kDeviceAuthSecret, enabled_features); |
| |
| EXPECT_EQ(SharingDeviceRegistrationResult::kSuccess, result_); |
| EXPECT_EQ(expected_sharing_info, local_sharing_info_); |
| EXPECT_EQ(expected_sharing_info, synced_sharing_info_); |
| EXPECT_TRUE(fcm_registration_); |
| } |