1 // Copyright 2014 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 "components/password_manager/core/browser/login_database.h"
10 #include "base/bind.h"
11 #include "base/files/file_path.h"
12 #include "base/logging.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/metrics/sparse_histogram.h"
15 #include "base/pickle.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/time/time.h"
19 #include "components/autofill/core/common/password_form.h"
20 #include "components/password_manager/core/browser/affiliation_utils.h"
21 #include "components/password_manager/core/browser/password_manager_client.h"
22 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
23 #include "google_apis/gaia/gaia_auth_util.h"
24 #include "google_apis/gaia/gaia_urls.h"
25 #include "sql/connection.h"
26 #include "sql/statement.h"
27 #include "sql/transaction.h"
28 #include "url/url_constants.h"
30 using autofill::PasswordForm
;
32 namespace password_manager
{
34 // The current version number of the login database schema.
35 const int kCurrentVersionNumber
= 15;
36 // The oldest version of the schema such that a legacy Chrome client using that
37 // version can still read/write the current database.
38 const int kCompatibleVersionNumber
= 14;
40 base::Pickle
SerializeVector(const std::vector
<base::string16
>& vec
) {
42 for (size_t i
= 0; i
< vec
.size(); ++i
) {
43 p
.WriteString16(vec
[i
]);
48 std::vector
<base::string16
> DeserializeVector(const base::Pickle
& p
) {
49 std::vector
<base::string16
> ret
;
52 base::PickleIterator
iterator(p
);
53 while (iterator
.ReadString16(&str
)) {
61 // Convenience enum for interacting with SQL queries that use all the columns.
62 enum LoginTableColumns
{
63 COLUMN_ORIGIN_URL
= 0,
65 COLUMN_USERNAME_ELEMENT
,
66 COLUMN_USERNAME_VALUE
,
67 COLUMN_PASSWORD_ELEMENT
,
68 COLUMN_PASSWORD_VALUE
,
69 COLUMN_SUBMIT_ELEMENT
,
74 COLUMN_BLACKLISTED_BY_USER
,
77 COLUMN_POSSIBLE_USERNAMES
,
83 COLUMN_FEDERATION_URL
,
84 COLUMN_SKIP_ZERO_CLICK
,
85 COLUMN_GENERATION_UPLOAD_STATUS
,
88 enum class HistogramSize
{ SMALL
, LARGE
};
90 // An enum for UMA reporting. Add values to the end only.
91 enum DatabaseInitError
{
94 START_TRANSACTION_ERROR
,
95 META_TABLE_INIT_ERROR
,
100 COMMIT_TRANSACTION_ERROR
,
102 DATABASE_INIT_ERROR_COUNT
,
105 void BindAddStatement(const PasswordForm
& form
,
106 const std::string
& encrypted_password
,
108 s
->BindString(COLUMN_ORIGIN_URL
, form
.origin
.spec());
109 s
->BindString(COLUMN_ACTION_URL
, form
.action
.spec());
110 s
->BindString16(COLUMN_USERNAME_ELEMENT
, form
.username_element
);
111 s
->BindString16(COLUMN_USERNAME_VALUE
, form
.username_value
);
112 s
->BindString16(COLUMN_PASSWORD_ELEMENT
, form
.password_element
);
113 s
->BindBlob(COLUMN_PASSWORD_VALUE
, encrypted_password
.data(),
114 static_cast<int>(encrypted_password
.length()));
115 s
->BindString16(COLUMN_SUBMIT_ELEMENT
, form
.submit_element
);
116 s
->BindString(COLUMN_SIGNON_REALM
, form
.signon_realm
);
117 s
->BindInt(COLUMN_SSL_VALID
, form
.ssl_valid
);
118 s
->BindInt(COLUMN_PREFERRED
, form
.preferred
);
119 s
->BindInt64(COLUMN_DATE_CREATED
, form
.date_created
.ToInternalValue());
120 s
->BindInt(COLUMN_BLACKLISTED_BY_USER
, form
.blacklisted_by_user
);
121 s
->BindInt(COLUMN_SCHEME
, form
.scheme
);
122 s
->BindInt(COLUMN_PASSWORD_TYPE
, form
.type
);
123 base::Pickle usernames_pickle
=
124 SerializeVector(form
.other_possible_usernames
);
125 s
->BindBlob(COLUMN_POSSIBLE_USERNAMES
,
126 usernames_pickle
.data(),
127 usernames_pickle
.size());
128 s
->BindInt(COLUMN_TIMES_USED
, form
.times_used
);
129 base::Pickle form_data_pickle
;
130 autofill::SerializeFormData(form
.form_data
, &form_data_pickle
);
131 s
->BindBlob(COLUMN_FORM_DATA
,
132 form_data_pickle
.data(),
133 form_data_pickle
.size());
134 s
->BindInt64(COLUMN_DATE_SYNCED
, form
.date_synced
.ToInternalValue());
135 s
->BindString16(COLUMN_DISPLAY_NAME
, form
.display_name
);
136 s
->BindString(COLUMN_ICON_URL
, form
.icon_url
.spec());
137 s
->BindString(COLUMN_FEDERATION_URL
, form
.federation_url
.spec());
138 s
->BindInt(COLUMN_SKIP_ZERO_CLICK
, form
.skip_zero_click
);
139 s
->BindInt(COLUMN_GENERATION_UPLOAD_STATUS
, form
.generation_upload_status
);
142 void AddCallback(int err
, sql::Statement
* /*stmt*/) {
143 if (err
== 19 /*SQLITE_CONSTRAINT*/)
144 DLOG(WARNING
) << "LoginDatabase::AddLogin updated an existing form";
147 bool DoesMatchConstraints(const PasswordForm
& form
) {
148 if (!IsValidAndroidFacetURI(form
.signon_realm
) && form
.origin
.is_empty()) {
149 DLOG(ERROR
) << "Constraint violation: form.origin is empty";
152 if (form
.signon_realm
.empty()) {
153 DLOG(ERROR
) << "Constraint violation: form.signon_realm is empty";
159 void LogDatabaseInitError(DatabaseInitError error
) {
160 UMA_HISTOGRAM_ENUMERATION("PasswordManager.LoginDatabaseInit", error
,
161 DATABASE_INIT_ERROR_COUNT
);
164 // UMA_* macros assume that the name never changes. This is a helper function
165 // where this assumption doesn't hold.
166 void LogDynamicUMAStat(const std::string
& name
,
171 base::HistogramBase
* counter
= base::Histogram::FactoryGet(
172 name
, min
, max
, bucket_count
,
173 base::HistogramBase::kUmaTargetedHistogramFlag
);
174 counter
->Add(sample
);
177 void LogAccountStat(const std::string
& name
, int sample
) {
178 LogDynamicUMAStat(name
, sample
, 0, 32, 6);
181 void LogTimesUsedStat(const std::string
& name
, int sample
) {
182 LogDynamicUMAStat(name
, sample
, 0, 100, 10);
185 void LogNumberOfAccountsForScheme(const std::string
& scheme
, int sample
) {
186 LogDynamicUMAStat("PasswordManager.TotalAccountsHiRes.WithScheme." + scheme
,
187 sample
, 1, 1000, 100);
190 void LogNumberOfAccountsReusingPassword(const std::string
& suffix
,
192 HistogramSize histogram_size
) {
193 int max
= histogram_size
== HistogramSize::LARGE
? 500 : 100;
194 int bucket_count
= histogram_size
== HistogramSize::LARGE
? 50 : 20;
195 LogDynamicUMAStat("PasswordManager.AccountsReusingPassword." + suffix
, sample
,
196 1, max
, bucket_count
);
199 // Records password reuse metrics given the |signon_realms| corresponding to a
200 // set of accounts that reuse the same password. See histograms.xml for details.
201 void LogPasswordReuseMetrics(const std::vector
<std::string
>& signon_realms
) {
202 struct StatisticsPerScheme
{
203 StatisticsPerScheme() : num_total_accounts(0) {}
205 // The number of accounts for each registry controlled domain.
206 std::map
<std::string
, int> num_accounts_per_registry_controlled_domain
;
208 // The number of accounts for each domain.
209 std::map
<std::string
, int> num_accounts_per_domain
;
211 // Total number of accounts with this scheme. This equals the sum of counts
212 // in either of the above maps.
213 int num_total_accounts
;
216 // The scheme (i.e. protocol) of the origin, not PasswordForm::scheme.
217 enum Scheme
{ SCHEME_HTTP
, SCHEME_HTTPS
};
218 const Scheme kAllSchemes
[] = {SCHEME_HTTP
, SCHEME_HTTPS
};
220 StatisticsPerScheme statistics
[arraysize(kAllSchemes
)];
221 std::map
<std::string
, std::string
> domain_to_registry_controlled_domain
;
223 for (const std::string
& signon_realm
: signon_realms
) {
224 const GURL
signon_realm_url(signon_realm
);
225 const std::string domain
= signon_realm_url
.host();
229 if (!domain_to_registry_controlled_domain
.count(domain
)) {
230 domain_to_registry_controlled_domain
[domain
] =
231 GetRegistryControlledDomain(signon_realm_url
);
232 if (domain_to_registry_controlled_domain
[domain
].empty())
233 domain_to_registry_controlled_domain
[domain
] = domain
;
235 const std::string
& registry_controlled_domain
=
236 domain_to_registry_controlled_domain
[domain
];
238 Scheme scheme
= SCHEME_HTTP
;
239 COMPILE_ASSERT(arraysize(kAllSchemes
) == 2, "Update this logic");
240 if (signon_realm_url
.SchemeIs(url::kHttpsScheme
))
241 scheme
= SCHEME_HTTPS
;
242 else if (!signon_realm_url
.SchemeIs(url::kHttpScheme
))
245 statistics
[scheme
].num_accounts_per_domain
[domain
]++;
246 statistics
[scheme
].num_accounts_per_registry_controlled_domain
247 [registry_controlled_domain
]++;
248 statistics
[scheme
].num_total_accounts
++;
251 // For each "source" account of either scheme, count the number of "target"
252 // accounts reusing the same password (of either scheme).
253 for (const Scheme scheme
: kAllSchemes
) {
254 for (const auto& kv
: statistics
[scheme
].num_accounts_per_domain
) {
255 const std::string
& domain(kv
.first
);
256 const int num_accounts_per_domain(kv
.second
);
257 const std::string
& registry_controlled_domain
=
258 domain_to_registry_controlled_domain
[domain
];
260 Scheme other_scheme
= scheme
== SCHEME_HTTP
? SCHEME_HTTPS
: SCHEME_HTTP
;
261 COMPILE_ASSERT(arraysize(kAllSchemes
) == 2, "Update |other_scheme|");
263 // Discount the account at hand from the number of accounts with the same
264 // domain and scheme.
265 int num_accounts_for_same_domain
[arraysize(kAllSchemes
)] = {};
266 num_accounts_for_same_domain
[scheme
] =
267 statistics
[scheme
].num_accounts_per_domain
[domain
] - 1;
268 num_accounts_for_same_domain
[other_scheme
] =
269 statistics
[other_scheme
].num_accounts_per_domain
[domain
];
271 // By definition, a PSL match requires the scheme to be the same.
272 int num_psl_matching_accounts
=
273 statistics
[scheme
].num_accounts_per_registry_controlled_domain
274 [registry_controlled_domain
] -
275 statistics
[scheme
].num_accounts_per_domain
[domain
];
277 // Discount PSL matches from the number of accounts with different domains
278 // but the same scheme.
279 int num_accounts_for_different_domain
[arraysize(kAllSchemes
)] = {};
280 num_accounts_for_different_domain
[scheme
] =
281 statistics
[scheme
].num_total_accounts
-
282 statistics
[scheme
].num_accounts_per_registry_controlled_domain
283 [registry_controlled_domain
];
284 num_accounts_for_different_domain
[other_scheme
] =
285 statistics
[other_scheme
].num_total_accounts
-
286 statistics
[other_scheme
].num_accounts_per_domain
[domain
];
288 std::string source_realm_kind
=
289 scheme
== SCHEME_HTTP
? "FromHttpRealm" : "FromHttpsRealm";
290 COMPILE_ASSERT(arraysize(kAllSchemes
) == 2, "Update |source_realm_kind|");
292 // So far, the calculation has been carried out once per "source" domain,
293 // but the metrics need to be recorded on a per-account basis. The set of
294 // metrics are the same for all accounts for the same domain, so simply
295 // report them as many times as accounts.
296 for (int i
= 0; i
< num_accounts_per_domain
; ++i
) {
297 LogNumberOfAccountsReusingPassword(
298 source_realm_kind
+ ".OnHttpRealmWithSameHost",
299 num_accounts_for_same_domain
[SCHEME_HTTP
], HistogramSize::SMALL
);
300 LogNumberOfAccountsReusingPassword(
301 source_realm_kind
+ ".OnHttpsRealmWithSameHost",
302 num_accounts_for_same_domain
[SCHEME_HTTPS
], HistogramSize::SMALL
);
303 LogNumberOfAccountsReusingPassword(
304 source_realm_kind
+ ".OnPSLMatchingRealm",
305 num_psl_matching_accounts
, HistogramSize::SMALL
);
307 LogNumberOfAccountsReusingPassword(
308 source_realm_kind
+ ".OnHttpRealmWithDifferentHost",
309 num_accounts_for_different_domain
[SCHEME_HTTP
],
310 HistogramSize::LARGE
);
311 LogNumberOfAccountsReusingPassword(
312 source_realm_kind
+ ".OnHttpsRealmWithDifferentHost",
313 num_accounts_for_different_domain
[SCHEME_HTTPS
],
314 HistogramSize::LARGE
);
316 LogNumberOfAccountsReusingPassword(
317 source_realm_kind
+ ".OnAnyRealmWithDifferentHost",
318 num_accounts_for_different_domain
[SCHEME_HTTP
] +
319 num_accounts_for_different_domain
[SCHEME_HTTPS
],
320 HistogramSize::LARGE
);
326 // Creates a table named |table_name| using our current schema.
327 bool CreateNewTable(sql::Connection
* db
,
328 const char* table_name
,
329 const char* extra_columns
) {
330 std::string query
= base::StringPrintf(
332 "origin_url VARCHAR NOT NULL, "
333 "action_url VARCHAR, "
334 "username_element VARCHAR, "
335 "username_value VARCHAR, "
336 "password_element VARCHAR, "
337 "password_value BLOB, "
338 "submit_element VARCHAR, "
339 "signon_realm VARCHAR NOT NULL,"
340 "ssl_valid INTEGER NOT NULL,"
341 "preferred INTEGER NOT NULL,"
342 "date_created INTEGER NOT NULL,"
343 "blacklisted_by_user INTEGER NOT NULL,"
344 "scheme INTEGER NOT NULL,"
345 "password_type INTEGER,"
346 "possible_usernames BLOB,"
347 "times_used INTEGER,"
349 "date_synced INTEGER,"
350 "display_name VARCHAR,"
352 "federation_url VARCHAR,"
353 "skip_zero_click INTEGER,"
355 "UNIQUE (origin_url, username_element, username_value, "
356 "password_element, signon_realm))",
357 table_name
, extra_columns
);
358 return db
->Execute(query
.c_str());
361 bool CreateIndexOnSignonRealm(sql::Connection
* db
, const char* table_name
) {
362 std::string query
= base::StringPrintf(
363 "CREATE INDEX logins_signon ON %s (signon_realm)", table_name
);
364 return db
->Execute(query
.c_str());
369 LoginDatabase::LoginDatabase(const base::FilePath
& db_path
)
370 : db_path_(db_path
), clear_password_values_(false) {
373 LoginDatabase::~LoginDatabase() {
376 bool LoginDatabase::Init() {
377 // Set pragmas for a small, private database (based on WebDatabase).
378 db_
.set_page_size(2048);
379 db_
.set_cache_size(32);
380 db_
.set_exclusive_locking();
381 db_
.set_restrict_to_user();
382 db_
.set_histogram_tag("Passwords");
384 if (!db_
.Open(db_path_
)) {
385 LogDatabaseInitError(OPEN_FILE_ERROR
);
386 LOG(ERROR
) << "Unable to open the password store database.";
390 sql::Transaction
transaction(&db_
);
391 if (!transaction
.Begin()) {
392 LogDatabaseInitError(START_TRANSACTION_ERROR
);
393 LOG(ERROR
) << "Unable to start a transaction.";
398 // Check the database version.
399 if (!meta_table_
.Init(&db_
, kCurrentVersionNumber
,
400 kCompatibleVersionNumber
)) {
401 LogDatabaseInitError(META_TABLE_INIT_ERROR
);
402 LOG(ERROR
) << "Unable to create the meta table.";
406 if (meta_table_
.GetCompatibleVersionNumber() > kCurrentVersionNumber
) {
407 LogDatabaseInitError(INCOMPATIBLE_VERSION
);
408 LOG(ERROR
) << "Password store database is too new, kCurrentVersionNumber="
409 << kCurrentVersionNumber
<< ", GetCompatibleVersionNumber="
410 << meta_table_
.GetCompatibleVersionNumber();
415 // Initialize the tables.
416 if (!InitLoginsTable()) {
417 LogDatabaseInitError(INIT_LOGINS_ERROR
);
418 LOG(ERROR
) << "Unable to initialize the logins table.";
423 if (!stats_table_
.Init(&db_
)) {
424 LogDatabaseInitError(INIT_STATS_ERROR
);
425 LOG(ERROR
) << "Unable to initialize the stats table.";
430 // If the file on disk is an older database version, bring it up to date.
431 if (meta_table_
.GetVersionNumber() < kCurrentVersionNumber
&&
432 !MigrateOldVersionsAsNeeded()) {
433 LogDatabaseInitError(MIGRATION_ERROR
);
434 UMA_HISTOGRAM_SPARSE_SLOWLY("PasswordManager.LoginDatabaseFailedVersion",
435 meta_table_
.GetVersionNumber());
436 LOG(ERROR
) << "Unable to migrate database from "
437 << meta_table_
.GetVersionNumber() << " to "
438 << kCurrentVersionNumber
;
443 if (!transaction
.Commit()) {
444 LogDatabaseInitError(COMMIT_TRANSACTION_ERROR
);
445 LOG(ERROR
) << "Unable to commit a transaction.";
450 LogDatabaseInitError(INIT_OK
);
454 bool LoginDatabase::MigrateOldVersionsAsNeeded() {
455 const int original_version
= meta_table_
.GetVersionNumber();
456 switch (original_version
) {
458 if (!db_
.Execute("ALTER TABLE logins "
459 "ADD COLUMN password_type INTEGER") ||
460 !db_
.Execute("ALTER TABLE logins "
461 "ADD COLUMN possible_usernames BLOB")) {
466 if (!db_
.Execute("ALTER TABLE logins ADD COLUMN times_used INTEGER")) {
471 // We need to check if the column exists because of
472 // https://crbug.com/295851
473 if (!db_
.DoesColumnExist("logins", "form_data") &&
474 !db_
.Execute("ALTER TABLE logins ADD COLUMN form_data BLOB")) {
480 "ALTER TABLE logins ADD COLUMN use_additional_auth INTEGER")) {
485 if (!db_
.Execute("ALTER TABLE logins ADD COLUMN date_synced INTEGER")) {
490 if (!db_
.Execute("ALTER TABLE logins ADD COLUMN display_name VARCHAR") ||
491 !db_
.Execute("ALTER TABLE logins ADD COLUMN avatar_url VARCHAR") ||
492 !db_
.Execute("ALTER TABLE logins "
493 "ADD COLUMN federation_url VARCHAR") ||
494 !db_
.Execute("ALTER TABLE logins ADD COLUMN is_zero_click INTEGER")) {
499 // Keep version 8 around even though no changes are made. See
500 // crbug.com/423716 for context.
505 s
.Assign(db_
.GetCachedStatement(SQL_FROM_HERE
,
508 "(date_created * ?) + ?"));
509 s
.BindInt64(0, base::Time::kMicrosecondsPerSecond
);
510 s
.BindInt64(1, base::Time::kTimeTToMicrosecondsOffset
);
516 // Remove use_additional_auth column from database schema
517 // crbug.com/423716 for context.
518 std::string fields_to_copy
=
519 "origin_url, action_url, username_element, username_value, "
520 "password_element, password_value, submit_element, "
521 "signon_realm, ssl_valid, preferred, date_created, "
522 "blacklisted_by_user, scheme, password_type, possible_usernames, "
523 "times_used, form_data, date_synced, display_name, avatar_url, "
524 "federation_url, is_zero_click";
525 auto copy_data_query
=
526 [&fields_to_copy
](const std::string
& from
, const std::string
& to
) {
527 return "INSERT INTO " + to
+ " SELECT " + fields_to_copy
+ " FROM " +
531 if (!db_
.Execute(("CREATE TEMPORARY TABLE logins_data(" + fields_to_copy
+
533 !db_
.Execute(copy_data_query("logins", "logins_data").c_str()) ||
534 !db_
.Execute("DROP TABLE logins") ||
536 ("CREATE TABLE logins(" + fields_to_copy
+ ")").c_str()) ||
537 !db_
.Execute(copy_data_query("logins_data", "logins").c_str()) ||
538 !db_
.Execute("DROP TABLE logins_data") ||
539 !CreateIndexOnSignonRealm(&db_
, "logins")) {
545 // Rename is_zero_click -> skip_zero_click. Note that previous versions
546 // may have incorrectly used a 6-column key (origin_url, username_element,
547 // username_value, password_element, signon_realm, submit_element).
548 // In that case, this step also restores the correct 5-column key;
549 // that is, the above without "submit_element".
550 const char copy_query
[] = "INSERT OR REPLACE INTO logins_new SELECT "
551 "origin_url, action_url, username_element, username_value, "
552 "password_element, password_value, submit_element, signon_realm, "
553 "ssl_valid, preferred, date_created, blacklisted_by_user, scheme, "
554 "password_type, possible_usernames, times_used, form_data, "
555 "date_synced, display_name, avatar_url, federation_url, is_zero_click"
557 if (!CreateNewTable(&db_
, "logins_new", "") ||
558 !db_
.Execute(copy_query
) ||
559 !db_
.Execute("DROP TABLE logins") ||
560 !db_
.Execute("ALTER TABLE logins_new RENAME TO logins") ||
561 !CreateIndexOnSignonRealm(&db_
, "logins")) {
568 "ALTER TABLE logins ADD COLUMN "
569 "generation_upload_status INTEGER"))
573 // The stats table was added. Nothing to do really.
576 // Rename avatar_url -> icon_url. Note that if the original version was
577 // at most 10, this renaming would have already happened in step 10,
578 // as |CreateNewTable| would create a table with the new column name.
579 if (original_version
> 10) {
580 const char copy_query
[] = "INSERT OR REPLACE INTO logins_new SELECT "
581 "origin_url, action_url, username_element, username_value, "
582 "password_element, password_value, submit_element, signon_realm, "
583 "ssl_valid, preferred, date_created, blacklisted_by_user, scheme, "
584 "password_type, possible_usernames, times_used, form_data, "
585 "date_synced, display_name, avatar_url, federation_url, "
586 "skip_zero_click, generation_upload_status FROM logins";
588 &db_
, "logins_new", "generation_upload_status INTEGER,") ||
589 !db_
.Execute(copy_query
) ||
590 !db_
.Execute("DROP TABLE logins") ||
591 !db_
.Execute("ALTER TABLE logins_new RENAME TO logins") ||
592 !CreateIndexOnSignonRealm(&db_
, "logins")) {
599 // No change of schema. Version 15 was introduced to force all databases
600 // through an otherwise no-op migration process that will, however, now
601 // correctly set the 'compatible version number'. Previously, it was
602 // always being set to (and forever left at) version 1.
605 // -------------------------------------------------------------------------
606 // DO NOT FORGET to update |kCompatibleVersionNumber| if you add a migration
607 // step that is a breaking change. This is needed so that an older version
608 // of the browser can fail with a meaningful error when opening a newer
609 // database, as opposed to failing on the first database operation.
610 // -------------------------------------------------------------------------
611 case kCurrentVersionNumber
:
612 // Already up to date.
613 meta_table_
.SetVersionNumber(kCurrentVersionNumber
);
614 meta_table_
.SetCompatibleVersionNumber(kCompatibleVersionNumber
);
622 bool LoginDatabase::InitLoginsTable() {
623 if (!db_
.DoesTableExist("logins")) {
624 if (!CreateNewTable(&db_
, "logins", "generation_upload_status INTEGER,")) {
628 if (!CreateIndexOnSignonRealm(&db_
, "logins")) {
636 void LoginDatabase::ReportMetrics(const std::string
& sync_username
,
637 bool custom_passphrase_sync_enabled
) {
638 sql::Statement
s(db_
.GetCachedStatement(
640 "SELECT signon_realm, password_type, blacklisted_by_user,"
641 "COUNT(username_value) FROM logins GROUP BY "
642 "signon_realm, password_type, blacklisted_by_user"));
647 std::string custom_passphrase
= "WithoutCustomPassphrase";
648 if (custom_passphrase_sync_enabled
) {
649 custom_passphrase
= "WithCustomPassphrase";
652 int total_user_created_accounts
= 0;
653 int total_generated_accounts
= 0;
654 int blacklisted_sites
= 0;
656 PasswordForm::Type password_type
=
657 static_cast<PasswordForm::Type
>(s
.ColumnInt(1));
658 int blacklisted
= s
.ColumnInt(2);
659 int accounts_per_site
= s
.ColumnInt(3);
662 } else if (password_type
== PasswordForm::TYPE_GENERATED
) {
663 total_generated_accounts
+= accounts_per_site
;
665 base::StringPrintf("PasswordManager.AccountsPerSite.AutoGenerated.%s",
666 custom_passphrase
.c_str()),
669 total_user_created_accounts
+= accounts_per_site
;
671 base::StringPrintf("PasswordManager.AccountsPerSite.UserCreated.%s",
672 custom_passphrase
.c_str()),
677 base::StringPrintf("PasswordManager.TotalAccounts.UserCreated.%s",
678 custom_passphrase
.c_str()),
679 total_user_created_accounts
);
681 base::StringPrintf("PasswordManager.TotalAccounts.AutoGenerated.%s",
682 custom_passphrase
.c_str()),
683 total_generated_accounts
);
684 LogAccountStat(base::StringPrintf("PasswordManager.BlacklistedSites.%s",
685 custom_passphrase
.c_str()),
688 sql::Statement
usage_statement(db_
.GetCachedStatement(
689 SQL_FROM_HERE
, "SELECT password_type, times_used FROM logins"));
691 if (!usage_statement
.is_valid())
694 while (usage_statement
.Step()) {
695 PasswordForm::Type type
=
696 static_cast<PasswordForm::Type
>(usage_statement
.ColumnInt(0));
698 if (type
== PasswordForm::TYPE_GENERATED
) {
699 LogTimesUsedStat(base::StringPrintf(
700 "PasswordManager.TimesPasswordUsed.AutoGenerated.%s",
701 custom_passphrase
.c_str()),
702 usage_statement
.ColumnInt(1));
705 base::StringPrintf("PasswordManager.TimesPasswordUsed.UserCreated.%s",
706 custom_passphrase
.c_str()),
707 usage_statement
.ColumnInt(1));
711 bool syncing_account_saved
= false;
712 if (!sync_username
.empty()) {
713 sql::Statement
sync_statement(db_
.GetCachedStatement(
715 "SELECT username_value FROM logins "
716 "WHERE signon_realm == ?"));
717 sync_statement
.BindString(
718 0, GaiaUrls::GetInstance()->gaia_url().GetOrigin().spec());
720 if (!sync_statement
.is_valid())
723 while (sync_statement
.Step()) {
724 std::string username
= sync_statement
.ColumnString(0);
725 if (gaia::AreEmailsSame(sync_username
, username
)) {
726 syncing_account_saved
= true;
731 UMA_HISTOGRAM_ENUMERATION("PasswordManager.SyncingAccountState",
732 2 * sync_username
.empty() + syncing_account_saved
,
735 sql::Statement
empty_usernames_statement(db_
.GetCachedStatement(
736 SQL_FROM_HERE
, "SELECT COUNT(*) FROM logins "
737 "WHERE blacklisted_by_user=0 AND username_value=''"));
738 if (empty_usernames_statement
.Step()) {
739 int empty_forms
= empty_usernames_statement
.ColumnInt(0);
740 UMA_HISTOGRAM_COUNTS_100("PasswordManager.EmptyUsernames.CountInDatabase",
744 sql::Statement
standalone_empty_usernames_statement(db_
.GetCachedStatement(
745 SQL_FROM_HERE
, "SELECT COUNT(*) FROM logins a "
746 "WHERE a.blacklisted_by_user=0 AND a.username_value='' "
747 "AND NOT EXISTS (SELECT * FROM logins b "
748 "WHERE b.blacklisted_by_user=0 AND b.username_value!='' "
749 "AND a.signon_realm = b.signon_realm)"));
750 if (standalone_empty_usernames_statement
.Step()) {
751 int num_entries
= standalone_empty_usernames_statement
.ColumnInt(0);
752 UMA_HISTOGRAM_COUNTS_100(
753 "PasswordManager.EmptyUsernames.WithoutCorrespondingNonempty",
757 sql::Statement
logins_with_schemes_statement(db_
.GetUniqueStatement(
758 "SELECT signon_realm, origin_url, ssl_valid, blacklisted_by_user "
761 if (!logins_with_schemes_statement
.is_valid())
764 int android_logins
= 0;
767 int https_logins
= 0;
768 int other_logins
= 0;
770 while (logins_with_schemes_statement
.Step()) {
771 std::string signon_realm
= logins_with_schemes_statement
.ColumnString(0);
772 GURL origin_url
= GURL(logins_with_schemes_statement
.ColumnString(1));
773 bool ssl_valid
= !!logins_with_schemes_statement
.ColumnInt(2);
774 bool blacklisted_by_user
= !!logins_with_schemes_statement
.ColumnInt(3);
775 if (blacklisted_by_user
)
778 if (IsValidAndroidFacetURI(signon_realm
)) {
780 } else if (origin_url
.SchemeIs(url::kHttpsScheme
)) {
782 metrics_util::LogUMAHistogramBoolean(
783 "PasswordManager.UserStoredPasswordWithInvalidSSLCert", !ssl_valid
);
784 } else if (origin_url
.SchemeIs(url::kHttpScheme
)) {
786 } else if (origin_url
.SchemeIs(url::kFtpScheme
)) {
793 LogNumberOfAccountsForScheme("Android", android_logins
);
794 LogNumberOfAccountsForScheme("Ftp", ftp_logins
);
795 LogNumberOfAccountsForScheme("Http", http_logins
);
796 LogNumberOfAccountsForScheme("Https", https_logins
);
797 LogNumberOfAccountsForScheme("Other", other_logins
);
799 sql::Statement
form_based_passwords_statement(
800 db_
.GetUniqueStatement("SELECT signon_realm, password_value FROM logins "
801 "WHERE blacklisted_by_user = 0 AND scheme = 0"));
803 std::map
<base::string16
, std::vector
<std::string
>> passwords_to_realms
;
804 while (form_based_passwords_statement
.Step()) {
805 std::string signon_realm
= form_based_passwords_statement
.ColumnString(0);
806 base::string16 decrypted_password
;
807 // Note that CryptProtectData() is non-deterministic, so passwords must be
808 // decrypted before checking equality.
809 if (!IsValidAndroidFacetURI(signon_realm
) &&
810 DecryptedString(form_based_passwords_statement
.ColumnString(1),
811 &decrypted_password
) == ENCRYPTION_RESULT_SUCCESS
) {
812 passwords_to_realms
[decrypted_password
].push_back(signon_realm
);
816 for (const auto& password_to_realms
: passwords_to_realms
)
817 LogPasswordReuseMetrics(password_to_realms
.second
);
820 PasswordStoreChangeList
LoginDatabase::AddLogin(const PasswordForm
& form
) {
821 PasswordStoreChangeList list
;
822 if (!DoesMatchConstraints(form
))
824 std::string encrypted_password
;
826 clear_password_values_
? base::string16() : form
.password_value
,
827 &encrypted_password
) != ENCRYPTION_RESULT_SUCCESS
)
830 // You *must* change LoginTableColumns if this query changes.
831 sql::Statement
s(db_
.GetCachedStatement(
833 "INSERT INTO logins "
834 "(origin_url, action_url, username_element, username_value, "
835 " password_element, password_value, submit_element, "
836 " signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
837 " scheme, password_type, possible_usernames, times_used, form_data, "
838 " date_synced, display_name, icon_url,"
839 " federation_url, skip_zero_click, generation_upload_status) VALUES "
840 "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
841 BindAddStatement(form
, encrypted_password
, &s
);
842 db_
.set_error_callback(base::Bind(&AddCallback
));
843 const bool success
= s
.Run();
844 db_
.reset_error_callback();
846 list
.push_back(PasswordStoreChange(PasswordStoreChange::ADD
, form
));
849 // Repeat the same statement but with REPLACE semantic.
850 s
.Assign(db_
.GetCachedStatement(
852 "INSERT OR REPLACE INTO logins "
853 "(origin_url, action_url, username_element, username_value, "
854 " password_element, password_value, submit_element, "
855 " signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
856 " scheme, password_type, possible_usernames, times_used, form_data, "
857 " date_synced, display_name, icon_url,"
858 " federation_url, skip_zero_click, generation_upload_status) VALUES "
859 "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
860 BindAddStatement(form
, encrypted_password
, &s
);
862 list
.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE
, form
));
863 list
.push_back(PasswordStoreChange(PasswordStoreChange::ADD
, form
));
868 PasswordStoreChangeList
LoginDatabase::UpdateLogin(const PasswordForm
& form
) {
869 std::string encrypted_password
;
871 clear_password_values_
? base::string16() : form
.password_value
,
872 &encrypted_password
) != ENCRYPTION_RESULT_SUCCESS
)
873 return PasswordStoreChangeList();
875 // Replacement is necessary to deal with updating imported credentials. See
876 // crbug.com/349138 for details.
877 sql::Statement
s(db_
.GetCachedStatement(SQL_FROM_HERE
,
878 "UPDATE OR REPLACE logins SET "
880 "password_value = ?, "
883 "possible_usernames = ?, "
885 "submit_element = ?, "
888 "blacklisted_by_user = ?, "
890 "password_type = ?, "
893 "federation_url = ?, "
894 "skip_zero_click = ?, "
895 "generation_upload_status = ? "
896 "WHERE origin_url = ? AND "
897 "username_element = ? AND "
898 "username_value = ? AND "
899 "password_element = ? AND "
900 "signon_realm = ?"));
901 s
.BindString(0, form
.action
.spec());
902 s
.BindBlob(1, encrypted_password
.data(),
903 static_cast<int>(encrypted_password
.length()));
904 s
.BindInt(2, form
.ssl_valid
);
905 s
.BindInt(3, form
.preferred
);
906 base::Pickle pickle
= SerializeVector(form
.other_possible_usernames
);
907 s
.BindBlob(4, pickle
.data(), pickle
.size());
908 s
.BindInt(5, form
.times_used
);
909 s
.BindString16(6, form
.submit_element
);
910 s
.BindInt64(7, form
.date_synced
.ToInternalValue());
911 s
.BindInt64(8, form
.date_created
.ToInternalValue());
912 s
.BindInt(9, form
.blacklisted_by_user
);
913 s
.BindInt(10, form
.scheme
);
914 s
.BindInt(11, form
.type
);
915 s
.BindString16(12, form
.display_name
);
916 s
.BindString(13, form
.icon_url
.spec());
917 s
.BindString(14, form
.federation_url
.spec());
918 s
.BindInt(15, form
.skip_zero_click
);
919 s
.BindInt(16, form
.generation_upload_status
);
921 // WHERE starts here.
922 s
.BindString(17, form
.origin
.spec());
923 s
.BindString16(18, form
.username_element
);
924 s
.BindString16(19, form
.username_value
);
925 s
.BindString16(20, form
.password_element
);
926 s
.BindString(21, form
.signon_realm
);
929 return PasswordStoreChangeList();
931 PasswordStoreChangeList list
;
932 if (db_
.GetLastChangeCount())
933 list
.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE
, form
));
938 bool LoginDatabase::RemoveLogin(const PasswordForm
& form
) {
939 if (form
.IsPublicSuffixMatch()) {
940 // Do not try to remove |form|. It is a modified copy of a password stored
941 // for a different origin, and it is not contained in the database.
944 // Remove a login by UNIQUE-constrained fields.
945 sql::Statement
s(db_
.GetCachedStatement(SQL_FROM_HERE
,
946 "DELETE FROM logins WHERE "
947 "origin_url = ? AND "
948 "username_element = ? AND "
949 "username_value = ? AND "
950 "password_element = ? AND "
951 "submit_element = ? AND "
952 "signon_realm = ? "));
953 s
.BindString(0, form
.origin
.spec());
954 s
.BindString16(1, form
.username_element
);
955 s
.BindString16(2, form
.username_value
);
956 s
.BindString16(3, form
.password_element
);
957 s
.BindString16(4, form
.submit_element
);
958 s
.BindString(5, form
.signon_realm
);
960 return s
.Run() && db_
.GetLastChangeCount() > 0;
963 bool LoginDatabase::RemoveLoginsCreatedBetween(base::Time delete_begin
,
964 base::Time delete_end
) {
965 sql::Statement
s(db_
.GetCachedStatement(SQL_FROM_HERE
,
966 "DELETE FROM logins WHERE "
967 "date_created >= ? AND date_created < ?"));
968 s
.BindInt64(0, delete_begin
.ToInternalValue());
969 s
.BindInt64(1, delete_end
.is_null() ? std::numeric_limits
<int64
>::max()
970 : delete_end
.ToInternalValue());
975 bool LoginDatabase::RemoveLoginsSyncedBetween(base::Time delete_begin
,
976 base::Time delete_end
) {
977 sql::Statement
s(db_
.GetCachedStatement(
979 "DELETE FROM logins WHERE date_synced >= ? AND date_synced < ?"));
980 s
.BindInt64(0, delete_begin
.ToInternalValue());
982 delete_end
.is_null() ? base::Time::Max().ToInternalValue()
983 : delete_end
.ToInternalValue());
989 LoginDatabase::EncryptionResult
LoginDatabase::InitPasswordFormFromStatement(
992 std::string encrypted_password
;
993 s
.ColumnBlobAsString(COLUMN_PASSWORD_VALUE
, &encrypted_password
);
994 base::string16 decrypted_password
;
995 EncryptionResult encryption_result
=
996 DecryptedString(encrypted_password
, &decrypted_password
);
997 if (encryption_result
!= ENCRYPTION_RESULT_SUCCESS
)
998 return encryption_result
;
1000 std::string tmp
= s
.ColumnString(COLUMN_ORIGIN_URL
);
1001 form
->origin
= GURL(tmp
);
1002 tmp
= s
.ColumnString(COLUMN_ACTION_URL
);
1003 form
->action
= GURL(tmp
);
1004 form
->username_element
= s
.ColumnString16(COLUMN_USERNAME_ELEMENT
);
1005 form
->username_value
= s
.ColumnString16(COLUMN_USERNAME_VALUE
);
1006 form
->password_element
= s
.ColumnString16(COLUMN_PASSWORD_ELEMENT
);
1007 form
->password_value
= decrypted_password
;
1008 form
->submit_element
= s
.ColumnString16(COLUMN_SUBMIT_ELEMENT
);
1009 tmp
= s
.ColumnString(COLUMN_SIGNON_REALM
);
1010 form
->signon_realm
= tmp
;
1011 form
->ssl_valid
= (s
.ColumnInt(COLUMN_SSL_VALID
) > 0);
1012 form
->preferred
= (s
.ColumnInt(COLUMN_PREFERRED
) > 0);
1013 form
->date_created
=
1014 base::Time::FromInternalValue(s
.ColumnInt64(COLUMN_DATE_CREATED
));
1015 form
->blacklisted_by_user
= (s
.ColumnInt(COLUMN_BLACKLISTED_BY_USER
) > 0);
1016 int scheme_int
= s
.ColumnInt(COLUMN_SCHEME
);
1017 DCHECK((scheme_int
>= 0) && (scheme_int
<= PasswordForm::SCHEME_OTHER
));
1018 form
->scheme
= static_cast<PasswordForm::Scheme
>(scheme_int
);
1019 int type_int
= s
.ColumnInt(COLUMN_PASSWORD_TYPE
);
1020 DCHECK(type_int
>= 0 && type_int
<= PasswordForm::TYPE_GENERATED
);
1021 form
->type
= static_cast<PasswordForm::Type
>(type_int
);
1022 if (s
.ColumnByteLength(COLUMN_POSSIBLE_USERNAMES
)) {
1023 base::Pickle
pickle(
1024 static_cast<const char*>(s
.ColumnBlob(COLUMN_POSSIBLE_USERNAMES
)),
1025 s
.ColumnByteLength(COLUMN_POSSIBLE_USERNAMES
));
1026 form
->other_possible_usernames
= DeserializeVector(pickle
);
1028 form
->times_used
= s
.ColumnInt(COLUMN_TIMES_USED
);
1029 if (s
.ColumnByteLength(COLUMN_FORM_DATA
)) {
1030 base::Pickle
form_data_pickle(
1031 static_cast<const char*>(s
.ColumnBlob(COLUMN_FORM_DATA
)),
1032 s
.ColumnByteLength(COLUMN_FORM_DATA
));
1033 base::PickleIterator
form_data_iter(form_data_pickle
);
1035 autofill::DeserializeFormData(&form_data_iter
, &form
->form_data
);
1036 metrics_util::FormDeserializationStatus status
=
1037 success
? metrics_util::LOGIN_DATABASE_SUCCESS
1038 : metrics_util::LOGIN_DATABASE_FAILURE
;
1039 metrics_util::LogFormDataDeserializationStatus(status
);
1042 base::Time::FromInternalValue(s
.ColumnInt64(COLUMN_DATE_SYNCED
));
1043 form
->display_name
= s
.ColumnString16(COLUMN_DISPLAY_NAME
);
1044 form
->icon_url
= GURL(s
.ColumnString(COLUMN_ICON_URL
));
1045 form
->federation_url
= GURL(s
.ColumnString(COLUMN_FEDERATION_URL
));
1046 form
->skip_zero_click
= (s
.ColumnInt(COLUMN_SKIP_ZERO_CLICK
) > 0);
1047 int generation_upload_status_int
=
1048 s
.ColumnInt(COLUMN_GENERATION_UPLOAD_STATUS
);
1049 DCHECK(generation_upload_status_int
>= 0 &&
1050 generation_upload_status_int
<= PasswordForm::UNKNOWN_STATUS
);
1051 form
->generation_upload_status
=
1052 static_cast<PasswordForm::GenerationUploadStatus
>(
1053 generation_upload_status_int
);
1054 return ENCRYPTION_RESULT_SUCCESS
;
1057 bool LoginDatabase::GetLogins(
1058 const PasswordForm
& form
,
1059 ScopedVector
<autofill::PasswordForm
>* forms
) const {
1061 // You *must* change LoginTableColumns if this query changes.
1062 const std::string sql_query
=
1063 "SELECT origin_url, action_url, "
1064 "username_element, username_value, "
1065 "password_element, password_value, submit_element, "
1066 "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
1067 "scheme, password_type, possible_usernames, times_used, form_data, "
1068 "date_synced, display_name, icon_url, "
1069 "federation_url, skip_zero_click, generation_upload_status "
1070 "FROM logins WHERE signon_realm == ? ";
1072 const GURL
signon_realm(form
.signon_realm
);
1073 std::string registered_domain
= GetRegistryControlledDomain(signon_realm
);
1074 const bool should_PSL_matching_apply
=
1075 form
.scheme
== PasswordForm::SCHEME_HTML
&&
1076 ShouldPSLDomainMatchingApply(registered_domain
);
1077 // PSL matching only applies to HTML forms.
1078 if (should_PSL_matching_apply
) {
1079 // We are extending the original SQL query with one that includes more
1080 // possible matches based on public suffix domain matching. Using a regexp
1081 // here is just an optimization to not have to parse all the stored entries
1082 // in the |logins| table. The result (scheme, domain and port) is verified
1083 // further down using GURL. See the functions SchemeMatches,
1084 // RegistryControlledDomainMatches and PortMatches.
1085 const std::string extended_sql_query
=
1086 sql_query
+ "OR signon_realm REGEXP ? ";
1087 // TODO(nyquist) Re-enable usage of GetCachedStatement when
1088 // http://crbug.com/248608 is fixed.
1089 s
.Assign(db_
.GetUniqueStatement(extended_sql_query
.c_str()));
1090 // We need to escape . in the domain. Since the domain has already been
1091 // sanitized using GURL, we do not need to escape any other characters.
1092 base::ReplaceChars(registered_domain
, ".", "\\.", ®istered_domain
);
1093 std::string scheme
= signon_realm
.scheme();
1094 // We need to escape . in the scheme. Since the scheme has already been
1095 // sanitized using GURL, we do not need to escape any other characters.
1096 // The scheme soap.beep is an example with '.'.
1097 base::ReplaceChars(scheme
, ".", "\\.", &scheme
);
1098 const std::string port
= signon_realm
.port();
1099 // For a signon realm such as http://foo.bar/, this regexp will match
1100 // domains on the form http://foo.bar/, http://www.foo.bar/,
1101 // http://www.mobile.foo.bar/. It will not match http://notfoo.bar/.
1102 // The scheme and port has to be the same as the observed form.
1103 std::string regexp
= "^(" + scheme
+ ":\\/\\/)([\\w-]+\\.)*" +
1104 registered_domain
+ "(:" + port
+ ")?\\/$";
1105 s
.BindString(0, form
.signon_realm
);
1106 s
.BindString(1, regexp
);
1108 UMA_HISTOGRAM_ENUMERATION("PasswordManager.PslDomainMatchTriggering",
1109 PSL_DOMAIN_MATCH_NOT_USED
,
1110 PSL_DOMAIN_MATCH_COUNT
);
1111 s
.Assign(db_
.GetCachedStatement(SQL_FROM_HERE
, sql_query
.c_str()));
1112 s
.BindString(0, form
.signon_realm
);
1115 return StatementToForms(&s
, should_PSL_matching_apply
? &form
: nullptr,
1119 bool LoginDatabase::GetLoginsCreatedBetween(
1120 const base::Time begin
,
1121 const base::Time end
,
1122 ScopedVector
<autofill::PasswordForm
>* forms
) const {
1124 sql::Statement
s(db_
.GetCachedStatement(
1126 "SELECT origin_url, action_url, "
1127 "username_element, username_value, "
1128 "password_element, password_value, submit_element, "
1129 "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
1130 "scheme, password_type, possible_usernames, times_used, form_data, "
1131 "date_synced, display_name, icon_url, "
1132 "federation_url, skip_zero_click, generation_upload_status FROM logins "
1133 "WHERE date_created >= ? AND date_created < ?"
1134 "ORDER BY origin_url"));
1135 s
.BindInt64(0, begin
.ToInternalValue());
1136 s
.BindInt64(1, end
.is_null() ? std::numeric_limits
<int64
>::max()
1137 : end
.ToInternalValue());
1139 return StatementToForms(&s
, nullptr, forms
);
1142 bool LoginDatabase::GetLoginsSyncedBetween(
1143 const base::Time begin
,
1144 const base::Time end
,
1145 ScopedVector
<autofill::PasswordForm
>* forms
) const {
1147 sql::Statement
s(db_
.GetCachedStatement(
1149 "SELECT origin_url, action_url, username_element, username_value, "
1150 "password_element, password_value, submit_element, signon_realm, "
1151 "ssl_valid, preferred, date_created, blacklisted_by_user, "
1152 "scheme, password_type, possible_usernames, times_used, form_data, "
1153 "date_synced, display_name, icon_url, "
1154 "federation_url, skip_zero_click, generation_upload_status FROM logins "
1155 "WHERE date_synced >= ? AND date_synced < ?"
1156 "ORDER BY origin_url"));
1157 s
.BindInt64(0, begin
.ToInternalValue());
1159 end
.is_null() ? base::Time::Max().ToInternalValue()
1160 : end
.ToInternalValue());
1162 return StatementToForms(&s
, nullptr, forms
);
1165 bool LoginDatabase::GetAutofillableLogins(
1166 ScopedVector
<autofill::PasswordForm
>* forms
) const {
1167 return GetAllLoginsWithBlacklistSetting(false, forms
);
1170 bool LoginDatabase::GetBlacklistLogins(
1171 ScopedVector
<autofill::PasswordForm
>* forms
) const {
1172 return GetAllLoginsWithBlacklistSetting(true, forms
);
1175 bool LoginDatabase::GetAllLoginsWithBlacklistSetting(
1177 ScopedVector
<autofill::PasswordForm
>* forms
) const {
1179 // You *must* change LoginTableColumns if this query changes.
1180 sql::Statement
s(db_
.GetCachedStatement(
1182 "SELECT origin_url, action_url, "
1183 "username_element, username_value, "
1184 "password_element, password_value, submit_element, "
1185 "signon_realm, ssl_valid, preferred, date_created, blacklisted_by_user, "
1186 "scheme, password_type, possible_usernames, times_used, form_data, "
1187 "date_synced, display_name, icon_url, "
1188 "federation_url, skip_zero_click, generation_upload_status FROM logins "
1189 "WHERE blacklisted_by_user == ? ORDER BY origin_url"));
1190 s
.BindInt(0, blacklisted
? 1 : 0);
1192 return StatementToForms(&s
, nullptr, forms
);
1195 bool LoginDatabase::DeleteAndRecreateDatabaseFile() {
1196 DCHECK(db_
.is_open());
1197 meta_table_
.Reset();
1199 sql::Connection::Delete(db_path_
);
1204 bool LoginDatabase::StatementToForms(
1205 sql::Statement
* statement
,
1206 const autofill::PasswordForm
* psl_match
,
1207 ScopedVector
<autofill::PasswordForm
>* forms
) {
1208 PSLDomainMatchMetric psl_domain_match_metric
= PSL_DOMAIN_MATCH_NONE
;
1211 while (statement
->Step()) {
1212 scoped_ptr
<PasswordForm
> new_form(new PasswordForm());
1213 EncryptionResult result
=
1214 InitPasswordFormFromStatement(new_form
.get(), *statement
);
1215 if (result
== ENCRYPTION_RESULT_SERVICE_FAILURE
)
1217 if (result
== ENCRYPTION_RESULT_ITEM_FAILURE
)
1219 DCHECK(result
== ENCRYPTION_RESULT_SUCCESS
);
1220 if (psl_match
&& psl_match
->signon_realm
!= new_form
->signon_realm
) {
1221 if (new_form
->scheme
!= PasswordForm::SCHEME_HTML
)
1222 continue; // Ignore non-HTML matches.
1224 if (!IsPublicSuffixDomainMatch(new_form
->signon_realm
,
1225 psl_match
->signon_realm
)) {
1229 psl_domain_match_metric
= PSL_DOMAIN_MATCH_FOUND
;
1230 // This is not a perfect match, so we need to create a new valid result.
1231 // We do this by copying over origin, signon realm and action from the
1232 // observed form and setting the original signon realm to what we found
1233 // in the database. We use the fact that |original_signon_realm| is
1234 // non-empty to communicate that this match was found using public
1236 new_form
->original_signon_realm
= new_form
->signon_realm
;
1237 new_form
->origin
= psl_match
->origin
;
1238 new_form
->signon_realm
= psl_match
->signon_realm
;
1239 new_form
->action
= psl_match
->action
;
1241 forms
->push_back(new_form
.Pass());
1245 UMA_HISTOGRAM_ENUMERATION("PasswordManager.PslDomainMatchTriggering",
1246 psl_domain_match_metric
, PSL_DOMAIN_MATCH_COUNT
);
1249 if (!statement
->Succeeded())
1254 } // namespace password_manager