Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "components/safe_browsing/password_protection/password_protection_servi ce.h" | 5 #include "components/safe_browsing/password_protection/password_protection_servi ce.h" |
| 6 | 6 |
| 7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/callback.h" | 8 #include "base/callback.h" |
| 9 #include "base/memory/ptr_util.h" | |
| 9 #include "base/metrics/histogram_macros.h" | 10 #include "base/metrics/histogram_macros.h" |
| 11 #include "base/strings/string_number_conversions.h" | |
| 12 #include "base/strings/string_util.h" | |
| 10 #include "components/safe_browsing_db/database_manager.h" | 13 #include "components/safe_browsing_db/database_manager.h" |
| 14 #include "components/safe_browsing_db/v4_protocol_manager_util.h" | |
| 11 #include "content/public/browser/browser_thread.h" | 15 #include "content/public/browser/browser_thread.h" |
| 12 | 16 |
| 13 using content::BrowserThread; | 17 using content::BrowserThread; |
| 14 | 18 |
| 15 namespace safe_browsing { | 19 namespace safe_browsing { |
| 16 | 20 |
| 21 namespace { | |
| 22 | |
| 23 // Keys for storing password protection verdict into a DictionaryValue. | |
| 24 static const char kCacheTTL[] = "cache_ttl"; | |
| 25 static const char kCacheCreationTime[] = "cache_creation_time"; | |
| 26 static const char kVerdictType[] = "verdict"; | |
| 27 | |
| 28 // Helper function to determine if the given origin matches content settings | |
| 29 // map's patterns. | |
| 30 bool OriginMatchPrimaryPattern( | |
| 31 const GURL& origin, | |
| 32 const ContentSettingsPattern& primary_pattern, | |
| 33 const ContentSettingsPattern& secondary_pattern_unused) { | |
| 34 return ContentSettingsPattern::FromURLNoWildcard(origin) == primary_pattern; | |
| 35 } | |
| 36 | |
| 37 // Convert a LoginReputationClientResponse proto into a DictionaryValue. | |
| 38 std::unique_ptr<base::DictionaryValue> CreateDictionaryFromVerdict( | |
| 39 LoginReputationClientResponse* verdict, | |
|
vakh (use Gerrit instead)
2017/03/16 22:04:30
this can be const
Jialiu Lin
2017/03/18 23:46:31
Done.
| |
| 40 const base::Time& receive_time) { | |
| 41 std::unique_ptr<base::DictionaryValue> result = | |
| 42 base::MakeUnique<base::DictionaryValue>(); | |
| 43 if (verdict) { | |
| 44 result->SetIntegerWithoutPathExpansion(kCacheTTL, | |
| 45 verdict->cache_duration_sec()); | |
| 46 result->SetIntegerWithoutPathExpansion( | |
| 47 kCacheCreationTime, static_cast<int>(receive_time.ToDoubleT())); | |
| 48 result->SetIntegerWithoutPathExpansion(kVerdictType, | |
| 49 verdict->verdict_type()); | |
| 50 } | |
| 51 return result; | |
| 52 } | |
| 53 | |
| 54 } // namespace | |
| 55 | |
| 17 PasswordProtectionService::PasswordProtectionService( | 56 PasswordProtectionService::PasswordProtectionService( |
| 18 const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager) | 57 const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager) |
| 19 : database_manager_(database_manager), weak_factory_(this) { | 58 : database_manager_(database_manager), weak_factory_(this) { |
| 20 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 59 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 21 } | 60 } |
| 22 | 61 |
| 23 PasswordProtectionService::~PasswordProtectionService() { | 62 PasswordProtectionService::~PasswordProtectionService() { |
| 24 weak_factory_.InvalidateWeakPtrs(); | 63 weak_factory_.InvalidateWeakPtrs(); |
| 25 } | 64 } |
| 26 | 65 |
| 27 void PasswordProtectionService::RecordPasswordReuse(const GURL& url) { | 66 void PasswordProtectionService::RecordPasswordReuse(const GURL& url) { |
| 28 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 67 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 29 DCHECK(database_manager_); | 68 DCHECK(database_manager_); |
| 30 if (!url.is_valid()) | 69 if (!url.is_valid()) |
| 31 return; | 70 return; |
| 32 | 71 |
| 33 BrowserThread::PostTaskAndReplyWithResult( | 72 BrowserThread::PostTaskAndReplyWithResult( |
| 34 BrowserThread::IO, FROM_HERE, | 73 BrowserThread::IO, FROM_HERE, |
| 35 base::Bind(&SafeBrowsingDatabaseManager::MatchCsdWhitelistUrl, | 74 base::Bind(&SafeBrowsingDatabaseManager::MatchCsdWhitelistUrl, |
| 36 database_manager_, url), | 75 database_manager_, url), |
| 37 base::Bind(&PasswordProtectionService::OnMatchCsdWhiteListResult, | 76 base::Bind(&PasswordProtectionService::OnMatchCsdWhiteListResult, |
| 38 GetWeakPtr())); | 77 GetWeakPtr())); |
| 39 } | 78 } |
| 40 | 79 |
| 80 LoginReputationClientResponse::VerdictType | |
| 81 PasswordProtectionService::GetCachedVerdict(HostContentSettingsMap* settings, | |
|
vakh (use Gerrit instead)
2017/03/16 22:04:29
const *settings?
Jialiu Lin
2017/03/18 23:46:31
Done.
| |
| 82 const GURL& url) { | |
| 83 // TODO(jialiul): Add UMA metrics to track if verdict is available, not | |
| 84 // available, or expired. | |
| 85 if (!settings || !url.is_valid()) { | |
|
Nathan Parker
2017/03/16 21:27:11
nit: do you want to DCHECK on settings, or is ther
Jialiu Lin
2017/03/18 23:46:31
Done.
| |
| 86 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; | |
| 87 } | |
| 88 | |
| 89 GURL origin_url = url.GetOrigin(); | |
| 90 std::unique_ptr<base::DictionaryValue> verdict_dictionary = | |
| 91 base::DictionaryValue::From(settings->GetWebsiteSetting( | |
| 92 origin_url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | |
| 93 std::string(), nullptr)); | |
| 94 // Return early if there is no verdict cached for this |origin_url|. | |
| 95 if (!verdict_dictionary.get() || verdict_dictionary->empty()) { | |
| 96 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; | |
| 97 } | |
| 98 | |
| 99 // For all the verdicts of the same origin, we key them by cache_expression | |
| 100 // field. And its corresponding value is a DictionaryValue contains its ttl, | |
| 101 // creation time, and verdict type info. | |
| 102 int cache_creation_time; | |
| 103 int cache_duration; | |
| 104 int verdict_type_value; | |
| 105 base::DictionaryValue* verdict_entry = nullptr; | |
| 106 for (base::DictionaryValue::Iterator it(*verdict_dictionary.get()); | |
| 107 !it.IsAtEnd(); it.Advance()) { | |
| 108 if (verdict_dictionary->GetDictionaryWithoutPathExpansion(it.key(), | |
| 109 &verdict_entry) && | |
|
Nathan Parker
2017/03/16 21:27:11
Is it an error if ParseVerdictEntry() returns fals
Jialiu Lin
2017/03/18 23:46:31
Done.
| |
| 110 ParseVerdictEntry(verdict_entry, &cache_creation_time, &cache_duration, | |
| 111 &verdict_type_value)) { | |
| 112 if (UrlMatchCacheExpression(url, it.key() /* cache_expression */) && | |
| 113 !IsCacheExpired(cache_creation_time, cache_duration)) { | |
|
Nathan Parker
2017/03/16 21:27:11
Do you think we need to remove expired entries, or
Jialiu Lin
2017/03/18 23:46:31
If the entry is expired, we are going to request a
| |
| 114 return static_cast<LoginReputationClientResponse::VerdictType>( | |
| 115 verdict_type_value); | |
|
Nathan Parker
2017/03/16 21:27:11
Something to think about: If we were to add new ve
Jialiu Lin
2017/03/18 23:46:31
Agreed. In the latest patch, I cache the serialize
| |
| 116 } | |
| 117 } | |
| 118 } | |
| 119 // We don't find any valid and matching verdict. | |
| 120 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; | |
| 121 } | |
| 122 | |
| 123 void PasswordProtectionService::CacheVerdict( | |
| 124 HostContentSettingsMap* settings, | |
|
vakh (use Gerrit instead)
2017/03/16 22:04:30
Since |settings| is being set, it is an output, so
Jialiu Lin
2017/03/18 23:46:31
Done.
| |
| 125 const GURL& url, | |
| 126 LoginReputationClientResponse* verdict, | |
| 127 const base::Time& receive_time) { | |
| 128 if (!verdict) | |
|
Nathan Parker
2017/03/16 21:27:11
Again, how about DCHECK instead?
Jialiu Lin
2017/03/18 23:46:31
Done.
| |
| 129 return; | |
| 130 | |
| 131 GURL origin_url = url.GetOrigin(); | |
| 132 std::unique_ptr<base::DictionaryValue> verdict_dictionary = | |
| 133 base::DictionaryValue::From(settings->GetWebsiteSetting( | |
| 134 origin_url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | |
| 135 std::string(), nullptr)); | |
| 136 | |
| 137 if (!verdict_dictionary.get()) | |
| 138 verdict_dictionary = base::MakeUnique<base::DictionaryValue>(); | |
| 139 | |
| 140 std::unique_ptr<base::DictionaryValue> verdict_entry = | |
| 141 CreateDictionaryFromVerdict(verdict, receive_time); | |
| 142 // If same cache_expression is already in this verdict_dictionary, we simply | |
| 143 // override it with a fresher verdict. | |
| 144 // TODO(jialiul): We assume cache expressions returned by CSD server are | |
| 145 // not overlapping with each other and are stable. Need to confirm with SB | |
|
Nathan Parker
2017/03/16 21:27:11
We could verify it on the client and do something
Jialiu Lin
2017/03/18 23:46:31
You're right. I just confirmed with Weining. Overl
| |
| 146 // backend if this assumption is always true. | |
| 147 verdict_dictionary->SetWithoutPathExpansion(verdict->cache_expression(), | |
| 148 std::move(verdict_entry)); | |
| 149 settings->SetWebsiteSettingDefaultScope( | |
| 150 origin_url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | |
| 151 std::string(), std::move(verdict_dictionary)); | |
| 152 } | |
| 153 | |
| 41 void PasswordProtectionService::OnMatchCsdWhiteListResult( | 154 void PasswordProtectionService::OnMatchCsdWhiteListResult( |
| 42 bool match_whitelist) { | 155 bool match_whitelist) { |
| 43 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 156 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 44 UMA_HISTOGRAM_BOOLEAN( | 157 UMA_HISTOGRAM_BOOLEAN( |
| 45 "PasswordManager.PasswordReuse.MainFrameMatchCsdWhitelist", | 158 "PasswordManager.PasswordReuse.MainFrameMatchCsdWhitelist", |
| 46 match_whitelist); | 159 match_whitelist); |
| 47 } | 160 } |
| 48 | 161 |
| 162 HostContentSettingsMap* | |
| 163 PasswordProtectionService::GetSettingMapForActiveProfile() { | |
| 164 return nullptr; | |
| 165 } | |
| 166 | |
| 167 void PasswordProtectionService::OnURLsDeleted( | |
| 168 history::HistoryService* history_service, | |
| 169 bool all_history, | |
| 170 bool expired, | |
| 171 const history::URLRows& deleted_rows, | |
| 172 const std::set<GURL>& favicon_urls) { | |
| 173 HostContentSettingsMap* setting_map = GetSettingMapForActiveProfile(); | |
| 174 if (!setting_map) | |
| 175 return; | |
| 176 | |
| 177 BrowserThread::PostTask( | |
| 178 BrowserThread::UI, FROM_HERE, | |
| 179 base::Bind(&PasswordProtectionService::RemoveContentSettingsOnURLsDeleted, | |
| 180 GetWeakPtr(), setting_map, all_history, deleted_rows)); | |
| 181 } | |
| 182 | |
| 183 void PasswordProtectionService::RemoveContentSettingsOnURLsDeleted( | |
| 184 HostContentSettingsMap* setting_map, | |
|
vakh (use Gerrit instead)
2017/03/16 22:04:30
|setting_map| at the end
Jialiu Lin
2017/03/18 23:46:31
Done.
| |
| 185 bool all_history, | |
| 186 const history::URLRows& deleted_rows) { | |
| 187 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 188 if (all_history) { | |
| 189 setting_map->ClearSettingsForOneType( | |
| 190 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION); | |
| 191 return; | |
| 192 } | |
| 193 | |
| 194 // For now, if a URL is deleted from history, we simply remove all the | |
| 195 // cached verdicts of the same origin. This is a pretty aggressive deletion. | |
| 196 // We might revisit this logic later to decide if we want to only delete the | |
| 197 // cached verdict whose cache expression matches this URL. | |
| 198 for (const history::URLRow& row : deleted_rows) { | |
| 199 setting_map->ClearSettingsForOneTypeWithPredicate( | |
| 200 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | |
| 201 base::Bind(&OriginMatchPrimaryPattern, row.url().GetOrigin())); | |
| 202 } | |
| 203 } | |
| 204 | |
| 205 // static | |
| 206 bool PasswordProtectionService::ParseVerdictEntry( | |
| 207 base::DictionaryValue* verdict_entry, | |
|
vakh (use Gerrit instead)
2017/03/16 22:04:30
|verdict_entry| const?
Jialiu Lin
2017/03/18 23:46:31
acknowledged. for some weird reason, base::Dictio
| |
| 208 int* out_cache_creation_time, | |
| 209 int* out_cache_duration, | |
| 210 int* out_verdict_type) { | |
| 211 return verdict_entry && | |
| 212 verdict_entry->GetInteger(kCacheCreationTime, | |
| 213 out_cache_creation_time) && | |
| 214 verdict_entry->GetInteger(kCacheTTL, out_cache_duration) && | |
| 215 verdict_entry->GetInteger(kVerdictType, out_verdict_type); | |
| 216 } | |
| 217 | |
| 218 bool PasswordProtectionService::UrlMatchCacheExpression( | |
| 219 const GURL& url, | |
| 220 const std::string& cache_expression) { | |
| 221 std::string canon_host; | |
| 222 std::string canon_path; | |
| 223 std::string canon_query; | |
| 224 V4ProtocolManagerUtil::CanonicalizeUrl(url, &canon_host, &canon_path, | |
| 225 &canon_query); | |
| 226 | |
| 227 std::vector<std::string> hosts; | |
| 228 if (url.HostIsIPAddress()) { | |
| 229 hosts.push_back(url.host()); | |
| 230 } else { | |
| 231 V4ProtocolManagerUtil::GenerateHostVariantsToCheck(canon_host, &hosts); | |
| 232 } | |
| 233 | |
| 234 std::vector<std::string> paths; | |
| 235 V4ProtocolManagerUtil::GeneratePathVariantsToCheck(canon_path, canon_query, | |
| 236 &paths); | |
| 237 | |
| 238 base::StringPiece cache_expression_trimed(base::TrimString( | |
| 239 cache_expression, "/", base::TrimPositions::TRIM_TRAILING)); | |
| 240 for (const std::string& host : hosts) { | |
|
Nathan Parker
2017/03/16 21:27:11
I'm a little worried about CPU/latency here, since
Jialiu Lin
2017/03/18 23:46:31
Rewrote this function significantly. Now only one
| |
| 241 for (const std::string& path : paths) { | |
|
vakh (use Gerrit instead)
2017/03/16 22:04:30
Most code in this function is similar to V4Protoco
Jialiu Lin
2017/03/18 23:46:31
This function is rewritten significantly in the la
| |
| 242 std::string host_path_combine(host); | |
| 243 host_path_combine.append(path); | |
| 244 if (cache_expression_trimed == | |
| 245 base::TrimString(host_path_combine, "/", | |
| 246 base::TrimPositions::TRIM_TRAILING)) { | |
| 247 return true; | |
| 248 } | |
| 249 } | |
| 250 } | |
| 251 return false; | |
| 252 } | |
| 253 | |
| 254 bool PasswordProtectionService::IsCacheExpired(int cache_creation_time, | |
| 255 int cache_duration) { | |
| 256 // TODO(jialiul): For now, we assume client's clock is accurate or almost | |
| 257 // accurate. Need some logic to handle cases where client's clock is way off. | |
| 258 return base::Time::Now().ToDoubleT() > | |
| 259 static_cast<double>(cache_creation_time + cache_duration); | |
| 260 } | |
| 261 | |
| 49 } // namespace safe_browsing | 262 } // namespace safe_browsing |
| OLD | NEW |