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_split.h" | |
| 13 #include "base/strings/string_util.h" | |
| 10 #include "components/safe_browsing_db/database_manager.h" | 14 #include "components/safe_browsing_db/database_manager.h" |
| 15 #include "components/safe_browsing_db/v4_protocol_manager_util.h" | |
| 11 #include "content/public/browser/browser_thread.h" | 16 #include "content/public/browser/browser_thread.h" |
| 12 | 17 |
| 13 using content::BrowserThread; | 18 using content::BrowserThread; |
| 14 | 19 |
| 15 namespace safe_browsing { | 20 namespace safe_browsing { |
| 16 | 21 |
| 22 namespace { | |
| 23 | |
| 24 // Keys for storing password protection verdict into a DictionaryValue. | |
| 25 static const char kCacheCreationTime[] = "cache_creation_time"; | |
| 26 static const char kVerdictProto[] = "verdict_proto"; | |
| 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 // Returns the number of path segments in |cache_expression_path|. | |
| 38 // For example, return 0 for "/", since there is no path after the leading | |
| 39 // slash; return 3 for "/abc/def/gh.html". | |
| 40 size_t GetPathDepth(const std::string& cache_expression_path) { | |
| 41 return base::SplitString(base::StringPiece(cache_expression_path), "/", | |
| 42 base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY) | |
| 43 .size(); | |
| 44 } | |
| 45 | |
| 46 } // namespace | |
| 47 | |
| 17 PasswordProtectionService::PasswordProtectionService( | 48 PasswordProtectionService::PasswordProtectionService( |
| 18 const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager) | 49 const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager) |
| 19 : database_manager_(database_manager), weak_factory_(this) { | 50 : database_manager_(database_manager), weak_factory_(this) { |
| 20 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 51 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 21 } | 52 } |
| 22 | 53 |
| 23 PasswordProtectionService::~PasswordProtectionService() { | 54 PasswordProtectionService::~PasswordProtectionService() { |
| 24 weak_factory_.InvalidateWeakPtrs(); | 55 weak_factory_.InvalidateWeakPtrs(); |
| 25 } | 56 } |
| 26 | 57 |
| 27 void PasswordProtectionService::RecordPasswordReuse(const GURL& url) { | 58 void PasswordProtectionService::RecordPasswordReuse(const GURL& url) { |
| 28 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 59 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 29 DCHECK(database_manager_); | 60 DCHECK(database_manager_); |
| 30 if (!url.is_valid()) | 61 if (!url.is_valid()) |
| 31 return; | 62 return; |
| 32 | 63 |
| 33 BrowserThread::PostTaskAndReplyWithResult( | 64 BrowserThread::PostTaskAndReplyWithResult( |
| 34 BrowserThread::IO, FROM_HERE, | 65 BrowserThread::IO, FROM_HERE, |
| 35 base::Bind(&SafeBrowsingDatabaseManager::MatchCsdWhitelistUrl, | 66 base::Bind(&SafeBrowsingDatabaseManager::MatchCsdWhitelistUrl, |
| 36 database_manager_, url), | 67 database_manager_, url), |
| 37 base::Bind(&PasswordProtectionService::OnMatchCsdWhiteListResult, | 68 base::Bind(&PasswordProtectionService::OnMatchCsdWhiteListResult, |
| 38 GetWeakPtr())); | 69 GetWeakPtr())); |
| 39 } | 70 } |
| 40 | 71 |
| 72 LoginReputationClientResponse::VerdictType | |
| 73 PasswordProtectionService::GetCachedVerdict( | |
| 74 const HostContentSettingsMap* settings, | |
| 75 const GURL& url) { | |
| 76 // TODO(jialiul): Add UMA metrics to track if verdict is available, not | |
| 77 // available, or expired. | |
| 78 DCHECK(settings); | |
| 79 if (!url.is_valid()) { | |
| 80 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; | |
| 81 } | |
| 82 | |
| 83 GURL origin_url = url.GetOrigin(); | |
| 84 std::unique_ptr<base::DictionaryValue> verdict_dictionary = | |
| 85 base::DictionaryValue::From(settings->GetWebsiteSetting( | |
| 86 origin_url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | |
| 87 std::string(), nullptr)); | |
| 88 // Return early if there is no verdict cached for this origin. | |
| 89 if (!verdict_dictionary.get() || verdict_dictionary->empty()) { | |
| 90 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; | |
| 91 } | |
| 92 | |
| 93 std::vector<std::string> paths; | |
| 94 GeneratePathVariantsWithoutQuery(url, &paths); | |
| 95 size_t max_path_depth = 0U; | |
| 96 LoginReputationClientResponse::VerdictType most_matching_verdict = | |
| 97 LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; | |
| 98 // For all the verdicts of the same origin, we key them by |cache_expression|. | |
| 99 // Its corresponding value is a DictionaryValue contains its creation time and | |
| 100 // the serialized verdict proto. | |
| 101 for (base::DictionaryValue::Iterator it(*verdict_dictionary.get()); | |
| 102 !it.IsAtEnd(); it.Advance()) { | |
| 103 base::DictionaryValue* verdict_entry = nullptr; | |
| 104 DCHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion( | |
|
vakh (use Gerrit instead)
2017/03/20 18:36:00
This statement seems to be modifying verdict_entry
Jialiu Lin
2017/03/20 19:05:54
Changed to CHECK instead.
| |
| 105 it.key() /* cache_expression */, &verdict_entry)); | |
| 106 int verdict_received_time; | |
| 107 LoginReputationClientResponse verdict; | |
| 108 DCHECK(ParseVerdictEntry(verdict_entry, &verdict_received_time, &verdict)); | |
|
vakh (use Gerrit instead)
2017/03/20 18:36:00
Same here.
Jialiu Lin
2017/03/20 19:05:54
Done.
| |
| 109 // Since password protection content settings are keyed by origin, we only | |
| 110 // need to compare the path part of the cache_expression and the given url. | |
| 111 std::string cache_expression_path = | |
| 112 GetCacheExpressionPath(verdict.cache_expression()); | |
| 113 | |
| 114 if (verdict.cache_expression_exact_match()) { | |
| 115 if (PathMatchCacheExpressionExactly(paths, cache_expression_path)) { | |
| 116 return IsCacheExpired(verdict_received_time, | |
| 117 verdict.cache_duration_sec()) | |
| 118 ? LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED | |
| 119 : verdict.verdict_type(); | |
| 120 } | |
| 121 } else { | |
| 122 // If it doesn't require exact match, we need to fine the most specific | |
| 123 // match. | |
| 124 size_t path_depth = GetPathDepth(cache_expression_path); | |
| 125 if (path_depth > max_path_depth && | |
| 126 PathVariantsMatchCacheExpression(paths, cache_expression_path) && | |
| 127 !IsCacheExpired(verdict_received_time, | |
| 128 verdict.cache_duration_sec())) { | |
| 129 max_path_depth = path_depth; | |
| 130 most_matching_verdict = verdict.verdict_type(); | |
| 131 } | |
| 132 } | |
| 133 } | |
| 134 return most_matching_verdict; | |
| 135 } | |
| 136 | |
| 137 void PasswordProtectionService::CacheVerdict( | |
| 138 const GURL& url, | |
| 139 LoginReputationClientResponse* verdict, | |
| 140 const base::Time& receive_time, | |
| 141 HostContentSettingsMap* settings) { | |
| 142 DCHECK(verdict); | |
| 143 | |
| 144 GURL origin_url = url.GetOrigin(); | |
| 145 std::unique_ptr<base::DictionaryValue> verdict_dictionary = | |
| 146 base::DictionaryValue::From(settings->GetWebsiteSetting( | |
| 147 origin_url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | |
| 148 std::string(), nullptr)); | |
| 149 | |
| 150 if (!verdict_dictionary.get()) | |
| 151 verdict_dictionary = base::MakeUnique<base::DictionaryValue>(); | |
| 152 | |
| 153 std::unique_ptr<base::DictionaryValue> verdict_entry = | |
| 154 CreateDictionaryFromVerdict(verdict, receive_time); | |
| 155 // If same cache_expression is already in this verdict_dictionary, we simply | |
| 156 // override it. | |
| 157 verdict_dictionary->SetWithoutPathExpansion(verdict->cache_expression(), | |
| 158 std::move(verdict_entry)); | |
| 159 settings->SetWebsiteSettingDefaultScope( | |
| 160 origin_url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | |
| 161 std::string(), std::move(verdict_dictionary)); | |
| 162 } | |
| 163 | |
| 41 void PasswordProtectionService::OnMatchCsdWhiteListResult( | 164 void PasswordProtectionService::OnMatchCsdWhiteListResult( |
| 42 bool match_whitelist) { | 165 bool match_whitelist) { |
| 43 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 166 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 44 UMA_HISTOGRAM_BOOLEAN( | 167 UMA_HISTOGRAM_BOOLEAN( |
| 45 "PasswordManager.PasswordReuse.MainFrameMatchCsdWhitelist", | 168 "PasswordManager.PasswordReuse.MainFrameMatchCsdWhitelist", |
| 46 match_whitelist); | 169 match_whitelist); |
| 47 } | 170 } |
| 48 | 171 |
| 172 HostContentSettingsMap* | |
| 173 PasswordProtectionService::GetSettingMapForActiveProfile() { | |
| 174 return nullptr; | |
|
vakh (use Gerrit instead)
2017/03/20 18:36:00
Is this nullptr intentional?
If so, why have this
Jialiu Lin
2017/03/20 19:05:54
Yes, TODO is in the .h file. Added a corresponding
| |
| 175 } | |
| 176 | |
| 177 void PasswordProtectionService::OnURLsDeleted( | |
| 178 history::HistoryService* history_service, | |
| 179 bool all_history, | |
| 180 bool expired, | |
| 181 const history::URLRows& deleted_rows, | |
| 182 const std::set<GURL>& favicon_urls) { | |
| 183 HostContentSettingsMap* setting_map = GetSettingMapForActiveProfile(); | |
| 184 if (!setting_map) | |
| 185 return; | |
| 186 | |
| 187 BrowserThread::PostTask( | |
| 188 BrowserThread::UI, FROM_HERE, | |
| 189 base::Bind(&PasswordProtectionService::RemoveContentSettingsOnURLsDeleted, | |
| 190 GetWeakPtr(), all_history, deleted_rows, setting_map)); | |
| 191 } | |
| 192 | |
| 193 void PasswordProtectionService::RemoveContentSettingsOnURLsDeleted( | |
| 194 bool all_history, | |
| 195 const history::URLRows& deleted_rows, | |
| 196 HostContentSettingsMap* setting_map) { | |
| 197 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
| 198 if (all_history) { | |
| 199 setting_map->ClearSettingsForOneType( | |
| 200 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION); | |
| 201 return; | |
| 202 } | |
| 203 | |
| 204 // For now, if a URL is deleted from history, we simply remove all the | |
| 205 // cached verdicts of the same origin. This is a pretty aggressive deletion. | |
| 206 // We might revisit this logic later to decide if we want to only delete the | |
| 207 // cached verdict whose cache expression matches this URL. | |
| 208 for (const history::URLRow& row : deleted_rows) { | |
| 209 setting_map->ClearSettingsForOneTypeWithPredicate( | |
| 210 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | |
| 211 base::Bind(&OriginMatchPrimaryPattern, row.url().GetOrigin())); | |
| 212 } | |
| 213 } | |
| 214 | |
| 215 // static | |
| 216 bool PasswordProtectionService::ParseVerdictEntry( | |
| 217 base::DictionaryValue* verdict_entry, | |
|
vakh (use Gerrit instead)
2017/03/20 18:36:00
const verdict_entry
Jialiu Lin
2017/03/20 19:05:54
Acknowledged. for some weird reason, base::Dictio
| |
| 218 int* out_verdict_received_time, | |
| 219 LoginReputationClientResponse* out_verdict) { | |
| 220 base::Value* binary_value = nullptr; | |
| 221 bool result = verdict_entry && out_verdict && | |
| 222 verdict_entry->GetInteger(kCacheCreationTime, | |
| 223 out_verdict_received_time) && | |
| 224 verdict_entry->Get(kVerdictProto, &binary_value); | |
| 225 if (result) { | |
|
vakh (use Gerrit instead)
2017/03/20 18:36:00
nit:
if (!result) {
return false;
}
DCHECK()
.
Jialiu Lin
2017/03/20 19:05:54
Done.
| |
| 226 DCHECK_EQ(base::Value::Type::BINARY, binary_value->type()); | |
| 227 const auto blob = binary_value->GetBlob(); | |
| 228 const std::string serialized_verdict_proto(blob.begin(), blob.end()); | |
| 229 return out_verdict->ParseFromString(serialized_verdict_proto); | |
| 230 } | |
| 231 | |
| 232 return false; | |
| 233 } | |
| 234 | |
| 235 bool PasswordProtectionService::PathMatchCacheExpressionExactly( | |
| 236 const std::vector<std::string>& generated_paths, | |
| 237 const std::string& cache_expression_path) { | |
| 238 size_t cache_expression_path_depth = GetPathDepth(cache_expression_path); | |
| 239 if (generated_paths.size() <= cache_expression_path_depth) { | |
| 240 return false; | |
| 241 } | |
| 242 std::string canonical_path = generated_paths.back(); | |
| 243 size_t last_slash_pos = canonical_path.find_last_of("/"); | |
| 244 DCHECK_NE(std::string::npos, last_slash_pos); | |
| 245 return canonical_path.substr(0, last_slash_pos + 1) == cache_expression_path; | |
| 246 } | |
| 247 | |
| 248 bool PasswordProtectionService::PathVariantsMatchCacheExpression( | |
| 249 const std::vector<std::string>& generated_paths, | |
| 250 const std::string& cache_expression_path) { | |
| 251 for (const auto& path : generated_paths) { | |
| 252 if (cache_expression_path == path) { | |
| 253 return true; | |
| 254 } | |
| 255 } | |
| 256 return false; | |
| 257 } | |
| 258 | |
| 259 bool PasswordProtectionService::IsCacheExpired(int cache_creation_time, | |
| 260 int cache_duration) { | |
| 261 // TODO(jialiul): For now, we assume client's clock is accurate or almost | |
| 262 // accurate. Need some logic to handle cases where client's clock is way | |
| 263 // off. | |
| 264 return base::Time::Now().ToDoubleT() > | |
| 265 static_cast<double>(cache_creation_time + cache_duration); | |
| 266 } | |
| 267 | |
| 268 // Generate path variants of the given URL. | |
| 269 void PasswordProtectionService::GeneratePathVariantsWithoutQuery( | |
| 270 const GURL& url, | |
| 271 std::vector<std::string>* paths) { | |
| 272 std::string canonical_path; | |
| 273 V4ProtocolManagerUtil::CanonicalizeUrl(url, nullptr, &canonical_path, | |
| 274 nullptr); | |
| 275 V4ProtocolManagerUtil::GeneratePathVariantsToCheck(canonical_path, | |
| 276 std::string(), paths); | |
| 277 } | |
| 278 | |
| 279 // Return the path of the cache expression. e.g.: | |
| 280 // "www.google.com" -> "/" | |
| 281 // "www.google.com/abc" -> "/abc/" | |
| 282 // "foo.com/foo/bar/" -> "/foo/bar/" | |
| 283 std::string PasswordProtectionService::GetCacheExpressionPath( | |
| 284 const std::string& cache_expression) { | |
| 285 DCHECK(!cache_expression.empty()); | |
| 286 std::string out_put(cache_expression); | |
| 287 // Append a trailing slash if needed. | |
| 288 if (out_put[out_put.length() - 1] != '/') | |
| 289 out_put.append("/"); | |
| 290 | |
| 291 size_t first_slash_pos = out_put.find_first_of("/"); | |
| 292 DCHECK_NE(std::string::npos, first_slash_pos); | |
| 293 return out_put.substr(first_slash_pos); | |
| 294 } | |
| 295 | |
| 296 // Convert a LoginReputationClientResponse proto into a DictionaryValue. | |
| 297 std::unique_ptr<base::DictionaryValue> | |
| 298 PasswordProtectionService::CreateDictionaryFromVerdict( | |
| 299 const LoginReputationClientResponse* verdict, | |
| 300 const base::Time& receive_time) { | |
| 301 std::unique_ptr<base::DictionaryValue> result = | |
| 302 base::MakeUnique<base::DictionaryValue>(); | |
| 303 if (verdict) { | |
|
vakh (use Gerrit instead)
2017/03/20 18:36:00
should this verdict check be a DCHECK instead?
Thi
Jialiu Lin
2017/03/20 19:05:54
you're right. No need to check here. Done.
| |
| 304 result->SetInteger(kCacheCreationTime, | |
| 305 static_cast<int>(receive_time.ToDoubleT())); | |
| 306 // Because DictionaryValue cannot take none-UTF8 string, we need to store | |
|
vakh (use Gerrit instead)
2017/03/20 18:36:00
non
Jialiu Lin
2017/03/20 19:05:54
Done.
| |
| 307 // serialized proto in its allowed binary format instead. | |
| 308 const std::string serialized_proto(verdict->SerializeAsString()); | |
| 309 const std::vector<char> verdict_blob(serialized_proto.begin(), | |
| 310 serialized_proto.end()); | |
| 311 std::unique_ptr<base::Value> binary_value = | |
| 312 base::MakeUnique<base::Value>(verdict_blob); | |
| 313 DCHECK_EQ(base::Value::Type::BINARY, binary_value->type()); | |
| 314 result->Set(kVerdictProto, std::move(binary_value)); | |
| 315 } | |
| 316 return result; | |
| 317 } | |
| 318 | |
| 49 } // namespace safe_browsing | 319 } // namespace safe_browsing |
| OLD | NEW |