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 |