| // 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 "components/safe_browsing/verdict_cache_manager.h" |
| |
| #include "base/base64.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/values.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/safe_browsing/proto/csd.pb.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace safe_browsing { |
| |
| class VerdictCacheManagerTest : public ::testing::Test { |
| public: |
| VerdictCacheManagerTest() {} |
| |
| void SetUp() override { |
| HostContentSettingsMap::RegisterProfilePrefs(test_pref_service_.registry()); |
| content_setting_map_ = new HostContentSettingsMap( |
| &test_pref_service_, false /* is_off_the_record */, |
| false /* store_last_modified */, |
| false /* migrate_requesting_and_top_level_origin_settings */); |
| cache_manager_ = std::make_unique<VerdictCacheManager>( |
| nullptr, content_setting_map_.get()); |
| } |
| |
| void TearDown() override { |
| cache_manager_.reset(); |
| content_setting_map_->ShutdownOnUIThread(); |
| } |
| |
| void CachePhishGuardVerdict( |
| const GURL& url, |
| LoginReputationClientRequest::TriggerType trigger, |
| PasswordType password_type, |
| LoginReputationClientResponse::VerdictType verdict, |
| int cache_duration_sec, |
| const std::string& cache_expression, |
| const base::Time& verdict_received_time) { |
| ASSERT_FALSE(cache_expression.empty()); |
| LoginReputationClientResponse response; |
| response.set_verdict_type(verdict); |
| response.set_cache_expression(cache_expression); |
| response.set_cache_duration_sec(cache_duration_sec); |
| cache_manager_->CachePhishGuardVerdict(url, trigger, password_type, |
| response, verdict_received_time); |
| } |
| |
| protected: |
| std::unique_ptr<VerdictCacheManager> cache_manager_; |
| scoped_refptr<HostContentSettingsMap> content_setting_map_; |
| |
| private: |
| content::TestBrowserThreadBundle thread_bundle_; |
| sync_preferences::TestingPrefServiceSyncable test_pref_service_; |
| }; |
| |
| TEST_F(VerdictCacheManagerTest, TestCanRetrieveCachedVerdict) { |
| GURL url("https://www.google.com/"); |
| |
| LoginReputationClientResponse cached_verdict; |
| cached_verdict.set_cache_expression("www.google.com/"); |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &cached_verdict)); |
| |
| CachePhishGuardVerdict(url, |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, |
| LoginReputationClientResponse::SAFE, 60, |
| "www.google.com/", base::Time::Now()); |
| |
| EXPECT_EQ(LoginReputationClientResponse::SAFE, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &cached_verdict)); |
| } |
| |
| TEST_F(VerdictCacheManagerTest, TestCacheSplitByTriggerType) { |
| GURL url("https://www.google.com/"); |
| |
| LoginReputationClientResponse cached_verdict; |
| cached_verdict.set_cache_expression("www.google.com/"); |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &cached_verdict)); |
| |
| CachePhishGuardVerdict(url, |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, |
| LoginReputationClientResponse::SAFE, 60, |
| "www.google.com/", base::Time::Now()); |
| |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &cached_verdict)); |
| } |
| |
| TEST_F(VerdictCacheManagerTest, TestCacheSplitByPasswordType) { |
| GURL url("https://www.google.com/"); |
| |
| LoginReputationClientResponse cached_verdict; |
| cached_verdict.set_cache_expression("www.google.com/"); |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &cached_verdict)); |
| |
| CachePhishGuardVerdict( |
| url, LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, |
| PasswordType::ENTERPRISE_PASSWORD, LoginReputationClientResponse::SAFE, |
| 60, "www.google.com/", base::Time::Now()); |
| |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &cached_verdict)); |
| } |
| |
| TEST_F(VerdictCacheManagerTest, TestGetStoredPhishGuardVerdictCount) { |
| GURL url("https://www.google.com/"); |
| |
| LoginReputationClientResponse cached_verdict; |
| cached_verdict.set_cache_expression("www.google.com/"); |
| EXPECT_EQ(0u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); |
| |
| CachePhishGuardVerdict( |
| url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::ENTERPRISE_PASSWORD, LoginReputationClientResponse::SAFE, |
| 60, "www.google.com/", base::Time::Now()); |
| |
| EXPECT_EQ(1u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); |
| |
| CachePhishGuardVerdict( |
| url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::ENTERPRISE_PASSWORD, LoginReputationClientResponse::SAFE, |
| 60, "www.google.com/", base::Time::Now()); |
| |
| EXPECT_EQ(1u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); |
| |
| CachePhishGuardVerdict( |
| url, LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::ENTERPRISE_PASSWORD, LoginReputationClientResponse::SAFE, |
| 60, "www.google.com/path", base::Time::Now()); |
| |
| EXPECT_EQ(2u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); |
| } |
| |
| TEST_F(VerdictCacheManagerTest, TestParseInvalidVerdictEntry) { |
| // Directly save an invalid cache entry. |
| LoginReputationClientResponse verdict; |
| verdict.set_verdict_type(LoginReputationClientResponse::SAFE); |
| verdict.set_cache_expression("www.google.com/"); |
| verdict.set_cache_duration_sec(60); |
| |
| std::string verdict_serialized; |
| verdict.SerializeToString(&verdict_serialized); |
| base::Base64Encode(verdict_serialized, &verdict_serialized); |
| |
| auto cache_dictionary = std::make_unique<base::DictionaryValue>(); |
| auto* verdict_dictionary = |
| cache_dictionary->SetKey("2", base::Value(base::Value::Type::DICTIONARY)); |
| auto* verdict_entry = verdict_dictionary->SetKey( |
| "www.google.com/", base::Value(base::Value::Type::DICTIONARY)); |
| verdict_entry->SetStringKey("cache_creation_time", "invalid_time"); |
| verdict_entry->SetStringKey("verdict_proto", verdict_serialized); |
| |
| content_setting_map_->SetWebsiteSettingDefaultScope( |
| GURL("http://www.google.com/"), GURL(), |
| CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), |
| std::move(cache_dictionary)); |
| |
| LoginReputationClientResponse cached_verdict; |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| GURL("https://www.google.com/"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &cached_verdict)); |
| } |
| |
| TEST_F(VerdictCacheManagerTest, TestRemoveCachedVerdictOnURLsDeleted) { |
| ASSERT_EQ(0u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); |
| ASSERT_EQ(0u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE)); |
| // Prepare 5 verdicts. Three are for origin "http://foo.com", and the others |
| // are for "http://bar.com". |
| base::Time now = base::Time::Now(); |
| CachePhishGuardVerdict(GURL("http://foo.com/abc/index.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, |
| LoginReputationClientResponse::LOW_REPUTATION, 600, |
| "foo.com/abc/", now); |
| CachePhishGuardVerdict(GURL("http://foo.com/abc/index.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::ENTERPRISE_PASSWORD, |
| LoginReputationClientResponse::LOW_REPUTATION, 600, |
| "foo.com/abc/", now); |
| CachePhishGuardVerdict(GURL("http://bar.com/index.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, |
| LoginReputationClientResponse::PHISHING, 600, |
| "bar.com", now); |
| ASSERT_EQ(3u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); |
| |
| CachePhishGuardVerdict(GURL("http://foo.com/abc/index.jsp"), |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, |
| PasswordType::PASSWORD_TYPE_UNKNOWN, |
| LoginReputationClientResponse::LOW_REPUTATION, 600, |
| "foo.com/abc/", now); |
| CachePhishGuardVerdict(GURL("http://bar.com/index.jsp"), |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, |
| PasswordType::PASSWORD_TYPE_UNKNOWN, |
| LoginReputationClientResponse::PHISHING, 600, |
| "bar.com", now); |
| ASSERT_EQ(2u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE)); |
| |
| // Delete a bar.com URL. Corresponding content setting keyed on |
| // origin "http://bar.com" should be removed, |
| history::URLRows deleted_urls; |
| deleted_urls.push_back(history::URLRow(GURL("http://bar.com"))); |
| |
| // Delete an arbitrary data URL, to ensure the service is robust against |
| // filtering only http/s URLs. See crbug.com/709758. |
| deleted_urls.push_back(history::URLRow(GURL("data:text/html, <p>hellow"))); |
| |
| cache_manager_->RemoveContentSettingsOnURLsDeleted(false /* all_history */, |
| deleted_urls); |
| EXPECT_EQ(2u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); |
| EXPECT_EQ(1u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE)); |
| |
| LoginReputationClientResponse actual_verdict; |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| GURL("http://bar.com"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &actual_verdict)); |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| GURL("http://bar.com"), |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, |
| PasswordType::PASSWORD_TYPE_UNKNOWN, &actual_verdict)); |
| |
| // If delete all history. All password protection content settings should be |
| // gone. |
| cache_manager_->RemoveContentSettingsOnURLsDeleted(true /* all_history */, |
| history::URLRows()); |
| EXPECT_EQ(0u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); |
| EXPECT_EQ(0u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE)); |
| } |
| |
| TEST_F(VerdictCacheManagerTest, TestCleanUpExpiredVerdict) { |
| // Prepare 4 verdicts for PASSWORD_REUSE_EVENT with SIGN_IN_PASSWORD type: |
| // (1) "foo.com/abc/" valid |
| // (2) "foo.com/def/" expired |
| // (3) "bar.com/abc/" expired |
| // (4) "bar.com/def/" expired |
| base::Time now = base::Time::Now(); |
| CachePhishGuardVerdict(GURL("https://foo.com/abc/index.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, |
| LoginReputationClientResponse::LOW_REPUTATION, 600, |
| "foo.com/abc/", now); |
| CachePhishGuardVerdict(GURL("https://foo.com/def/index.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, |
| LoginReputationClientResponse::LOW_REPUTATION, 0, |
| "foo.com/def/", now); |
| CachePhishGuardVerdict(GURL("https://bar.com/abc/index.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, |
| LoginReputationClientResponse::PHISHING, 0, |
| "bar.com/abc/", now); |
| CachePhishGuardVerdict(GURL("https://bar.com/def/index.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, |
| LoginReputationClientResponse::PHISHING, 0, |
| "bar.com/def/", now); |
| ASSERT_EQ(4u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); |
| |
| // Prepare 2 verdicts for UNFAMILIAR_LOGIN_PAGE: |
| // (1) "bar.com/def/" valid |
| // (2) "bar.com/xyz/" expired |
| CachePhishGuardVerdict(GURL("https://bar.com/def/index.jsp"), |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, |
| PasswordType::PASSWORD_TYPE_UNKNOWN, |
| LoginReputationClientResponse::SAFE, 600, |
| "bar.com/def/", now); |
| CachePhishGuardVerdict(GURL("https://bar.com/xyz/index.jsp"), |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, |
| PasswordType::PASSWORD_TYPE_UNKNOWN, |
| LoginReputationClientResponse::PHISHING, 0, |
| "bar.com/xyz/", now); |
| ASSERT_EQ(2u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE)); |
| |
| cache_manager_->CleanUpExpiredVerdicts(); |
| |
| ASSERT_EQ(1u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT)); |
| ASSERT_EQ(1u, cache_manager_->GetStoredPhishGuardVerdictCount( |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE)); |
| LoginReputationClientResponse actual_verdict; |
| // Has cached PASSWORD_REUSE_EVENT verdict for foo.com/abc/. |
| EXPECT_EQ(LoginReputationClientResponse::LOW_REPUTATION, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| GURL("https://foo.com/abc/test.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &actual_verdict)); |
| // No cached PASSWORD_REUSE_EVENT verdict for foo.com/def. |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| GURL("https://foo.com/def/index.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &actual_verdict)); |
| // No cached PASSWORD_REUSE_EVENT verdict for bar.com/abc. |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| GURL("https://bar.com/abc/index.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &actual_verdict)); |
| // No cached PASSWORD_REUSE_EVENT verdict for bar.com/def. |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| GURL("https://bar.com/def/index.jsp"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, &actual_verdict)); |
| |
| // Has cached UNFAMILIAR_LOGIN_PAGE verdict for bar.com/def. |
| EXPECT_EQ(LoginReputationClientResponse::SAFE, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| GURL("https://bar.com/def/index.jsp"), |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, |
| PasswordType::PASSWORD_TYPE_UNKNOWN, &actual_verdict)); |
| |
| // No cached UNFAMILIAR_LOGIN_PAGE verdict for bar.com/xyz. |
| EXPECT_EQ(LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED, |
| cache_manager_->GetCachedPhishGuardVerdict( |
| GURL("https://bar.com/xyz/index.jsp"), |
| LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, |
| PasswordType::PASSWORD_TYPE_UNKNOWN, &actual_verdict)); |
| } |
| |
| TEST_F(VerdictCacheManagerTest, TestCleanUpExpiredVerdictWithInvalidEntry) { |
| // Directly save an invalid cache entry. |
| LoginReputationClientResponse verdict; |
| verdict.set_verdict_type(LoginReputationClientResponse::SAFE); |
| verdict.set_cache_expression("www.google.com/"); |
| verdict.set_cache_duration_sec(60); |
| |
| std::string verdict_serialized; |
| verdict.SerializeToString(&verdict_serialized); |
| base::Base64Encode(verdict_serialized, &verdict_serialized); |
| |
| auto cache_dictionary = std::make_unique<base::DictionaryValue>(); |
| auto* verdict_dictionary = |
| cache_dictionary->SetKey("1", base::Value(base::Value::Type::DICTIONARY)); |
| auto* verdict_entry = verdict_dictionary->SetKey( |
| "www.google.com/path", base::Value(base::Value::Type::DICTIONARY)); |
| verdict_entry->SetStringKey("cache_creation_time", "invalid_time"); |
| verdict_entry->SetStringKey("verdict_proto", verdict_serialized); |
| |
| content_setting_map_->SetWebsiteSettingDefaultScope( |
| GURL("http://www.google.com/"), GURL(), |
| CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), |
| std::move(cache_dictionary)); |
| |
| // Save one valid entry |
| CachePhishGuardVerdict(GURL("https://www.google.com"), |
| LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| PasswordType::PRIMARY_ACCOUNT_PASSWORD, |
| LoginReputationClientResponse::SAFE, 60, |
| "www.google.com/", base::Time::Now()); |
| |
| // Verify we saved two entries under PasswordType PRIMARY_ACCOUNT_PASSWORD |
| EXPECT_EQ(2U, |
| content_setting_map_ |
| ->GetWebsiteSetting(GURL("http://www.google.com/"), GURL(), |
| CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, |
| std::string(), nullptr) |
| ->FindDictKey("1") |
| ->DictSize()); |
| |
| cache_manager_->CleanUpExpiredVerdicts(); |
| |
| // One should have been cleaned up |
| EXPECT_EQ(1U, |
| content_setting_map_ |
| ->GetWebsiteSetting(GURL("http://www.google.com/"), GURL(), |
| CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, |
| std::string(), nullptr) |
| ->FindDictKey("1") |
| ->DictSize()); |
| } |
| |
| } // namespace safe_browsing |