| 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 kCacheTTL[] = "cache_ttl"; |
| 26 static const char kCacheCreationTime[] = "cache_creation_time"; |
| 27 static const char kVerdictType[] = "verdict"; |
| 28 |
| 29 // Helper function to determine if the given origin matches content settings |
| 30 // map's patterns. |
| 31 bool OriginMatchPrimaryPattern( |
| 32 const GURL& origin, |
| 33 const ContentSettingsPattern& primary_pattern, |
| 34 const ContentSettingsPattern& secondary_pattern_unused) { |
| 35 return ContentSettingsPattern::FromURLNoWildcard(origin) == primary_pattern; |
| 36 } |
| 37 |
| 38 // Convert a LoginReputationClientResponse proto into a DictionaryValue. |
| 39 std::unique_ptr<base::DictionaryValue> CreateDictionaryFromVerdict( |
| 40 const LoginReputationClientResponse* verdict, |
| 41 const base::Time& receive_time) { |
| 42 std::unique_ptr<base::DictionaryValue> result = |
| 43 base::MakeUnique<base::DictionaryValue>(); |
| 44 if (verdict) { |
| 45 result->SetIntegerWithoutPathExpansion(kCacheTTL, |
| 46 verdict->cache_duration_sec()); |
| 47 result->SetIntegerWithoutPathExpansion( |
| 48 kCacheCreationTime, static_cast<int>(receive_time.ToDoubleT())); |
| 49 result->SetIntegerWithoutPathExpansion(kVerdictType, |
| 50 verdict->verdict_type()); |
| 51 } |
| 52 return result; |
| 53 } |
| 54 |
| 55 // Returns the number of path segments |cache_expression| has. |
| 56 // For example, return 0 for "www.123.com", since there is no path after the |
| 57 // origin; return 3 for www.123.com/abc/def/gh.html since there are three |
| 58 // path segments after its origin. |
| 59 size_t GetPathDepth(const base::StringPiece& cache_expression) { |
| 60 return base::SplitString(cache_expression, "/", base::KEEP_WHITESPACE, |
| 61 base::SPLIT_WANT_NONEMPTY) |
| 62 .size(); |
| 63 } |
| 64 |
| 65 } // namespace |
| 66 |
| 17 PasswordProtectionService::PasswordProtectionService( | 67 PasswordProtectionService::PasswordProtectionService( |
| 18 const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager) | 68 const scoped_refptr<SafeBrowsingDatabaseManager>& database_manager) |
| 19 : database_manager_(database_manager), weak_factory_(this) { | 69 : database_manager_(database_manager), weak_factory_(this) { |
| 20 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 70 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 21 } | 71 } |
| 22 | 72 |
| 23 PasswordProtectionService::~PasswordProtectionService() { | 73 PasswordProtectionService::~PasswordProtectionService() { |
| 24 weak_factory_.InvalidateWeakPtrs(); | 74 weak_factory_.InvalidateWeakPtrs(); |
| 25 } | 75 } |
| 26 | 76 |
| 27 void PasswordProtectionService::RecordPasswordReuse(const GURL& url) { | 77 void PasswordProtectionService::RecordPasswordReuse(const GURL& url) { |
| 28 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 78 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 29 DCHECK(database_manager_); | 79 DCHECK(database_manager_); |
| 30 if (!url.is_valid()) | 80 if (!url.is_valid()) |
| 31 return; | 81 return; |
| 32 | 82 |
| 33 BrowserThread::PostTaskAndReplyWithResult( | 83 BrowserThread::PostTaskAndReplyWithResult( |
| 34 BrowserThread::IO, FROM_HERE, | 84 BrowserThread::IO, FROM_HERE, |
| 35 base::Bind(&SafeBrowsingDatabaseManager::MatchCsdWhitelistUrl, | 85 base::Bind(&SafeBrowsingDatabaseManager::MatchCsdWhitelistUrl, |
| 36 database_manager_, url), | 86 database_manager_, url), |
| 37 base::Bind(&PasswordProtectionService::OnMatchCsdWhiteListResult, | 87 base::Bind(&PasswordProtectionService::OnMatchCsdWhiteListResult, |
| 38 GetWeakPtr())); | 88 GetWeakPtr())); |
| 39 } | 89 } |
| 40 | 90 |
| 91 LoginReputationClientResponse::VerdictType |
| 92 PasswordProtectionService::GetCachedVerdict( |
| 93 const HostContentSettingsMap* settings, |
| 94 const GURL& url) { |
| 95 // TODO(jialiul): Add UMA metrics to track if verdict is available, not |
| 96 // available, or expired. |
| 97 DCHECK(settings); |
| 98 if (!url.is_valid()) { |
| 99 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; |
| 100 } |
| 101 |
| 102 GURL origin_url = url.GetOrigin(); |
| 103 std::unique_ptr<base::DictionaryValue> verdict_dictionary = |
| 104 base::DictionaryValue::From(settings->GetWebsiteSetting( |
| 105 origin_url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, |
| 106 std::string(), nullptr)); |
| 107 // Return early if there is no verdict cached for this |origin_url|. |
| 108 if (!verdict_dictionary.get() || verdict_dictionary->empty()) { |
| 109 return LoginReputationClientResponse::VERDICT_TYPE_UNSPECIFIED; |
| 110 } |
| 111 |
| 112 // For all the verdicts of the same origin, we key them by cache_expression |
| 113 // field. And its corresponding value is a DictionaryValue contains its ttl, |
| 114 // creation time, and verdict type info. |
| 115 std::vector<std::string> hosts; |
| 116 std::vector<std::string> paths; |
| 117 V4ProtocolManagerUtil::GenerateHostAndPathVariants(url, &hosts, &paths); |
| 118 int cache_creation_time; |
| 119 int cache_duration; |
| 120 int verdict_type_value; |
| 121 base::DictionaryValue* verdict_entry = nullptr; |
| 122 int max_path_depth = 0; |
| 123 LoginReputationClientResponse::VerdictType most_matching_verdict; |
| 124 for (base::DictionaryValue::Iterator it(*verdict_dictionary.get()); |
| 125 !it.IsAtEnd(); it.Advance()) { |
| 126 base::StringPiece cache_expression_trimed( |
| 127 base::TrimString(it.key(), "/", base::TrimPositions::TRIM_TRAILING)); |
| 128 int path_depth = GetPathDepth(cache_expression_trimed); |
| 129 if (path_depth <= max_path_depth) |
| 130 continue; |
| 131 DCHECK(verdict_dictionary->GetDictionaryWithoutPathExpansion( |
| 132 it.key(), &verdict_entry)); |
| 133 DCHECK(ParseVerdictEntry(verdict_entry, &cache_creation_time, |
| 134 &cache_duration, &verdict_type_value)); |
| 135 |
| 136 if (HostPathVariantsMatchCacheExpression( |
| 137 hosts, paths, cache_expression_trimed /* cache_expression */) && |
| 138 !IsCacheExpired(cache_creation_time, cache_duration)) { |
| 139 max_path_depth = path_depth; |
| 140 most_matching_verdict = |
| 141 static_cast<LoginReputationClientResponse::VerdictType>( |
| 142 verdict_type_value); |
| 143 } |
| 144 } |
| 145 return most_matching_verdict; |
| 146 } |
| 147 |
| 148 void PasswordProtectionService::CacheVerdict( |
| 149 const GURL& url, |
| 150 LoginReputationClientResponse* verdict, |
| 151 const base::Time& receive_time, |
| 152 HostContentSettingsMap* settings) { |
| 153 DCHECK(verdict); |
| 154 |
| 155 GURL origin_url = url.GetOrigin(); |
| 156 std::unique_ptr<base::DictionaryValue> verdict_dictionary = |
| 157 base::DictionaryValue::From(settings->GetWebsiteSetting( |
| 158 origin_url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, |
| 159 std::string(), nullptr)); |
| 160 |
| 161 if (!verdict_dictionary.get()) |
| 162 verdict_dictionary = base::MakeUnique<base::DictionaryValue>(); |
| 163 |
| 164 std::unique_ptr<base::DictionaryValue> verdict_entry = |
| 165 CreateDictionaryFromVerdict(verdict, receive_time); |
| 166 // If same cache_expression is already in this verdict_dictionary, we simply |
| 167 // override it with a fresher verdict. |
| 168 verdict_dictionary->SetWithoutPathExpansion(verdict->cache_expression(), |
| 169 std::move(verdict_entry)); |
| 170 settings->SetWebsiteSettingDefaultScope( |
| 171 origin_url, GURL(), CONTENT_SETTINGS_TYPE_PASSWORD_PROTECTION, |
| 172 std::string(), std::move(verdict_dictionary)); |
| 173 } |
| 174 |
| 41 void PasswordProtectionService::OnMatchCsdWhiteListResult( | 175 void PasswordProtectionService::OnMatchCsdWhiteListResult( |
| 42 bool match_whitelist) { | 176 bool match_whitelist) { |
| 43 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 177 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 44 UMA_HISTOGRAM_BOOLEAN( | 178 UMA_HISTOGRAM_BOOLEAN( |
| 45 "PasswordManager.PasswordReuse.MainFrameMatchCsdWhitelist", | 179 "PasswordManager.PasswordReuse.MainFrameMatchCsdWhitelist", |
| 46 match_whitelist); | 180 match_whitelist); |
| 47 } | 181 } |
| 48 | 182 |
| 183 HostContentSettingsMap* |
| 184 PasswordProtectionService::GetSettingMapForActiveProfile() { |
| 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 const base::DictionaryValue* verdict_entry, |
| 229 int* out_cache_creation_time, |
| 230 int* out_cache_duration, |
| 231 int* out_verdict_type) { |
| 232 return verdict_entry && |
| 233 verdict_entry->GetInteger(kCacheCreationTime, |
| 234 out_cache_creation_time) && |
| 235 verdict_entry->GetInteger(kCacheTTL, out_cache_duration) && |
| 236 verdict_entry->GetInteger(kVerdictType, out_verdict_type); |
| 237 } |
| 238 |
| 239 bool PasswordProtectionService::HostPathVariantsMatchCacheExpression( |
| 240 const std::vector<std::string>& hosts, |
| 241 const std::vector<std::string>& paths, |
| 242 const base::StringPiece& cache_expression) { |
| 243 for (const auto& host : hosts) |
| 244 for (const auto& path : paths) { |
| 245 std::string host_path_combine(host); |
| 246 host_path_combine.append(path); |
| 247 if (cache_expression == |
| 248 base::TrimString(host_path_combine, "/", |
| 249 base::TrimPositions::TRIM_TRAILING)) { |
| 250 return true; |
| 251 } |
| 252 } |
| 253 return false; |
| 254 } |
| 255 |
| 256 bool PasswordProtectionService::IsCacheExpired(int cache_creation_time, |
| 257 int cache_duration) { |
| 258 // TODO(jialiul): For now, we assume client's clock is accurate or almost |
| 259 // accurate. Need some logic to handle cases where client's clock is way off. |
| 260 return base::Time::Now().ToDoubleT() > |
| 261 static_cast<double>(cache_creation_time + cache_duration); |
| 262 } |
| 263 |
| 49 } // namespace safe_browsing | 264 } // namespace safe_browsing |
| OLD | NEW |