Chromium Code Reviews| 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..dff530b13438ee9d75ae4bf3162e86f89fa7fd7c 100644 |
| --- a/components/safe_browsing/password_protection/password_protection_service.cc |
| +++ b/components/safe_browsing/password_protection/password_protection_service.cc |
| @@ -6,14 +6,53 @@ |
| #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_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 kCacheTTL[] = "cache_ttl"; |
| +static const char kCacheCreationTime[] = "cache_creation_time"; |
| +static const char kVerdictType[] = "verdict"; |
| + |
| +// 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; |
| +} |
| + |
| +// Convert a LoginReputationClientResponse proto into a DictionaryValue. |
| +std::unique_ptr<base::DictionaryValue> CreateDictionaryFromVerdict( |
| + LoginReputationClientResponse* verdict, |
| + const base::Time& receive_time) { |
| + std::unique_ptr<base::DictionaryValue> result = |
| + base::MakeUnique<base::DictionaryValue>(); |
| + if (verdict) { |
| + result->SetIntegerWithoutPathExpansion(kCacheTTL, |
| + verdict->cache_duration_sec()); |
| + result->SetIntegerWithoutPathExpansion( |
| + kCacheCreationTime, static_cast<int>(receive_time.ToDoubleT())); |
| + result->SetIntegerWithoutPathExpansion(kVerdictType, |
| + verdict->verdict_type()); |
| + } |
| + return result; |
| +} |
| + |
| +} // namespace |
| + |
| PasswordProtectionService::PasswordProtectionService( |
| const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager) |
| : database_manager_(database_manager), weak_factory_(this) { |
| @@ -38,6 +77,79 @@ void PasswordProtectionService::RecordPasswordReuse(const GURL& url) { |
| GetWeakPtr())); |
| } |
| +LoginReputationClientResponse::VerdictType |
| +PasswordProtectionService::GetCachedVerdict(HostContentSettingsMap* settings, |
| + const GURL& url) { |
| + // TODO(jialiul): Add UMA metrics to track if verdict is available, not |
| + // available, or expired. |
| + if (!settings || !url.is_valid()) { |
| + return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; |
| + } |
| + |
| + GURL origin_url = url.GetOrigin(); |
| + std::unique_ptr<base::DictionaryValue> verdict_dictionary = |
| + base::DictionaryValue::From(settings->GetWebsiteSetting( |
| + origin_url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, |
| + std::string(), nullptr)); |
| + // Return early if there is no verdict cached for this |origin_url|. |
| + if (!verdict_dictionary.get() || verdict_dictionary->empty()) { |
| + return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; |
| + } |
| + |
| + // For all the verdicts of the same origin, we key them by cache_expression |
| + // field. And its corresponding value is a DictionaryValue contains its ttl, |
| + // creation time, and verdict type info. |
| + int cache_creation_time; |
| + int cache_duration; |
| + int verdict_type_value; |
| + for (base::DictionaryValue::Iterator it(*verdict_dictionary.get()); |
| + !it.IsAtEnd(); it.Advance()) { |
| + base::DictionaryValue* verdict_entry = nullptr; |
| + DCHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion( |
| + it.key(), &verdict_entry)); |
|
lpz
2017/03/15 15:14:58
nit: alignment?
Jialiu Lin
2017/03/15 17:47:31
Acknowledged.
git cl format insists this is the b
|
| + DCHECK(ParseVerdictEntry(verdict_entry, &cache_creation_time, |
| + &cache_duration, &verdict_type_value)); |
| + if (UrlMatchCacheExpression(url, it.key() /* cache_expression */) && |
| + !IsCacheExpired(cache_creation_time, cache_duration)) { |
| + return static_cast<LoginReputationClientResponse::VerdictType>( |
| + verdict_type_value); |
| + } |
| + } |
| + // We don't find any valid and matching verdict. |
| + return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; |
| +} |
| + |
| +void PasswordProtectionService::CacheVerdict( |
| + HostContentSettingsMap* settings, |
| + const GURL& url, |
| + LoginReputationClientResponse* verdict, |
| + const base::Time& receive_time) { |
| + if (!verdict) |
| + return; |
| + |
| + GURL origin_url = url.GetOrigin(); |
| + std::unique_ptr<base::DictionaryValue> verdict_dictionary = |
| + base::DictionaryValue::From(settings->GetWebsiteSetting( |
| + origin_url, 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 with a fresher verdict. |
| + // TODO(jialiul): We assume cache expressions returned by CSD server are |
| + // not overlapping with each other and are stable. Need to confirm with SB |
| + // backend if this assumption is always true. |
| + verdict_dictionary->SetWithoutPathExpansion(verdict->cache_expression(), |
| + std::move(verdict_entry)); |
| + settings->SetWebsiteSettingDefaultScope( |
| + origin_url, 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 +158,104 @@ void PasswordProtectionService::OnMatchCsdWhiteListResult( |
| match_whitelist); |
| } |
| +HostContentSettingsMap* |
| +PasswordProtectionService::GetSettingMapForActiveProfile() { |
| + 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(), setting_map, all_history, deleted_rows)); |
| +} |
| + |
| +void PasswordProtectionService::RemoveContentSettingsOnURLsDeleted( |
| + HostContentSettingsMap* setting_map, |
| + bool all_history, |
| + const history::URLRows& deleted_rows) { |
| + 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_cache_creation_time, |
| + int* out_cache_duration, |
| + int* out_verdict_type) { |
| + return verdict_entry && |
| + verdict_entry->GetInteger(kCacheCreationTime, |
| + out_cache_creation_time) && |
| + verdict_entry->GetInteger(kCacheTTL, out_cache_duration) && |
| + verdict_entry->GetInteger(kVerdictType, out_verdict_type); |
| +} |
| + |
| +bool PasswordProtectionService::UrlMatchCacheExpression( |
| + const GURL& url, |
| + const std::string& cache_expression) { |
| + std::string canon_host; |
| + std::string canon_path; |
| + std::string canon_query; |
| + V4ProtocolManagerUtil::CanonicalizeUrl(url, &canon_host, &canon_path, |
| + &canon_query); |
| + |
| + std::vector<std::string> hosts; |
| + if (url.HostIsIPAddress()) { |
| + hosts.push_back(url.host()); |
| + } else { |
| + V4ProtocolManagerUtil::GenerateHostVariantsToCheck(canon_host, &hosts); |
| + } |
| + |
| + std::vector<std::string> paths; |
| + V4ProtocolManagerUtil::GeneratePathVariantsToCheck(canon_path, canon_query, |
| + &paths); |
| + |
| + base::StringPiece cache_expression_trimed(base::TrimString( |
| + cache_expression, "/", base::TrimPositions::TRIM_TRAILING)); |
| + for (const std::string& host : hosts) { |
| + for (const std::string& path : paths) { |
| + std::string host_path_combine(host); |
| + host_path_combine.append(path); |
| + if (cache_expression_trimed == |
| + base::TrimString(host_path_combine, "/", |
| + base::TrimPositions::TRIM_TRAILING)) { |
| + 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); |
| +} |
| + |
| } // namespace safe_browsing |