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

Side by Side Diff: components/safe_browsing/password_protection/password_protection_service.cc

Issue 2747313002: PasswordProtectionService verdict cache management (Closed)
Patch Set: refine comment in csd.proto 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 unified diff | Download patch
OLDNEW
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698