| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 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_proxy_mac.h" | |
| 6 | |
| 7 #include <utility> | |
| 8 | |
| 9 #include "base/files/scoped_temp_dir.h" | |
| 10 #include "base/macros.h" | |
| 11 #include "base/memory/ptr_util.h" | |
| 12 #include "base/run_loop.h" | |
| 13 #include "base/scoped_observer.h" | |
| 14 #include "base/strings/utf_string_conversions.h" | |
| 15 #include "base/test/histogram_tester.h" | |
| 16 #include "chrome/browser/prefs/browser_prefs.h" | |
| 17 #include "components/os_crypt/os_crypt_mocker.h" | |
| 18 #include "components/password_manager/core/browser/login_database.h" | |
| 19 #include "components/password_manager/core/browser/password_manager_test_utils.h
" | |
| 20 #include "components/password_manager/core/browser/password_store_consumer.h" | |
| 21 #include "components/password_manager/core/common/password_manager_pref_names.h" | |
| 22 #include "components/sync_preferences/testing_pref_service_syncable.h" | |
| 23 #include "content/public/browser/browser_thread.h" | |
| 24 #include "content/public/test/test_browser_thread_bundle.h" | |
| 25 #include "testing/gmock/include/gmock/gmock.h" | |
| 26 #include "testing/gtest/include/gtest/gtest.h" | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 using autofill::PasswordForm; | |
| 31 using content::BrowserThread; | |
| 32 using password_manager::MigrationStatus; | |
| 33 using password_manager::PasswordStore; | |
| 34 using password_manager::PasswordStoreChange; | |
| 35 using password_manager::PasswordStoreChangeList; | |
| 36 using testing::_; | |
| 37 using testing::ElementsAre; | |
| 38 using testing::IsEmpty; | |
| 39 using testing::Pointee; | |
| 40 | |
| 41 class MockPasswordStoreConsumer | |
| 42 : public password_manager::PasswordStoreConsumer { | |
| 43 public: | |
| 44 MockPasswordStoreConsumer() = default; | |
| 45 | |
| 46 void WaitForResult() { | |
| 47 base::RunLoop run_loop; | |
| 48 nested_loop_ = &run_loop; | |
| 49 run_loop.Run(); | |
| 50 nested_loop_ = nullptr; | |
| 51 } | |
| 52 | |
| 53 const std::vector<std::unique_ptr<PasswordForm>>& forms() const { | |
| 54 return forms_; | |
| 55 } | |
| 56 | |
| 57 private: | |
| 58 void OnGetPasswordStoreResults( | |
| 59 std::vector<std::unique_ptr<PasswordForm>> results) override { | |
| 60 forms_.swap(results); | |
| 61 if (nested_loop_) | |
| 62 nested_loop_->Quit(); | |
| 63 } | |
| 64 | |
| 65 std::vector<std::unique_ptr<PasswordForm>> forms_; | |
| 66 base::RunLoop* nested_loop_ = nullptr; | |
| 67 | |
| 68 DISALLOW_COPY_AND_ASSIGN(MockPasswordStoreConsumer); | |
| 69 }; | |
| 70 | |
| 71 class MockPasswordStoreObserver | |
| 72 : public password_manager::PasswordStore::Observer { | |
| 73 public: | |
| 74 explicit MockPasswordStoreObserver(PasswordStoreProxyMac* password_store) | |
| 75 : guard_(this) { | |
| 76 guard_.Add(password_store); | |
| 77 } | |
| 78 MOCK_METHOD1(OnLoginsChanged, | |
| 79 void(const password_manager::PasswordStoreChangeList& changes)); | |
| 80 | |
| 81 private: | |
| 82 ScopedObserver<PasswordStoreProxyMac, MockPasswordStoreObserver> guard_; | |
| 83 | |
| 84 DISALLOW_COPY_AND_ASSIGN(MockPasswordStoreObserver); | |
| 85 }; | |
| 86 | |
| 87 // A mock LoginDatabase that simulates a failing Init() method. | |
| 88 class BadLoginDatabase : public password_manager::LoginDatabase { | |
| 89 public: | |
| 90 BadLoginDatabase() : password_manager::LoginDatabase(base::FilePath()) {} | |
| 91 ~BadLoginDatabase() override {} | |
| 92 | |
| 93 // LoginDatabase: | |
| 94 bool Init() override { return false; } | |
| 95 | |
| 96 private: | |
| 97 DISALLOW_COPY_AND_ASSIGN(BadLoginDatabase); | |
| 98 }; | |
| 99 | |
| 100 class PasswordStoreProxyMacTest | |
| 101 : public testing::TestWithParam<MigrationStatus> { | |
| 102 public: | |
| 103 PasswordStoreProxyMacTest(); | |
| 104 ~PasswordStoreProxyMacTest() override; | |
| 105 | |
| 106 void CreateAndInitPasswordStore( | |
| 107 std::unique_ptr<password_manager::LoginDatabase> login_db); | |
| 108 | |
| 109 void ClosePasswordStore(); | |
| 110 | |
| 111 // Do a store-level query to wait for all the previously enqueued operations | |
| 112 // to finish. | |
| 113 void FinishAsyncProcessing(); | |
| 114 | |
| 115 // Add/Update/Remove |form| and verify the operation succeeded. | |
| 116 void AddForm(const PasswordForm& form); | |
| 117 void UpdateForm(const PasswordForm& form); | |
| 118 void RemoveForm(const PasswordForm& form); | |
| 119 | |
| 120 base::FilePath test_login_db_file_path() const; | |
| 121 | |
| 122 // Returns the expected migration status after the password store was inited. | |
| 123 MigrationStatus GetTargetStatus() const; | |
| 124 | |
| 125 password_manager::LoginDatabase* login_db() const { | |
| 126 return store_->login_metadata_db(); | |
| 127 } | |
| 128 | |
| 129 PasswordStoreProxyMac* store() { return store_.get(); } | |
| 130 | |
| 131 protected: | |
| 132 content::TestBrowserThreadBundle ui_thread_; | |
| 133 | |
| 134 base::ScopedTempDir db_dir_; | |
| 135 scoped_refptr<PasswordStoreProxyMac> store_; | |
| 136 sync_preferences::TestingPrefServiceSyncable testing_prefs_; | |
| 137 }; | |
| 138 | |
| 139 PasswordStoreProxyMacTest::PasswordStoreProxyMacTest() { | |
| 140 EXPECT_TRUE(db_dir_.CreateUniqueTempDir()); | |
| 141 chrome::RegisterUserProfilePrefs(testing_prefs_.registry()); | |
| 142 testing_prefs_.SetInteger(password_manager::prefs::kKeychainMigrationStatus, | |
| 143 static_cast<int>(GetParam())); | |
| 144 // Ensure that LoginDatabase will use the mock keychain if it needs to | |
| 145 // encrypt/decrypt a password. | |
| 146 OSCryptMocker::SetUpWithSingleton(); | |
| 147 } | |
| 148 | |
| 149 PasswordStoreProxyMacTest::~PasswordStoreProxyMacTest() { | |
| 150 ClosePasswordStore(); | |
| 151 OSCryptMocker::TearDown(); | |
| 152 } | |
| 153 | |
| 154 void PasswordStoreProxyMacTest::CreateAndInitPasswordStore( | |
| 155 std::unique_ptr<password_manager::LoginDatabase> login_db) { | |
| 156 store_ = new PasswordStoreProxyMac( | |
| 157 BrowserThread::GetTaskRunnerForThread(BrowserThread::UI), | |
| 158 std::move(login_db), &testing_prefs_); | |
| 159 ASSERT_TRUE(store_->Init(syncer::SyncableService::StartSyncFlare(), nullptr)); | |
| 160 } | |
| 161 | |
| 162 void PasswordStoreProxyMacTest::ClosePasswordStore() { | |
| 163 if (!store_) | |
| 164 return; | |
| 165 store_->ShutdownOnUIThread(); | |
| 166 EXPECT_FALSE(store_->GetBackgroundTaskRunner()); | |
| 167 store_ = nullptr; | |
| 168 } | |
| 169 | |
| 170 void PasswordStoreProxyMacTest::FinishAsyncProcessing() { | |
| 171 // Do a store-level query to wait for all the previously enqueued operations | |
| 172 // to finish. | |
| 173 MockPasswordStoreConsumer consumer; | |
| 174 store_->GetLogins({PasswordForm::SCHEME_HTML, std::string(), GURL()}, | |
| 175 &consumer); | |
| 176 consumer.WaitForResult(); | |
| 177 } | |
| 178 | |
| 179 base::FilePath PasswordStoreProxyMacTest::test_login_db_file_path() const { | |
| 180 return db_dir_.GetPath().Append(FILE_PATH_LITERAL("login.db")); | |
| 181 } | |
| 182 | |
| 183 MigrationStatus PasswordStoreProxyMacTest::GetTargetStatus() const { | |
| 184 if (GetParam() == MigrationStatus::NOT_STARTED || | |
| 185 GetParam() == MigrationStatus::FAILED_ONCE || | |
| 186 GetParam() == MigrationStatus::FAILED_TWICE) { | |
| 187 return MigrationStatus::MIGRATION_STOPPED; | |
| 188 } | |
| 189 return GetParam(); | |
| 190 } | |
| 191 | |
| 192 void PasswordStoreProxyMacTest::AddForm(const PasswordForm& form) { | |
| 193 MockPasswordStoreObserver mock_observer(store()); | |
| 194 | |
| 195 password_manager::PasswordStoreChangeList list; | |
| 196 list.push_back(password_manager::PasswordStoreChange( | |
| 197 password_manager::PasswordStoreChange::ADD, form)); | |
| 198 EXPECT_CALL(mock_observer, OnLoginsChanged(list)); | |
| 199 store()->AddLogin(form); | |
| 200 FinishAsyncProcessing(); | |
| 201 } | |
| 202 | |
| 203 void PasswordStoreProxyMacTest::UpdateForm(const PasswordForm& form) { | |
| 204 MockPasswordStoreObserver mock_observer(store()); | |
| 205 | |
| 206 password_manager::PasswordStoreChangeList list; | |
| 207 list.push_back(password_manager::PasswordStoreChange( | |
| 208 password_manager::PasswordStoreChange::UPDATE, form)); | |
| 209 EXPECT_CALL(mock_observer, OnLoginsChanged(list)); | |
| 210 store()->UpdateLogin(form); | |
| 211 FinishAsyncProcessing(); | |
| 212 } | |
| 213 | |
| 214 void PasswordStoreProxyMacTest::RemoveForm(const PasswordForm& form) { | |
| 215 MockPasswordStoreObserver mock_observer(store()); | |
| 216 | |
| 217 password_manager::PasswordStoreChangeList list; | |
| 218 list.push_back(password_manager::PasswordStoreChange( | |
| 219 password_manager::PasswordStoreChange::REMOVE, form)); | |
| 220 EXPECT_CALL(mock_observer, OnLoginsChanged(list)); | |
| 221 store()->RemoveLogin(form); | |
| 222 FinishAsyncProcessing(); | |
| 223 } | |
| 224 | |
| 225 // ----------- Tests ------------- | |
| 226 | |
| 227 TEST_P(PasswordStoreProxyMacTest, Sanity) { | |
| 228 base::HistogramTester histogram_tester; | |
| 229 | |
| 230 CreateAndInitPasswordStore(base::MakeUnique<password_manager::LoginDatabase>( | |
| 231 test_login_db_file_path())); | |
| 232 FinishAsyncProcessing(); | |
| 233 ClosePasswordStore(); | |
| 234 | |
| 235 int status = testing_prefs_.GetInteger( | |
| 236 password_manager::prefs::kKeychainMigrationStatus); | |
| 237 EXPECT_EQ(static_cast<int>(GetTargetStatus()), status); | |
| 238 histogram_tester.ExpectUniqueSample( | |
| 239 "PasswordManager.KeychainMigration.Status", status, 1); | |
| 240 } | |
| 241 | |
| 242 TEST_P(PasswordStoreProxyMacTest, StartAndStop) { | |
| 243 base::HistogramTester histogram_tester; | |
| 244 // PasswordStore::ShutdownOnUIThread() immediately follows | |
| 245 // PasswordStore::Init(). The message loop isn't running in between. Anyway, | |
| 246 // PasswordStore should not collapse. | |
| 247 CreateAndInitPasswordStore(base::MakeUnique<password_manager::LoginDatabase>( | |
| 248 test_login_db_file_path())); | |
| 249 ClosePasswordStore(); | |
| 250 | |
| 251 histogram_tester.ExpectUniqueSample( | |
| 252 "PasswordManager.KeychainMigration.Status", | |
| 253 static_cast<int>(GetTargetStatus()), 1); | |
| 254 } | |
| 255 | |
| 256 TEST_P(PasswordStoreProxyMacTest, OperationsOnABadDatabaseSilentlyFail) { | |
| 257 // Verify that operations on a PasswordStore with a bad database cause no | |
| 258 // explosions, but fail without side effect, return no data and trigger no | |
| 259 // notifications. | |
| 260 CreateAndInitPasswordStore(base::MakeUnique<BadLoginDatabase>()); | |
| 261 FinishAsyncProcessing(); | |
| 262 EXPECT_FALSE(login_db()); | |
| 263 | |
| 264 // The store should outlive the observer. | |
| 265 scoped_refptr<PasswordStoreProxyMac> store_refptr = store(); | |
| 266 MockPasswordStoreObserver mock_observer(store()); | |
| 267 EXPECT_CALL(mock_observer, OnLoginsChanged(_)).Times(0); | |
| 268 | |
| 269 // Add a new autofillable login + a blacklisted login. | |
| 270 password_manager::PasswordFormData www_form_data = { | |
| 271 PasswordForm::SCHEME_HTML, | |
| 272 "http://www.facebook.com/", | |
| 273 "http://www.facebook.com/index.html", | |
| 274 "login", | |
| 275 L"username", | |
| 276 L"password", | |
| 277 L"submit", | |
| 278 L"not_joe_user", | |
| 279 L"12345", | |
| 280 true, | |
| 281 1}; | |
| 282 std::unique_ptr<PasswordForm> form = | |
| 283 CreatePasswordFormFromDataForTesting(www_form_data); | |
| 284 std::unique_ptr<PasswordForm> blacklisted_form(new PasswordForm(*form)); | |
| 285 blacklisted_form->signon_realm = "http://foo.example.com"; | |
| 286 blacklisted_form->origin = GURL("http://foo.example.com/origin"); | |
| 287 blacklisted_form->action = GURL("http://foo.example.com/action"); | |
| 288 blacklisted_form->blacklisted_by_user = true; | |
| 289 store()->AddLogin(*form); | |
| 290 store()->AddLogin(*blacklisted_form); | |
| 291 FinishAsyncProcessing(); | |
| 292 | |
| 293 // Get all logins; autofillable logins; blacklisted logins. | |
| 294 MockPasswordStoreConsumer mock_consumer; | |
| 295 store()->GetLogins(PasswordStore::FormDigest(*form), &mock_consumer); | |
| 296 mock_consumer.WaitForResult(); | |
| 297 EXPECT_THAT(mock_consumer.forms(), IsEmpty()); | |
| 298 | |
| 299 store()->GetAutofillableLogins(&mock_consumer); | |
| 300 mock_consumer.WaitForResult(); | |
| 301 EXPECT_THAT(mock_consumer.forms(), IsEmpty()); | |
| 302 | |
| 303 store()->GetBlacklistLogins(&mock_consumer); | |
| 304 mock_consumer.WaitForResult(); | |
| 305 EXPECT_THAT(mock_consumer.forms(), IsEmpty()); | |
| 306 | |
| 307 // Report metrics. | |
| 308 store()->ReportMetrics("Test Username", true); | |
| 309 FinishAsyncProcessing(); | |
| 310 | |
| 311 // Change the login. | |
| 312 form->password_value = base::ASCIIToUTF16("a different password"); | |
| 313 store()->UpdateLogin(*form); | |
| 314 FinishAsyncProcessing(); | |
| 315 | |
| 316 // Delete one login; a range of logins. | |
| 317 store()->RemoveLogin(*form); | |
| 318 store()->RemoveLoginsCreatedBetween(base::Time(), base::Time::Max(), | |
| 319 base::Closure()); | |
| 320 store()->RemoveLoginsSyncedBetween(base::Time(), base::Time::Max()); | |
| 321 FinishAsyncProcessing(); | |
| 322 | |
| 323 // Verify no notifications are fired during shutdown either. | |
| 324 ClosePasswordStore(); | |
| 325 } | |
| 326 | |
| 327 INSTANTIATE_TEST_CASE_P(, | |
| 328 PasswordStoreProxyMacTest, | |
| 329 testing::Values(MigrationStatus::NOT_STARTED, | |
| 330 MigrationStatus::MIGRATED, | |
| 331 MigrationStatus::FAILED_ONCE, | |
| 332 MigrationStatus::FAILED_TWICE, | |
| 333 MigrationStatus::MIGRATED_DELETED, | |
| 334 MigrationStatus::MIGRATED_PARTIALLY, | |
| 335 MigrationStatus::MIGRATION_STOPPED)); | |
| 336 | |
| 337 } // namespace | |
| OLD | NEW |