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

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

Issue 2747313002: PasswordProtectionService verdict cache management (Closed)
Patch Set: Cache verdict based on hostname Created 3 years, 9 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 d0331fc4d30f12a84578b937d2c861b52006be93..e8f348e533bf8275ff3e785902b9faccb9be4804 100644
--- a/components/safe_browsing/password_protection/password_protection_service.cc
+++ b/components/safe_browsing/password_protection/password_protection_service.cc
@@ -6,14 +6,54 @@
#include "base/bind.h"
#include "base/callback.h"
+#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
#include "components/safe_browsing_db/database_manager.h"
+#include "components/safe_browsing_db/v4_protocol_manager_util.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace safe_browsing {
+namespace {
+
+// Keys for storing password protection verdict into a DictionaryValue.
+static const char kCacheCreationTime[] = "cache_creation_time";
+static const char kVerdictProto[] = "verdict_proto";
+
+// Helper function to determine if the given origin matches content settings
+// map's patterns.
+bool OriginMatchPrimaryPattern(
+ const GURL& origin,
+ const ContentSettingsPattern& primary_pattern,
+ const ContentSettingsPattern& secondary_pattern_unused) {
+ return ContentSettingsPattern::FromURLNoWildcard(origin) == primary_pattern;
+}
+
+// Returns the number of path segments in |cache_expression_path|.
+// For example, return 0 for "/", since there is no path after the leading
+// slash; return 3 for "/abc/def/gh.html".
+size_t GetPathDepth(const std::string& cache_expression_path) {
+ return base::SplitString(base::StringPiece(cache_expression_path), "/",
+ base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)
+ .size();
+}
+
+// Given a URL of either http or https scheme, return its scheme://hostname.
+// e.g., "https://www.foo.com:80/bar/test.cgi" -> "http://www.foo.com".
+GURL GetHostNameWithHTTPScheme(const GURL& url) {
+ DCHECK(url.SchemeIsHTTPOrHTTPS());
+ std::string result(url::kHttpScheme);
+ result.append(url::kStandardSchemeSeparator).append(url.HostNoBrackets());
+ return GURL(result);
+}
+
+} // namespace
+
PasswordProtectionService::PasswordProtectionService(
const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager)
: database_manager_(database_manager), weak_factory_(this) {
@@ -38,6 +78,98 @@ void PasswordProtectionService::RecordPasswordReuse(const GURL& url) {
GetWeakPtr()));
}
+LoginReputationClientResponse::VerdictType
+PasswordProtectionService::GetCachedVerdict(
+ const HostContentSettingsMap* settings,
+ const GURL& url) {
+ // TODO(jialiul): Add UMA metrics to track if verdict is available, not
+ // available, or expired.
+ DCHECK(settings);
+ if (!url.is_valid()) {
+ return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED;
+ }
+
+ GURL hostname = GetHostNameWithHTTPScheme(url);
+ std::unique_ptr<base::DictionaryValue> verdict_dictionary =
+ base::DictionaryValue::From(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()) {
+ return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED;
+ }
+
+ std::vector<std::string> paths;
+ GeneratePathVariantsWithoutQuery(url, &paths);
+ size_t max_path_depth = 0U;
+ LoginReputationClientResponse::VerdictType most_matching_verdict =
+ LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED;
+ // 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()) {
+ base::DictionaryValue* verdict_entry = nullptr;
+ CHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion(
+ it.key() /* cache_expression */, &verdict_entry));
+ int verdict_received_time;
+ LoginReputationClientResponse verdict;
+ CHECK(ParseVerdictEntry(verdict_entry, &verdict_received_time, &verdict));
+ // Since password protection content settings are keyed by origin, we only
+ // need to compare the path part of the cache_expression and the given url.
+ std::string cache_expression_path =
+ GetCacheExpressionPath(verdict.cache_expression());
+
+ if (verdict.cache_expression_exact_match()) {
+ if (PathMatchCacheExpressionExactly(paths, cache_expression_path)) {
+ return IsCacheExpired(verdict_received_time,
+ verdict.cache_duration_sec())
+ ? LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED
+ : verdict.verdict_type();
+ }
+ } else {
+ // If it doesn't require exact match, we need to fine the most specific
+ // match.
+ size_t path_depth = GetPathDepth(cache_expression_path);
+ if (path_depth > max_path_depth &&
+ PathVariantsMatchCacheExpression(paths, cache_expression_path) &&
+ !IsCacheExpired(verdict_received_time,
+ verdict.cache_duration_sec())) {
+ max_path_depth = path_depth;
+ most_matching_verdict = verdict.verdict_type();
+ }
+ }
+ }
+ return most_matching_verdict;
+}
+
+void PasswordProtectionService::CacheVerdict(
+ const GURL& url,
+ LoginReputationClientResponse* verdict,
+ const base::Time& receive_time,
+ HostContentSettingsMap* settings) {
+ DCHECK(verdict);
+
+ GURL hostname = GetHostNameWithHTTPScheme(url);
+ std::unique_ptr<base::DictionaryValue> verdict_dictionary =
+ base::DictionaryValue::From(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 same cache_expression is already in this verdict_dictionary, we simply
+ // override it.
+ verdict_dictionary->SetWithoutPathExpansion(verdict->cache_expression(),
+ std::move(verdict_entry));
+ settings->SetWebsiteSettingDefaultScope(
+ hostname, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION,
+ std::string(), std::move(verdict_dictionary));
+}
+
void PasswordProtectionService::OnMatchCsdWhiteListResult(
bool match_whitelist) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
@@ -46,4 +178,150 @@ void PasswordProtectionService::OnMatchCsdWhiteListResult(
match_whitelist);
}
+HostContentSettingsMap*
+PasswordProtectionService::GetSettingMapForActiveProfile() {
+ // TODO(jialiul): Make this a pure virtual function when we have a derived
+ // class ready in chrome/browser/safe_browsing directory.
+ return nullptr;
+}
+
+void PasswordProtectionService::OnURLsDeleted(
+ history::HistoryService* history_service,
+ bool all_history,
+ bool expired,
+ const history::URLRows& deleted_rows,
+ const std::set<GURL>& favicon_urls) {
+ HostContentSettingsMap* setting_map = GetSettingMapForActiveProfile();
+ if (!setting_map)
+ return;
+
+ BrowserThread::PostTask(
+ BrowserThread::UI, FROM_HERE,
+ base::Bind(&PasswordProtectionService::RemoveContentSettingsOnURLsDeleted,
+ GetWeakPtr(), all_history, deleted_rows, setting_map));
+}
+
+void PasswordProtectionService::RemoveContentSettingsOnURLsDeleted(
+ bool all_history,
+ const history::URLRows& deleted_rows,
+ HostContentSettingsMap* setting_map) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ if (all_history) {
+ setting_map->ClearSettingsForOneType(
+ CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION);
+ return;
+ }
+
+ // For now, if a URL is deleted from history, we simply remove all the
+ // cached verdicts of the same origin. This is a pretty aggressive deletion.
+ // We might revisit this logic later to decide if we want to only delete the
+ // cached verdict whose cache expression matches this URL.
+ for (const history::URLRow& row : deleted_rows) {
+ setting_map->ClearSettingsForOneTypeWithPredicate(
+ CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION,
+ base::Bind(&OriginMatchPrimaryPattern, row.url().GetOrigin()));
+ }
+}
+
+// static
+bool PasswordProtectionService::ParseVerdictEntry(
+ base::DictionaryValue* verdict_entry,
+ int* out_verdict_received_time,
+ LoginReputationClientResponse* out_verdict) {
+ base::Value* binary_value = nullptr;
+ bool result = verdict_entry && out_verdict &&
+ verdict_entry->GetInteger(kCacheCreationTime,
+ out_verdict_received_time) &&
+ verdict_entry->Get(kVerdictProto, &binary_value);
+ if (!result)
+ return false;
+ DCHECK(result);
+ DCHECK_EQ(base::Value::Type::BINARY, binary_value->type());
+ const auto blob = binary_value->GetBlob();
+ const std::string serialized_verdict_proto(blob.begin(), blob.end());
+ return out_verdict->ParseFromString(serialized_verdict_proto);
+}
+
+bool PasswordProtectionService::PathMatchCacheExpressionExactly(
+ const std::vector<std::string>& generated_paths,
+ const std::string& cache_expression_path) {
+ size_t cache_expression_path_depth = GetPathDepth(cache_expression_path);
+ if (generated_paths.size() <= cache_expression_path_depth) {
+ return false;
+ }
+ std::string canonical_path = generated_paths.back();
+ size_t last_slash_pos = canonical_path.find_last_of("/");
+ DCHECK_NE(std::string::npos, last_slash_pos);
+ return canonical_path.substr(0, last_slash_pos + 1) == cache_expression_path;
+}
+
+bool PasswordProtectionService::PathVariantsMatchCacheExpression(
+ const std::vector<std::string>& generated_paths,
+ const std::string& cache_expression_path) {
+ for (const auto& path : generated_paths) {
+ if (cache_expression_path == path) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool PasswordProtectionService::IsCacheExpired(int cache_creation_time,
+ int cache_duration) {
+ // TODO(jialiul): For now, we assume client's clock is accurate or almost
+ // accurate. Need some logic to handle cases where client's clock is way
+ // off.
+ return base::Time::Now().ToDoubleT() >
+ static_cast<double>(cache_creation_time + cache_duration);
+}
+
+// Generate path variants of the given URL.
+void PasswordProtectionService::GeneratePathVariantsWithoutQuery(
+ const GURL& url,
+ std::vector<std::string>* paths) {
+ std::string canonical_path;
+ V4ProtocolManagerUtil::CanonicalizeUrl(url, nullptr, &canonical_path,
+ nullptr);
+ V4ProtocolManagerUtil::GeneratePathVariantsToCheck(canonical_path,
+ std::string(), paths);
+}
+
+// Return the path of the cache expression. e.g.:
+// "www.google.com" -> "/"
+// "www.google.com/abc" -> "/abc/"
+// "foo.com/foo/bar/" -> "/foo/bar/"
+std::string PasswordProtectionService::GetCacheExpressionPath(
+ const std::string& cache_expression) {
+ DCHECK(!cache_expression.empty());
+ std::string out_put(cache_expression);
+ // Append a trailing slash if needed.
+ if (out_put[out_put.length() - 1] != '/')
+ out_put.append("/");
+
+ size_t first_slash_pos = out_put.find_first_of("/");
+ DCHECK_NE(std::string::npos, first_slash_pos);
+ return out_put.substr(first_slash_pos);
+}
+
+// Convert a LoginReputationClientResponse proto into a DictionaryValue.
+std::unique_ptr<base::DictionaryValue>
+PasswordProtectionService::CreateDictionaryFromVerdict(
+ const LoginReputationClientResponse* verdict,
+ const base::Time& receive_time) {
+ std::unique_ptr<base::DictionaryValue> result =
+ base::MakeUnique<base::DictionaryValue>();
+ result->SetInteger(kCacheCreationTime,
+ static_cast<int>(receive_time.ToDoubleT()));
+ // Because DictionaryValue cannot take non-UTF8 string, we need to store
+ // serialized proto in its allowed binary format instead.
+ const std::string serialized_proto(verdict->SerializeAsString());
+ const std::vector<char> verdict_blob(serialized_proto.begin(),
+ serialized_proto.end());
+ std::unique_ptr<base::Value> binary_value =
+ base::MakeUnique<base::Value>(verdict_blob);
+ DCHECK_EQ(base::Value::Type::BINARY, binary_value->type());
+ result->Set(kVerdictProto, std::move(binary_value));
+ return result;
+}
+
} // namespace safe_browsing

Powered by Google App Engine
This is Rietveld 408576698