Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(751)

Unified Diff: components/safe_browsing/password_protection/password_protection_service.cc

Issue 2911293003: Reland: Cache protected password entry and password on focus ping separately. (Closed)
Patch Set: Fix Crashes by Using GetDictionaryWithoutPathExpansion Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/safe_browsing/password_protection/password_protection_service.cc
diff --git a/components/safe_browsing/password_protection/password_protection_service.cc b/components/safe_browsing/password_protection/password_protection_service.cc
index c7ec2a9da6a9fde1039c7fe0a56bd3bad6657ce8..fca28e9a0cc21f3d2b805cf930b9d710f95c6962 100644
--- a/components/safe_browsing/password_protection/password_protection_service.cc
+++ b/components/safe_browsing/password_protection/password_protection_service.cc
@@ -4,6 +4,9 @@
#include "components/safe_browsing/password_protection/password_protection_service.h"
+#include <stddef.h>
+#include <string>
+
#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
@@ -38,6 +41,7 @@ const char kVerdictProto[] = "verdict_proto";
const int kRequestTimeoutMs = 10000;
const char kPasswordProtectionRequestUrl[] =
"https://sb-ssl.google.com/safebrowsing/clientreport/login";
+const char kPasswordOnFocusCacheKey[] = "password_on_focus_cache_key";
// Helper function to determine if the given origin matches content settings
// map's patterns.
@@ -87,7 +91,8 @@ PasswordProtectionService::PasswordProtectionService(
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
HistoryService* history_service,
HostContentSettingsMap* host_content_settings_map)
- : stored_verdict_count_(-1),
+ : stored_verdict_count_password_on_focus_(-1),
+ stored_verdict_count_password_entry_(-1),
database_manager_(database_manager),
request_context_getter_(request_context_getter),
history_service_observer_(this),
@@ -122,24 +127,48 @@ bool PasswordProtectionService::CanGetReputationOfURL(const GURL& url) {
hostname.find('.') != std::string::npos;
}
+// We cache both types of pings under the same content settings type (
+// CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION). Since UNFAMILIAR_LOGING_PAGE
+// verdicts are only enabled on extended reporting users, we cache them one
+// layer lower in the content setting DictionaryValue than PASSWORD_REUSE_EVENT
+// verdicts.
+// In other words, to cache a UNFAMILIAR_LOGIN_PAGE verdict we needs two levels
+// of keys: (1) origin, (2) cache expression returned in verdict.
+// To cache a PASSWORD_REUSE_EVENT, three levels of keys are used:
+// (1) origin, (2) 2nd level key is always |kPasswordOnFocusCacheKey|,
+// (3) cache expression.
LoginReputationClientResponse::VerdictType
PasswordProtectionService::GetCachedVerdict(
const GURL& url,
+ TriggerType trigger_type,
LoginReputationClientResponse* out_response) {
+ DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE ||
+ trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT);
+
if (!url.is_valid())
return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED;
- DCHECK(content_settings_);
-
GURL hostname = GetHostNameWithHTTPScheme(url);
- std::unique_ptr<base::DictionaryValue> verdict_dictionary =
+ std::unique_ptr<base::DictionaryValue> cache_dictionary =
base::DictionaryValue::From(content_settings_->GetWebsiteSetting(
hostname, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION,
std::string(), nullptr));
- // Return early if there is no verdict cached for this origin.
- if (!verdict_dictionary.get() || verdict_dictionary->empty())
+
+ if (!cache_dictionary.get() || cache_dictionary->empty())
return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED;
+ base::DictionaryValue* verdict_dictionary = nullptr;
+ if (trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) {
+ // All UNFAMILIAR_LOGIN_PAGE verdicts (a.k.a password on focus ping)
+ // are cached under |kPasswordOnFocusCacheKey|.
+ if (!cache_dictionary->GetDictionaryWithoutPathExpansion(
+ base::StringPiece(kPasswordOnFocusCacheKey), &verdict_dictionary)) {
+ return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED;
+ }
+ } else {
+ verdict_dictionary = cache_dictionary.get();
+ }
+
std::vector<std::string> paths;
GeneratePathVariantsWithoutQuery(url, &paths);
int max_path_depth = -1;
@@ -148,8 +177,12 @@ PasswordProtectionService::GetCachedVerdict(
// For all the verdicts of the same origin, we key them by |cache_expression|.
// Its corresponding value is a DictionaryValue contains its creation time and
// the serialized verdict proto.
- for (base::DictionaryValue::Iterator it(*verdict_dictionary.get());
- !it.IsAtEnd(); it.Advance()) {
+ for (base::DictionaryValue::Iterator it(*verdict_dictionary); !it.IsAtEnd();
+ it.Advance()) {
+ if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT &&
+ it.key() == base::StringPiece(kPasswordOnFocusCacheKey)) {
+ continue;
+ }
base::DictionaryValue* verdict_entry = nullptr;
CHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion(
it.key() /* cache_expression */, &verdict_entry));
@@ -180,39 +213,65 @@ PasswordProtectionService::GetCachedVerdict(
void PasswordProtectionService::CacheVerdict(
const GURL& url,
+ TriggerType trigger_type,
LoginReputationClientResponse* verdict,
const base::Time& receive_time) {
DCHECK(verdict);
DCHECK(content_settings_);
+ DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE ||
+ trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT);
GURL hostname = GetHostNameWithHTTPScheme(url);
- std::unique_ptr<base::DictionaryValue> verdict_dictionary =
+ int* stored_verdict_count =
+ trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE
+ ? &stored_verdict_count_password_on_focus_
+ : &stored_verdict_count_password_entry_;
+ std::unique_ptr<base::DictionaryValue> cache_dictionary =
base::DictionaryValue::From(content_settings_->GetWebsiteSetting(
hostname, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION,
std::string(), nullptr));
- if (!verdict_dictionary.get())
- verdict_dictionary = base::MakeUnique<base::DictionaryValue>();
-
- std::unique_ptr<base::DictionaryValue> verdict_entry =
- CreateDictionaryFromVerdict(verdict, receive_time);
+ if (!cache_dictionary || !cache_dictionary.get())
+ cache_dictionary = base::MakeUnique<base::DictionaryValue>();
+
+ std::unique_ptr<base::DictionaryValue> verdict_entry(
+ CreateDictionaryFromVerdict(verdict, receive_time));
+
+ base::DictionaryValue* verdict_dictionary = nullptr;
+ if (trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) {
+ // All UNFAMILIAR_LOGIN_PAGE verdicts (a.k.a password on focus ping)
+ // are cached under |kPasswordOnFocusCacheKey|.
+ if (!cache_dictionary->GetDictionaryWithoutPathExpansion(
+ base::StringPiece(kPasswordOnFocusCacheKey), &verdict_dictionary)) {
+ verdict_dictionary = cache_dictionary->SetDictionaryWithoutPathExpansion(
+ base::StringPiece(kPasswordOnFocusCacheKey),
+ base::MakeUnique<base::DictionaryValue>());
+ }
+ } else {
+ verdict_dictionary = cache_dictionary.get();
+ }
// Increases stored verdict count if we haven't seen this cache expression
// before.
if (!verdict_dictionary->HasKey(verdict->cache_expression()))
- stored_verdict_count_ = GetStoredVerdictCount() + 1;
+ *stored_verdict_count = GetStoredVerdictCount(trigger_type) + 1;
+
// If same cache_expression is already in this verdict_dictionary, we simply
// override it.
verdict_dictionary->SetWithoutPathExpansion(verdict->cache_expression(),
std::move(verdict_entry));
content_settings_->SetWebsiteSettingDefaultScope(
hostname, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION,
- std::string(), std::move(verdict_dictionary));
+ std::string(), std::move(cache_dictionary));
}
void PasswordProtectionService::CleanUpExpiredVerdicts() {
DCHECK(content_settings_);
- if (GetStoredVerdictCount() <= 0)
+
+ if (GetStoredVerdictCount(
+ LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) <= 0 &&
+ GetStoredVerdictCount(
+ LoginReputationClientRequest::PASSWORD_REUSE_EVENT) <= 0)
return;
ContentSettingsForOneType password_protection_settings;
@@ -224,44 +283,29 @@ void PasswordProtectionService::CleanUpExpiredVerdicts() {
password_protection_settings) {
GURL primary_pattern_url = GURL(source.primary_pattern.ToString());
// Find all verdicts associated with this origin.
- std::unique_ptr<base::DictionaryValue> verdict_dictionary =
+ std::unique_ptr<base::DictionaryValue> cache_dictionary =
base::DictionaryValue::From(content_settings_->GetWebsiteSetting(
primary_pattern_url, GURL(),
CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), nullptr));
- std::vector<std::string> expired_keys;
- for (base::DictionaryValue::Iterator it(*verdict_dictionary.get());
- !it.IsAtEnd(); it.Advance()) {
- base::DictionaryValue* verdict_entry = nullptr;
- CHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion(
- it.key(), &verdict_entry));
- int verdict_received_time;
- LoginReputationClientResponse verdict;
- CHECK(ParseVerdictEntry(verdict_entry, &verdict_received_time, &verdict));
-
- if (IsCacheExpired(verdict_received_time, verdict.cache_duration_sec())) {
- // Since DictionaryValue::Iterator cannot be used to modify the
- // dictionary, we record the keys of expired verdicts in |expired_keys|
- // and remove them in the next for-loop.
- expired_keys.push_back(it.key());
- }
- }
-
- for (const std::string& key : expired_keys) {
- verdict_dictionary->RemoveWithoutPathExpansion(key, nullptr);
- stored_verdict_count_--;
- }
-
- if (verdict_dictionary->size() == 0u) {
+ bool has_expired_password_on_focus_entry = RemoveExpiredVerdicts(
+ LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE,
+ cache_dictionary.get());
+ bool has_expired_password_reuse_entry = RemoveExpiredVerdicts(
+ LoginReputationClientRequest::PASSWORD_REUSE_EVENT,
+ cache_dictionary.get());
+
+ if (cache_dictionary->size() == 0u) {
content_settings_->ClearSettingsForOneTypeWithPredicate(
CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, base::Time(),
base::Bind(&OriginMatchPrimaryPattern, primary_pattern_url));
- } else if (expired_keys.size() > 0u) {
+ } else if (has_expired_password_on_focus_entry ||
+ has_expired_password_reuse_entry) {
// Set the website setting of this origin with the updated
// |verdict_diectionary|.
content_settings_->SetWebsiteSettingDefaultScope(
primary_pattern_url, GURL(),
CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(),
- std::move(verdict_dictionary));
+ std::move(cache_dictionary));
}
}
}
@@ -272,14 +316,15 @@ void PasswordProtectionService::StartRequest(
const GURL& password_form_action,
const GURL& password_form_frame_url,
const std::string& saved_domain,
- LoginReputationClientRequest::TriggerType type,
+ TriggerType trigger_type,
bool password_field_exists) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scoped_refptr<PasswordProtectionRequest> request(
new PasswordProtectionRequest(
web_contents, main_frame_url, password_form_action,
- password_form_frame_url, saved_domain, type, password_field_exists,
- this, GetRequestTimeoutInMS()));
+ password_form_frame_url, saved_domain, trigger_type,
+ password_field_exists, this, GetRequestTimeoutInMS()));
+
DCHECK(request);
request->Start();
requests_.insert(std::move(request));
@@ -332,11 +377,11 @@ void PasswordProtectionService::RequestFinished(
if (response) {
if (!already_cached) {
- CacheVerdict(request->main_frame_url(), response.get(),
- base::Time::Now());
+ CacheVerdict(request->main_frame_url(), request->trigger_type(),
+ response.get(), base::Time::Now());
}
- if (request->request_type() ==
+ if (request->trigger_type() ==
LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE &&
response->verdict_type() == LoginReputationClientResponse::PHISHING &&
base::FeatureList::IsEnabled(kPasswordProtectionInterstitial)) {
@@ -379,30 +424,49 @@ GURL PasswordProtectionService::GetPasswordProtectionRequestUrl() {
return url.Resolve("?key=" + net::EscapeQueryParamValue(api_key, true));
}
-int PasswordProtectionService::GetStoredVerdictCount() {
+int PasswordProtectionService::GetStoredVerdictCount(TriggerType trigger_type) {
DCHECK(content_settings_);
+ DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE ||
+ trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT);
+ int* stored_verdict_count =
+ trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE
+ ? &stored_verdict_count_password_on_focus_
+ : &stored_verdict_count_password_entry_;
// If we have already computed this, return its value.
- if (stored_verdict_count_ >= 0)
- return stored_verdict_count_;
+ if (*stored_verdict_count >= 0)
+ return *stored_verdict_count;
ContentSettingsForOneType password_protection_settings;
content_settings_->GetSettingsForOneType(
CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(),
&password_protection_settings);
- stored_verdict_count_ = 0;
+ stored_verdict_count_password_on_focus_ = 0;
+ stored_verdict_count_password_entry_ = 0;
if (password_protection_settings.empty())
return 0;
for (const ContentSettingPatternSource& source :
password_protection_settings) {
- std::unique_ptr<base::DictionaryValue> verdict_dictionary =
+ std::unique_ptr<base::DictionaryValue> cache_dictionary =
base::DictionaryValue::From(content_settings_->GetWebsiteSetting(
GURL(source.primary_pattern.ToString()), GURL(),
CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), nullptr));
- if (verdict_dictionary.get() && !verdict_dictionary->empty())
- stored_verdict_count_ += static_cast<int>(verdict_dictionary->size());
+ if (cache_dictionary.get() && !cache_dictionary->empty()) {
+ stored_verdict_count_password_entry_ +=
+ static_cast<int>(cache_dictionary->size());
+ base::DictionaryValue* password_on_focus_dict = nullptr;
+ if (cache_dictionary->GetDictionaryWithoutPathExpansion(
+ base::StringPiece(kPasswordOnFocusCacheKey),
+ &password_on_focus_dict)) {
+ // Substracts 1 from password_entry count if |kPasswordOnFocusCacheKey|
+ // presents.
+ stored_verdict_count_password_entry_ -= 1;
+ stored_verdict_count_password_on_focus_ +=
+ static_cast<int>(password_on_focus_dict->size());
+ }
+ }
}
- return stored_verdict_count_;
+ return *stored_verdict_count;
}
int PasswordProtectionService::GetRequestTimeoutInMS() {
@@ -410,7 +474,7 @@ int PasswordProtectionService::GetRequestTimeoutInMS() {
}
void PasswordProtectionService::FillUserPopulation(
- const LoginReputationClientRequest::TriggerType& request_type,
+ TriggerType trigger_type,
LoginReputationClientRequest* request_proto) {
ChromeUserPopulation* user_population = request_proto->mutable_population();
user_population->set_user_population(
@@ -440,8 +504,10 @@ void PasswordProtectionService::OnURLsDeleted(
bool expired,
const history::URLRows& deleted_rows,
const std::set<GURL>& favicon_urls) {
- if (stored_verdict_count_ <= 0)
+ if (stored_verdict_count_password_on_focus_ <= 0 &&
+ stored_verdict_count_password_entry_ <= 0) {
return;
+ }
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
@@ -463,7 +529,8 @@ void PasswordProtectionService::RemoveContentSettingsOnURLsDeleted(
if (all_history) {
content_settings_->ClearSettingsForOneType(
CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION);
- stored_verdict_count_ = 0;
+ stored_verdict_count_password_on_focus_ = 0;
+ stored_verdict_count_password_entry_ = 0;
return;
}
@@ -474,24 +541,106 @@ void PasswordProtectionService::RemoveContentSettingsOnURLsDeleted(
for (const history::URLRow& row : deleted_rows) {
if (!row.url().SchemeIsHTTPOrHTTPS())
continue;
- GURL url_key = GetHostNameWithHTTPScheme(row.url());
- std::unique_ptr<base::DictionaryValue> verdict_dictionary =
- base::DictionaryValue::From(content_settings_->GetWebsiteSetting(
- url_key, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION,
- std::string(), nullptr));
- // Move on if we have no cached verdict for this deleted history row.
- if (!verdict_dictionary.get() || verdict_dictionary->empty())
- continue;
-
- int verdict_count = static_cast<int>(verdict_dictionary->size());
- stored_verdict_count_ = GetStoredVerdictCount() - verdict_count;
+ GURL url_key = GetHostNameWithHTTPScheme(row.url());
+ stored_verdict_count_password_on_focus_ =
+ GetStoredVerdictCount(
+ LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) -
+ GetVerdictCountForURL(
+ url_key, LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE);
+ stored_verdict_count_password_entry_ =
+ GetStoredVerdictCount(
+ LoginReputationClientRequest::PASSWORD_REUSE_EVENT) -
+ GetVerdictCountForURL(
+ url_key, LoginReputationClientRequest::PASSWORD_REUSE_EVENT);
content_settings_->ClearSettingsForOneTypeWithPredicate(
CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, base::Time(),
base::Bind(&OriginMatchPrimaryPattern, url_key));
}
}
+int PasswordProtectionService::GetVerdictCountForURL(const GURL& url,
+ TriggerType trigger_type) {
+ DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE ||
+ trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT);
+ std::unique_ptr<base::DictionaryValue> cache_dictionary =
+ base::DictionaryValue::From(content_settings_->GetWebsiteSetting(
+ url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(),
+ nullptr));
+ if (!cache_dictionary.get() || cache_dictionary->empty())
+ return 0;
+
+ if (trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) {
+ base::DictionaryValue* password_on_focus_dict = nullptr;
+ return cache_dictionary->GetDictionaryWithoutPathExpansion(
+ base::StringPiece(kPasswordOnFocusCacheKey),
+ &password_on_focus_dict)
+ ? password_on_focus_dict->size()
+ : 0;
+ } else {
+ return cache_dictionary->HasKey(base::StringPiece(kPasswordOnFocusCacheKey))
+ ? cache_dictionary->size() - 1
+ : cache_dictionary->size();
+ }
+}
+
+bool PasswordProtectionService::RemoveExpiredVerdicts(
+ TriggerType trigger_type,
+ base::DictionaryValue* cache_dictionary) {
+ DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE ||
+ trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT);
+ base::DictionaryValue* verdict_dictionary = nullptr;
+ if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT) {
+ verdict_dictionary = cache_dictionary;
+ } else {
+ if (!cache_dictionary->GetDictionaryWithoutPathExpansion(
+ base::StringPiece(kPasswordOnFocusCacheKey), &verdict_dictionary)) {
+ return false;
+ }
+ }
+
+ if (!verdict_dictionary || verdict_dictionary->empty())
+ return false;
+
+ std::vector<std::string> expired_keys;
+ for (base::DictionaryValue::Iterator it(*verdict_dictionary); !it.IsAtEnd();
+ it.Advance()) {
+ if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT &&
+ it.key() == std::string(kPasswordOnFocusCacheKey))
+ continue;
+
+ base::DictionaryValue* verdict_entry = nullptr;
+ CHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion(
+ it.key(), &verdict_entry));
+ int verdict_received_time;
+ LoginReputationClientResponse verdict;
+ CHECK(ParseVerdictEntry(verdict_entry, &verdict_received_time, &verdict));
+
+ if (IsCacheExpired(verdict_received_time, verdict.cache_duration_sec())) {
+ // Since DictionaryValue::Iterator cannot be used to modify the
+ // dictionary, we record the keys of expired verdicts in |expired_keys|
+ // and remove them in the next for-loop.
+ expired_keys.push_back(it.key());
+ }
+ }
+
+ for (const std::string& key : expired_keys) {
+ verdict_dictionary->RemoveWithoutPathExpansion(key, nullptr);
+ if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT)
+ stored_verdict_count_password_entry_--;
+ else
+ stored_verdict_count_password_on_focus_--;
+ }
+
+ if (trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE &&
+ verdict_dictionary->size() == 0U) {
+ cache_dictionary->RemoveWithoutPathExpansion(
+ base::StringPiece(kPasswordOnFocusCacheKey), nullptr);
+ }
+
+ return expired_keys.size() > 0U;
+}
+
// static
bool PasswordProtectionService::ParseVerdictEntry(
base::DictionaryValue* verdict_entry,

Powered by Google App Engine
This is Rietveld 408576698