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

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

Powered by Google App Engine
This is Rietveld 408576698