Home | History | Annotate | Download | only in password_manager
      1 // Copyright (c) 2010 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.
      4 
      5 #include "chrome/browser/password_manager/login_database.h"
      6 
      7 #include <algorithm>
      8 #include <limits>
      9 
     10 #include "app/sql/statement.h"
     11 #include "app/sql/transaction.h"
     12 #include "base/file_path.h"
     13 #include "base/file_util.h"
     14 #include "base/logging.h"
     15 #include "base/metrics/histogram.h"
     16 #include "base/time.h"
     17 #include "base/utf_string_conversions.h"
     18 
     19 using webkit_glue::PasswordForm;
     20 
     21 static const int kCurrentVersionNumber = 1;
     22 static const int kCompatibleVersionNumber = 1;
     23 
     24 namespace {
     25 
     26 // Convenience enum for interacting with SQL queries that use all the columns.
     27 enum LoginTableColumns {
     28   COLUMN_ORIGIN_URL = 0,
     29   COLUMN_ACTION_URL,
     30   COLUMN_USERNAME_ELEMENT,
     31   COLUMN_USERNAME_VALUE,
     32   COLUMN_PASSWORD_ELEMENT,
     33   COLUMN_PASSWORD_VALUE,
     34   COLUMN_SUBMIT_ELEMENT,
     35   COLUMN_SIGNON_REALM,
     36   COLUMN_SSL_VALID,
     37   COLUMN_PREFERRED,
     38   COLUMN_DATE_CREATED,
     39   COLUMN_BLACKLISTED_BY_USER,
     40   COLUMN_SCHEME
     41 };
     42 
     43 }  // namespace
     44 
     45 LoginDatabase::LoginDatabase() {
     46 }
     47 
     48 LoginDatabase::~LoginDatabase() {
     49 }
     50 
     51 bool LoginDatabase::Init(const FilePath& db_path) {
     52   // Set pragmas for a small, private database (based on WebDatabase).
     53   db_.set_page_size(2048);
     54   db_.set_cache_size(32);
     55   db_.set_exclusive_locking();
     56 
     57   if (!db_.Open(db_path)) {
     58     LOG(WARNING) << "Unable to open the password store database.";
     59     return false;
     60   }
     61 
     62   sql::Transaction transaction(&db_);
     63   transaction.Begin();
     64 
     65   // Check the database version.
     66   if (!meta_table_.Init(&db_, kCurrentVersionNumber,
     67                         kCompatibleVersionNumber)) {
     68     db_.Close();
     69     return false;
     70   }
     71   if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
     72     LOG(WARNING) << "Password store database is too new.";
     73     db_.Close();
     74     return false;
     75   }
     76 
     77   // Initialize the tables.
     78   if (!InitLoginsTable()) {
     79     LOG(WARNING) << "Unable to initialize the password store database.";
     80     db_.Close();
     81     return false;
     82   }
     83 
     84   // Save the path for DeleteDatabaseFile().
     85   db_path_ = db_path;
     86 
     87   // If the file on disk is an older database version, bring it up to date.
     88   MigrateOldVersionsAsNeeded();
     89 
     90   if (!transaction.Commit()) {
     91     db_.Close();
     92     return false;
     93   }
     94   return true;
     95 }
     96 
     97 void LoginDatabase::MigrateOldVersionsAsNeeded() {
     98   switch (meta_table_.GetVersionNumber()) {
     99     case kCurrentVersionNumber:
    100       // No migration needed.
    101       return;
    102   }
    103 }
    104 
    105 bool LoginDatabase::InitLoginsTable() {
    106   if (!db_.DoesTableExist("logins")) {
    107     if (!db_.Execute("CREATE TABLE logins ("
    108                      "origin_url VARCHAR NOT NULL, "
    109                      "action_url VARCHAR, "
    110                      "username_element VARCHAR, "
    111                      "username_value VARCHAR, "
    112                      "password_element VARCHAR, "
    113                      "password_value BLOB, "
    114                      "submit_element VARCHAR, "
    115                      "signon_realm VARCHAR NOT NULL,"
    116                      "ssl_valid INTEGER NOT NULL,"
    117                      "preferred INTEGER NOT NULL,"
    118                      "date_created INTEGER NOT NULL,"
    119                      "blacklisted_by_user INTEGER NOT NULL,"
    120                      "scheme INTEGER NOT NULL,"
    121                      "UNIQUE "
    122                      "(origin_url, username_element, "
    123                      "username_value, password_element, "
    124                      "submit_element, signon_realm))")) {
    125       NOTREACHED();
    126       return false;
    127     }
    128     if (!db_.Execute("CREATE INDEX logins_signon ON "
    129                      "logins (signon_realm)")) {
    130       NOTREACHED();
    131       return false;
    132     }
    133   }
    134   return true;
    135 }
    136 
    137 void LoginDatabase::ReportMetrics() {
    138   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    139       "SELECT signon_realm, COUNT(username_value) FROM logins "
    140       "GROUP BY signon_realm"));
    141   if (!s) {
    142     NOTREACHED() << "Statement prepare failed";
    143     return;
    144   }
    145 
    146   int total_accounts = 0;
    147   while (s.Step()) {
    148     int accounts_per_site = s.ColumnInt(1);
    149     total_accounts += accounts_per_site;
    150     UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.AccountsPerSite",
    151                                 accounts_per_site, 0, 32, 6);
    152   }
    153   UMA_HISTOGRAM_CUSTOM_COUNTS("PasswordManager.TotalAccounts",
    154                               total_accounts, 0, 32, 6);
    155 
    156   return;
    157 }
    158 
    159 bool LoginDatabase::AddLogin(const PasswordForm& form) {
    160   // You *must* change LoginTableColumns if this query changes.
    161   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    162       "INSERT OR REPLACE INTO logins "
    163       "(origin_url, action_url, username_element, username_value, "
    164       " password_element, password_value, submit_element, "
    165       " signon_realm, ssl_valid, preferred, date_created, "
    166       " blacklisted_by_user, scheme) "
    167       "VALUES "
    168       "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
    169   if (!s) {
    170     NOTREACHED() << "Statement prepare failed";
    171     return false;
    172   }
    173 
    174   s.BindString(COLUMN_ORIGIN_URL, form.origin.spec());
    175   s.BindString(COLUMN_ACTION_URL, form.action.spec());
    176   s.BindString16(COLUMN_USERNAME_ELEMENT, form.username_element);
    177   s.BindString16(COLUMN_USERNAME_VALUE, form.username_value);
    178   s.BindString16(COLUMN_PASSWORD_ELEMENT, form.password_element);
    179   std::string encrypted_password = EncryptedString(form.password_value);
    180   s.BindBlob(COLUMN_PASSWORD_VALUE, encrypted_password.data(),
    181               static_cast<int>(encrypted_password.length()));
    182   s.BindString16(COLUMN_SUBMIT_ELEMENT, form.submit_element);
    183   s.BindString(COLUMN_SIGNON_REALM, form.signon_realm);
    184   s.BindInt(COLUMN_SSL_VALID, form.ssl_valid);
    185   s.BindInt(COLUMN_PREFERRED, form.preferred);
    186   s.BindInt64(COLUMN_DATE_CREATED, form.date_created.ToTimeT());
    187   s.BindInt(COLUMN_BLACKLISTED_BY_USER, form.blacklisted_by_user);
    188   s.BindInt(COLUMN_SCHEME, form.scheme);
    189   if (!s.Run()) {
    190     NOTREACHED();
    191     return false;
    192   }
    193   return true;
    194 }
    195 
    196 bool LoginDatabase::UpdateLogin(const PasswordForm& form, int* items_changed) {
    197   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    198       "UPDATE logins SET "
    199       "action_url = ?, "
    200       "password_value = ?, "
    201       "ssl_valid = ?, "
    202       "preferred = ? "
    203       "WHERE origin_url = ? AND "
    204       "username_element = ? AND "
    205       "username_value = ? AND "
    206       "password_element = ? AND "
    207       "signon_realm = ?"));
    208   if (!s) {
    209     NOTREACHED() << "Statement prepare failed";
    210     return false;
    211   }
    212 
    213   s.BindString(0, form.action.spec());
    214   std::string encrypted_password = EncryptedString(form.password_value);
    215   s.BindBlob(1, encrypted_password.data(),
    216              static_cast<int>(encrypted_password.length()));
    217   s.BindInt(2, form.ssl_valid);
    218   s.BindInt(3, form.preferred);
    219   s.BindString(4, form.origin.spec());
    220   s.BindString16(5, form.username_element);
    221   s.BindString16(6, form.username_value);
    222   s.BindString16(7, form.password_element);
    223   s.BindString(8, form.signon_realm);
    224 
    225   if (!s.Run()) {
    226     NOTREACHED();
    227     return false;
    228   }
    229   if (items_changed) {
    230     *items_changed = db_.GetLastChangeCount();
    231   }
    232   return true;
    233 }
    234 
    235 bool LoginDatabase::RemoveLogin(const PasswordForm& form) {
    236   // Remove a login by UNIQUE-constrained fields.
    237   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    238       "DELETE FROM logins WHERE "
    239       "origin_url = ? AND "
    240       "username_element = ? AND "
    241       "username_value = ? AND "
    242       "password_element = ? AND "
    243       "submit_element = ? AND "
    244       "signon_realm = ? "));
    245   if (!s) {
    246     NOTREACHED() << "Statement prepare failed";
    247     return false;
    248   }
    249 
    250   s.BindString(0, form.origin.spec());
    251   s.BindString16(1, form.username_element);
    252   s.BindString16(2, form.username_value);
    253   s.BindString16(3, form.password_element);
    254   s.BindString16(4, form.submit_element);
    255   s.BindString(5, form.signon_realm);
    256 
    257   if (!s.Run()) {
    258     NOTREACHED();
    259     return false;
    260   }
    261   return true;
    262 }
    263 
    264 bool LoginDatabase::RemoveLoginsCreatedBetween(const base::Time delete_begin,
    265                                                const base::Time delete_end) {
    266   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    267       "DELETE FROM logins WHERE "
    268       "date_created >= ? AND date_created < ?"));
    269   if (!s) {
    270     NOTREACHED() << "Statement prepare failed";
    271     return false;
    272   }
    273   s.BindInt64(0, delete_begin.ToTimeT());
    274   s.BindInt64(1, delete_end.is_null() ? std::numeric_limits<int64>::max()
    275                                       : delete_end.ToTimeT());
    276 
    277   return s.Run();
    278 }
    279 
    280 void LoginDatabase::InitPasswordFormFromStatement(PasswordForm* form,
    281                                                   sql::Statement& s) const {
    282   std::string tmp = s.ColumnString(COLUMN_ORIGIN_URL);
    283   form->origin = GURL(tmp);
    284   tmp = s.ColumnString(COLUMN_ACTION_URL);
    285   form->action = GURL(tmp);
    286   form->username_element = s.ColumnString16(COLUMN_USERNAME_ELEMENT);
    287   form->username_value = s.ColumnString16(COLUMN_USERNAME_VALUE);
    288   form->password_element = s.ColumnString16(COLUMN_PASSWORD_ELEMENT);
    289   std::string encrypted_password;
    290   s.ColumnBlobAsString(COLUMN_PASSWORD_VALUE, &encrypted_password);
    291   form->password_value = DecryptedString(encrypted_password);
    292   form->submit_element = s.ColumnString16(COLUMN_SUBMIT_ELEMENT);
    293   tmp = s.ColumnString(COLUMN_SIGNON_REALM);
    294   form->signon_realm = tmp;
    295   form->ssl_valid = (s.ColumnInt(COLUMN_SSL_VALID) > 0);
    296   form->preferred = (s.ColumnInt(COLUMN_PREFERRED) > 0);
    297   form->date_created = base::Time::FromTimeT(
    298       s.ColumnInt64(COLUMN_DATE_CREATED));
    299   form->blacklisted_by_user = (s.ColumnInt(COLUMN_BLACKLISTED_BY_USER) > 0);
    300   int scheme_int = s.ColumnInt(COLUMN_SCHEME);
    301   DCHECK((scheme_int >= 0) && (scheme_int <= PasswordForm::SCHEME_OTHER));
    302   form->scheme = static_cast<PasswordForm::Scheme>(scheme_int);
    303 }
    304 
    305 bool LoginDatabase::GetLogins(const PasswordForm& form,
    306                               std::vector<PasswordForm*>* forms) const {
    307   DCHECK(forms);
    308   // You *must* change LoginTableColumns if this query changes.
    309   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    310       "SELECT origin_url, action_url, "
    311       "username_element, username_value, "
    312       "password_element, password_value, "
    313       "submit_element, signon_realm, ssl_valid, preferred, "
    314       "date_created, blacklisted_by_user, scheme FROM logins "
    315       "WHERE signon_realm == ? "));
    316   if (!s) {
    317     NOTREACHED() << "Statement prepare failed";
    318     return false;
    319   }
    320 
    321   s.BindString(0, form.signon_realm);
    322 
    323   while (s.Step()) {
    324     PasswordForm* new_form = new PasswordForm();
    325     InitPasswordFormFromStatement(new_form, s);
    326 
    327     forms->push_back(new_form);
    328   }
    329   return s.Succeeded();
    330 }
    331 
    332 bool LoginDatabase::GetLoginsCreatedBetween(
    333     const base::Time begin,
    334     const base::Time end,
    335     std::vector<webkit_glue::PasswordForm*>* forms) const {
    336   DCHECK(forms);
    337   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    338       "SELECT origin_url, action_url, "
    339       "username_element, username_value, "
    340       "password_element, password_value, "
    341       "submit_element, signon_realm, ssl_valid, preferred, "
    342       "date_created, blacklisted_by_user, scheme FROM logins "
    343       "WHERE date_created >= ? AND date_created < ?"
    344       "ORDER BY origin_url"));
    345 
    346   if (!s) {
    347     NOTREACHED() << "Statement prepare failed";
    348     return false;
    349   }
    350   s.BindInt64(0, begin.ToTimeT());
    351   s.BindInt64(1, end.is_null() ? std::numeric_limits<int64>::max()
    352                                : end.ToTimeT());
    353 
    354   while (s.Step()) {
    355     PasswordForm* new_form = new PasswordForm();
    356     InitPasswordFormFromStatement(new_form, s);
    357 
    358     forms->push_back(new_form);
    359   }
    360   return s.Succeeded();
    361 }
    362 
    363 bool LoginDatabase::GetAutofillableLogins(
    364     std::vector<PasswordForm*>* forms) const {
    365   return GetAllLoginsWithBlacklistSetting(false, forms);
    366 }
    367 
    368 bool LoginDatabase::GetBlacklistLogins(
    369     std::vector<PasswordForm*>* forms) const {
    370   return GetAllLoginsWithBlacklistSetting(true, forms);
    371 }
    372 
    373 bool LoginDatabase::GetAllLoginsWithBlacklistSetting(
    374     bool blacklisted, std::vector<PasswordForm*>* forms) const {
    375   DCHECK(forms);
    376   // You *must* change LoginTableColumns if this query changes.
    377   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    378       "SELECT origin_url, action_url, "
    379       "username_element, username_value, "
    380       "password_element, password_value, "
    381       "submit_element, signon_realm, ssl_valid, preferred, "
    382       "date_created, blacklisted_by_user, scheme FROM logins "
    383       "WHERE blacklisted_by_user == ? "
    384       "ORDER BY origin_url"));
    385 
    386   if (!s) {
    387     NOTREACHED() << "Statement prepare failed";
    388     return false;
    389   }
    390   s.BindInt(0, blacklisted ? 1 : 0);
    391 
    392   while (s.Step()) {
    393     PasswordForm* new_form = new PasswordForm();
    394     InitPasswordFormFromStatement(new_form, s);
    395 
    396     forms->push_back(new_form);
    397   }
    398   return s.Succeeded();
    399 }
    400 
    401 bool LoginDatabase::DeleteAndRecreateDatabaseFile() {
    402   DCHECK(db_.is_open());
    403   meta_table_.Reset();
    404   db_.Close();
    405   file_util::Delete(db_path_, false);
    406   return Init(db_path_);
    407 }
    408