| 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 <stddef.h> |
| 8 #include <string> |
| 9 |
| 7 #include "base/base64.h" | 10 #include "base/base64.h" |
| 8 #include "base/bind.h" | 11 #include "base/bind.h" |
| 9 #include "base/callback.h" | 12 #include "base/callback.h" |
| 10 #include "base/memory/ptr_util.h" | 13 #include "base/memory/ptr_util.h" |
| 11 #include "base/metrics/field_trial.h" | 14 #include "base/metrics/field_trial.h" |
| 12 #include "base/metrics/histogram_macros.h" | 15 #include "base/metrics/histogram_macros.h" |
| 13 #include "base/strings/string_number_conversions.h" | 16 #include "base/strings/string_number_conversions.h" |
| 14 #include "base/strings/string_split.h" | 17 #include "base/strings/string_split.h" |
| 15 #include "base/strings/string_util.h" | 18 #include "base/strings/string_util.h" |
| 16 #include "components/content_settings/core/browser/host_content_settings_map.h" | 19 #include "components/content_settings/core/browser/host_content_settings_map.h" |
| (...skipping 14 matching lines...) Expand all Loading... |
| 31 namespace safe_browsing { | 34 namespace safe_browsing { |
| 32 | 35 |
| 33 namespace { | 36 namespace { |
| 34 | 37 |
| 35 // Keys for storing password protection verdict into a DictionaryValue. | 38 // Keys for storing password protection verdict into a DictionaryValue. |
| 36 const char kCacheCreationTime[] = "cache_creation_time"; | 39 const char kCacheCreationTime[] = "cache_creation_time"; |
| 37 const char kVerdictProto[] = "verdict_proto"; | 40 const char kVerdictProto[] = "verdict_proto"; |
| 38 const int kRequestTimeoutMs = 10000; | 41 const int kRequestTimeoutMs = 10000; |
| 39 const char kPasswordProtectionRequestUrl[] = | 42 const char kPasswordProtectionRequestUrl[] = |
| 40 "https://sb-ssl.google.com/safebrowsing/clientreport/login"; | 43 "https://sb-ssl.google.com/safebrowsing/clientreport/login"; |
| 44 const char kPasswordOnFocusCacheKey[] = "password_on_focus_cache_key"; |
| 41 | 45 |
| 42 // Helper function to determine if the given origin matches content settings | 46 // Helper function to determine if the given origin matches content settings |
| 43 // map's patterns. | 47 // map's patterns. |
| 44 bool OriginMatchPrimaryPattern( | 48 bool OriginMatchPrimaryPattern( |
| 45 const GURL& origin, | 49 const GURL& origin, |
| 46 const ContentSettingsPattern& primary_pattern, | 50 const ContentSettingsPattern& primary_pattern, |
| 47 const ContentSettingsPattern& secondary_pattern_unused) { | 51 const ContentSettingsPattern& secondary_pattern_unused) { |
| 48 return ContentSettingsPattern::FromURLNoWildcard(origin) == primary_pattern; | 52 return ContentSettingsPattern::FromURLNoWildcard(origin) == primary_pattern; |
| 49 } | 53 } |
| 50 | 54 |
| (...skipping 29 matching lines...) Expand all Loading... |
| 80 const char kPasswordOnFocusRequestOutcomeHistogramName[] = | 84 const char kPasswordOnFocusRequestOutcomeHistogramName[] = |
| 81 "PasswordProtection.RequestOutcome.PasswordFieldOnFocus"; | 85 "PasswordProtection.RequestOutcome.PasswordFieldOnFocus"; |
| 82 const char kPasswordEntryRequestOutcomeHistogramName[] = | 86 const char kPasswordEntryRequestOutcomeHistogramName[] = |
| 83 "PasswordProtection.RequestOutcome.ProtectedPasswordEntry"; | 87 "PasswordProtection.RequestOutcome.ProtectedPasswordEntry"; |
| 84 | 88 |
| 85 PasswordProtectionService::PasswordProtectionService( | 89 PasswordProtectionService::PasswordProtectionService( |
| 86 const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager, | 90 const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager, |
| 87 scoped_refptr<net::URLRequestContextGetter> request_context_getter, | 91 scoped_refptr<net::URLRequestContextGetter> request_context_getter, |
| 88 HistoryService* history_service, | 92 HistoryService* history_service, |
| 89 HostContentSettingsMap* host_content_settings_map) | 93 HostContentSettingsMap* host_content_settings_map) |
| 90 : stored_verdict_count_(-1), | 94 : stored_verdict_count_password_on_focus_(-1), |
| 95 stored_verdict_count_password_entry_(-1), |
| 91 database_manager_(database_manager), | 96 database_manager_(database_manager), |
| 92 request_context_getter_(request_context_getter), | 97 request_context_getter_(request_context_getter), |
| 93 history_service_observer_(this), | 98 history_service_observer_(this), |
| 94 content_settings_(host_content_settings_map), | 99 content_settings_(host_content_settings_map), |
| 95 weak_factory_(this) { | 100 weak_factory_(this) { |
| 96 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 101 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 97 if (history_service) | 102 if (history_service) |
| 98 history_service_observer_.Add(history_service); | 103 history_service_observer_.Add(history_service); |
| 99 } | 104 } |
| 100 | 105 |
| (...skipping 14 matching lines...) Expand all Loading... |
| 115 | 120 |
| 116 bool PasswordProtectionService::CanGetReputationOfURL(const GURL& url) { | 121 bool PasswordProtectionService::CanGetReputationOfURL(const GURL& url) { |
| 117 if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) | 122 if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) |
| 118 return false; | 123 return false; |
| 119 | 124 |
| 120 const std::string& hostname = url.HostNoBrackets(); | 125 const std::string& hostname = url.HostNoBrackets(); |
| 121 return !net::IsLocalhost(hostname) && !net::IsHostnameNonUnique(hostname) && | 126 return !net::IsLocalhost(hostname) && !net::IsHostnameNonUnique(hostname) && |
| 122 hostname.find('.') != std::string::npos; | 127 hostname.find('.') != std::string::npos; |
| 123 } | 128 } |
| 124 | 129 |
| 130 // We cache both types of pings under the same content settings type ( |
| 131 // CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION). Since UNFAMILIAR_LOGING_PAGE |
| 132 // verdicts are only enabled on extended reporting users, we cache them one |
| 133 // layer lower in the content setting DictionaryValue than PASSWORD_REUSE_EVENT |
| 134 // verdicts. |
| 135 // In other words, to cache a UNFAMILIAR_LOGIN_PAGE verdict we needs two levels |
| 136 // of keys: (1) origin, (2) cache expression returned in verdict. |
| 137 // To cache a PASSWORD_REUSE_EVENT, three levels of keys are used: |
| 138 // (1) origin, (2) 2nd level key is always |kPasswordOnFocusCacheKey|, |
| 139 // (3) cache expression. |
| 125 LoginReputationClientResponse::VerdictType | 140 LoginReputationClientResponse::VerdictType |
| 126 PasswordProtectionService::GetCachedVerdict( | 141 PasswordProtectionService::GetCachedVerdict( |
| 127 const GURL& url, | 142 const GURL& url, |
| 143 TriggerType trigger_type, |
| 128 LoginReputationClientResponse* out_response) { | 144 LoginReputationClientResponse* out_response) { |
| 145 DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE || |
| 146 trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT); |
| 147 |
| 129 if (!url.is_valid()) | 148 if (!url.is_valid()) |
| 130 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; | 149 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; |
| 131 | 150 |
| 132 DCHECK(content_settings_); | |
| 133 | |
| 134 GURL hostname = GetHostNameWithHTTPScheme(url); | 151 GURL hostname = GetHostNameWithHTTPScheme(url); |
| 135 std::unique_ptr<base::DictionaryValue> verdict_dictionary = | 152 std::unique_ptr<base::DictionaryValue> cache_dictionary = |
| 136 base::DictionaryValue::From(content_settings_->GetWebsiteSetting( | 153 base::DictionaryValue::From(content_settings_->GetWebsiteSetting( |
| 137 hostname, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | 154 hostname, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, |
| 138 std::string(), nullptr)); | 155 std::string(), nullptr)); |
| 139 // Return early if there is no verdict cached for this origin. | 156 |
| 140 if (!verdict_dictionary.get() || verdict_dictionary->empty()) | 157 if (!cache_dictionary.get() || cache_dictionary->empty()) |
| 141 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; | 158 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; |
| 142 | 159 |
| 160 base::DictionaryValue* verdict_dictionary = nullptr; |
| 161 if (trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) { |
| 162 // All UNFAMILIAR_LOGIN_PAGE verdicts (a.k.a password on focus ping) |
| 163 // are cached under |kPasswordOnFocusCacheKey|. |
| 164 if (!cache_dictionary->GetDictionaryWithoutPathExpansion( |
| 165 base::StringPiece(kPasswordOnFocusCacheKey), &verdict_dictionary)) { |
| 166 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; |
| 167 } |
| 168 } else { |
| 169 verdict_dictionary = cache_dictionary.get(); |
| 170 } |
| 171 |
| 143 std::vector<std::string> paths; | 172 std::vector<std::string> paths; |
| 144 GeneratePathVariantsWithoutQuery(url, &paths); | 173 GeneratePathVariantsWithoutQuery(url, &paths); |
| 145 int max_path_depth = -1; | 174 int max_path_depth = -1; |
| 146 LoginReputationClientResponse::VerdictType most_matching_verdict = | 175 LoginReputationClientResponse::VerdictType most_matching_verdict = |
| 147 LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; | 176 LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; |
| 148 // For all the verdicts of the same origin, we key them by |cache_expression|. | 177 // For all the verdicts of the same origin, we key them by |cache_expression|. |
| 149 // Its corresponding value is a DictionaryValue contains its creation time and | 178 // Its corresponding value is a DictionaryValue contains its creation time and |
| 150 // the serialized verdict proto. | 179 // the serialized verdict proto. |
| 151 for (base::DictionaryValue::Iterator it(*verdict_dictionary.get()); | 180 for (base::DictionaryValue::Iterator it(*verdict_dictionary); !it.IsAtEnd(); |
| 152 !it.IsAtEnd(); it.Advance()) { | 181 it.Advance()) { |
| 182 if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT && |
| 183 it.key() == base::StringPiece(kPasswordOnFocusCacheKey)) { |
| 184 continue; |
| 185 } |
| 153 base::DictionaryValue* verdict_entry = nullptr; | 186 base::DictionaryValue* verdict_entry = nullptr; |
| 154 CHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion( | 187 CHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion( |
| 155 it.key() /* cache_expression */, &verdict_entry)); | 188 it.key() /* cache_expression */, &verdict_entry)); |
| 156 int verdict_received_time; | 189 int verdict_received_time; |
| 157 LoginReputationClientResponse verdict; | 190 LoginReputationClientResponse verdict; |
| 158 CHECK(ParseVerdictEntry(verdict_entry, &verdict_received_time, &verdict)); | 191 CHECK(ParseVerdictEntry(verdict_entry, &verdict_received_time, &verdict)); |
| 159 // Since password protection content settings are keyed by origin, we only | 192 // Since password protection content settings are keyed by origin, we only |
| 160 // need to compare the path part of the cache_expression and the given url. | 193 // need to compare the path part of the cache_expression and the given url. |
| 161 std::string cache_expression_path = | 194 std::string cache_expression_path = |
| 162 GetCacheExpressionPath(verdict.cache_expression()); | 195 GetCacheExpressionPath(verdict.cache_expression()); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 173 ? LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED | 206 ? LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED |
| 174 : verdict.verdict_type(); | 207 : verdict.verdict_type(); |
| 175 out_response->CopyFrom(verdict); | 208 out_response->CopyFrom(verdict); |
| 176 } | 209 } |
| 177 } | 210 } |
| 178 return most_matching_verdict; | 211 return most_matching_verdict; |
| 179 } | 212 } |
| 180 | 213 |
| 181 void PasswordProtectionService::CacheVerdict( | 214 void PasswordProtectionService::CacheVerdict( |
| 182 const GURL& url, | 215 const GURL& url, |
| 216 TriggerType trigger_type, |
| 183 LoginReputationClientResponse* verdict, | 217 LoginReputationClientResponse* verdict, |
| 184 const base::Time& receive_time) { | 218 const base::Time& receive_time) { |
| 185 DCHECK(verdict); | 219 DCHECK(verdict); |
| 186 DCHECK(content_settings_); | 220 DCHECK(content_settings_); |
| 221 DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE || |
| 222 trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT); |
| 187 | 223 |
| 188 GURL hostname = GetHostNameWithHTTPScheme(url); | 224 GURL hostname = GetHostNameWithHTTPScheme(url); |
| 189 std::unique_ptr<base::DictionaryValue> verdict_dictionary = | 225 int* stored_verdict_count = |
| 226 trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE |
| 227 ? &stored_verdict_count_password_on_focus_ |
| 228 : &stored_verdict_count_password_entry_; |
| 229 std::unique_ptr<base::DictionaryValue> cache_dictionary = |
| 190 base::DictionaryValue::From(content_settings_->GetWebsiteSetting( | 230 base::DictionaryValue::From(content_settings_->GetWebsiteSetting( |
| 191 hostname, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | 231 hostname, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, |
| 192 std::string(), nullptr)); | 232 std::string(), nullptr)); |
| 193 | 233 |
| 194 if (!verdict_dictionary.get()) | 234 if (!cache_dictionary || !cache_dictionary.get()) |
| 195 verdict_dictionary = base::MakeUnique<base::DictionaryValue>(); | 235 cache_dictionary = base::MakeUnique<base::DictionaryValue>(); |
| 196 | 236 |
| 197 std::unique_ptr<base::DictionaryValue> verdict_entry = | 237 std::unique_ptr<base::DictionaryValue> verdict_entry( |
| 198 CreateDictionaryFromVerdict(verdict, receive_time); | 238 CreateDictionaryFromVerdict(verdict, receive_time)); |
| 239 |
| 240 base::DictionaryValue* verdict_dictionary = nullptr; |
| 241 if (trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) { |
| 242 // All UNFAMILIAR_LOGIN_PAGE verdicts (a.k.a password on focus ping) |
| 243 // are cached under |kPasswordOnFocusCacheKey|. |
| 244 if (!cache_dictionary->GetDictionaryWithoutPathExpansion( |
| 245 base::StringPiece(kPasswordOnFocusCacheKey), &verdict_dictionary)) { |
| 246 verdict_dictionary = cache_dictionary->SetDictionaryWithoutPathExpansion( |
| 247 base::StringPiece(kPasswordOnFocusCacheKey), |
| 248 base::MakeUnique<base::DictionaryValue>()); |
| 249 } |
| 250 } else { |
| 251 verdict_dictionary = cache_dictionary.get(); |
| 252 } |
| 199 | 253 |
| 200 // Increases stored verdict count if we haven't seen this cache expression | 254 // Increases stored verdict count if we haven't seen this cache expression |
| 201 // before. | 255 // before. |
| 202 if (!verdict_dictionary->HasKey(verdict->cache_expression())) | 256 if (!verdict_dictionary->HasKey(verdict->cache_expression())) |
| 203 stored_verdict_count_ = GetStoredVerdictCount() + 1; | 257 *stored_verdict_count = GetStoredVerdictCount(trigger_type) + 1; |
| 258 |
| 204 // If same cache_expression is already in this verdict_dictionary, we simply | 259 // If same cache_expression is already in this verdict_dictionary, we simply |
| 205 // override it. | 260 // override it. |
| 206 verdict_dictionary->SetWithoutPathExpansion(verdict->cache_expression(), | 261 verdict_dictionary->SetWithoutPathExpansion(verdict->cache_expression(), |
| 207 std::move(verdict_entry)); | 262 std::move(verdict_entry)); |
| 208 content_settings_->SetWebsiteSettingDefaultScope( | 263 content_settings_->SetWebsiteSettingDefaultScope( |
| 209 hostname, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | 264 hostname, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, |
| 210 std::string(), std::move(verdict_dictionary)); | 265 std::string(), std::move(cache_dictionary)); |
| 211 } | 266 } |
| 212 | 267 |
| 213 void PasswordProtectionService::CleanUpExpiredVerdicts() { | 268 void PasswordProtectionService::CleanUpExpiredVerdicts() { |
| 214 DCHECK(content_settings_); | 269 DCHECK(content_settings_); |
| 215 if (GetStoredVerdictCount() <= 0) | 270 |
| 271 if (GetStoredVerdictCount( |
| 272 LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) <= 0 && |
| 273 GetStoredVerdictCount( |
| 274 LoginReputationClientRequest::PASSWORD_REUSE_EVENT) <= 0) |
| 216 return; | 275 return; |
| 217 | 276 |
| 218 ContentSettingsForOneType password_protection_settings; | 277 ContentSettingsForOneType password_protection_settings; |
| 219 content_settings_->GetSettingsForOneType( | 278 content_settings_->GetSettingsForOneType( |
| 220 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), | 279 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), |
| 221 &password_protection_settings); | 280 &password_protection_settings); |
| 222 | 281 |
| 223 for (const ContentSettingPatternSource& source : | 282 for (const ContentSettingPatternSource& source : |
| 224 password_protection_settings) { | 283 password_protection_settings) { |
| 225 GURL primary_pattern_url = GURL(source.primary_pattern.ToString()); | 284 GURL primary_pattern_url = GURL(source.primary_pattern.ToString()); |
| 226 // Find all verdicts associated with this origin. | 285 // Find all verdicts associated with this origin. |
| 227 std::unique_ptr<base::DictionaryValue> verdict_dictionary = | 286 std::unique_ptr<base::DictionaryValue> cache_dictionary = |
| 228 base::DictionaryValue::From(content_settings_->GetWebsiteSetting( | 287 base::DictionaryValue::From(content_settings_->GetWebsiteSetting( |
| 229 primary_pattern_url, GURL(), | 288 primary_pattern_url, GURL(), |
| 230 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), nullptr)); | 289 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), nullptr)); |
| 231 std::vector<std::string> expired_keys; | 290 bool has_expired_password_on_focus_entry = RemoveExpiredVerdicts( |
| 232 for (base::DictionaryValue::Iterator it(*verdict_dictionary.get()); | 291 LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, |
| 233 !it.IsAtEnd(); it.Advance()) { | 292 cache_dictionary.get()); |
| 234 base::DictionaryValue* verdict_entry = nullptr; | 293 bool has_expired_password_reuse_entry = RemoveExpiredVerdicts( |
| 235 CHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion( | 294 LoginReputationClientRequest::PASSWORD_REUSE_EVENT, |
| 236 it.key(), &verdict_entry)); | 295 cache_dictionary.get()); |
| 237 int verdict_received_time; | |
| 238 LoginReputationClientResponse verdict; | |
| 239 CHECK(ParseVerdictEntry(verdict_entry, &verdict_received_time, &verdict)); | |
| 240 | 296 |
| 241 if (IsCacheExpired(verdict_received_time, verdict.cache_duration_sec())) { | 297 if (cache_dictionary->size() == 0u) { |
| 242 // Since DictionaryValue::Iterator cannot be used to modify the | |
| 243 // dictionary, we record the keys of expired verdicts in |expired_keys| | |
| 244 // and remove them in the next for-loop. | |
| 245 expired_keys.push_back(it.key()); | |
| 246 } | |
| 247 } | |
| 248 | |
| 249 for (const std::string& key : expired_keys) { | |
| 250 verdict_dictionary->RemoveWithoutPathExpansion(key, nullptr); | |
| 251 stored_verdict_count_--; | |
| 252 } | |
| 253 | |
| 254 if (verdict_dictionary->size() == 0u) { | |
| 255 content_settings_->ClearSettingsForOneTypeWithPredicate( | 298 content_settings_->ClearSettingsForOneTypeWithPredicate( |
| 256 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, base::Time(), | 299 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, base::Time(), |
| 257 base::Bind(&OriginMatchPrimaryPattern, primary_pattern_url)); | 300 base::Bind(&OriginMatchPrimaryPattern, primary_pattern_url)); |
| 258 } else if (expired_keys.size() > 0u) { | 301 } else if (has_expired_password_on_focus_entry || |
| 302 has_expired_password_reuse_entry) { |
| 259 // Set the website setting of this origin with the updated | 303 // Set the website setting of this origin with the updated |
| 260 // |verdict_diectionary|. | 304 // |verdict_diectionary|. |
| 261 content_settings_->SetWebsiteSettingDefaultScope( | 305 content_settings_->SetWebsiteSettingDefaultScope( |
| 262 primary_pattern_url, GURL(), | 306 primary_pattern_url, GURL(), |
| 263 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), | 307 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), |
| 264 std::move(verdict_dictionary)); | 308 std::move(cache_dictionary)); |
| 265 } | 309 } |
| 266 } | 310 } |
| 267 } | 311 } |
| 268 | 312 |
| 269 void PasswordProtectionService::StartRequest( | 313 void PasswordProtectionService::StartRequest( |
| 270 WebContents* web_contents, | 314 WebContents* web_contents, |
| 271 const GURL& main_frame_url, | 315 const GURL& main_frame_url, |
| 272 const GURL& password_form_action, | 316 const GURL& password_form_action, |
| 273 const GURL& password_form_frame_url, | 317 const GURL& password_form_frame_url, |
| 274 const std::string& saved_domain, | 318 const std::string& saved_domain, |
| 275 LoginReputationClientRequest::TriggerType type, | 319 TriggerType trigger_type, |
| 276 bool password_field_exists) { | 320 bool password_field_exists) { |
| 277 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 321 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 278 scoped_refptr<PasswordProtectionRequest> request( | 322 scoped_refptr<PasswordProtectionRequest> request( |
| 279 new PasswordProtectionRequest( | 323 new PasswordProtectionRequest( |
| 280 web_contents, main_frame_url, password_form_action, | 324 web_contents, main_frame_url, password_form_action, |
| 281 password_form_frame_url, saved_domain, type, password_field_exists, | 325 password_form_frame_url, saved_domain, trigger_type, |
| 282 this, GetRequestTimeoutInMS())); | 326 password_field_exists, this, GetRequestTimeoutInMS())); |
| 327 |
| 283 DCHECK(request); | 328 DCHECK(request); |
| 284 request->Start(); | 329 request->Start(); |
| 285 requests_.insert(std::move(request)); | 330 requests_.insert(std::move(request)); |
| 286 } | 331 } |
| 287 | 332 |
| 288 void PasswordProtectionService::MaybeStartPasswordFieldOnFocusRequest( | 333 void PasswordProtectionService::MaybeStartPasswordFieldOnFocusRequest( |
| 289 WebContents* web_contents, | 334 WebContents* web_contents, |
| 290 const GURL& main_frame_url, | 335 const GURL& main_frame_url, |
| 291 const GURL& password_form_action, | 336 const GURL& password_form_action, |
| 292 const GURL& password_form_frame_url) { | 337 const GURL& password_form_frame_url) { |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 325 | 370 |
| 326 void PasswordProtectionService::RequestFinished( | 371 void PasswordProtectionService::RequestFinished( |
| 327 PasswordProtectionRequest* request, | 372 PasswordProtectionRequest* request, |
| 328 bool already_cached, | 373 bool already_cached, |
| 329 std::unique_ptr<LoginReputationClientResponse> response) { | 374 std::unique_ptr<LoginReputationClientResponse> response) { |
| 330 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 375 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 331 DCHECK(request); | 376 DCHECK(request); |
| 332 | 377 |
| 333 if (response) { | 378 if (response) { |
| 334 if (!already_cached) { | 379 if (!already_cached) { |
| 335 CacheVerdict(request->main_frame_url(), response.get(), | 380 CacheVerdict(request->main_frame_url(), request->trigger_type(), |
| 336 base::Time::Now()); | 381 response.get(), base::Time::Now()); |
| 337 } | 382 } |
| 338 | 383 |
| 339 if (request->request_type() == | 384 if (request->trigger_type() == |
| 340 LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE && | 385 LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE && |
| 341 response->verdict_type() == LoginReputationClientResponse::PHISHING && | 386 response->verdict_type() == LoginReputationClientResponse::PHISHING && |
| 342 base::FeatureList::IsEnabled(kPasswordProtectionInterstitial)) { | 387 base::FeatureList::IsEnabled(kPasswordProtectionInterstitial)) { |
| 343 ShowPhishingInterstitial(request->main_frame_url(), | 388 ShowPhishingInterstitial(request->main_frame_url(), |
| 344 response->verdict_token(), | 389 response->verdict_token(), |
| 345 request->web_contents()); | 390 request->web_contents()); |
| 346 } | 391 } |
| 347 } | 392 } |
| 348 | 393 |
| 349 // Finished processing this request. Remove it from pending list. | 394 // Finished processing this request. Remove it from pending list. |
| (...skipping 22 matching lines...) Expand all Loading... |
| 372 return database_manager_; | 417 return database_manager_; |
| 373 } | 418 } |
| 374 | 419 |
| 375 GURL PasswordProtectionService::GetPasswordProtectionRequestUrl() { | 420 GURL PasswordProtectionService::GetPasswordProtectionRequestUrl() { |
| 376 GURL url(kPasswordProtectionRequestUrl); | 421 GURL url(kPasswordProtectionRequestUrl); |
| 377 std::string api_key = google_apis::GetAPIKey(); | 422 std::string api_key = google_apis::GetAPIKey(); |
| 378 DCHECK(!api_key.empty()); | 423 DCHECK(!api_key.empty()); |
| 379 return url.Resolve("?key=" + net::EscapeQueryParamValue(api_key, true)); | 424 return url.Resolve("?key=" + net::EscapeQueryParamValue(api_key, true)); |
| 380 } | 425 } |
| 381 | 426 |
| 382 int PasswordProtectionService::GetStoredVerdictCount() { | 427 int PasswordProtectionService::GetStoredVerdictCount(TriggerType trigger_type) { |
| 383 DCHECK(content_settings_); | 428 DCHECK(content_settings_); |
| 429 DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE || |
| 430 trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT); |
| 431 int* stored_verdict_count = |
| 432 trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE |
| 433 ? &stored_verdict_count_password_on_focus_ |
| 434 : &stored_verdict_count_password_entry_; |
| 384 // If we have already computed this, return its value. | 435 // If we have already computed this, return its value. |
| 385 if (stored_verdict_count_ >= 0) | 436 if (*stored_verdict_count >= 0) |
| 386 return stored_verdict_count_; | 437 return *stored_verdict_count; |
| 387 | 438 |
| 388 ContentSettingsForOneType password_protection_settings; | 439 ContentSettingsForOneType password_protection_settings; |
| 389 content_settings_->GetSettingsForOneType( | 440 content_settings_->GetSettingsForOneType( |
| 390 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), | 441 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), |
| 391 &password_protection_settings); | 442 &password_protection_settings); |
| 392 stored_verdict_count_ = 0; | 443 stored_verdict_count_password_on_focus_ = 0; |
| 444 stored_verdict_count_password_entry_ = 0; |
| 393 if (password_protection_settings.empty()) | 445 if (password_protection_settings.empty()) |
| 394 return 0; | 446 return 0; |
| 395 | 447 |
| 396 for (const ContentSettingPatternSource& source : | 448 for (const ContentSettingPatternSource& source : |
| 397 password_protection_settings) { | 449 password_protection_settings) { |
| 398 std::unique_ptr<base::DictionaryValue> verdict_dictionary = | 450 std::unique_ptr<base::DictionaryValue> cache_dictionary = |
| 399 base::DictionaryValue::From(content_settings_->GetWebsiteSetting( | 451 base::DictionaryValue::From(content_settings_->GetWebsiteSetting( |
| 400 GURL(source.primary_pattern.ToString()), GURL(), | 452 GURL(source.primary_pattern.ToString()), GURL(), |
| 401 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), nullptr)); | 453 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), nullptr)); |
| 402 if (verdict_dictionary.get() && !verdict_dictionary->empty()) | 454 if (cache_dictionary.get() && !cache_dictionary->empty()) { |
| 403 stored_verdict_count_ += static_cast<int>(verdict_dictionary->size()); | 455 stored_verdict_count_password_entry_ += |
| 456 static_cast<int>(cache_dictionary->size()); |
| 457 base::DictionaryValue* password_on_focus_dict = nullptr; |
| 458 if (cache_dictionary->GetDictionaryWithoutPathExpansion( |
| 459 base::StringPiece(kPasswordOnFocusCacheKey), |
| 460 &password_on_focus_dict)) { |
| 461 // Substracts 1 from password_entry count if |kPasswordOnFocusCacheKey| |
| 462 // presents. |
| 463 stored_verdict_count_password_entry_ -= 1; |
| 464 stored_verdict_count_password_on_focus_ += |
| 465 static_cast<int>(password_on_focus_dict->size()); |
| 466 } |
| 467 } |
| 404 } | 468 } |
| 405 return stored_verdict_count_; | 469 return *stored_verdict_count; |
| 406 } | 470 } |
| 407 | 471 |
| 408 int PasswordProtectionService::GetRequestTimeoutInMS() { | 472 int PasswordProtectionService::GetRequestTimeoutInMS() { |
| 409 return kRequestTimeoutMs; | 473 return kRequestTimeoutMs; |
| 410 } | 474 } |
| 411 | 475 |
| 412 void PasswordProtectionService::FillUserPopulation( | 476 void PasswordProtectionService::FillUserPopulation( |
| 413 const LoginReputationClientRequest::TriggerType& request_type, | 477 TriggerType trigger_type, |
| 414 LoginReputationClientRequest* request_proto) { | 478 LoginReputationClientRequest* request_proto) { |
| 415 ChromeUserPopulation* user_population = request_proto->mutable_population(); | 479 ChromeUserPopulation* user_population = request_proto->mutable_population(); |
| 416 user_population->set_user_population( | 480 user_population->set_user_population( |
| 417 IsExtendedReporting() ? ChromeUserPopulation::EXTENDED_REPORTING | 481 IsExtendedReporting() ? ChromeUserPopulation::EXTENDED_REPORTING |
| 418 : ChromeUserPopulation::SAFE_BROWSING); | 482 : ChromeUserPopulation::SAFE_BROWSING); |
| 419 user_population->set_is_history_sync_enabled(IsHistorySyncEnabled()); | 483 user_population->set_is_history_sync_enabled(IsHistorySyncEnabled()); |
| 420 | 484 |
| 421 base::FieldTrial* low_reputation_field_trial = | 485 base::FieldTrial* low_reputation_field_trial = |
| 422 base::FeatureList::GetFieldTrial(kPasswordFieldOnFocusPinging); | 486 base::FeatureList::GetFieldTrial(kPasswordFieldOnFocusPinging); |
| 423 if (low_reputation_field_trial) { | 487 if (low_reputation_field_trial) { |
| 424 user_population->add_finch_active_groups( | 488 user_population->add_finch_active_groups( |
| 425 low_reputation_field_trial->trial_name() + "|" + | 489 low_reputation_field_trial->trial_name() + "|" + |
| 426 low_reputation_field_trial->group_name()); | 490 low_reputation_field_trial->group_name()); |
| 427 } | 491 } |
| 428 base::FieldTrial* password_entry_field_trial = | 492 base::FieldTrial* password_entry_field_trial = |
| 429 base::FeatureList::GetFieldTrial(kProtectedPasswordEntryPinging); | 493 base::FeatureList::GetFieldTrial(kProtectedPasswordEntryPinging); |
| 430 if (password_entry_field_trial) { | 494 if (password_entry_field_trial) { |
| 431 user_population->add_finch_active_groups( | 495 user_population->add_finch_active_groups( |
| 432 low_reputation_field_trial->trial_name() + "|" + | 496 low_reputation_field_trial->trial_name() + "|" + |
| 433 low_reputation_field_trial->group_name()); | 497 low_reputation_field_trial->group_name()); |
| 434 } | 498 } |
| 435 } | 499 } |
| 436 | 500 |
| 437 void PasswordProtectionService::OnURLsDeleted( | 501 void PasswordProtectionService::OnURLsDeleted( |
| 438 history::HistoryService* history_service, | 502 history::HistoryService* history_service, |
| 439 bool all_history, | 503 bool all_history, |
| 440 bool expired, | 504 bool expired, |
| 441 const history::URLRows& deleted_rows, | 505 const history::URLRows& deleted_rows, |
| 442 const std::set<GURL>& favicon_urls) { | 506 const std::set<GURL>& favicon_urls) { |
| 443 if (stored_verdict_count_ <= 0) | 507 if (stored_verdict_count_password_on_focus_ <= 0 && |
| 508 stored_verdict_count_password_entry_ <= 0) { |
| 444 return; | 509 return; |
| 510 } |
| 445 | 511 |
| 446 BrowserThread::PostTask( | 512 BrowserThread::PostTask( |
| 447 BrowserThread::UI, FROM_HERE, | 513 BrowserThread::UI, FROM_HERE, |
| 448 base::Bind(&PasswordProtectionService::RemoveContentSettingsOnURLsDeleted, | 514 base::Bind(&PasswordProtectionService::RemoveContentSettingsOnURLsDeleted, |
| 449 GetWeakPtr(), all_history, deleted_rows)); | 515 GetWeakPtr(), all_history, deleted_rows)); |
| 450 } | 516 } |
| 451 | 517 |
| 452 void PasswordProtectionService::HistoryServiceBeingDeleted( | 518 void PasswordProtectionService::HistoryServiceBeingDeleted( |
| 453 history::HistoryService* history_service) { | 519 history::HistoryService* history_service) { |
| 454 history_service_observer_.RemoveAll(); | 520 history_service_observer_.RemoveAll(); |
| 455 } | 521 } |
| 456 | 522 |
| 457 void PasswordProtectionService::RemoveContentSettingsOnURLsDeleted( | 523 void PasswordProtectionService::RemoveContentSettingsOnURLsDeleted( |
| 458 bool all_history, | 524 bool all_history, |
| 459 const history::URLRows& deleted_rows) { | 525 const history::URLRows& deleted_rows) { |
| 460 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 526 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 461 DCHECK(content_settings_); | 527 DCHECK(content_settings_); |
| 462 | 528 |
| 463 if (all_history) { | 529 if (all_history) { |
| 464 content_settings_->ClearSettingsForOneType( | 530 content_settings_->ClearSettingsForOneType( |
| 465 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION); | 531 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION); |
| 466 stored_verdict_count_ = 0; | 532 stored_verdict_count_password_on_focus_ = 0; |
| 533 stored_verdict_count_password_entry_ = 0; |
| 467 return; | 534 return; |
| 468 } | 535 } |
| 469 | 536 |
| 470 // For now, if a URL is deleted from history, we simply remove all the | 537 // For now, if a URL is deleted from history, we simply remove all the |
| 471 // cached verdicts of the same origin. This is a pretty aggressive deletion. | 538 // cached verdicts of the same origin. This is a pretty aggressive deletion. |
| 472 // We might revisit this logic later to decide if we want to only delete the | 539 // We might revisit this logic later to decide if we want to only delete the |
| 473 // cached verdict whose cache expression matches this URL. | 540 // cached verdict whose cache expression matches this URL. |
| 474 for (const history::URLRow& row : deleted_rows) { | 541 for (const history::URLRow& row : deleted_rows) { |
| 475 if (!row.url().SchemeIsHTTPOrHTTPS()) | 542 if (!row.url().SchemeIsHTTPOrHTTPS()) |
| 476 continue; | 543 continue; |
| 544 |
| 477 GURL url_key = GetHostNameWithHTTPScheme(row.url()); | 545 GURL url_key = GetHostNameWithHTTPScheme(row.url()); |
| 478 std::unique_ptr<base::DictionaryValue> verdict_dictionary = | 546 stored_verdict_count_password_on_focus_ = |
| 479 base::DictionaryValue::From(content_settings_->GetWebsiteSetting( | 547 GetStoredVerdictCount( |
| 480 url_key, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, | 548 LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) - |
| 481 std::string(), nullptr)); | 549 GetVerdictCountForURL( |
| 482 | 550 url_key, LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE); |
| 483 // Move on if we have no cached verdict for this deleted history row. | 551 stored_verdict_count_password_entry_ = |
| 484 if (!verdict_dictionary.get() || verdict_dictionary->empty()) | 552 GetStoredVerdictCount( |
| 485 continue; | 553 LoginReputationClientRequest::PASSWORD_REUSE_EVENT) - |
| 486 | 554 GetVerdictCountForURL( |
| 487 int verdict_count = static_cast<int>(verdict_dictionary->size()); | 555 url_key, LoginReputationClientRequest::PASSWORD_REUSE_EVENT); |
| 488 stored_verdict_count_ = GetStoredVerdictCount() - verdict_count; | |
| 489 content_settings_->ClearSettingsForOneTypeWithPredicate( | 556 content_settings_->ClearSettingsForOneTypeWithPredicate( |
| 490 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, base::Time(), | 557 CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, base::Time(), |
| 491 base::Bind(&OriginMatchPrimaryPattern, url_key)); | 558 base::Bind(&OriginMatchPrimaryPattern, url_key)); |
| 492 } | 559 } |
| 493 } | 560 } |
| 494 | 561 |
| 562 int PasswordProtectionService::GetVerdictCountForURL(const GURL& url, |
| 563 TriggerType trigger_type) { |
| 564 DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE || |
| 565 trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT); |
| 566 std::unique_ptr<base::DictionaryValue> cache_dictionary = |
| 567 base::DictionaryValue::From(content_settings_->GetWebsiteSetting( |
| 568 url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, std::string(), |
| 569 nullptr)); |
| 570 if (!cache_dictionary.get() || cache_dictionary->empty()) |
| 571 return 0; |
| 572 |
| 573 if (trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE) { |
| 574 base::DictionaryValue* password_on_focus_dict = nullptr; |
| 575 return cache_dictionary->GetDictionaryWithoutPathExpansion( |
| 576 base::StringPiece(kPasswordOnFocusCacheKey), |
| 577 &password_on_focus_dict) |
| 578 ? password_on_focus_dict->size() |
| 579 : 0; |
| 580 } else { |
| 581 return cache_dictionary->HasKey(base::StringPiece(kPasswordOnFocusCacheKey)) |
| 582 ? cache_dictionary->size() - 1 |
| 583 : cache_dictionary->size(); |
| 584 } |
| 585 } |
| 586 |
| 587 bool PasswordProtectionService::RemoveExpiredVerdicts( |
| 588 TriggerType trigger_type, |
| 589 base::DictionaryValue* cache_dictionary) { |
| 590 DCHECK(trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE || |
| 591 trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT); |
| 592 base::DictionaryValue* verdict_dictionary = nullptr; |
| 593 if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT) { |
| 594 verdict_dictionary = cache_dictionary; |
| 595 } else { |
| 596 if (!cache_dictionary->GetDictionaryWithoutPathExpansion( |
| 597 base::StringPiece(kPasswordOnFocusCacheKey), &verdict_dictionary)) { |
| 598 return false; |
| 599 } |
| 600 } |
| 601 |
| 602 if (!verdict_dictionary || verdict_dictionary->empty()) |
| 603 return false; |
| 604 |
| 605 std::vector<std::string> expired_keys; |
| 606 for (base::DictionaryValue::Iterator it(*verdict_dictionary); !it.IsAtEnd(); |
| 607 it.Advance()) { |
| 608 if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT && |
| 609 it.key() == std::string(kPasswordOnFocusCacheKey)) |
| 610 continue; |
| 611 |
| 612 base::DictionaryValue* verdict_entry = nullptr; |
| 613 CHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion( |
| 614 it.key(), &verdict_entry)); |
| 615 int verdict_received_time; |
| 616 LoginReputationClientResponse verdict; |
| 617 CHECK(ParseVerdictEntry(verdict_entry, &verdict_received_time, &verdict)); |
| 618 |
| 619 if (IsCacheExpired(verdict_received_time, verdict.cache_duration_sec())) { |
| 620 // Since DictionaryValue::Iterator cannot be used to modify the |
| 621 // dictionary, we record the keys of expired verdicts in |expired_keys| |
| 622 // and remove them in the next for-loop. |
| 623 expired_keys.push_back(it.key()); |
| 624 } |
| 625 } |
| 626 |
| 627 for (const std::string& key : expired_keys) { |
| 628 verdict_dictionary->RemoveWithoutPathExpansion(key, nullptr); |
| 629 if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT) |
| 630 stored_verdict_count_password_entry_--; |
| 631 else |
| 632 stored_verdict_count_password_on_focus_--; |
| 633 } |
| 634 |
| 635 if (trigger_type == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE && |
| 636 verdict_dictionary->size() == 0U) { |
| 637 cache_dictionary->RemoveWithoutPathExpansion( |
| 638 base::StringPiece(kPasswordOnFocusCacheKey), nullptr); |
| 639 } |
| 640 |
| 641 return expired_keys.size() > 0U; |
| 642 } |
| 643 |
| 495 // static | 644 // static |
| 496 bool PasswordProtectionService::ParseVerdictEntry( | 645 bool PasswordProtectionService::ParseVerdictEntry( |
| 497 base::DictionaryValue* verdict_entry, | 646 base::DictionaryValue* verdict_entry, |
| 498 int* out_verdict_received_time, | 647 int* out_verdict_received_time, |
| 499 LoginReputationClientResponse* out_verdict) { | 648 LoginReputationClientResponse* out_verdict) { |
| 500 std::string serialized_verdict_proto; | 649 std::string serialized_verdict_proto; |
| 501 return verdict_entry && out_verdict && | 650 return verdict_entry && out_verdict && |
| 502 verdict_entry->GetInteger(kCacheCreationTime, | 651 verdict_entry->GetInteger(kCacheCreationTime, |
| 503 out_verdict_received_time) && | 652 out_verdict_received_time) && |
| 504 verdict_entry->GetString(kVerdictProto, &serialized_verdict_proto) && | 653 verdict_entry->GetString(kVerdictProto, &serialized_verdict_proto) && |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 585 if (is_password_entry_ping) { | 734 if (is_password_entry_ping) { |
| 586 UMA_HISTOGRAM_ENUMERATION(kPasswordEntryRequestOutcomeHistogramName, reason, | 735 UMA_HISTOGRAM_ENUMERATION(kPasswordEntryRequestOutcomeHistogramName, reason, |
| 587 MAX_OUTCOME); | 736 MAX_OUTCOME); |
| 588 } else { | 737 } else { |
| 589 UMA_HISTOGRAM_ENUMERATION(kPasswordOnFocusRequestOutcomeHistogramName, | 738 UMA_HISTOGRAM_ENUMERATION(kPasswordOnFocusRequestOutcomeHistogramName, |
| 590 reason, MAX_OUTCOME); | 739 reason, MAX_OUTCOME); |
| 591 } | 740 } |
| 592 } | 741 } |
| 593 | 742 |
| 594 } // namespace safe_browsing | 743 } // namespace safe_browsing |
| OLD | NEW |