password_store_x.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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. 4 5#include "chrome/browser/password_manager/password_store_x.h" 6 7#include <algorithm> 8#include <map> 9#include <vector> 10 11#include "base/bind.h" 12#include "base/logging.h" 13#include "base/stl_util.h" 14#include "chrome/browser/password_manager/password_store_change.h" 15#include "chrome/browser/prefs/pref_service.h" 16#include "chrome/common/chrome_notification_types.h" 17#include "chrome/common/pref_names.h" 18#include "content/public/browser/browser_thread.h" 19#include "content/public/browser/notification_service.h" 20 21using content::BrowserThread; 22using std::vector; 23using content::PasswordForm; 24 25PasswordStoreX::PasswordStoreX(LoginDatabase* login_db, 26 Profile* profile, 27 NativeBackend* backend) 28 : PasswordStoreDefault(login_db, profile), 29 backend_(backend), migration_checked_(!backend), allow_fallback_(false) { 30} 31 32PasswordStoreX::~PasswordStoreX() {} 33 34void PasswordStoreX::AddLoginImpl(const PasswordForm& form) { 35 CheckMigration(); 36 if (use_native_backend() && backend_->AddLogin(form)) { 37 PasswordStoreChangeList changes; 38 changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form)); 39 content::NotificationService::current()->Notify( 40 chrome::NOTIFICATION_LOGINS_CHANGED, 41 content::Source<PasswordStore>(this), 42 content::Details<PasswordStoreChangeList>(&changes)); 43 allow_fallback_ = false; 44 } else if (allow_default_store()) { 45 PasswordStoreDefault::AddLoginImpl(form); 46 } 47} 48 49void PasswordStoreX::UpdateLoginImpl(const PasswordForm& form) { 50 CheckMigration(); 51 if (use_native_backend() && backend_->UpdateLogin(form)) { 52 PasswordStoreChangeList changes; 53 changes.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE, form)); 54 content::NotificationService::current()->Notify( 55 chrome::NOTIFICATION_LOGINS_CHANGED, 56 content::Source<PasswordStore>(this), 57 content::Details<PasswordStoreChangeList>(&changes)); 58 allow_fallback_ = false; 59 } else if (allow_default_store()) { 60 PasswordStoreDefault::UpdateLoginImpl(form); 61 } 62} 63 64void PasswordStoreX::RemoveLoginImpl(const PasswordForm& form) { 65 CheckMigration(); 66 if (use_native_backend() && backend_->RemoveLogin(form)) { 67 PasswordStoreChangeList changes; 68 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, form)); 69 content::NotificationService::current()->Notify( 70 chrome::NOTIFICATION_LOGINS_CHANGED, 71 content::Source<PasswordStore>(this), 72 content::Details<PasswordStoreChangeList>(&changes)); 73 allow_fallback_ = false; 74 } else if (allow_default_store()) { 75 PasswordStoreDefault::RemoveLoginImpl(form); 76 } 77} 78 79void PasswordStoreX::RemoveLoginsCreatedBetweenImpl( 80 const base::Time& delete_begin, 81 const base::Time& delete_end) { 82 CheckMigration(); 83 vector<PasswordForm*> forms; 84 if (use_native_backend() && 85 backend_->GetLoginsCreatedBetween(delete_begin, delete_end, &forms) && 86 backend_->RemoveLoginsCreatedBetween(delete_begin, delete_end)) { 87 PasswordStoreChangeList changes; 88 for (vector<PasswordForm*>::const_iterator it = forms.begin(); 89 it != forms.end(); ++it) { 90 changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, 91 **it)); 92 } 93 content::NotificationService::current()->Notify( 94 chrome::NOTIFICATION_LOGINS_CHANGED, 95 content::Source<PasswordStore>(this), 96 content::Details<PasswordStoreChangeList>(&changes)); 97 allow_fallback_ = false; 98 } else if (allow_default_store()) { 99 PasswordStoreDefault::RemoveLoginsCreatedBetweenImpl(delete_begin, 100 delete_end); 101 } 102 STLDeleteElements(&forms); 103} 104 105namespace { 106struct LoginLessThan { 107 bool operator()(const PasswordForm* a, const PasswordForm* b) { 108 return a->origin < b->origin; 109 } 110}; 111} // anonymous namespace 112 113void PasswordStoreX::SortLoginsByOrigin(NativeBackend::PasswordFormList* list) { 114 // In login_database.cc, the query has ORDER BY origin_url. Simulate that. 115 std::sort(list->begin(), list->end(), LoginLessThan()); 116} 117 118void PasswordStoreX::GetLoginsImpl(GetLoginsRequest* request, 119 const PasswordForm& form) { 120 CheckMigration(); 121 if (use_native_backend() && backend_->GetLogins(form, &request->value)) { 122 SortLoginsByOrigin(&request->value); 123 ForwardLoginsResult(request); 124 // The native backend may succeed and return no data even while locked, if 125 // the query did not match anything stored. So we continue to allow fallback 126 // until we perform a write operation, or until a read returns actual data. 127 if (request->value.size() > 0) 128 allow_fallback_ = false; 129 } else if (allow_default_store()) { 130 PasswordStoreDefault::GetLoginsImpl(request, form); 131 } else { 132 // The consumer will be left hanging unless we reply. 133 ForwardLoginsResult(request); 134 } 135} 136 137void PasswordStoreX::GetAutofillableLoginsImpl(GetLoginsRequest* request) { 138 CheckMigration(); 139 if (use_native_backend() && 140 backend_->GetAutofillableLogins(&request->value)) { 141 SortLoginsByOrigin(&request->value); 142 ForwardLoginsResult(request); 143 // See GetLoginsImpl() for why we disallow fallback conditionally here. 144 if (request->value.size() > 0) 145 allow_fallback_ = false; 146 } else if (allow_default_store()) { 147 PasswordStoreDefault::GetAutofillableLoginsImpl(request); 148 } else { 149 // The consumer will be left hanging unless we reply. 150 ForwardLoginsResult(request); 151 } 152} 153 154void PasswordStoreX::GetBlacklistLoginsImpl(GetLoginsRequest* request) { 155 CheckMigration(); 156 if (use_native_backend() && 157 backend_->GetBlacklistLogins(&request->value)) { 158 SortLoginsByOrigin(&request->value); 159 ForwardLoginsResult(request); 160 // See GetLoginsImpl() for why we disallow fallback conditionally here. 161 if (request->value.size() > 0) 162 allow_fallback_ = false; 163 } else if (allow_default_store()) { 164 PasswordStoreDefault::GetBlacklistLoginsImpl(request); 165 } else { 166 // The consumer will be left hanging unless we reply. 167 ForwardLoginsResult(request); 168 } 169} 170 171bool PasswordStoreX::FillAutofillableLogins(vector<PasswordForm*>* forms) { 172 CheckMigration(); 173 if (use_native_backend() && backend_->GetAutofillableLogins(forms)) { 174 // See GetLoginsImpl() for why we disallow fallback conditionally here. 175 if (forms->size() > 0) 176 allow_fallback_ = false; 177 return true; 178 } 179 if (allow_default_store()) 180 return PasswordStoreDefault::FillAutofillableLogins(forms); 181 return false; 182} 183 184bool PasswordStoreX::FillBlacklistLogins(vector<PasswordForm*>* forms) { 185 CheckMigration(); 186 if (use_native_backend() && backend_->GetBlacklistLogins(forms)) { 187 // See GetLoginsImpl() for why we disallow fallback conditionally here. 188 if (forms->size() > 0) 189 allow_fallback_ = false; 190 return true; 191 } 192 if (allow_default_store()) 193 return PasswordStoreDefault::FillBlacklistLogins(forms); 194 return false; 195} 196 197void PasswordStoreX::CheckMigration() { 198 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 199 if (migration_checked_ || !backend_.get()) 200 return; 201 migration_checked_ = true; 202 ssize_t migrated = MigrateLogins(); 203 if (migrated > 0) { 204 VLOG(1) << "Migrated " << migrated << " passwords to native store."; 205 } else if (migrated == 0) { 206 // As long as we are able to migrate some passwords, we know the native 207 // store is working. But if there is nothing to migrate, the "migration" 208 // can succeed even when the native store would fail. In this case we 209 // allow a later fallback to the default store. Once any later operation 210 // succeeds on the native store, we will no longer allow fallback. 211 allow_fallback_ = true; 212 } else { 213 LOG(WARNING) << "Native password store migration failed! " << 214 "Falling back on default (unencrypted) store."; 215 backend_.reset(NULL); 216 } 217} 218 219bool PasswordStoreX::allow_default_store() { 220 if (allow_fallback_) { 221 LOG(WARNING) << "Native password store failed! " << 222 "Falling back on default (unencrypted) store."; 223 backend_.reset(NULL); 224 // Don't warn again. We'll use the default store because backend_ is NULL. 225 allow_fallback_ = false; 226 } 227 return !backend_.get(); 228} 229 230ssize_t PasswordStoreX::MigrateLogins() { 231 DCHECK(backend_.get()); 232 vector<PasswordForm*> forms; 233 bool ok = PasswordStoreDefault::FillAutofillableLogins(&forms) && 234 PasswordStoreDefault::FillBlacklistLogins(&forms); 235 if (ok) { 236 // We add all the passwords (and blacklist entries) to the native backend 237 // before attempting to remove any from the login database, to make sure we 238 // don't somehow end up with some of the passwords in one store and some in 239 // another. We'll always have at least one intact store this way. 240 for (size_t i = 0; i < forms.size(); ++i) { 241 if (!backend_->AddLogin(*forms[i])) { 242 ok = false; 243 break; 244 } 245 } 246 if (ok) { 247 for (size_t i = 0; i < forms.size(); ++i) { 248 // If even one of these calls to RemoveLoginImpl() succeeds, then we 249 // should prefer the native backend to the now-incomplete login 250 // database. Thus we want to return a success status even in the case 251 // where some fail. The only real problem with this is that we might 252 // leave passwords in the login database and never come back to clean 253 // them out if any of these calls do fail. 254 PasswordStoreDefault::RemoveLoginImpl(*forms[i]); 255 } 256 // Finally, delete the database file itself. We remove the passwords from 257 // it before deleting the file just in case there is some problem deleting 258 // the file (e.g. directory is not writable, but file is), which would 259 // otherwise cause passwords to re-migrate next (or maybe every) time. 260 DeleteAndRecreateDatabaseFile(); 261 } 262 } 263 ssize_t result = ok ? forms.size() : -1; 264 STLDeleteElements(&forms); 265 return result; 266} 267 268#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) && defined(OS_POSIX) 269// static 270void PasswordStoreX::RegisterUserPrefs(PrefService* prefs) { 271 // Normally we should be on the UI thread here, but in tests we might not. 272 prefs->RegisterBooleanPref(prefs::kPasswordsUseLocalProfileId, 273 false, // default: passwords don't use local ids 274 PrefService::UNSYNCABLE_PREF); 275} 276 277// static 278bool PasswordStoreX::PasswordsUseLocalProfileId(PrefService* prefs) { 279 // Normally we should be on the UI thread here, but in tests we might not. 280 return prefs->GetBoolean(prefs::kPasswordsUseLocalProfileId); 281} 282 283namespace { 284// This function is a hack to do something not entirely thread safe: the pref 285// service comes from the UI thread, but it's not ref counted. We keep a pointer 286// to it on the DB thread, and need to invoke a method on the UI thread. This 287// function does that for us without requiring ref counting the pref service. 288// TODO(mdm): Fix this if it becomes a problem. Given that this function will 289// be called once ever per profile, it probably will not cause a problem... 290void UISetPasswordsUseLocalProfileId(PrefService* prefs) { 291 prefs->SetBoolean(prefs::kPasswordsUseLocalProfileId, true); 292} 293} // anonymous namespace 294 295// static 296void PasswordStoreX::SetPasswordsUseLocalProfileId(PrefService* prefs) { 297 // This method should work on any thread, but we expect the DB thread. 298 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 299 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 300 base::Bind(&UISetPasswordsUseLocalProfileId, prefs)); 301} 302#endif // !defined(OS_MACOSX) && !defined(OS_CHROMEOS) && defined(OS_POSIX) 303