Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / password_manager / login_database.cc
blobd55ec2d094e67cf44a849f2e86928bd9ef244560
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/password_manager/login_database.h"
7 #include <algorithm>
8 #include <limits>
10 #include "base/files/file_path.h"
11 #include "base/logging.h"
12 #include "base/metrics/histogram.h"
13 #include "base/pickle.h"
14 #include "base/strings/string_util.h"
15 #include "base/time/time.h"
16 #include "components/autofill/core/common/password_form.h"
17 #include "sql/connection.h"
18 #include "sql/statement.h"
19 #include "sql/transaction.h"
21 using autofill::PasswordForm;
23 static const int kCurrentVersionNumber = 4;
24 static const int kCompatibleVersionNumber = 1;
26 namespace {
28 // Convenience enum for interacting with SQL queries that use all the columns.
29 enum LoginTableColumns {
30 COLUMN_ORIGIN_URL = 0,
31 COLUMN_ACTION_URL,
32 COLUMN_USERNAME_ELEMENT,
33 COLUMN_USERNAME_VALUE,
34 COLUMN_PASSWORD_ELEMENT,
35 COLUMN_PASSWORD_VALUE,
36 COLUMN_SUBMIT_ELEMENT,
37 COLUMN_SIGNON_REALM,
38 COLUMN_SSL_VALID,
39 COLUMN_PREFERRED,
40 COLUMN_DATE_CREATED,
41 COLUMN_BLACKLISTED_BY_USER,
42 COLUMN_SCHEME,
43 COLUMN_PASSWORD_TYPE,
44 COLUMN_POSSIBLE_USERNAMES,
45 COLUMN_TIMES_USED,
46 COLUMN_FORM_DATA
49 } // namespace
51 LoginDatabase::LoginDatabase() {
54 LoginDatabase::~LoginDatabase() {
57 bool LoginDatabase::Init(const base::FilePath& db_path) {
58 // Set pragmas for a small, private database (based on WebDatabase).
59 db_.set_page_size(2048);
60 db_.set_cache_size(32);
61 db_.set_exclusive_locking();
62 db_.set_restrict_to_user();
64 if (!db_.Open(db_path)) {
65 LOG(WARNING) << "Unable to open the password store database.";
66 return false;
69 sql::Transaction transaction(&db_);
70 transaction.Begin();
72 // Check the database version.
73 if (!meta_table_.Init(&db_, kCurrentVersionNumber,
74 kCompatibleVersionNumber)) {
75 db_.Close();
76 return false;
78 if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
79 LOG(WARNING) << "Password store database is too new.";
80 db_.Close();
81 return false;
84 // Initialize the tables.
85 if (!InitLoginsTable()) {
86 LOG(WARNING) << "Unable to initialize the password store database.";
87 db_.Close();
88 return false;
91 // Save the path for DeleteDatabaseFile().
92 db_path_ = db_path;
94 // If the file on disk is an older database version, bring it up to date.
95 if (!MigrateOldVersionsAsNeeded()) {
96 LOG(WARNING) << "Unable to migrate database";
97 db_.Close();
98 return false;
101 if (!transaction.Commit()) {
102 db_.Close();
103 return false;
106 return true;
109 bool LoginDatabase::MigrateOldVersionsAsNeeded() {
110 switch (meta_table_.GetVersionNumber()) {
111 case 1:
112 if (!db_.Execute("ALTER TABLE logins "
113 "ADD COLUMN password_type INTEGER") ||
114 !db_.Execute("ALTER TABLE logins "
115 "ADD COLUMN possible_usernames BLOB")) {
116 return false;
118 meta_table_.SetVersionNumber(2);
119 // Fall through.
120 case 2:
121 if (!db_.Execute("ALTER TABLE logins ADD COLUMN times_used INTEGER")) {
122 return false;
124 meta_table_.SetVersionNumber(3);
125 // Fall through.
126 case 3:
127 // We need to check if the column exists because of
128 // https://crbug.com/295851
129 if (!db_.DoesColumnExist("logins", "form_data") &&
130 !db_.Execute("ALTER TABLE logins ADD COLUMN form_data BLOB")) {
131 return false;
133 meta_table_.SetVersionNumber(4);
134 // Fall through.
135 case kCurrentVersionNumber:
136 // Already up to date
137 return true;
138 default:
139 NOTREACHED();
140 return false;
144 bool LoginDatabase::InitLoginsTable() {
145 if (!db_.DoesTableExist("logins")) {
146 if (!db_.Execute("CREATE TABLE logins ("
147 "origin_url VARCHAR NOT NULL, "
148 "action_url VARCHAR, "
149 "username_element VARCHAR, "
150 "username_value VARCHAR, "
151 "password_element VARCHAR, "
152 "password_value BLOB, "
153 "submit_element VARCHAR, "
154 "signon_realm VARCHAR NOT NULL,"
155 "ssl_valid INTEGER NOT NULL,"
156 "preferred INTEGER NOT NULL,"
157 "date_created INTEGER NOT NULL,"
158 "blacklisted_by_user INTEGER NOT NULL,"
159 "scheme INTEGER NOT NULL,"
160 "password_type INTEGER,"
161 "possible_usernames BLOB,"
162 "times_used INTEGER,"
163 "form_data BLOB,"
164 "UNIQUE "
165 "(origin_url, username_element, "
166 "username_value, password_element, "
167 "submit_element, signon_realm))")) {
168 NOTREACHED();
169 return false;
171 if (!db_.Execute("CREATE INDEX logins_signon ON "
172 "logins (signon_realm)")) {
173 NOTREACHED();
174 return false;
177 return true;
180 void LoginDatabase::ReportMetrics() {
181 sql::Statement s(db_.GetCachedStatement(
182 SQL_FROM_HERE,
183 "SELECT signon_realm, blacklisted_by_user, COUNT(username_value) "
184 "FROM logins GROUP BY signon_realm, blacklisted_by_user"));
186 if (!s.is_valid())
187 return;
189 int total_accounts = 0;
190 int blacklisted_sites = 0;
191 while (s.Step()) {
192 int blacklisted = s.ColumnInt(1);
193 int accounts_per_site = s.ColumnInt(2);
194 if (blacklisted) {
195 ++blacklisted_sites;
196 } else {
197 total_accounts += accounts_per_site;
198 UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.AccountsPerSite",
199 accounts_per_site, 0, 32, 6);
202 UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.TotalAccounts",
203 total_accounts, 0, 32, 6);
204 UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.BlacklistedSites",
205 blacklisted_sites, 0, 32, 6);
207 sql::Statement usage_statement(db_.GetCachedStatement(
208 SQL_FROM_HERE,
209 "SELECT password_type, times_used FROM logins"));
211 if (!usage_statement.is_valid())
212 return;
214 while (usage_statement.Step()) {
215 PasswordForm::Type type = static_cast<PasswordForm::Type>(
216 usage_statement.ColumnInt(0));
218 if (type == PasswordForm::TYPE_GENERATED) {
219 UMA_HISTOGRAM_CUSTOM_COUNTS(
220 "PasswordManager.TimesGeneratedPasswordUsed",
221 usage_statement.ColumnInt(1), 0, 100, 10);
222 } else {
223 UMA_HISTOGRAM_CUSTOM_COUNTS(
224 "PasswordManager.TimesPasswordUsed",
225 usage_statement.ColumnInt(1), 0, 100, 10);
230 bool LoginDatabase::AddLogin(const PasswordForm& form) {
231 std::string encrypted_password;
232 if (EncryptedString(form.password_value, &encrypted_password) !=
233 ENCRYPTION_RESULT_SUCCESS)
234 return false;
236 // You *must* change LoginTableColumns if this query changes.
237 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
238 "INSERT OR REPLACE INTO logins "
239 "(origin_url, action_url, username_element, username_value, "
240 " password_element, password_value, submit_element, "
241 " signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
242 " scheme, password_type, possible_usernames, times_used, form_data) "
243 "VALUES "
244 "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
245 s.BindString(COLUMN_ORIGIN_URL, form.origin.spec());
246 s.BindString(COLUMN_ACTION_URL, form.action.spec());
247 s.BindString16(COLUMN_USERNAME_ELEMENT, form.username_element);
248 s.BindString16(COLUMN_USERNAME_VALUE, form.username_value);
249 s.BindString16(COLUMN_PASSWORD_ELEMENT, form.password_element);
250 s.BindBlob(COLUMN_PASSWORD_VALUE, encrypted_password.data(),
251 static_cast<int>(encrypted_password.length()));
252 s.BindString16(COLUMN_SUBMIT_ELEMENT, form.submit_element);
253 s.BindString(COLUMN_SIGNON_REALM, form.signon_realm);
254 s.BindInt(COLUMN_SSL_VALID, form.ssl_valid);
255 s.BindInt(COLUMN_PREFERRED, form.preferred);
256 s.BindInt64(COLUMN_DATE_CREATED, form.date_created.ToTimeT());
257 s.BindInt(COLUMN_BLACKLISTED_BY_USER, form.blacklisted_by_user);
258 s.BindInt(COLUMN_SCHEME, form.scheme);
259 s.BindInt(COLUMN_PASSWORD_TYPE, form.type);
260 Pickle usernames_pickle = SerializeVector(form.other_possible_usernames);
261 s.BindBlob(COLUMN_POSSIBLE_USERNAMES,
262 usernames_pickle.data(),
263 usernames_pickle.size());
264 s.BindInt(COLUMN_TIMES_USED, form.times_used);
265 Pickle form_data_pickle;
266 autofill::SerializeFormData(form.form_data, &form_data_pickle);
267 s.BindBlob(COLUMN_FORM_DATA,
268 form_data_pickle.data(),
269 form_data_pickle.size());
271 return s.Run();
274 bool LoginDatabase::UpdateLogin(const PasswordForm& form, int* items_changed) {
275 std::string encrypted_password;
276 if (EncryptedString(form.password_value, &encrypted_password) !=
277 ENCRYPTION_RESULT_SUCCESS)
278 return false;
280 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
281 "UPDATE logins SET "
282 "action_url = ?, "
283 "password_value = ?, "
284 "ssl_valid = ?, "
285 "preferred = ?, "
286 "possible_usernames = ?, "
287 "times_used = ? "
288 "WHERE origin_url = ? AND "
289 "username_element = ? AND "
290 "username_value = ? AND "
291 "password_element = ? AND "
292 "signon_realm = ?"));
293 s.BindString(0, form.action.spec());
294 s.BindBlob(1, encrypted_password.data(),
295 static_cast<int>(encrypted_password.length()));
296 s.BindInt(2, form.ssl_valid);
297 s.BindInt(3, form.preferred);
298 Pickle pickle = SerializeVector(form.other_possible_usernames);
299 s.BindBlob(4, pickle.data(), pickle.size());
300 s.BindInt(5, form.times_used);
301 s.BindString(6, form.origin.spec());
302 s.BindString16(7, form.username_element);
303 s.BindString16(8, form.username_value);
304 s.BindString16(9, form.password_element);
305 s.BindString(10, form.signon_realm);
307 if (!s.Run())
308 return false;
310 if (items_changed)
311 *items_changed = db_.GetLastChangeCount();
313 return true;
316 bool LoginDatabase::RemoveLogin(const PasswordForm& form) {
317 // Remove a login by UNIQUE-constrained fields.
318 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
319 "DELETE FROM logins WHERE "
320 "origin_url = ? AND "
321 "username_element = ? AND "
322 "username_value = ? AND "
323 "password_element = ? AND "
324 "submit_element = ? AND "
325 "signon_realm = ? "));
326 s.BindString(0, form.origin.spec());
327 s.BindString16(1, form.username_element);
328 s.BindString16(2, form.username_value);
329 s.BindString16(3, form.password_element);
330 s.BindString16(4, form.submit_element);
331 s.BindString(5, form.signon_realm);
333 return s.Run();
336 bool LoginDatabase::RemoveLoginsCreatedBetween(const base::Time delete_begin,
337 const base::Time delete_end) {
338 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
339 "DELETE FROM logins WHERE "
340 "date_created >= ? AND date_created < ?"));
341 s.BindInt64(0, delete_begin.ToTimeT());
342 s.BindInt64(1, delete_end.is_null() ? std::numeric_limits<int64>::max()
343 : delete_end.ToTimeT());
345 return s.Run();
348 LoginDatabase::EncryptionResult LoginDatabase::InitPasswordFormFromStatement(
349 PasswordForm* form,
350 sql::Statement& s) const {
351 std::string encrypted_password;
352 s.ColumnBlobAsString(COLUMN_PASSWORD_VALUE, &encrypted_password);
353 base::string16 decrypted_password;
354 EncryptionResult encryption_result =
355 DecryptedString(encrypted_password, &decrypted_password);
356 if (encryption_result != ENCRYPTION_RESULT_SUCCESS)
357 return encryption_result;
359 std::string tmp = s.ColumnString(COLUMN_ORIGIN_URL);
360 form->origin = GURL(tmp);
361 tmp = s.ColumnString(COLUMN_ACTION_URL);
362 form->action = GURL(tmp);
363 form->username_element = s.ColumnString16(COLUMN_USERNAME_ELEMENT);
364 form->username_value = s.ColumnString16(COLUMN_USERNAME_VALUE);
365 form->password_element = s.ColumnString16(COLUMN_PASSWORD_ELEMENT);
366 form->password_value = decrypted_password;
367 form->submit_element = s.ColumnString16(COLUMN_SUBMIT_ELEMENT);
368 tmp = s.ColumnString(COLUMN_SIGNON_REALM);
369 form->signon_realm = tmp;
370 form->ssl_valid = (s.ColumnInt(COLUMN_SSL_VALID) > 0);
371 form->preferred = (s.ColumnInt(COLUMN_PREFERRED) > 0);
372 form->date_created = base::Time::FromTimeT(
373 s.ColumnInt64(COLUMN_DATE_CREATED));
374 form->blacklisted_by_user = (s.ColumnInt(COLUMN_BLACKLISTED_BY_USER) > 0);
375 int scheme_int = s.ColumnInt(COLUMN_SCHEME);
376 DCHECK((scheme_int >= 0) && (scheme_int <= PasswordForm::SCHEME_OTHER));
377 form->scheme = static_cast<PasswordForm::Scheme>(scheme_int);
378 int type_int = s.ColumnInt(COLUMN_PASSWORD_TYPE);
379 DCHECK(type_int >= 0 && type_int <= PasswordForm::TYPE_GENERATED);
380 form->type = static_cast<PasswordForm::Type>(type_int);
381 Pickle pickle(
382 static_cast<const char*>(s.ColumnBlob(COLUMN_POSSIBLE_USERNAMES)),
383 s.ColumnByteLength(COLUMN_POSSIBLE_USERNAMES));
384 form->other_possible_usernames = DeserializeVector(pickle);
385 form->times_used = s.ColumnInt(COLUMN_TIMES_USED);
386 Pickle form_data_pickle(
387 static_cast<const char*>(s.ColumnBlob(COLUMN_FORM_DATA)),
388 s.ColumnByteLength(COLUMN_FORM_DATA));
389 PickleIterator form_data_iter(form_data_pickle);
390 autofill::DeserializeFormData(&form_data_iter, &form->form_data);
391 return ENCRYPTION_RESULT_SUCCESS;
394 bool LoginDatabase::GetLogins(const PasswordForm& form,
395 std::vector<PasswordForm*>* forms) const {
396 DCHECK(forms);
397 // You *must* change LoginTableColumns if this query changes.
398 const std::string sql_query = "SELECT origin_url, action_url, "
399 "username_element, username_value, "
400 "password_element, password_value, submit_element, "
401 "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
402 "scheme, password_type, possible_usernames, times_used, form_data "
403 "FROM logins WHERE signon_realm == ? ";
404 sql::Statement s;
405 const GURL signon_realm(form.signon_realm);
406 std::string registered_domain =
407 PSLMatchingHelper::GetRegistryControlledDomain(signon_realm);
408 PSLMatchingHelper::PSLDomainMatchMetric psl_domain_match_metric =
409 PSLMatchingHelper::PSL_DOMAIN_MATCH_NONE;
410 if (psl_helper_.ShouldPSLDomainMatchingApply(registered_domain)) {
411 // We are extending the original SQL query with one that includes more
412 // possible matches based on public suffix domain matching. Using a regexp
413 // here is just an optimization to not have to parse all the stored entries
414 // in the |logins| table. The result (scheme, domain and port) is verified
415 // further down using GURL. See the functions SchemeMatches,
416 // RegistryControlledDomainMatches and PortMatches.
417 const std::string extended_sql_query =
418 sql_query + "OR signon_realm REGEXP ? ";
419 // TODO(nyquist) Re-enable usage of GetCachedStatement when
420 // http://crbug.com/248608 is fixed.
421 s.Assign(db_.GetUniqueStatement(extended_sql_query.c_str()));
422 // We need to escape . in the domain. Since the domain has already been
423 // sanitized using GURL, we do not need to escape any other characters.
424 base::ReplaceChars(registered_domain, ".", "\\.", &registered_domain);
425 std::string scheme = signon_realm.scheme();
426 // We need to escape . in the scheme. Since the scheme has already been
427 // sanitized using GURL, we do not need to escape any other characters.
428 // The scheme soap.beep is an example with '.'.
429 base::ReplaceChars(scheme, ".", "\\.", &scheme);
430 const std::string port = signon_realm.port();
431 // For a signon realm such as http://foo.bar/, this regexp will match
432 // domains on the form http://foo.bar/, http://www.foo.bar/,
433 // http://www.mobile.foo.bar/. It will not match http://notfoo.bar/.
434 // The scheme and port has to be the same as the observed form.
435 std::string regexp = "^(" + scheme + ":\\/\\/)([\\w-]+\\.)*" +
436 registered_domain + "(:" + port + ")?\\/$";
437 s.BindString(0, form.signon_realm);
438 s.BindString(1, regexp);
439 } else {
440 psl_domain_match_metric = PSLMatchingHelper::PSL_DOMAIN_MATCH_DISABLED;
441 s.Assign(db_.GetCachedStatement(SQL_FROM_HERE, sql_query.c_str()));
442 s.BindString(0, form.signon_realm);
445 while (s.Step()) {
446 scoped_ptr<PasswordForm> new_form(new PasswordForm());
447 EncryptionResult result = InitPasswordFormFromStatement(new_form.get(), s);
448 if (result == ENCRYPTION_RESULT_SERVICE_FAILURE)
449 return false;
450 if (result == ENCRYPTION_RESULT_ITEM_FAILURE)
451 continue;
452 DCHECK(result == ENCRYPTION_RESULT_SUCCESS);
453 if (psl_helper_.IsMatchingEnabled()) {
454 if (!PSLMatchingHelper::IsPublicSuffixDomainMatch(new_form->signon_realm,
455 form.signon_realm)) {
456 // The database returned results that should not match. Skipping result.
457 continue;
459 if (form.signon_realm != new_form->signon_realm) {
460 psl_domain_match_metric = PSLMatchingHelper::PSL_DOMAIN_MATCH_FOUND;
461 // This is not a perfect match, so we need to create a new valid result.
462 // We do this by copying over origin, signon realm and action from the
463 // observed form and setting the original signon realm to what we found
464 // in the database. We use the fact that |original_signon_realm| is
465 // non-empty to communicate that this match was found using public
466 // suffix matching.
467 new_form->original_signon_realm = new_form->signon_realm;
468 new_form->origin = form.origin;
469 new_form->signon_realm = form.signon_realm;
470 new_form->action = form.action;
473 forms->push_back(new_form.release());
475 UMA_HISTOGRAM_ENUMERATION("PasswordManager.PslDomainMatchTriggering",
476 psl_domain_match_metric,
477 PSLMatchingHelper::PSL_DOMAIN_MATCH_COUNT);
478 return s.Succeeded();
481 bool LoginDatabase::GetLoginsCreatedBetween(
482 const base::Time begin,
483 const base::Time end,
484 std::vector<autofill::PasswordForm*>* forms) const {
485 DCHECK(forms);
486 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
487 "SELECT origin_url, action_url, "
488 "username_element, username_value, "
489 "password_element, password_value, submit_element, "
490 "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
491 "scheme, password_type, possible_usernames, times_used, form_data "
492 "FROM logins WHERE date_created >= ? AND date_created < ?"
493 "ORDER BY origin_url"));
494 s.BindInt64(0, begin.ToTimeT());
495 s.BindInt64(1, end.is_null() ? std::numeric_limits<int64>::max()
496 : end.ToTimeT());
498 while (s.Step()) {
499 scoped_ptr<PasswordForm> new_form(new PasswordForm());
500 EncryptionResult result = InitPasswordFormFromStatement(new_form.get(), s);
501 if (result == ENCRYPTION_RESULT_SERVICE_FAILURE)
502 return false;
503 if (result == ENCRYPTION_RESULT_ITEM_FAILURE)
504 continue;
505 DCHECK(result == ENCRYPTION_RESULT_SUCCESS);
506 forms->push_back(new_form.release());
508 return s.Succeeded();
511 bool LoginDatabase::GetAutofillableLogins(
512 std::vector<PasswordForm*>* forms) const {
513 return GetAllLoginsWithBlacklistSetting(false, forms);
516 bool LoginDatabase::GetBlacklistLogins(
517 std::vector<PasswordForm*>* forms) const {
518 return GetAllLoginsWithBlacklistSetting(true, forms);
521 bool LoginDatabase::GetAllLoginsWithBlacklistSetting(
522 bool blacklisted, std::vector<PasswordForm*>* forms) const {
523 DCHECK(forms);
524 // You *must* change LoginTableColumns if this query changes.
525 sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
526 "SELECT origin_url, action_url, "
527 "username_element, username_value, "
528 "password_element, password_value, submit_element, "
529 "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
530 "scheme, password_type, possible_usernames, times_used, form_data "
531 "FROM logins WHERE blacklisted_by_user == ? "
532 "ORDER BY origin_url"));
533 s.BindInt(0, blacklisted ? 1 : 0);
535 while (s.Step()) {
536 scoped_ptr<PasswordForm> new_form(new PasswordForm());
537 EncryptionResult result = InitPasswordFormFromStatement(new_form.get(), s);
538 if (result == ENCRYPTION_RESULT_SERVICE_FAILURE)
539 return false;
540 if (result == ENCRYPTION_RESULT_ITEM_FAILURE)
541 continue;
542 DCHECK(result == ENCRYPTION_RESULT_SUCCESS);
543 forms->push_back(new_form.release());
545 return s.Succeeded();
548 bool LoginDatabase::DeleteAndRecreateDatabaseFile() {
549 DCHECK(db_.is_open());
550 meta_table_.Reset();
551 db_.Close();
552 sql::Connection::Delete(db_path_);
553 return Init(db_path_);
556 Pickle LoginDatabase::SerializeVector(
557 const std::vector<base::string16>& vec) const {
558 Pickle p;
559 for (size_t i = 0; i < vec.size(); ++i) {
560 p.WriteString16(vec[i]);
562 return p;
565 std::vector<base::string16> LoginDatabase::DeserializeVector(
566 const Pickle& p) const {
567 std::vector<base::string16> ret;
568 base::string16 str;
570 PickleIterator iterator(p);
571 while (iterator.ReadString16(&str)) {
572 ret.push_back(str);
574 return ret;