OLD | NEW |
| (Empty) |
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_mac.h" | |
6 | |
7 #include <stddef.h> | |
8 | |
9 #include <string> | |
10 | |
11 #include "base/files/scoped_temp_dir.h" | |
12 #include "base/macros.h" | |
13 #include "base/memory/ptr_util.h" | |
14 #include "base/message_loop/message_loop.h" | |
15 #include "base/run_loop.h" | |
16 #include "base/scoped_observer.h" | |
17 #include "base/stl_util.h" | |
18 #include "base/strings/string_util.h" | |
19 #include "base/strings/utf_string_conversions.h" | |
20 #include "base/synchronization/waitable_event.h" | |
21 #include "base/test/histogram_tester.h" | |
22 #include "base/threading/thread_task_runner_handle.h" | |
23 #include "chrome/browser/password_manager/password_store_mac_internal.h" | |
24 #include "chrome/common/chrome_paths.h" | |
25 #include "components/os_crypt/os_crypt_mocker.h" | |
26 #include "components/password_manager/core/browser/login_database.h" | |
27 #include "components/password_manager/core/browser/password_manager_test_utils.h
" | |
28 #include "components/password_manager/core/browser/password_manager_util.h" | |
29 #include "components/password_manager/core/browser/password_store_consumer.h" | |
30 #include "components/password_manager/core/browser/password_store_origin_unittes
t.h" | |
31 #include "content/public/browser/browser_thread.h" | |
32 #include "content/public/test/test_browser_thread_bundle.h" | |
33 #include "content/public/test/test_utils.h" | |
34 #include "crypto/mock_apple_keychain.h" | |
35 #include "testing/gmock/include/gmock/gmock.h" | |
36 #include "testing/gtest/include/gtest/gtest.h" | |
37 #include "url/origin.h" | |
38 | |
39 using autofill::PasswordForm; | |
40 using base::ASCIIToUTF16; | |
41 using base::WideToUTF16; | |
42 using content::BrowserThread; | |
43 using crypto::MockAppleKeychain; | |
44 using internal_keychain_helpers::FormsMatchForMerge; | |
45 using internal_keychain_helpers::STRICT_FORM_MATCH; | |
46 using password_manager::CreatePasswordFormFromDataForTesting; | |
47 using password_manager::LoginDatabase; | |
48 using password_manager::PasswordFormData; | |
49 using password_manager::PasswordStore; | |
50 using password_manager::PasswordStoreChange; | |
51 using password_manager::PasswordStoreChangeList; | |
52 using password_manager::PasswordStoreConsumer; | |
53 using testing::_; | |
54 using testing::DoAll; | |
55 using testing::Invoke; | |
56 using testing::IsEmpty; | |
57 using testing::SizeIs; | |
58 using testing::WithArg; | |
59 | |
60 namespace { | |
61 | |
62 ACTION(QuitUIMessageLoop) { | |
63 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
64 base::MessageLoop::current()->QuitWhenIdle(); | |
65 } | |
66 | |
67 // From the mock's argument #0 of type const std::vector<PasswordForm*>& takes | |
68 // the first form and copies it to the form pointed to by |target_form_ptr|. | |
69 ACTION_P(SaveACopyOfFirstForm, target_form_ptr) { | |
70 ASSERT_FALSE(arg0.empty()); | |
71 *target_form_ptr = *arg0[0]; | |
72 } | |
73 | |
74 void Noop() {} | |
75 | |
76 class MockPasswordStoreConsumer : public PasswordStoreConsumer { | |
77 public: | |
78 MOCK_METHOD1(OnGetPasswordStoreResultsConstRef, | |
79 void(const std::vector<std::unique_ptr<PasswordForm>>&)); | |
80 | |
81 // GMock cannot mock methods with move-only args. | |
82 void OnGetPasswordStoreResults( | |
83 std::vector<std::unique_ptr<PasswordForm>> results) override { | |
84 OnGetPasswordStoreResultsConstRef(results); | |
85 } | |
86 }; | |
87 | |
88 // A LoginDatabase that simulates an Init() method that takes a long time. | |
89 class SlowToInitLoginDatabase : public password_manager::LoginDatabase { | |
90 public: | |
91 // Creates an instance whose Init() method will block until |event| is | |
92 // signaled. |event| must outlive |this|. | |
93 SlowToInitLoginDatabase(const base::FilePath& db_path, | |
94 base::WaitableEvent* event) | |
95 : password_manager::LoginDatabase(db_path), event_(event) {} | |
96 ~SlowToInitLoginDatabase() override {} | |
97 | |
98 // LoginDatabase: | |
99 bool Init() override { | |
100 event_->Wait(); | |
101 return password_manager::LoginDatabase::Init(); | |
102 } | |
103 | |
104 private: | |
105 base::WaitableEvent* event_; | |
106 | |
107 DISALLOW_COPY_AND_ASSIGN(SlowToInitLoginDatabase); | |
108 }; | |
109 | |
110 #pragma mark - | |
111 | |
112 // Macro to simplify calling CheckFormsAgainstExpectations with a useful label. | |
113 #define CHECK_FORMS(forms, expectations, i) \ | |
114 CheckFormsAgainstExpectations(forms, expectations, #forms, i) | |
115 | |
116 // Ensures that the data in |forms| match |expectations|, causing test failures | |
117 // for any discrepencies. | |
118 // TODO(stuartmorgan): This is current order-dependent; ideally it shouldn't | |
119 // matter if |forms| and |expectations| are scrambled. | |
120 void CheckFormsAgainstExpectations( | |
121 const std::vector<std::unique_ptr<PasswordForm>>& forms, | |
122 const std::vector<PasswordFormData*>& expectations, | |
123 | |
124 const char* forms_label, | |
125 unsigned int test_number) { | |
126 EXPECT_EQ(expectations.size(), forms.size()) << forms_label << " in test " | |
127 << test_number; | |
128 if (expectations.size() != forms.size()) | |
129 return; | |
130 | |
131 for (unsigned int i = 0; i < expectations.size(); ++i) { | |
132 SCOPED_TRACE(testing::Message() << forms_label << " in test " << test_number | |
133 << ", item " << i); | |
134 PasswordForm* form = forms[i].get(); | |
135 PasswordFormData* expectation = expectations[i]; | |
136 EXPECT_EQ(expectation->scheme, form->scheme); | |
137 EXPECT_EQ(std::string(expectation->signon_realm), form->signon_realm); | |
138 EXPECT_EQ(GURL(expectation->origin), form->origin); | |
139 EXPECT_EQ(GURL(expectation->action), form->action); | |
140 EXPECT_EQ(WideToUTF16(expectation->submit_element), form->submit_element); | |
141 EXPECT_EQ(WideToUTF16(expectation->username_element), | |
142 form->username_element); | |
143 EXPECT_EQ(WideToUTF16(expectation->password_element), | |
144 form->password_element); | |
145 if (expectation->username_value) { | |
146 EXPECT_EQ(WideToUTF16(expectation->username_value), form->username_value); | |
147 EXPECT_EQ(WideToUTF16(expectation->username_value), form->display_name); | |
148 EXPECT_TRUE(form->skip_zero_click); | |
149 if (expectation->password_value && | |
150 wcscmp(expectation->password_value, | |
151 password_manager::kTestingFederatedLoginMarker) == 0) { | |
152 EXPECT_TRUE(form->password_value.empty()); | |
153 EXPECT_EQ( | |
154 url::Origin(GURL(password_manager::kTestingFederationUrlSpec)), | |
155 form->federation_origin); | |
156 } else { | |
157 EXPECT_EQ(WideToUTF16(expectation->password_value), | |
158 form->password_value); | |
159 EXPECT_TRUE(form->federation_origin.unique()); | |
160 } | |
161 } else { | |
162 EXPECT_TRUE(form->blacklisted_by_user); | |
163 } | |
164 EXPECT_EQ(expectation->preferred, form->preferred); | |
165 EXPECT_DOUBLE_EQ(expectation->creation_time, | |
166 form->date_created.ToDoubleT()); | |
167 base::Time created = base::Time::FromDoubleT(expectation->creation_time); | |
168 EXPECT_EQ( | |
169 created + base::TimeDelta::FromDays( | |
170 password_manager::kTestingDaysAfterPasswordsAreSynced), | |
171 form->date_synced); | |
172 EXPECT_EQ(GURL(password_manager::kTestingIconUrlSpec), form->icon_url); | |
173 } | |
174 } | |
175 | |
176 PasswordStoreChangeList AddChangeForForm(const PasswordForm& form) { | |
177 return PasswordStoreChangeList( | |
178 1, PasswordStoreChange(PasswordStoreChange::ADD, form)); | |
179 } | |
180 | |
181 class PasswordStoreMacTestDelegate { | |
182 public: | |
183 PasswordStoreMacTestDelegate(); | |
184 ~PasswordStoreMacTestDelegate(); | |
185 | |
186 PasswordStoreMac* store() { return store_.get(); } | |
187 | |
188 static void FinishAsyncProcessing(); | |
189 | |
190 private: | |
191 void Initialize(); | |
192 | |
193 void ClosePasswordStore(); | |
194 | |
195 base::FilePath test_login_db_file_path() const; | |
196 | |
197 base::MessageLoopForUI message_loop_; | |
198 base::ScopedTempDir db_dir_; | |
199 std::unique_ptr<LoginDatabase> login_db_; | |
200 scoped_refptr<PasswordStoreMac> store_; | |
201 | |
202 DISALLOW_COPY_AND_ASSIGN(PasswordStoreMacTestDelegate); | |
203 }; | |
204 | |
205 PasswordStoreMacTestDelegate::PasswordStoreMacTestDelegate() { | |
206 Initialize(); | |
207 } | |
208 | |
209 PasswordStoreMacTestDelegate::~PasswordStoreMacTestDelegate() { | |
210 ClosePasswordStore(); | |
211 } | |
212 | |
213 void PasswordStoreMacTestDelegate::FinishAsyncProcessing() { | |
214 base::RunLoop().RunUntilIdle(); | |
215 } | |
216 | |
217 void PasswordStoreMacTestDelegate::Initialize() { | |
218 ASSERT_TRUE(db_dir_.CreateUniqueTempDir()); | |
219 | |
220 // Ensure that LoginDatabase will use the mock keychain if it needs to | |
221 // encrypt/decrypt a password. | |
222 OSCryptMocker::SetUpWithSingleton(); | |
223 login_db_.reset(new LoginDatabase(test_login_db_file_path())); | |
224 ASSERT_TRUE(login_db_->Init()); | |
225 | |
226 // Create and initialize the password store. | |
227 store_ = new PasswordStoreMac(base::ThreadTaskRunnerHandle::Get(), | |
228 base::ThreadTaskRunnerHandle::Get(), | |
229 base::WrapUnique(new MockAppleKeychain)); | |
230 store_->set_login_metadata_db(login_db_.get()); | |
231 store_->login_metadata_db()->set_clear_password_values(false); | |
232 } | |
233 | |
234 void PasswordStoreMacTestDelegate::ClosePasswordStore() { | |
235 store_->ShutdownOnUIThread(); | |
236 FinishAsyncProcessing(); | |
237 OSCryptMocker::TearDown(); | |
238 } | |
239 | |
240 base::FilePath PasswordStoreMacTestDelegate::test_login_db_file_path() const { | |
241 return db_dir_.GetPath().Append(FILE_PATH_LITERAL("login.db")); | |
242 } | |
243 | |
244 } // namespace | |
245 | |
246 namespace password_manager { | |
247 | |
248 INSTANTIATE_TYPED_TEST_CASE_P(Mac, | |
249 PasswordStoreOriginTest, | |
250 PasswordStoreMacTestDelegate); | |
251 | |
252 } // namespace password_manager | |
253 | |
254 #pragma mark - | |
255 | |
256 class PasswordStoreMacInternalsTest : public testing::Test { | |
257 public: | |
258 void SetUp() override { | |
259 MockAppleKeychain::KeychainTestData test_data[] = { | |
260 // Basic HTML form. | |
261 {kSecAuthenticationTypeHTMLForm, "some.domain.com", | |
262 kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z", "joe_user", | |
263 "sekrit", false}, | |
264 // HTML form with path. | |
265 {kSecAuthenticationTypeHTMLForm, "some.domain.com", | |
266 kSecProtocolTypeHTTP, "/insecure.html", 0, NULL, "19991231235959Z", | |
267 "joe_user", "sekrit", false}, | |
268 // Secure HTML form with path. | |
269 {kSecAuthenticationTypeHTMLForm, "some.domain.com", | |
270 kSecProtocolTypeHTTPS, "/secure.html", 0, NULL, "20100908070605Z", | |
271 "secure_user", "password", false}, | |
272 // True negative item. | |
273 {kSecAuthenticationTypeHTMLForm, "dont.remember.com", | |
274 kSecProtocolTypeHTTP, NULL, 0, NULL, "20000101000000Z", "", "", true}, | |
275 // De-facto negative item, type one. | |
276 {kSecAuthenticationTypeHTMLForm, "dont.remember.com", | |
277 kSecProtocolTypeHTTP, NULL, 0, NULL, "20000101000000Z", | |
278 "Password Not Stored", "", false}, | |
279 // De-facto negative item, type two. | |
280 {kSecAuthenticationTypeHTMLForm, "dont.remember.com", | |
281 kSecProtocolTypeHTTPS, NULL, 0, NULL, "20000101000000Z", | |
282 "Password Not Stored", " ", false}, | |
283 // HTTP auth basic, with port and path. | |
284 {kSecAuthenticationTypeHTTPBasic, "some.domain.com", | |
285 kSecProtocolTypeHTTP, "/insecure.html", 4567, "low_security", | |
286 "19980330100000Z", "basic_auth_user", "basic", false}, | |
287 // HTTP auth digest, secure. | |
288 {kSecAuthenticationTypeHTTPDigest, "some.domain.com", | |
289 kSecProtocolTypeHTTPS, NULL, 0, "high_security", "19980330100000Z", | |
290 "digest_auth_user", "digest", false}, | |
291 // An FTP password with an invalid date, for edge-case testing. | |
292 {kSecAuthenticationTypeDefault, "a.server.com", kSecProtocolTypeFTP, | |
293 NULL, 0, NULL, "20010203040", "abc", "123", false}, | |
294 // Password for an Android application. | |
295 {kSecAuthenticationTypeHTMLForm, "android://hash@com.domain.some/", | |
296 kSecProtocolTypeHTTPS, "", 0, NULL, "20150515141312Z", "joe_user", | |
297 "secret", false}, | |
298 }; | |
299 | |
300 keychain_ = new MockAppleKeychain(); | |
301 | |
302 for (unsigned int i = 0; i < arraysize(test_data); ++i) { | |
303 keychain_->AddTestItem(test_data[i]); | |
304 } | |
305 } | |
306 | |
307 void TearDown() override { | |
308 ExpectCreatesAndFreesBalanced(); | |
309 ExpectCreatorCodesSet(); | |
310 delete keychain_; | |
311 } | |
312 | |
313 protected: | |
314 // Causes a test failure unless everything returned from keychain_'s | |
315 // ItemCopyAttributesAndData, SearchCreateFromAttributes, and SearchCopyNext | |
316 // was correctly freed. | |
317 void ExpectCreatesAndFreesBalanced() { | |
318 EXPECT_EQ(0, keychain_->UnfreedSearchCount()); | |
319 EXPECT_EQ(0, keychain_->UnfreedKeychainItemCount()); | |
320 EXPECT_EQ(0, keychain_->UnfreedAttributeDataCount()); | |
321 } | |
322 | |
323 // Causes a test failure unless any Keychain items added during the test have | |
324 // their creator code set. | |
325 void ExpectCreatorCodesSet() { | |
326 EXPECT_TRUE(keychain_->CreatorCodesSetForAddedItems()); | |
327 } | |
328 | |
329 MockAppleKeychain* keychain_; | |
330 }; | |
331 | |
332 #pragma mark - | |
333 | |
334 TEST_F(PasswordStoreMacInternalsTest, TestKeychainToFormTranslation) { | |
335 typedef struct { | |
336 const PasswordForm::Scheme scheme; | |
337 const char* signon_realm; | |
338 const char* origin; | |
339 const wchar_t* username; // Set to NULL to check for a blacklist entry. | |
340 const wchar_t* password; | |
341 const int creation_year; | |
342 const int creation_month; | |
343 const int creation_day; | |
344 const int creation_hour; | |
345 const int creation_minute; | |
346 const int creation_second; | |
347 } TestExpectations; | |
348 | |
349 TestExpectations expected[] = { | |
350 {PasswordForm::SCHEME_HTML, "http://some.domain.com/", | |
351 "http://some.domain.com/", L"joe_user", L"sekrit", 2002, 6, 1, 17, 15, | |
352 0}, | |
353 {PasswordForm::SCHEME_HTML, "http://some.domain.com/", | |
354 "http://some.domain.com/insecure.html", L"joe_user", L"sekrit", 1999, 12, | |
355 31, 23, 59, 59}, | |
356 {PasswordForm::SCHEME_HTML, "https://some.domain.com/", | |
357 "https://some.domain.com/secure.html", L"secure_user", L"password", 2010, | |
358 9, 8, 7, 6, 5}, | |
359 {PasswordForm::SCHEME_HTML, "http://dont.remember.com/", | |
360 "http://dont.remember.com/", NULL, NULL, 2000, 1, 1, 0, 0, 0}, | |
361 {PasswordForm::SCHEME_HTML, "http://dont.remember.com/", | |
362 "http://dont.remember.com/", NULL, NULL, 2000, 1, 1, 0, 0, 0}, | |
363 {PasswordForm::SCHEME_HTML, "https://dont.remember.com/", | |
364 "https://dont.remember.com/", NULL, NULL, 2000, 1, 1, 0, 0, 0}, | |
365 {PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security", | |
366 "http://some.domain.com:4567/insecure.html", L"basic_auth_user", | |
367 L"basic", 1998, 03, 30, 10, 00, 00}, | |
368 {PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security", | |
369 "https://some.domain.com/", L"digest_auth_user", L"digest", 1998, 3, 30, | |
370 10, 0, 0}, | |
371 // This one gives us an invalid date, which we will treat as a "NULL" date | |
372 // which is 1601. | |
373 {PasswordForm::SCHEME_OTHER, "http://a.server.com/", | |
374 "http://a.server.com/", L"abc", L"123", 1601, 1, 1, 0, 0, 0}, | |
375 {PasswordForm::SCHEME_HTML, "android://hash@com.domain.some/", "", | |
376 L"joe_user", L"secret", 2015, 5, 15, 14, 13, 12}, | |
377 }; | |
378 | |
379 for (unsigned int i = 0; i < arraysize(expected); ++i) { | |
380 SCOPED_TRACE(testing::Message("In iteration ") << i); | |
381 // Create our fake KeychainItemRef; see MockAppleKeychain docs. | |
382 SecKeychainItemRef keychain_item = | |
383 reinterpret_cast<SecKeychainItemRef>(i + 1); | |
384 PasswordForm form; | |
385 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( | |
386 *keychain_, keychain_item, &form, true); | |
387 | |
388 EXPECT_TRUE(parsed); | |
389 | |
390 EXPECT_EQ(expected[i].scheme, form.scheme); | |
391 EXPECT_EQ(GURL(expected[i].origin), form.origin); | |
392 EXPECT_EQ(std::string(expected[i].signon_realm), form.signon_realm); | |
393 if (expected[i].username) { | |
394 EXPECT_EQ(WideToUTF16(expected[i].username), form.username_value); | |
395 EXPECT_EQ(WideToUTF16(expected[i].password), form.password_value); | |
396 EXPECT_FALSE(form.blacklisted_by_user); | |
397 } else { | |
398 EXPECT_TRUE(form.blacklisted_by_user); | |
399 } | |
400 base::Time::Exploded exploded_time; | |
401 form.date_created.UTCExplode(&exploded_time); | |
402 EXPECT_EQ(expected[i].creation_year, exploded_time.year); | |
403 EXPECT_EQ(expected[i].creation_month, exploded_time.month); | |
404 EXPECT_EQ(expected[i].creation_day, exploded_time.day_of_month); | |
405 EXPECT_EQ(expected[i].creation_hour, exploded_time.hour); | |
406 EXPECT_EQ(expected[i].creation_minute, exploded_time.minute); | |
407 EXPECT_EQ(expected[i].creation_second, exploded_time.second); | |
408 } | |
409 | |
410 { | |
411 // Use an invalid ref, to make sure errors are reported. | |
412 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(99); | |
413 PasswordForm form; | |
414 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( | |
415 *keychain_, keychain_item, &form, true); | |
416 EXPECT_FALSE(parsed); | |
417 } | |
418 } | |
419 | |
420 TEST_F(PasswordStoreMacInternalsTest, TestKeychainSearch) { | |
421 struct TestDataAndExpectation { | |
422 const PasswordFormData data; | |
423 const size_t expected_fill_matches; | |
424 const size_t expected_merge_matches; | |
425 }; | |
426 // Most fields are left blank because we don't care about them for searching. | |
427 TestDataAndExpectation test_data[] = { | |
428 // An HTML form we've seen. | |
429 {{PasswordForm::SCHEME_HTML, "http://some.domain.com/", NULL, NULL, NULL, | |
430 NULL, NULL, L"joe_user", NULL, false, 0}, | |
431 2, | |
432 2}, | |
433 {{PasswordForm::SCHEME_HTML, "http://some.domain.com/", NULL, NULL, NULL, | |
434 NULL, NULL, L"wrong_user", NULL, false, 0}, | |
435 2, | |
436 0}, | |
437 // An HTML form we haven't seen | |
438 {{PasswordForm::SCHEME_HTML, "http://www.unseendomain.com/", NULL, NULL, | |
439 NULL, NULL, NULL, L"joe_user", NULL, false, 0}, | |
440 0, | |
441 0}, | |
442 // Basic auth that should match. | |
443 {{PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security", | |
444 NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, 0}, | |
445 1, | |
446 1}, | |
447 // Basic auth with the wrong port. | |
448 {{PasswordForm::SCHEME_BASIC, "http://some.domain.com:1111/low_security", | |
449 NULL, NULL, NULL, NULL, NULL, L"basic_auth_user", NULL, false, 0}, | |
450 0, | |
451 0}, | |
452 // Digest auth we've saved under https, visited with http. | |
453 {{PasswordForm::SCHEME_DIGEST, "http://some.domain.com/high_security", | |
454 NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, 0}, | |
455 0, | |
456 0}, | |
457 // Digest auth that should match. | |
458 {{PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security", | |
459 NULL, NULL, NULL, NULL, NULL, L"wrong_user", NULL, false, 0}, | |
460 1, | |
461 0}, | |
462 // Digest auth with the wrong domain. | |
463 {{PasswordForm::SCHEME_DIGEST, "https://some.domain.com/other_domain", | |
464 NULL, NULL, NULL, NULL, NULL, L"digest_auth_user", NULL, false, 0}, | |
465 0, | |
466 0}, | |
467 // Android credentials (both legacy ones with origin, and without). | |
468 {{PasswordForm::SCHEME_HTML, "android://hash@com.domain.some/", | |
469 "android://hash@com.domain.some/", NULL, NULL, NULL, NULL, L"joe_user", | |
470 NULL, false, 0}, | |
471 1, | |
472 1}, | |
473 {{PasswordForm::SCHEME_HTML, "android://hash@com.domain.some/", NULL, | |
474 NULL, NULL, NULL, NULL, L"joe_user", NULL, false, 0}, | |
475 1, | |
476 1}, | |
477 // Federated logins do not have a corresponding Keychain entry, and should | |
478 // not match the username/password stored for the same application. Note | |
479 // that it will match for filling, however, because that part does not | |
480 // know | |
481 // that it is a federated login. | |
482 {{PasswordForm::SCHEME_HTML, "android://hash@com.domain.some/", NULL, | |
483 NULL, NULL, NULL, NULL, L"joe_user", | |
484 password_manager::kTestingFederatedLoginMarker, false, 0}, | |
485 1, | |
486 0}, | |
487 /// Garbage forms should have no matches. | |
488 {{PasswordForm::SCHEME_HTML, "foo/bar/baz", NULL, NULL, NULL, NULL, NULL, | |
489 NULL, NULL, false, 0}, | |
490 0, | |
491 0}, | |
492 }; | |
493 | |
494 MacKeychainPasswordFormAdapter keychain_adapter(keychain_); | |
495 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); | |
496 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); | |
497 for (unsigned int i = 0; i < arraysize(test_data); ++i) { | |
498 std::unique_ptr<PasswordForm> query_form = | |
499 CreatePasswordFormFromDataForTesting(test_data[i].data); | |
500 | |
501 // Check matches treating the form as a fill target. | |
502 std::vector<std::unique_ptr<PasswordForm>> matching_items = | |
503 keychain_adapter.PasswordsFillingForm(query_form->signon_realm, | |
504 query_form->scheme); | |
505 EXPECT_EQ(test_data[i].expected_fill_matches, matching_items.size()); | |
506 | |
507 // Check matches treating the form as a merging target. | |
508 EXPECT_EQ(test_data[i].expected_merge_matches > 0, | |
509 keychain_adapter.HasPasswordsMergeableWithForm(*query_form)); | |
510 std::vector<SecKeychainItemRef> keychain_items; | |
511 std::vector<internal_keychain_helpers::ItemFormPair> item_form_pairs = | |
512 internal_keychain_helpers:: | |
513 ExtractAllKeychainItemAttributesIntoPasswordForms(&keychain_items, | |
514 *keychain_); | |
515 matching_items = | |
516 internal_keychain_helpers::ExtractPasswordsMergeableWithForm( | |
517 *keychain_, item_form_pairs, *query_form); | |
518 EXPECT_EQ(test_data[i].expected_merge_matches, matching_items.size()); | |
519 for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin(); | |
520 i != keychain_items.end(); ++i) { | |
521 keychain_->Free(*i); | |
522 } | |
523 | |
524 // None of the pre-seeded items are owned by us, so none should match an | |
525 // owned-passwords-only search. | |
526 matching_items = owned_keychain_adapter.PasswordsFillingForm( | |
527 query_form->signon_realm, query_form->scheme); | |
528 EXPECT_EQ(0U, matching_items.size()); | |
529 } | |
530 } | |
531 | |
532 // Changes just the origin path of |form|. | |
533 static void SetPasswordFormPath(PasswordForm* form, const char* path) { | |
534 GURL::Replacements replacement; | |
535 std::string new_value(path); | |
536 replacement.SetPathStr(new_value); | |
537 form->origin = form->origin.ReplaceComponents(replacement); | |
538 } | |
539 | |
540 // Changes just the signon_realm port of |form|. | |
541 static void SetPasswordFormPort(PasswordForm* form, const char* port) { | |
542 GURL::Replacements replacement; | |
543 std::string new_value(port); | |
544 replacement.SetPortStr(new_value); | |
545 GURL signon_gurl = GURL(form->signon_realm); | |
546 form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec(); | |
547 } | |
548 | |
549 // Changes just the signon_ream auth realm of |form|. | |
550 static void SetPasswordFormRealm(PasswordForm* form, const char* realm) { | |
551 GURL::Replacements replacement; | |
552 std::string new_value(realm); | |
553 replacement.SetPathStr(new_value); | |
554 GURL signon_gurl = GURL(form->signon_realm); | |
555 form->signon_realm = signon_gurl.ReplaceComponents(replacement).spec(); | |
556 } | |
557 | |
558 TEST_F(PasswordStoreMacInternalsTest, TestKeychainExactSearch) { | |
559 MacKeychainPasswordFormAdapter keychain_adapter(keychain_); | |
560 | |
561 PasswordFormData base_form_data[] = { | |
562 {PasswordForm::SCHEME_HTML, "http://some.domain.com/", | |
563 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL, | |
564 L"joe_user", NULL, true, 0}, | |
565 {PasswordForm::SCHEME_BASIC, "http://some.domain.com:4567/low_security", | |
566 "http://some.domain.com:4567/insecure.html", NULL, NULL, NULL, NULL, | |
567 L"basic_auth_user", NULL, true, 0}, | |
568 {PasswordForm::SCHEME_DIGEST, "https://some.domain.com/high_security", | |
569 "https://some.domain.com", NULL, NULL, NULL, NULL, L"digest_auth_user", | |
570 NULL, true, 0}, | |
571 }; | |
572 | |
573 for (unsigned int i = 0; i < arraysize(base_form_data); ++i) { | |
574 // Create a base form and make sure we find a match. | |
575 std::unique_ptr<PasswordForm> base_form = | |
576 CreatePasswordFormFromDataForTesting(base_form_data[i]); | |
577 EXPECT_TRUE(keychain_adapter.HasPasswordsMergeableWithForm(*base_form)); | |
578 EXPECT_TRUE(keychain_adapter.HasPasswordExactlyMatchingForm(*base_form)); | |
579 | |
580 // Make sure that the matching isn't looser than it should be by checking | |
581 // that slightly altered forms don't match. | |
582 std::vector<std::unique_ptr<PasswordForm>> modified_forms; | |
583 | |
584 modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); | |
585 modified_forms.back()->username_value = ASCIIToUTF16("wrong_user"); | |
586 | |
587 modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); | |
588 SetPasswordFormPath(modified_forms.back().get(), "elsewhere.html"); | |
589 | |
590 modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); | |
591 modified_forms.back()->scheme = PasswordForm::SCHEME_OTHER; | |
592 | |
593 modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); | |
594 SetPasswordFormPort(modified_forms.back().get(), "1234"); | |
595 | |
596 modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); | |
597 modified_forms.back()->blacklisted_by_user = true; | |
598 | |
599 if (base_form->scheme == PasswordForm::SCHEME_BASIC || | |
600 base_form->scheme == PasswordForm::SCHEME_DIGEST) { | |
601 modified_forms.push_back(base::MakeUnique<PasswordForm>(*base_form)); | |
602 SetPasswordFormRealm(modified_forms.back().get(), "incorrect"); | |
603 } | |
604 | |
605 for (unsigned int j = 0; j < modified_forms.size(); ++j) { | |
606 bool match = | |
607 keychain_adapter.HasPasswordExactlyMatchingForm(*modified_forms[j]); | |
608 EXPECT_FALSE(match) << "In modified version " << j << " of base form " | |
609 << i; | |
610 } | |
611 } | |
612 } | |
613 | |
614 TEST_F(PasswordStoreMacInternalsTest, TestKeychainAdd) { | |
615 struct TestDataAndExpectation { | |
616 PasswordFormData data; | |
617 bool should_succeed; | |
618 }; | |
619 TestDataAndExpectation test_data[] = { | |
620 // Test a variety of scheme/port/protocol/path variations. | |
621 {{PasswordForm::SCHEME_HTML, "http://web.site.com/", | |
622 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL, | |
623 L"anonymous", L"knock-knock", false, 0}, | |
624 true}, | |
625 {{PasswordForm::SCHEME_HTML, "https://web.site.com/", | |
626 "https://web.site.com/", NULL, NULL, NULL, NULL, L"admin", L"p4ssw0rd", | |
627 false, 0}, | |
628 true}, | |
629 {{PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm", | |
630 "http://a.site.com:2222/", NULL, NULL, NULL, NULL, L"username", | |
631 L"password", false, 0}, | |
632 true}, | |
633 {{PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm", | |
634 "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL, | |
635 L"testname", L"testpass", false, 0}, | |
636 true}, | |
637 // Test that Android credentials can be stored. Also check the legacy form | |
638 // when |origin| was still filled with the Android URI (and not left | |
639 // empty). | |
640 {{PasswordForm::SCHEME_HTML, "android://hash@com.example.alpha/", "", | |
641 NULL, NULL, NULL, NULL, L"joe_user", L"password", false, 0}, | |
642 true}, | |
643 {{PasswordForm::SCHEME_HTML, "android://hash@com.example.beta/", | |
644 "android://hash@com.example.beta/", NULL, NULL, NULL, NULL, | |
645 L"jane_user", L"password2", false, 0}, | |
646 true}, | |
647 // Make sure that garbage forms are rejected. | |
648 {{PasswordForm::SCHEME_HTML, "gobbledygook", "gobbledygook", NULL, NULL, | |
649 NULL, NULL, L"anonymous", L"knock-knock", false, 0}, | |
650 false}, | |
651 // Test that failing to update a duplicate (forced using the magic failure | |
652 // password; see MockAppleKeychain::ItemModifyAttributesAndData) is | |
653 // reported. | |
654 {{PasswordForm::SCHEME_HTML, "http://some.domain.com", | |
655 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL, | |
656 L"joe_user", L"fail_me", false, 0}, | |
657 false}, | |
658 }; | |
659 | |
660 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); | |
661 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); | |
662 | |
663 for (unsigned int i = 0; i < arraysize(test_data); ++i) { | |
664 std::unique_ptr<PasswordForm> in_form = | |
665 CreatePasswordFormFromDataForTesting(test_data[i].data); | |
666 bool add_succeeded = owned_keychain_adapter.AddPassword(*in_form); | |
667 EXPECT_EQ(test_data[i].should_succeed, add_succeeded); | |
668 if (add_succeeded) { | |
669 EXPECT_TRUE( | |
670 owned_keychain_adapter.HasPasswordsMergeableWithForm(*in_form)); | |
671 EXPECT_TRUE( | |
672 owned_keychain_adapter.HasPasswordExactlyMatchingForm(*in_form)); | |
673 } | |
674 } | |
675 | |
676 // Test that adding duplicate item updates the existing item. | |
677 // TODO(engedy): Add a test to verify that updating Android credentials work. | |
678 // See: https://crbug.com/476851. | |
679 { | |
680 PasswordFormData data = {PasswordForm::SCHEME_HTML, | |
681 "http://some.domain.com", | |
682 "http://some.domain.com/insecure.html", | |
683 NULL, | |
684 NULL, | |
685 NULL, | |
686 NULL, | |
687 L"joe_user", | |
688 L"updated_password", | |
689 false, | |
690 0}; | |
691 std::unique_ptr<PasswordForm> update_form = | |
692 CreatePasswordFormFromDataForTesting(data); | |
693 MacKeychainPasswordFormAdapter keychain_adapter(keychain_); | |
694 EXPECT_TRUE(keychain_adapter.AddPassword(*update_form)); | |
695 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(2); | |
696 PasswordForm stored_form; | |
697 internal_keychain_helpers::FillPasswordFormFromKeychainItem( | |
698 *keychain_, keychain_item, &stored_form, true); | |
699 EXPECT_EQ(update_form->password_value, stored_form.password_value); | |
700 } | |
701 } | |
702 | |
703 TEST_F(PasswordStoreMacInternalsTest, TestKeychainRemove) { | |
704 struct TestDataAndExpectation { | |
705 PasswordFormData data; | |
706 bool should_succeed; | |
707 }; | |
708 TestDataAndExpectation test_data[] = { | |
709 // Test deletion of an item that we add. | |
710 {{PasswordForm::SCHEME_HTML, "http://web.site.com/", | |
711 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL, | |
712 L"anonymous", L"knock-knock", false, 0}, | |
713 true}, | |
714 // Test that Android credentials can be removed. Also check the legacy | |
715 // case when |origin| was still filled with the Android URI (and not left | |
716 // empty). | |
717 {{PasswordForm::SCHEME_HTML, "android://hash@com.example.alpha/", "", | |
718 NULL, NULL, NULL, NULL, L"joe_user", L"secret", false, 0}, | |
719 true}, | |
720 {{PasswordForm::SCHEME_HTML, "android://hash@com.example.beta/", | |
721 "android://hash@com.example.beta/", NULL, NULL, NULL, NULL, | |
722 L"jane_user", L"secret", false, 0}, | |
723 true}, | |
724 // Make sure we don't delete items we don't own. | |
725 {{PasswordForm::SCHEME_HTML, "http://some.domain.com/", | |
726 "http://some.domain.com/insecure.html", NULL, NULL, NULL, NULL, | |
727 L"joe_user", NULL, true, 0}, | |
728 false}, | |
729 }; | |
730 | |
731 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); | |
732 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); | |
733 | |
734 // Add our test items (except the last one) so that we can delete them. | |
735 for (unsigned int i = 0; i + 1 < arraysize(test_data); ++i) { | |
736 std::unique_ptr<PasswordForm> add_form = | |
737 CreatePasswordFormFromDataForTesting(test_data[i].data); | |
738 EXPECT_TRUE(owned_keychain_adapter.AddPassword(*add_form)); | |
739 } | |
740 | |
741 for (unsigned int i = 0; i < arraysize(test_data); ++i) { | |
742 std::unique_ptr<PasswordForm> form = | |
743 CreatePasswordFormFromDataForTesting(test_data[i].data); | |
744 EXPECT_EQ(test_data[i].should_succeed, | |
745 owned_keychain_adapter.RemovePassword(*form)); | |
746 | |
747 MacKeychainPasswordFormAdapter keychain_adapter(keychain_); | |
748 bool match = keychain_adapter.HasPasswordExactlyMatchingForm(*form); | |
749 EXPECT_EQ(test_data[i].should_succeed, !match); | |
750 } | |
751 } | |
752 | |
753 TEST_F(PasswordStoreMacInternalsTest, TestFormMatch) { | |
754 PasswordForm base_form; | |
755 base_form.signon_realm = std::string("http://some.domain.com/"); | |
756 base_form.origin = GURL("http://some.domain.com/page.html"); | |
757 base_form.username_value = ASCIIToUTF16("joe_user"); | |
758 | |
759 { | |
760 // Check that everything unimportant can be changed. | |
761 PasswordForm different_form(base_form); | |
762 different_form.username_element = ASCIIToUTF16("username"); | |
763 different_form.submit_element = ASCIIToUTF16("submit"); | |
764 different_form.username_element = ASCIIToUTF16("password"); | |
765 different_form.password_value = ASCIIToUTF16("sekrit"); | |
766 different_form.action = GURL("http://some.domain.com/action.cgi"); | |
767 different_form.preferred = true; | |
768 different_form.date_created = base::Time::Now(); | |
769 EXPECT_TRUE( | |
770 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); | |
771 | |
772 // Check that path differences don't prevent a match. | |
773 base_form.origin = GURL("http://some.domain.com/other_page.html"); | |
774 EXPECT_TRUE( | |
775 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); | |
776 } | |
777 | |
778 // Check that any one primary key changing is enough to prevent matching. | |
779 { | |
780 PasswordForm different_form(base_form); | |
781 different_form.scheme = PasswordForm::SCHEME_DIGEST; | |
782 EXPECT_FALSE( | |
783 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); | |
784 } | |
785 { | |
786 PasswordForm different_form(base_form); | |
787 different_form.signon_realm = std::string("http://some.domain.com:8080/"); | |
788 EXPECT_FALSE( | |
789 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); | |
790 } | |
791 { | |
792 PasswordForm different_form(base_form); | |
793 different_form.username_value = ASCIIToUTF16("john.doe"); | |
794 EXPECT_FALSE( | |
795 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); | |
796 } | |
797 { | |
798 PasswordForm different_form(base_form); | |
799 different_form.blacklisted_by_user = true; | |
800 EXPECT_FALSE( | |
801 FormsMatchForMerge(base_form, different_form, STRICT_FORM_MATCH)); | |
802 } | |
803 | |
804 // Blacklist forms should *never* match for merging, even when identical | |
805 // (and certainly not when only one is a blacklist entry). | |
806 { | |
807 PasswordForm form_a(base_form); | |
808 form_a.blacklisted_by_user = true; | |
809 PasswordForm form_b(form_a); | |
810 EXPECT_FALSE(FormsMatchForMerge(form_a, form_b, STRICT_FORM_MATCH)); | |
811 } | |
812 | |
813 // Federated login forms should never match for merging either. | |
814 { | |
815 PasswordForm form_b(base_form); | |
816 form_b.federation_origin = | |
817 url::Origin(GURL(password_manager::kTestingFederationUrlSpec)); | |
818 EXPECT_FALSE(FormsMatchForMerge(base_form, form_b, STRICT_FORM_MATCH)); | |
819 EXPECT_FALSE(FormsMatchForMerge(form_b, base_form, STRICT_FORM_MATCH)); | |
820 EXPECT_FALSE(FormsMatchForMerge(form_b, form_b, STRICT_FORM_MATCH)); | |
821 } | |
822 } | |
823 | |
824 TEST_F(PasswordStoreMacInternalsTest, TestFormMerge) { | |
825 // Set up a bunch of test data to use in varying combinations. | |
826 PasswordFormData keychain_user_1 = {PasswordForm::SCHEME_HTML, | |
827 "http://some.domain.com/", | |
828 "http://some.domain.com/", | |
829 "", | |
830 L"", | |
831 L"", | |
832 L"", | |
833 L"joe_user", | |
834 L"sekrit", | |
835 false, | |
836 1010101010}; | |
837 PasswordFormData keychain_user_1_with_path = { | |
838 PasswordForm::SCHEME_HTML, | |
839 "http://some.domain.com/", | |
840 "http://some.domain.com/page.html", | |
841 "", | |
842 L"", | |
843 L"", | |
844 L"", | |
845 L"joe_user", | |
846 L"otherpassword", | |
847 false, | |
848 1010101010}; | |
849 PasswordFormData keychain_user_2 = {PasswordForm::SCHEME_HTML, | |
850 "http://some.domain.com/", | |
851 "http://some.domain.com/", | |
852 "", | |
853 L"", | |
854 L"", | |
855 L"", | |
856 L"john.doe", | |
857 L"sesame", | |
858 false, | |
859 958739876}; | |
860 PasswordFormData keychain_blacklist = {PasswordForm::SCHEME_HTML, | |
861 "http://some.domain.com/", | |
862 "http://some.domain.com/", | |
863 "", | |
864 L"", | |
865 L"", | |
866 L"", | |
867 NULL, | |
868 NULL, | |
869 false, | |
870 1010101010}; | |
871 PasswordFormData keychain_android = {PasswordForm::SCHEME_HTML, | |
872 "android://hash@com.domain.some/", | |
873 "", | |
874 "", | |
875 L"", | |
876 L"", | |
877 L"", | |
878 L"joe_user", | |
879 L"secret", | |
880 false, | |
881 1234567890}; | |
882 | |
883 PasswordFormData db_user_1 = {PasswordForm::SCHEME_HTML, | |
884 "http://some.domain.com/", | |
885 "http://some.domain.com/", | |
886 "http://some.domain.com/action.cgi", | |
887 L"submit", | |
888 L"username", | |
889 L"password", | |
890 L"joe_user", | |
891 L"", | |
892 true, | |
893 1212121212}; | |
894 PasswordFormData db_user_1_with_path = { | |
895 PasswordForm::SCHEME_HTML, | |
896 "http://some.domain.com/", | |
897 "http://some.domain.com/page.html", | |
898 "http://some.domain.com/handlepage.cgi", | |
899 L"submit", | |
900 L"username", | |
901 L"password", | |
902 L"joe_user", | |
903 L"", | |
904 true, | |
905 1234567890}; | |
906 PasswordFormData db_user_3_with_path = { | |
907 PasswordForm::SCHEME_HTML, | |
908 "http://some.domain.com/", | |
909 "http://some.domain.com/page.html", | |
910 "http://some.domain.com/handlepage.cgi", | |
911 L"submit", | |
912 L"username", | |
913 L"password", | |
914 L"second-account", | |
915 L"", | |
916 true, | |
917 1240000000}; | |
918 PasswordFormData database_blacklist_with_path = { | |
919 PasswordForm::SCHEME_HTML, | |
920 "http://some.domain.com/", | |
921 "http://some.domain.com/path.html", | |
922 "http://some.domain.com/action.cgi", | |
923 L"submit", | |
924 L"username", | |
925 L"password", | |
926 NULL, | |
927 NULL, | |
928 true, | |
929 1212121212}; | |
930 PasswordFormData db_android = {PasswordForm::SCHEME_HTML, | |
931 "android://hash@com.domain.some/", | |
932 "android://hash@com.domain.some/", | |
933 "", | |
934 L"", | |
935 L"", | |
936 L"", | |
937 L"joe_user", | |
938 L"", | |
939 false, | |
940 1234567890}; | |
941 PasswordFormData db_federated = { | |
942 PasswordForm::SCHEME_HTML, | |
943 "android://hash@com.domain.some/", | |
944 "android://hash@com.domain.some/", | |
945 "", | |
946 L"", | |
947 L"", | |
948 L"", | |
949 L"joe_user", | |
950 password_manager::kTestingFederatedLoginMarker, | |
951 false, | |
952 3434343434}; | |
953 | |
954 PasswordFormData merged_user_1 = {PasswordForm::SCHEME_HTML, | |
955 "http://some.domain.com/", | |
956 "http://some.domain.com/", | |
957 "http://some.domain.com/action.cgi", | |
958 L"submit", | |
959 L"username", | |
960 L"password", | |
961 L"joe_user", | |
962 L"sekrit", | |
963 true, | |
964 1212121212}; | |
965 PasswordFormData merged_user_1_with_db_path = { | |
966 PasswordForm::SCHEME_HTML, | |
967 "http://some.domain.com/", | |
968 "http://some.domain.com/page.html", | |
969 "http://some.domain.com/handlepage.cgi", | |
970 L"submit", | |
971 L"username", | |
972 L"password", | |
973 L"joe_user", | |
974 L"sekrit", | |
975 true, | |
976 1234567890}; | |
977 PasswordFormData merged_user_1_with_both_paths = { | |
978 PasswordForm::SCHEME_HTML, | |
979 "http://some.domain.com/", | |
980 "http://some.domain.com/page.html", | |
981 "http://some.domain.com/handlepage.cgi", | |
982 L"submit", | |
983 L"username", | |
984 L"password", | |
985 L"joe_user", | |
986 L"otherpassword", | |
987 true, | |
988 1234567890}; | |
989 PasswordFormData merged_android = {PasswordForm::SCHEME_HTML, | |
990 "android://hash@com.domain.some/", | |
991 "android://hash@com.domain.some/", | |
992 "", | |
993 L"", | |
994 L"", | |
995 L"", | |
996 L"joe_user", | |
997 L"secret", | |
998 false, | |
999 1234567890}; | |
1000 | |
1001 // Build up the big multi-dimensional array of data sets that will actually | |
1002 // drive the test. Use vectors rather than arrays so that initialization is | |
1003 // simple. | |
1004 enum { | |
1005 KEYCHAIN_INPUT = 0, | |
1006 DATABASE_INPUT, | |
1007 MERGE_OUTPUT, | |
1008 KEYCHAIN_OUTPUT, | |
1009 DATABASE_OUTPUT, | |
1010 MERGE_IO_ARRAY_COUNT // termination marker | |
1011 }; | |
1012 const unsigned int kTestCount = 5; | |
1013 std::vector<std::vector<std::vector<PasswordFormData*>>> test_data( | |
1014 MERGE_IO_ARRAY_COUNT, std::vector<std::vector<PasswordFormData*>>( | |
1015 kTestCount, std::vector<PasswordFormData*>())); | |
1016 unsigned int current_test = 0; | |
1017 | |
1018 // Test a merge with a few accounts in both systems, with partial overlap. | |
1019 CHECK(current_test < kTestCount); | |
1020 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1); | |
1021 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_2); | |
1022 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1); | |
1023 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path); | |
1024 test_data[DATABASE_INPUT][current_test].push_back(&db_user_3_with_path); | |
1025 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1); | |
1026 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1_with_db_path); | |
1027 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_2); | |
1028 test_data[DATABASE_OUTPUT][current_test].push_back(&db_user_3_with_path); | |
1029 | |
1030 // Test a merge where Chrome has a blacklist entry, and the keychain has | |
1031 // a stored account. | |
1032 ++current_test; | |
1033 CHECK(current_test < kTestCount); | |
1034 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1); | |
1035 test_data[DATABASE_INPUT][current_test].push_back( | |
1036 &database_blacklist_with_path); | |
1037 // We expect both to be present because a blacklist could be specific to a | |
1038 // subpath, and we want access to the password on other paths. | |
1039 test_data[MERGE_OUTPUT][current_test].push_back( | |
1040 &database_blacklist_with_path); | |
1041 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_user_1); | |
1042 | |
1043 // Test a merge where Chrome has an account, and Keychain has a blacklist | |
1044 // (from another browser) and the Chrome password data. | |
1045 ++current_test; | |
1046 CHECK(current_test < kTestCount); | |
1047 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_blacklist); | |
1048 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1); | |
1049 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1); | |
1050 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1); | |
1051 test_data[KEYCHAIN_OUTPUT][current_test].push_back(&keychain_blacklist); | |
1052 | |
1053 // Test that matches are done using exact path when possible. | |
1054 ++current_test; | |
1055 CHECK(current_test < kTestCount); | |
1056 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1); | |
1057 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_user_1_with_path); | |
1058 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1); | |
1059 test_data[DATABASE_INPUT][current_test].push_back(&db_user_1_with_path); | |
1060 test_data[MERGE_OUTPUT][current_test].push_back(&merged_user_1); | |
1061 test_data[MERGE_OUTPUT][current_test].push_back( | |
1062 &merged_user_1_with_both_paths); | |
1063 | |
1064 // Test that Android credentails are matched correctly and that federated | |
1065 // credentials are not tried to be matched with a Keychain item. | |
1066 ++current_test; | |
1067 CHECK(current_test < kTestCount); | |
1068 test_data[KEYCHAIN_INPUT][current_test].push_back(&keychain_android); | |
1069 test_data[DATABASE_INPUT][current_test].push_back(&db_federated); | |
1070 test_data[DATABASE_INPUT][current_test].push_back(&db_android); | |
1071 test_data[MERGE_OUTPUT][current_test].push_back(&db_federated); | |
1072 test_data[MERGE_OUTPUT][current_test].push_back(&merged_android); | |
1073 | |
1074 for (unsigned int test_case = 0; test_case <= current_test; ++test_case) { | |
1075 std::vector<std::unique_ptr<PasswordForm>> keychain_forms; | |
1076 for (std::vector<PasswordFormData*>::iterator i = | |
1077 test_data[KEYCHAIN_INPUT][test_case].begin(); | |
1078 i != test_data[KEYCHAIN_INPUT][test_case].end(); ++i) { | |
1079 keychain_forms.push_back(CreatePasswordFormFromDataForTesting(*(*i))); | |
1080 } | |
1081 std::vector<std::unique_ptr<PasswordForm>> database_forms; | |
1082 for (std::vector<PasswordFormData*>::iterator i = | |
1083 test_data[DATABASE_INPUT][test_case].begin(); | |
1084 i != test_data[DATABASE_INPUT][test_case].end(); ++i) { | |
1085 database_forms.push_back(CreatePasswordFormFromDataForTesting(*(*i))); | |
1086 } | |
1087 | |
1088 std::vector<std::unique_ptr<PasswordForm>> merged_forms; | |
1089 internal_keychain_helpers::MergePasswordForms( | |
1090 &keychain_forms, &database_forms, &merged_forms); | |
1091 | |
1092 CHECK_FORMS(keychain_forms, test_data[KEYCHAIN_OUTPUT][test_case], | |
1093 test_case); | |
1094 CHECK_FORMS(database_forms, test_data[DATABASE_OUTPUT][test_case], | |
1095 test_case); | |
1096 CHECK_FORMS(merged_forms, test_data[MERGE_OUTPUT][test_case], test_case); | |
1097 } | |
1098 } | |
1099 | |
1100 TEST_F(PasswordStoreMacInternalsTest, TestPasswordBulkLookup) { | |
1101 PasswordFormData db_data[] = { | |
1102 {PasswordForm::SCHEME_HTML, "http://some.domain.com/", | |
1103 "http://some.domain.com/", "http://some.domain.com/action.cgi", | |
1104 L"submit", L"username", L"password", L"joe_user", L"", true, 1212121212}, | |
1105 {PasswordForm::SCHEME_HTML, "http://some.domain.com/", | |
1106 "http://some.domain.com/page.html", | |
1107 "http://some.domain.com/handlepage.cgi", L"submit", L"username", | |
1108 L"password", L"joe_user", L"", true, 1234567890}, | |
1109 {PasswordForm::SCHEME_HTML, "http://some.domain.com/", | |
1110 "http://some.domain.com/page.html", | |
1111 "http://some.domain.com/handlepage.cgi", L"submit", L"username", | |
1112 L"password", L"second-account", L"", true, 1240000000}, | |
1113 {PasswordForm::SCHEME_HTML, "http://dont.remember.com/", | |
1114 "http://dont.remember.com/", "http://dont.remember.com/handlepage.cgi", | |
1115 L"submit", L"username", L"password", L"joe_user", L"", true, 1240000000}, | |
1116 {PasswordForm::SCHEME_HTML, "http://some.domain.com/", | |
1117 "http://some.domain.com/path.html", "http://some.domain.com/action.cgi", | |
1118 L"submit", L"username", L"password", NULL, NULL, true, 1212121212}, | |
1119 }; | |
1120 std::vector<std::unique_ptr<PasswordForm>> database_forms; | |
1121 for (const PasswordFormData& form_data : db_data) { | |
1122 database_forms.push_back(CreatePasswordFormFromDataForTesting(form_data)); | |
1123 } | |
1124 std::vector<std::unique_ptr<PasswordForm>> merged_forms; | |
1125 internal_keychain_helpers::GetPasswordsForForms(*keychain_, &database_forms, | |
1126 &merged_forms); | |
1127 EXPECT_EQ(2U, database_forms.size()); | |
1128 ASSERT_EQ(3U, merged_forms.size()); | |
1129 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[0]->password_value); | |
1130 EXPECT_EQ(ASCIIToUTF16("sekrit"), merged_forms[1]->password_value); | |
1131 EXPECT_TRUE(merged_forms[2]->blacklisted_by_user); | |
1132 } | |
1133 | |
1134 TEST_F(PasswordStoreMacInternalsTest, TestBlacklistedFiltering) { | |
1135 PasswordFormData db_data[] = { | |
1136 {PasswordForm::SCHEME_HTML, "http://dont.remember.com/", | |
1137 "http://dont.remember.com/", "http://dont.remember.com/handlepage.cgi", | |
1138 L"submit", L"username", L"password", L"joe_user", L"non_empty_password", | |
1139 true, 1240000000}, | |
1140 {PasswordForm::SCHEME_HTML, "https://dont.remember.com/", | |
1141 "https://dont.remember.com/", | |
1142 "https://dont.remember.com/handlepage_secure.cgi", L"submit", | |
1143 L"username", L"password", L"joe_user", L"non_empty_password", true, | |
1144 1240000000}, | |
1145 }; | |
1146 std::vector<std::unique_ptr<PasswordForm>> database_forms; | |
1147 for (const PasswordFormData& form_data : db_data) { | |
1148 database_forms.push_back(CreatePasswordFormFromDataForTesting(form_data)); | |
1149 } | |
1150 std::vector<std::unique_ptr<PasswordForm>> merged_forms; | |
1151 internal_keychain_helpers::GetPasswordsForForms(*keychain_, &database_forms, | |
1152 &merged_forms); | |
1153 EXPECT_EQ(2U, database_forms.size()); | |
1154 ASSERT_EQ(0U, merged_forms.size()); | |
1155 } | |
1156 | |
1157 TEST_F(PasswordStoreMacInternalsTest, TestFillPasswordFormFromKeychainItem) { | |
1158 // When |extract_password_data| is false, the password field must be empty, | |
1159 // and |blacklisted_by_user| must be false. | |
1160 SecKeychainItemRef keychain_item = reinterpret_cast<SecKeychainItemRef>(1); | |
1161 PasswordForm form_without_extracted_password; | |
1162 bool parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( | |
1163 *keychain_, keychain_item, &form_without_extracted_password, | |
1164 false); // Do not extract password. | |
1165 EXPECT_TRUE(parsed); | |
1166 ASSERT_TRUE(form_without_extracted_password.password_value.empty()); | |
1167 ASSERT_FALSE(form_without_extracted_password.blacklisted_by_user); | |
1168 | |
1169 // When |extract_password_data| is true and the keychain entry has a non-empty | |
1170 // password, the password field must be non-empty, and the value of | |
1171 // |blacklisted_by_user| must be false. | |
1172 keychain_item = reinterpret_cast<SecKeychainItemRef>(1); | |
1173 PasswordForm form_with_extracted_password; | |
1174 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( | |
1175 *keychain_, keychain_item, &form_with_extracted_password, | |
1176 true); // Extract password. | |
1177 EXPECT_TRUE(parsed); | |
1178 ASSERT_EQ(ASCIIToUTF16("sekrit"), | |
1179 form_with_extracted_password.password_value); | |
1180 ASSERT_FALSE(form_with_extracted_password.blacklisted_by_user); | |
1181 | |
1182 // When |extract_password_data| is true and the keychain entry has an empty | |
1183 // username and password (""), the password field must be empty, and the value | |
1184 // of |blacklisted_by_user| must be true. | |
1185 keychain_item = reinterpret_cast<SecKeychainItemRef>(4); | |
1186 PasswordForm negative_form; | |
1187 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( | |
1188 *keychain_, keychain_item, &negative_form, | |
1189 true); // Extract password. | |
1190 EXPECT_TRUE(parsed); | |
1191 ASSERT_TRUE(negative_form.username_value.empty()); | |
1192 ASSERT_TRUE(negative_form.password_value.empty()); | |
1193 ASSERT_TRUE(negative_form.blacklisted_by_user); | |
1194 | |
1195 // When |extract_password_data| is true and the keychain entry has an empty | |
1196 // password (""), the password field must be empty (""), and the value of | |
1197 // |blacklisted_by_user| must be true. | |
1198 keychain_item = reinterpret_cast<SecKeychainItemRef>(5); | |
1199 PasswordForm form_with_empty_password_a; | |
1200 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( | |
1201 *keychain_, keychain_item, &form_with_empty_password_a, | |
1202 true); // Extract password. | |
1203 EXPECT_TRUE(parsed); | |
1204 ASSERT_TRUE(form_with_empty_password_a.password_value.empty()); | |
1205 ASSERT_TRUE(form_with_empty_password_a.blacklisted_by_user); | |
1206 | |
1207 // When |extract_password_data| is true and the keychain entry has a single | |
1208 // space password (" "), the password field must be a single space (" "), and | |
1209 // the value of |blacklisted_by_user| must be true. | |
1210 keychain_item = reinterpret_cast<SecKeychainItemRef>(6); | |
1211 PasswordForm form_with_empty_password_b; | |
1212 parsed = internal_keychain_helpers::FillPasswordFormFromKeychainItem( | |
1213 *keychain_, keychain_item, &form_with_empty_password_b, | |
1214 true); // Extract password. | |
1215 EXPECT_TRUE(parsed); | |
1216 ASSERT_EQ(ASCIIToUTF16(" "), form_with_empty_password_b.password_value); | |
1217 ASSERT_TRUE(form_with_empty_password_b.blacklisted_by_user); | |
1218 } | |
1219 | |
1220 TEST_F(PasswordStoreMacInternalsTest, TestPasswordGetAll) { | |
1221 MacKeychainPasswordFormAdapter keychain_adapter(keychain_); | |
1222 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain_); | |
1223 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); | |
1224 | |
1225 // Add a few passwords of various types so that we own some. | |
1226 PasswordFormData owned_password_data[] = { | |
1227 {PasswordForm::SCHEME_HTML, "http://web.site.com/", | |
1228 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL, | |
1229 L"anonymous", L"knock-knock", false, 0}, | |
1230 {PasswordForm::SCHEME_BASIC, "http://a.site.com:2222/therealm", | |
1231 "http://a.site.com:2222/", NULL, NULL, NULL, NULL, L"username", | |
1232 L"password", false, 0}, | |
1233 {PasswordForm::SCHEME_DIGEST, "https://digest.site.com/differentrealm", | |
1234 "https://digest.site.com/secure.html", NULL, NULL, NULL, NULL, | |
1235 L"testname", L"testpass", false, 0}, | |
1236 }; | |
1237 for (unsigned int i = 0; i < arraysize(owned_password_data); ++i) { | |
1238 std::unique_ptr<PasswordForm> form = | |
1239 CreatePasswordFormFromDataForTesting(owned_password_data[i]); | |
1240 owned_keychain_adapter.AddPassword(*form); | |
1241 } | |
1242 | |
1243 std::vector<std::unique_ptr<PasswordForm>> all_passwords = | |
1244 keychain_adapter.GetAllPasswordFormPasswords(); | |
1245 EXPECT_EQ(9 + arraysize(owned_password_data), all_passwords.size()); | |
1246 | |
1247 std::vector<std::unique_ptr<PasswordForm>> owned_passwords = | |
1248 owned_keychain_adapter.GetAllPasswordFormPasswords(); | |
1249 EXPECT_EQ(arraysize(owned_password_data), owned_passwords.size()); | |
1250 } | |
1251 | |
1252 #pragma mark - | |
1253 | |
1254 class PasswordStoreMacTest : public testing::Test { | |
1255 public: | |
1256 PasswordStoreMacTest() = default; | |
1257 | |
1258 void SetUp() override { | |
1259 ASSERT_TRUE(db_dir_.CreateUniqueTempDir()); | |
1260 histogram_tester_.reset(new base::HistogramTester); | |
1261 | |
1262 // Ensure that LoginDatabase will use the mock keychain if it needs to | |
1263 // encrypt/decrypt a password. | |
1264 OSCryptMocker::SetUpWithSingleton(); | |
1265 login_db_.reset( | |
1266 new password_manager::LoginDatabase(test_login_db_file_path())); | |
1267 thread_.reset(new base::Thread("Chrome_PasswordStore_Thread")); | |
1268 ASSERT_TRUE(thread_->Start()); | |
1269 ASSERT_TRUE(thread_->task_runner()->PostTask( | |
1270 FROM_HERE, base::Bind(&PasswordStoreMacTest::InitLoginDatabase, | |
1271 base::Unretained(login_db_.get())))); | |
1272 CreateAndInitPasswordStore(login_db_.get()); | |
1273 // Make sure deferred initialization is performed before some tests start | |
1274 // accessing the |login_db| directly. | |
1275 FinishAsyncProcessing(); | |
1276 } | |
1277 | |
1278 void TearDown() override { | |
1279 ClosePasswordStore(); | |
1280 thread_.reset(); | |
1281 login_db_.reset(); | |
1282 // Whatever a test did, PasswordStoreMac stores only empty password values | |
1283 // in LoginDatabase. The empty valus do not require encryption and therefore | |
1284 // OSCrypt shouldn't call the Keychain. The histogram doesn't cover the | |
1285 // internet passwords. | |
1286 if (histogram_tester_) { | |
1287 histogram_tester_->ExpectTotalCount("OSX.Keychain.Access", 0); | |
1288 } | |
1289 OSCryptMocker::TearDown(); | |
1290 } | |
1291 | |
1292 static void InitLoginDatabase(password_manager::LoginDatabase* login_db) { | |
1293 ASSERT_TRUE(login_db->Init()); | |
1294 } | |
1295 | |
1296 void CreateAndInitPasswordStore(password_manager::LoginDatabase* login_db) { | |
1297 store_ = new PasswordStoreMac( | |
1298 base::ThreadTaskRunnerHandle::Get(), nullptr, | |
1299 base::WrapUnique<AppleKeychain>(new MockAppleKeychain)); | |
1300 ASSERT_TRUE(thread_->task_runner()->PostTask( | |
1301 FROM_HERE, base::Bind(&PasswordStoreMac::InitWithTaskRunner, store_, | |
1302 thread_->task_runner()))); | |
1303 | |
1304 ASSERT_TRUE(thread_->task_runner()->PostTask( | |
1305 FROM_HERE, base::Bind(&PasswordStoreMac::set_login_metadata_db, store_, | |
1306 base::Unretained(login_db)))); | |
1307 } | |
1308 | |
1309 void ClosePasswordStore() { | |
1310 if (!store_) | |
1311 return; | |
1312 | |
1313 store_->ShutdownOnUIThread(); | |
1314 store_ = nullptr; | |
1315 } | |
1316 | |
1317 // Verifies that the given |form| can be properly stored so that it can be | |
1318 // retrieved by FillMatchingLogins() and GetAutofillableLogins(), and then it | |
1319 // can be properly removed. | |
1320 void VerifyCredentialLifecycle(const PasswordForm& form) { | |
1321 // Run everything twice to make sure no garbage is left behind that would | |
1322 // prevent storing the form a second time. | |
1323 for (size_t iteration = 0; iteration < 2; ++iteration) { | |
1324 SCOPED_TRACE(testing::Message("Iteration: ") << iteration); | |
1325 | |
1326 MockPasswordStoreConsumer mock_consumer; | |
1327 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(IsEmpty())) | |
1328 .WillOnce(QuitUIMessageLoop()); | |
1329 store()->GetAutofillableLogins(&mock_consumer); | |
1330 base::RunLoop().Run(); | |
1331 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer); | |
1332 | |
1333 store()->AddLogin(form); | |
1334 FinishAsyncProcessing(); | |
1335 | |
1336 PasswordForm returned_form; | |
1337 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u))) | |
1338 .WillOnce( | |
1339 DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop())); | |
1340 | |
1341 // The query operations will also do some housekeeping: they will remove | |
1342 // dangling credentials in the LoginDatabase without a matching Keychain | |
1343 // item when one is expected. If the logic that stores the Keychain item | |
1344 // is incorrect, this will wipe the newly added form before the second | |
1345 // query. | |
1346 store()->GetAutofillableLogins(&mock_consumer); | |
1347 base::RunLoop().Run(); | |
1348 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer); | |
1349 EXPECT_EQ(form, returned_form); | |
1350 | |
1351 PasswordStore::FormDigest query_form(form); | |
1352 EXPECT_CALL(mock_consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u))) | |
1353 .WillOnce( | |
1354 DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop())); | |
1355 store()->GetLogins(query_form, &mock_consumer); | |
1356 base::RunLoop().Run(); | |
1357 ::testing::Mock::VerifyAndClearExpectations(&mock_consumer); | |
1358 EXPECT_EQ(form, returned_form); | |
1359 | |
1360 store()->RemoveLogin(form); | |
1361 } | |
1362 } | |
1363 | |
1364 base::FilePath test_login_db_file_path() const { | |
1365 return db_dir_.GetPath().Append(FILE_PATH_LITERAL("login.db")); | |
1366 } | |
1367 | |
1368 password_manager::LoginDatabase* login_db() const { | |
1369 return store_->login_metadata_db(); | |
1370 } | |
1371 | |
1372 MockAppleKeychain* keychain() { | |
1373 return static_cast<MockAppleKeychain*>(store_->keychain()); | |
1374 } | |
1375 | |
1376 void FinishAsyncProcessing() { | |
1377 scoped_refptr<content::MessageLoopRunner> runner = | |
1378 new content::MessageLoopRunner; | |
1379 ASSERT_TRUE(thread_->task_runner()->PostTaskAndReply( | |
1380 FROM_HERE, base::Bind(&Noop), runner->QuitClosure())); | |
1381 runner->Run(); | |
1382 } | |
1383 | |
1384 PasswordStoreMac* store() { return store_.get(); } | |
1385 | |
1386 protected: | |
1387 content::TestBrowserThreadBundle test_browser_thread_bundle_; | |
1388 // Thread that the synchronous methods are run on. | |
1389 std::unique_ptr<base::Thread> thread_; | |
1390 | |
1391 base::ScopedTempDir db_dir_; | |
1392 std::unique_ptr<password_manager::LoginDatabase> login_db_; | |
1393 scoped_refptr<PasswordStoreMac> store_; | |
1394 std::unique_ptr<base::HistogramTester> histogram_tester_; | |
1395 }; | |
1396 | |
1397 TEST_F(PasswordStoreMacTest, TestStoreUpdate) { | |
1398 // Insert a password into both the database and the keychain. | |
1399 // This is done manually, rather than through store_->AddLogin, because the | |
1400 // Mock Keychain isn't smart enough to be able to support update generically, | |
1401 // so some.domain.com triggers special handling to test it that make inserting | |
1402 // fail. | |
1403 PasswordFormData joint_data = {PasswordForm::SCHEME_HTML, | |
1404 "http://some.domain.com/", | |
1405 "http://some.domain.com/insecure.html", | |
1406 "login.cgi", | |
1407 L"username", | |
1408 L"password", | |
1409 L"submit", | |
1410 L"joe_user", | |
1411 L"sekrit", | |
1412 true, | |
1413 1}; | |
1414 std::unique_ptr<PasswordForm> joint_form = | |
1415 CreatePasswordFormFromDataForTesting(joint_data); | |
1416 EXPECT_EQ(AddChangeForForm(*joint_form), login_db()->AddLogin(*joint_form)); | |
1417 MockAppleKeychain::KeychainTestData joint_keychain_data = { | |
1418 kSecAuthenticationTypeHTMLForm, | |
1419 "some.domain.com", | |
1420 kSecProtocolTypeHTTP, | |
1421 "/insecure.html", | |
1422 0, | |
1423 NULL, | |
1424 "20020601171500Z", | |
1425 "joe_user", | |
1426 "sekrit", | |
1427 false}; | |
1428 keychain()->AddTestItem(joint_keychain_data); | |
1429 | |
1430 // Insert a password into the keychain only. | |
1431 MockAppleKeychain::KeychainTestData keychain_only_data = { | |
1432 kSecAuthenticationTypeHTMLForm, | |
1433 "keychain.only.com", | |
1434 kSecProtocolTypeHTTP, | |
1435 NULL, | |
1436 0, | |
1437 NULL, | |
1438 "20020601171500Z", | |
1439 "keychain", | |
1440 "only", | |
1441 false}; | |
1442 keychain()->AddTestItem(keychain_only_data); | |
1443 | |
1444 struct UpdateData { | |
1445 PasswordFormData form_data; | |
1446 const char* password; // NULL indicates no entry should be present. | |
1447 }; | |
1448 | |
1449 // Make a series of update calls. | |
1450 UpdateData updates[] = { | |
1451 // Update the keychain+db passwords (the normal password update case). | |
1452 { | |
1453 {PasswordForm::SCHEME_HTML, "http://some.domain.com/", | |
1454 "http://some.domain.com/insecure.html", "login.cgi", L"username", | |
1455 L"password", L"submit", L"joe_user", L"53krit", true, 2}, | |
1456 "53krit", | |
1457 }, | |
1458 // Update the keychain-only password; this simulates the initial use of a | |
1459 // password stored by another browsers. | |
1460 { | |
1461 {PasswordForm::SCHEME_HTML, "http://keychain.only.com/", | |
1462 "http://keychain.only.com/login.html", "login.cgi", L"username", | |
1463 L"password", L"submit", L"keychain", L"only", true, 2}, | |
1464 "only", | |
1465 }, | |
1466 // Update a password that doesn't exist in either location. This tests the | |
1467 // case where a form is filled, then the stored login is removed, then the | |
1468 // form is submitted. | |
1469 { | |
1470 {PasswordForm::SCHEME_HTML, "http://different.com/", | |
1471 "http://different.com/index.html", "login.cgi", L"username", | |
1472 L"password", L"submit", L"abc", L"123", true, 2}, | |
1473 NULL, | |
1474 }, | |
1475 }; | |
1476 for (unsigned int i = 0; i < arraysize(updates); ++i) { | |
1477 std::unique_ptr<PasswordForm> form = | |
1478 CreatePasswordFormFromDataForTesting(updates[i].form_data); | |
1479 store_->UpdateLogin(*form); | |
1480 } | |
1481 | |
1482 FinishAsyncProcessing(); | |
1483 | |
1484 MacKeychainPasswordFormAdapter keychain_adapter(keychain()); | |
1485 for (unsigned int i = 0; i < arraysize(updates); ++i) { | |
1486 SCOPED_TRACE(testing::Message("iteration ") << i); | |
1487 std::unique_ptr<PasswordForm> query_form = | |
1488 CreatePasswordFormFromDataForTesting(updates[i].form_data); | |
1489 | |
1490 std::vector<std::unique_ptr<PasswordForm>> matching_items = | |
1491 keychain_adapter.PasswordsFillingForm(query_form->signon_realm, | |
1492 query_form->scheme); | |
1493 if (updates[i].password) { | |
1494 EXPECT_GT(matching_items.size(), 0U); | |
1495 if (matching_items.size() >= 1) | |
1496 EXPECT_EQ(ASCIIToUTF16(updates[i].password), | |
1497 matching_items[0]->password_value); | |
1498 } else { | |
1499 EXPECT_EQ(0U, matching_items.size()); | |
1500 } | |
1501 | |
1502 std::vector<std::unique_ptr<PasswordForm>> matching_db_items; | |
1503 EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(*query_form), | |
1504 &matching_db_items)); | |
1505 EXPECT_EQ(updates[i].password ? 1U : 0U, matching_db_items.size()); | |
1506 } | |
1507 } | |
1508 | |
1509 TEST_F(PasswordStoreMacTest, TestDBKeychainAssociation) { | |
1510 // Tests that association between the keychain and login database parts of a | |
1511 // password added by fuzzy (PSL) matching works. | |
1512 // 1. Add a password for www.facebook.com | |
1513 // 2. Get a password for m.facebook.com. This fuzzy matches and returns the | |
1514 // www.facebook.com password. | |
1515 // 3. Add the returned password for m.facebook.com. | |
1516 // 4. Remove both passwords. | |
1517 // -> check: that both are gone from the login DB and the keychain | |
1518 // This test should in particular ensure that we don't keep passwords in the | |
1519 // keychain just before we think we still have other (fuzzy-)matching entries | |
1520 // for them in the login database. (For example, here if we deleted the | |
1521 // www.facebook.com password from the login database, we should not be blocked | |
1522 // from deleting it from the keystore just becaus the m.facebook.com password | |
1523 // fuzzy-matches the www.facebook.com one.) | |
1524 | |
1525 // 1. Add a password for www.facebook.com | |
1526 PasswordFormData www_form_data = {PasswordForm::SCHEME_HTML, | |
1527 "http://www.facebook.com/", | |
1528 "http://www.facebook.com/index.html", | |
1529 "login", | |
1530 L"username", | |
1531 L"password", | |
1532 L"submit", | |
1533 L"joe_user", | |
1534 L"sekrit", | |
1535 true, | |
1536 1}; | |
1537 std::unique_ptr<PasswordForm> www_form = | |
1538 CreatePasswordFormFromDataForTesting(www_form_data); | |
1539 EXPECT_EQ(AddChangeForForm(*www_form), login_db()->AddLogin(*www_form)); | |
1540 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain()); | |
1541 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); | |
1542 owned_keychain_adapter.AddPassword(*www_form); | |
1543 | |
1544 // 2. Get a password for m.facebook.com. | |
1545 PasswordForm m_form(*www_form); | |
1546 m_form.signon_realm = "http://m.facebook.com"; | |
1547 m_form.origin = GURL("http://m.facebook.com/index.html"); | |
1548 | |
1549 MockPasswordStoreConsumer consumer; | |
1550 store_->GetLogins(PasswordStore::FormDigest(m_form), &consumer); | |
1551 PasswordForm returned_form; | |
1552 EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(SizeIs(1u))) | |
1553 .WillOnce( | |
1554 DoAll(SaveACopyOfFirstForm(&returned_form), QuitUIMessageLoop())); | |
1555 base::RunLoop().Run(); | |
1556 | |
1557 // 3. Add the returned password for m.facebook.com. | |
1558 returned_form.signon_realm = "http://m.facebook.com"; | |
1559 returned_form.origin = GURL("http://m.facebook.com/index.html"); | |
1560 EXPECT_EQ(AddChangeForForm(returned_form), | |
1561 login_db()->AddLogin(returned_form)); | |
1562 owned_keychain_adapter.AddPassword(m_form); | |
1563 | |
1564 // 4. Remove both passwords. | |
1565 store_->RemoveLogin(*www_form); | |
1566 store_->RemoveLogin(m_form); | |
1567 FinishAsyncProcessing(); | |
1568 | |
1569 // No trace of www.facebook.com. | |
1570 std::vector<std::unique_ptr<PasswordForm>> matching_items = | |
1571 owned_keychain_adapter.PasswordsFillingForm(www_form->signon_realm, | |
1572 www_form->scheme); | |
1573 EXPECT_EQ(0u, matching_items.size()); | |
1574 | |
1575 std::vector<std::unique_ptr<PasswordForm>> matching_db_items; | |
1576 EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(*www_form), | |
1577 &matching_db_items)); | |
1578 EXPECT_EQ(0u, matching_db_items.size()); | |
1579 | |
1580 // No trace of m.facebook.com. | |
1581 matching_items = owned_keychain_adapter.PasswordsFillingForm( | |
1582 m_form.signon_realm, m_form.scheme); | |
1583 EXPECT_EQ(0u, matching_items.size()); | |
1584 | |
1585 EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(m_form), | |
1586 &matching_db_items)); | |
1587 EXPECT_EQ(0u, matching_db_items.size()); | |
1588 } | |
1589 | |
1590 namespace { | |
1591 | |
1592 class PasswordsChangeObserver | |
1593 : public password_manager::PasswordStore::Observer { | |
1594 public: | |
1595 explicit PasswordsChangeObserver(PasswordStoreMac* store) : observer_(this) { | |
1596 observer_.Add(store); | |
1597 } | |
1598 | |
1599 void WaitAndVerify(PasswordStoreMacTest* test) { | |
1600 test->FinishAsyncProcessing(); | |
1601 ::testing::Mock::VerifyAndClearExpectations(this); | |
1602 } | |
1603 | |
1604 // password_manager::PasswordStore::Observer: | |
1605 MOCK_METHOD1(OnLoginsChanged, | |
1606 void(const password_manager::PasswordStoreChangeList& changes)); | |
1607 | |
1608 private: | |
1609 ScopedObserver<password_manager::PasswordStore, PasswordsChangeObserver> | |
1610 observer_; | |
1611 }; | |
1612 | |
1613 password_manager::PasswordStoreChangeList GetAddChangeList( | |
1614 const PasswordForm& form) { | |
1615 password_manager::PasswordStoreChange change( | |
1616 password_manager::PasswordStoreChange::ADD, form); | |
1617 return password_manager::PasswordStoreChangeList(1, change); | |
1618 } | |
1619 | |
1620 // Tests RemoveLoginsCreatedBetween or RemoveLoginsSyncedBetween depending on | |
1621 // |check_created|. | |
1622 void CheckRemoveLoginsBetween(PasswordStoreMacTest* test, bool check_created) { | |
1623 PasswordFormData www_form_data_facebook = { | |
1624 PasswordForm::SCHEME_HTML, | |
1625 "http://www.facebook.com/", | |
1626 "http://www.facebook.com/index.html", | |
1627 "login", | |
1628 L"submit", | |
1629 L"username", | |
1630 L"password", | |
1631 L"joe_user", | |
1632 L"sekrit", | |
1633 true, | |
1634 0}; | |
1635 // The old form doesn't have elements names. | |
1636 PasswordFormData www_form_data_facebook_old = { | |
1637 PasswordForm::SCHEME_HTML, | |
1638 "http://www.facebook.com/", | |
1639 "http://www.facebook.com/index.html", | |
1640 "login", | |
1641 L"", | |
1642 L"", | |
1643 L"", | |
1644 L"joe_user", | |
1645 L"oldsekrit", | |
1646 true, | |
1647 0}; | |
1648 PasswordFormData www_form_data_other = {PasswordForm::SCHEME_HTML, | |
1649 "http://different.com/", | |
1650 "http://different.com/index.html", | |
1651 "login", | |
1652 L"submit", | |
1653 L"username", | |
1654 L"password", | |
1655 L"different_joe_user", | |
1656 L"sekrit", | |
1657 true, | |
1658 0}; | |
1659 std::unique_ptr<PasswordForm> form_facebook = | |
1660 CreatePasswordFormFromDataForTesting(www_form_data_facebook); | |
1661 std::unique_ptr<PasswordForm> form_facebook_old = | |
1662 CreatePasswordFormFromDataForTesting(www_form_data_facebook_old); | |
1663 std::unique_ptr<PasswordForm> form_other = | |
1664 CreatePasswordFormFromDataForTesting(www_form_data_other); | |
1665 base::Time now = base::Time::Now(); | |
1666 base::Time next_day = now + base::TimeDelta::FromDays(1); | |
1667 if (check_created) { | |
1668 form_facebook_old->date_created = now; | |
1669 form_facebook->date_created = next_day; | |
1670 form_other->date_created = next_day; | |
1671 } else { | |
1672 form_facebook_old->date_synced = now; | |
1673 form_facebook->date_synced = next_day; | |
1674 form_other->date_synced = next_day; | |
1675 } | |
1676 | |
1677 PasswordsChangeObserver observer(test->store()); | |
1678 test->store()->AddLogin(*form_facebook_old); | |
1679 test->store()->AddLogin(*form_facebook); | |
1680 test->store()->AddLogin(*form_other); | |
1681 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_facebook_old))); | |
1682 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_facebook))); | |
1683 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_other))); | |
1684 observer.WaitAndVerify(test); | |
1685 | |
1686 // Check the keychain content. | |
1687 MacKeychainPasswordFormAdapter owned_keychain_adapter(test->keychain()); | |
1688 owned_keychain_adapter.SetFindsOnlyOwnedItems(false); | |
1689 std::vector<std::unique_ptr<PasswordForm>> matching_items( | |
1690 owned_keychain_adapter.PasswordsFillingForm(form_facebook->signon_realm, | |
1691 form_facebook->scheme)); | |
1692 EXPECT_EQ(1u, matching_items.size()); | |
1693 matching_items = owned_keychain_adapter.PasswordsFillingForm( | |
1694 form_other->signon_realm, form_other->scheme); | |
1695 EXPECT_EQ(1u, matching_items.size()); | |
1696 | |
1697 // Remove facebook. | |
1698 if (check_created) { | |
1699 test->store()->RemoveLoginsCreatedBetween(base::Time(), next_day, | |
1700 base::Closure()); | |
1701 } else { | |
1702 test->store()->RemoveLoginsSyncedBetween(base::Time(), next_day); | |
1703 } | |
1704 password_manager::PasswordStoreChangeList list; | |
1705 form_facebook_old->password_value.clear(); | |
1706 form_facebook->password_value.clear(); | |
1707 list.push_back(password_manager::PasswordStoreChange( | |
1708 password_manager::PasswordStoreChange::REMOVE, *form_facebook_old)); | |
1709 list.push_back(password_manager::PasswordStoreChange( | |
1710 password_manager::PasswordStoreChange::REMOVE, *form_facebook)); | |
1711 EXPECT_CALL(observer, OnLoginsChanged(list)); | |
1712 list.clear(); | |
1713 observer.WaitAndVerify(test); | |
1714 | |
1715 matching_items = owned_keychain_adapter.PasswordsFillingForm( | |
1716 form_facebook->signon_realm, form_facebook->scheme); | |
1717 EXPECT_EQ(0u, matching_items.size()); | |
1718 matching_items = owned_keychain_adapter.PasswordsFillingForm( | |
1719 form_other->signon_realm, form_other->scheme); | |
1720 EXPECT_EQ(1u, matching_items.size()); | |
1721 | |
1722 // Remove form_other. | |
1723 if (check_created) { | |
1724 test->store()->RemoveLoginsCreatedBetween(next_day, base::Time(), | |
1725 base::Closure()); | |
1726 } else { | |
1727 test->store()->RemoveLoginsSyncedBetween(next_day, base::Time()); | |
1728 } | |
1729 form_other->password_value.clear(); | |
1730 list.push_back(password_manager::PasswordStoreChange( | |
1731 password_manager::PasswordStoreChange::REMOVE, *form_other)); | |
1732 EXPECT_CALL(observer, OnLoginsChanged(list)); | |
1733 observer.WaitAndVerify(test); | |
1734 matching_items = owned_keychain_adapter.PasswordsFillingForm( | |
1735 form_other->signon_realm, form_other->scheme); | |
1736 EXPECT_EQ(0u, matching_items.size()); | |
1737 } | |
1738 | |
1739 } // namespace | |
1740 | |
1741 TEST_F(PasswordStoreMacTest, TestRemoveLoginsCreatedBetween) { | |
1742 CheckRemoveLoginsBetween(this, true); | |
1743 } | |
1744 | |
1745 TEST_F(PasswordStoreMacTest, TestRemoveLoginsSyncedBetween) { | |
1746 CheckRemoveLoginsBetween(this, false); | |
1747 } | |
1748 | |
1749 TEST_F(PasswordStoreMacTest, TestDisableAutoSignInForOrigins) { | |
1750 PasswordFormData www_form_data_facebook = { | |
1751 PasswordForm::SCHEME_HTML, | |
1752 "http://www.facebook.com/", | |
1753 "http://www.facebook.com/index.html", | |
1754 "login", | |
1755 L"submit", | |
1756 L"username", | |
1757 L"password", | |
1758 L"joe_user", | |
1759 L"sekrit", | |
1760 true, | |
1761 0}; | |
1762 std::unique_ptr<PasswordForm> form_facebook = | |
1763 CreatePasswordFormFromDataForTesting(www_form_data_facebook); | |
1764 form_facebook->skip_zero_click = false; | |
1765 | |
1766 PasswordFormData www_form_data_google = { | |
1767 PasswordForm::SCHEME_HTML, | |
1768 "http://www.google.com/", | |
1769 "http://www.google.com/foo/bar/index.html", | |
1770 "login", | |
1771 L"submit", | |
1772 L"username", | |
1773 L"password", | |
1774 L"joe_user", | |
1775 L"sekrit", | |
1776 true, | |
1777 0}; | |
1778 std::unique_ptr<PasswordForm> form_google = | |
1779 CreatePasswordFormFromDataForTesting(www_form_data_google); | |
1780 form_google->skip_zero_click = false; | |
1781 | |
1782 // Add the zero-clickable forms to the database. | |
1783 PasswordsChangeObserver observer(store()); | |
1784 store()->AddLogin(*form_facebook); | |
1785 store()->AddLogin(*form_google); | |
1786 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_facebook))); | |
1787 EXPECT_CALL(observer, OnLoginsChanged(GetAddChangeList(*form_google))); | |
1788 observer.WaitAndVerify(this); | |
1789 | |
1790 std::vector<std::unique_ptr<PasswordForm>> forms; | |
1791 EXPECT_TRUE(login_db()->GetAutoSignInLogins(&forms)); | |
1792 EXPECT_EQ(2u, forms.size()); | |
1793 EXPECT_FALSE(forms[0]->skip_zero_click); | |
1794 EXPECT_FALSE(forms[1]->skip_zero_click); | |
1795 | |
1796 store()->DisableAutoSignInForOrigins( | |
1797 base::Bind(static_cast<bool (*)(const GURL&, const GURL&)>(operator==), | |
1798 form_google->origin), | |
1799 base::Closure()); | |
1800 FinishAsyncProcessing(); | |
1801 | |
1802 EXPECT_TRUE(login_db()->GetAutoSignInLogins(&forms)); | |
1803 EXPECT_EQ(1u, forms.size()); | |
1804 EXPECT_EQ(form_facebook->origin, forms[0]->origin); | |
1805 } | |
1806 | |
1807 TEST_F(PasswordStoreMacTest, TestRemoveLoginsMultiProfile) { | |
1808 // Make sure that RemoveLoginsCreatedBetween does affect only the correct | |
1809 // profile. | |
1810 | |
1811 // Add a third-party password. | |
1812 MockAppleKeychain::KeychainTestData keychain_data = { | |
1813 kSecAuthenticationTypeHTMLForm, | |
1814 "some.domain.com", | |
1815 kSecProtocolTypeHTTP, | |
1816 "/insecure.html", | |
1817 0, | |
1818 NULL, | |
1819 "20020601171500Z", | |
1820 "joe_user", | |
1821 "sekrit", | |
1822 false}; | |
1823 keychain()->AddTestItem(keychain_data); | |
1824 | |
1825 // Add a password through the adapter. It has the "Chrome" creator tag. | |
1826 // However, it's not referenced by the password database. | |
1827 MacKeychainPasswordFormAdapter owned_keychain_adapter(keychain()); | |
1828 owned_keychain_adapter.SetFindsOnlyOwnedItems(true); | |
1829 PasswordFormData www_form_data1 = {PasswordForm::SCHEME_HTML, | |
1830 "http://www.facebook.com/", | |
1831 "http://www.facebook.com/index.html", | |
1832 "login", | |
1833 L"username", | |
1834 L"password", | |
1835 L"submit", | |
1836 L"joe_user", | |
1837 L"sekrit", | |
1838 true, | |
1839 1}; | |
1840 std::unique_ptr<PasswordForm> www_form = | |
1841 CreatePasswordFormFromDataForTesting(www_form_data1); | |
1842 EXPECT_TRUE(owned_keychain_adapter.AddPassword(*www_form)); | |
1843 | |
1844 // Add a password from the current profile. | |
1845 PasswordFormData www_form_data2 = {PasswordForm::SCHEME_HTML, | |
1846 "http://www.facebook.com/", | |
1847 "http://www.facebook.com/index.html", | |
1848 "login", | |
1849 L"username", | |
1850 L"password", | |
1851 L"submit", | |
1852 L"not_joe_user", | |
1853 L"12345", | |
1854 true, | |
1855 1}; | |
1856 www_form = CreatePasswordFormFromDataForTesting(www_form_data2); | |
1857 store_->AddLogin(*www_form); | |
1858 FinishAsyncProcessing(); | |
1859 | |
1860 std::vector<std::unique_ptr<PasswordForm>> matching_items; | |
1861 EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(*www_form), | |
1862 &matching_items)); | |
1863 EXPECT_EQ(1u, matching_items.size()); | |
1864 | |
1865 store_->RemoveLoginsCreatedBetween(base::Time(), base::Time(), | |
1866 base::Closure()); | |
1867 FinishAsyncProcessing(); | |
1868 | |
1869 // Check the second facebook form is gone. | |
1870 EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(*www_form), | |
1871 &matching_items)); | |
1872 EXPECT_EQ(0u, matching_items.size()); | |
1873 | |
1874 // Check the first facebook form is still there. | |
1875 std::vector<std::unique_ptr<PasswordForm>> matching_keychain_items; | |
1876 matching_keychain_items = owned_keychain_adapter.PasswordsFillingForm( | |
1877 www_form->signon_realm, www_form->scheme); | |
1878 ASSERT_EQ(1u, matching_keychain_items.size()); | |
1879 EXPECT_EQ(ASCIIToUTF16("joe_user"), | |
1880 matching_keychain_items[0]->username_value); | |
1881 | |
1882 // Check the third-party password is still there. | |
1883 owned_keychain_adapter.SetFindsOnlyOwnedItems(false); | |
1884 matching_keychain_items = owned_keychain_adapter.PasswordsFillingForm( | |
1885 "http://some.domain.com/insecure.html", PasswordForm::SCHEME_HTML); | |
1886 ASSERT_EQ(1u, matching_keychain_items.size()); | |
1887 } | |
1888 | |
1889 // Add a facebook form to the store but not to the keychain. The form is to be | |
1890 // implicitly deleted. However, the observers shouldn't get notified about | |
1891 // deletion of non-existent forms like m.facebook.com. | |
1892 TEST_F(PasswordStoreMacTest, SilentlyRemoveOrphanedForm) { | |
1893 testing::StrictMock<password_manager::MockPasswordStoreObserver> | |
1894 mock_observer; | |
1895 store()->AddObserver(&mock_observer); | |
1896 | |
1897 // 1. Add a password for www.facebook.com to the LoginDatabase. | |
1898 PasswordFormData www_form_data = {PasswordForm::SCHEME_HTML, | |
1899 "http://www.facebook.com/", | |
1900 "http://www.facebook.com/index.html", | |
1901 "login", | |
1902 L"username", | |
1903 L"password", | |
1904 L"submit", | |
1905 L"joe_user", | |
1906 L"", | |
1907 true, | |
1908 1}; | |
1909 std::unique_ptr<PasswordForm> www_form( | |
1910 CreatePasswordFormFromDataForTesting(www_form_data)); | |
1911 EXPECT_EQ(AddChangeForForm(*www_form), login_db()->AddLogin(*www_form)); | |
1912 | |
1913 // 2. Get a PSL-matched password for m.facebook.com. The observer isn't | |
1914 // notified because the form isn't in the database. | |
1915 PasswordForm m_form(*www_form); | |
1916 m_form.signon_realm = "http://m.facebook.com"; | |
1917 m_form.origin = GURL("http://m.facebook.com/index.html"); | |
1918 | |
1919 MockPasswordStoreConsumer consumer; | |
1920 ON_CALL(consumer, OnGetPasswordStoreResultsConstRef(_)) | |
1921 .WillByDefault(QuitUIMessageLoop()); | |
1922 EXPECT_CALL(mock_observer, OnLoginsChanged(_)).Times(0); | |
1923 // The PSL-matched form isn't returned because there is no actual password in | |
1924 // the keychain. | |
1925 EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty())); | |
1926 store_->GetLogins(PasswordStore::FormDigest(m_form), &consumer); | |
1927 base::RunLoop().Run(); | |
1928 std::vector<std::unique_ptr<PasswordForm>> all_forms; | |
1929 EXPECT_TRUE(login_db()->GetAutofillableLogins(&all_forms)); | |
1930 EXPECT_EQ(1u, all_forms.size()); | |
1931 ::testing::Mock::VerifyAndClearExpectations(&mock_observer); | |
1932 | |
1933 // 3. Get a password for www.facebook.com. The form is implicitly removed and | |
1934 // the observer is notified. | |
1935 password_manager::PasswordStoreChangeList list; | |
1936 list.push_back(password_manager::PasswordStoreChange( | |
1937 password_manager::PasswordStoreChange::REMOVE, *www_form)); | |
1938 EXPECT_CALL(mock_observer, OnLoginsChanged(list)); | |
1939 EXPECT_CALL(consumer, OnGetPasswordStoreResultsConstRef(IsEmpty())); | |
1940 store_->GetLogins(PasswordStore::FormDigest(*www_form), &consumer); | |
1941 base::RunLoop().Run(); | |
1942 EXPECT_TRUE(login_db()->GetAutofillableLogins(&all_forms)); | |
1943 EXPECT_EQ(0u, all_forms.size()); | |
1944 } | |
1945 | |
1946 // Verify that Android app passwords can be stored, retrieved, and deleted. | |
1947 // Regression test for http://crbug.com/455551 | |
1948 TEST_F(PasswordStoreMacTest, StoringAndRetrievingAndroidCredentials) { | |
1949 PasswordForm form; | |
1950 form.signon_realm = "android://7x7IDboo8u9YKraUsbmVkuf1@net.rateflix.app/"; | |
1951 form.username_value = base::UTF8ToUTF16("randomusername"); | |
1952 form.password_value = base::UTF8ToUTF16("password"); | |
1953 | |
1954 VerifyCredentialLifecycle(form); | |
1955 } | |
1956 | |
1957 // Verify that federated credentials can be stored, retrieved and deleted. | |
1958 TEST_F(PasswordStoreMacTest, StoringAndRetrievingFederatedCredentials) { | |
1959 PasswordForm form; | |
1960 form.signon_realm = "android://7x7IDboo8u9YKraUsbmVkuf1@net.rateflix.app/"; | |
1961 form.federation_origin = | |
1962 url::Origin(GURL(password_manager::kTestingFederationUrlSpec)); | |
1963 form.username_value = base::UTF8ToUTF16("randomusername"); | |
1964 form.password_value = base::UTF8ToUTF16(""); // No password. | |
1965 | |
1966 VerifyCredentialLifecycle(form); | |
1967 } | |
1968 | |
1969 void CheckMigrationResult(PasswordStoreMac::MigrationResult expected_result, | |
1970 PasswordStoreMac::MigrationResult result) { | |
1971 EXPECT_EQ(expected_result, result); | |
1972 QuitUIMessageLoop(); | |
1973 } | |
1974 | |
1975 // Import the passwords from the Keychain to LoginDatabase. | |
1976 TEST_F(PasswordStoreMacTest, ImportFromKeychain) { | |
1977 PasswordForm form1; | |
1978 form1.origin = GURL("http://accounts.google.com/LoginAuth"); | |
1979 form1.signon_realm = "http://accounts.google.com/"; | |
1980 form1.username_value = ASCIIToUTF16("my_username"); | |
1981 form1.password_value = ASCIIToUTF16("my_password"); | |
1982 | |
1983 PasswordForm form2; | |
1984 form2.origin = GURL("http://facebook.com/Login"); | |
1985 form2.signon_realm = "http://facebook.com/"; | |
1986 form2.username_value = ASCIIToUTF16("my_username"); | |
1987 form2.password_value = ASCIIToUTF16("my_password"); | |
1988 | |
1989 PasswordForm blacklisted_form; | |
1990 blacklisted_form.origin = GURL("http://badsite.com/Login"); | |
1991 blacklisted_form.signon_realm = "http://badsite.com/"; | |
1992 blacklisted_form.blacklisted_by_user = true; | |
1993 | |
1994 store()->AddLogin(form1); | |
1995 store()->AddLogin(form2); | |
1996 store()->AddLogin(blacklisted_form); | |
1997 FinishAsyncProcessing(); | |
1998 | |
1999 ASSERT_TRUE(base::PostTaskAndReplyWithResult( | |
2000 thread_->task_runner().get(), FROM_HERE, | |
2001 base::Bind(&PasswordStoreMac::ImportFromKeychain, login_db(), keychain()), | |
2002 base::Bind(&CheckMigrationResult, PasswordStoreMac::MIGRATION_OK))); | |
2003 FinishAsyncProcessing(); | |
2004 | |
2005 // The password should be stored in the database by now. | |
2006 std::vector<std::unique_ptr<PasswordForm>> matching_items; | |
2007 EXPECT_TRUE( | |
2008 login_db()->GetLogins(PasswordStore::FormDigest(form1), &matching_items)); | |
2009 ASSERT_EQ(1u, matching_items.size()); | |
2010 EXPECT_EQ(form1, *matching_items[0]); | |
2011 | |
2012 EXPECT_TRUE( | |
2013 login_db()->GetLogins(PasswordStore::FormDigest(form2), &matching_items)); | |
2014 ASSERT_EQ(1u, matching_items.size()); | |
2015 EXPECT_EQ(form2, *matching_items[0]); | |
2016 | |
2017 EXPECT_TRUE(login_db()->GetLogins(PasswordStore::FormDigest(blacklisted_form), | |
2018 &matching_items)); | |
2019 ASSERT_EQ(1u, matching_items.size()); | |
2020 EXPECT_EQ(blacklisted_form, *matching_items[0]); | |
2021 | |
2022 // The passwords are encrypted using a key from the Keychain. | |
2023 EXPECT_TRUE( | |
2024 histogram_tester_->GetHistogramSamplesSinceCreation("OSX.Keychain.Access") | |
2025 ->TotalCount()); | |
2026 histogram_tester_.reset(); | |
2027 } | |
2028 | |
2029 // Import a federated credential while the Keychain is locked. | |
2030 TEST_F(PasswordStoreMacTest, ImportFederatedFromLockedKeychain) { | |
2031 keychain()->set_locked(true); | |
2032 PasswordForm form1; | |
2033 form1.origin = GURL("http://example.com/Login"); | |
2034 form1.signon_realm = "http://example.com/"; | |
2035 form1.username_value = ASCIIToUTF16("my_username"); | |
2036 form1.federation_origin = url::Origin(GURL("https://accounts.google.com/")); | |
2037 | |
2038 store()->AddLogin(form1); | |
2039 FinishAsyncProcessing(); | |
2040 ASSERT_TRUE(base::PostTaskAndReplyWithResult( | |
2041 thread_->task_runner().get(), FROM_HERE, | |
2042 base::Bind(&PasswordStoreMac::ImportFromKeychain, login_db(), keychain()), | |
2043 base::Bind(&CheckMigrationResult, PasswordStoreMac::MIGRATION_OK))); | |
2044 FinishAsyncProcessing(); | |
2045 | |
2046 std::vector<std::unique_ptr<PasswordForm>> matching_items; | |
2047 EXPECT_TRUE( | |
2048 login_db()->GetLogins(PasswordStore::FormDigest(form1), &matching_items)); | |
2049 ASSERT_EQ(1u, matching_items.size()); | |
2050 EXPECT_EQ(form1, *matching_items[0]); | |
2051 } | |
2052 | |
2053 // Try to import while the Keychain is locked but the encryption key had been | |
2054 // read earlier. | |
2055 TEST_F(PasswordStoreMacTest, ImportFromLockedKeychainError) { | |
2056 PasswordForm form1; | |
2057 form1.origin = GURL("http://accounts.google.com/LoginAuth"); | |
2058 form1.signon_realm = "http://accounts.google.com/"; | |
2059 form1.username_value = ASCIIToUTF16("my_username"); | |
2060 form1.password_value = ASCIIToUTF16("my_password"); | |
2061 store()->AddLogin(form1); | |
2062 FinishAsyncProcessing(); | |
2063 | |
2064 // Add a second keychain item matching the Database entry. | |
2065 PasswordForm form2 = form1; | |
2066 form2.origin = GURL("http://accounts.google.com/Login"); | |
2067 form2.password_value = ASCIIToUTF16("1234"); | |
2068 MacKeychainPasswordFormAdapter adapter(keychain()); | |
2069 EXPECT_TRUE(adapter.AddPassword(form2)); | |
2070 | |
2071 keychain()->set_locked(true); | |
2072 ASSERT_TRUE(base::PostTaskAndReplyWithResult( | |
2073 thread_->task_runner().get(), FROM_HERE, | |
2074 base::Bind(&PasswordStoreMac::ImportFromKeychain, login_db(), keychain()), | |
2075 base::Bind(&CheckMigrationResult, PasswordStoreMac::MIGRATION_PARTIAL))); | |
2076 FinishAsyncProcessing(); | |
2077 | |
2078 std::vector<std::unique_ptr<PasswordForm>> matching_items; | |
2079 EXPECT_TRUE( | |
2080 login_db()->GetLogins(PasswordStore::FormDigest(form1), &matching_items)); | |
2081 EXPECT_EQ(0u, matching_items.size()); | |
2082 | |
2083 histogram_tester_->ExpectUniqueSample( | |
2084 "PasswordManager.KeychainMigration.NumPasswordsOnFailure", 1, 1); | |
2085 histogram_tester_->ExpectUniqueSample( | |
2086 "PasswordManager.KeychainMigration.NumFailedPasswords", 1, 1); | |
2087 // Don't test the encryption key access. | |
2088 histogram_tester_.reset(); | |
2089 } | |
2090 | |
2091 // Delete the Chrome-owned password from the Keychain. | |
2092 TEST_F(PasswordStoreMacTest, CleanUpKeychain) { | |
2093 MockAppleKeychain::KeychainTestData data1 = { kSecAuthenticationTypeHTMLForm, | |
2094 "some.domain.com", kSecProtocolTypeHTTP, NULL, 0, NULL, "20020601171500Z", | |
2095 "joe_user", "sekrit", false}; | |
2096 keychain()->AddTestItem(data1); | |
2097 | |
2098 MacKeychainPasswordFormAdapter keychain_adapter(keychain()); | |
2099 PasswordFormData data2 = { PasswordForm::SCHEME_HTML, "http://web.site.com/", | |
2100 "http://web.site.com/path/to/page.html", NULL, NULL, NULL, NULL, | |
2101 L"anonymous", L"knock-knock", false, 0 }; | |
2102 keychain_adapter.AddPassword(*CreatePasswordFormFromDataForTesting(data2)); | |
2103 std::vector<std::unique_ptr<PasswordForm>> passwords = | |
2104 keychain_adapter.GetAllPasswordFormPasswords(); | |
2105 EXPECT_EQ(2u, passwords.size()); | |
2106 | |
2107 // Delete everyhting but only the Chrome-owned item should be affected. | |
2108 PasswordStoreMac::CleanUpKeychain(keychain(), passwords); | |
2109 passwords = keychain_adapter.GetAllPasswordFormPasswords(); | |
2110 ASSERT_EQ(1u, passwords.size()); | |
2111 EXPECT_EQ("http://some.domain.com/", passwords[0]->signon_realm); | |
2112 EXPECT_EQ(ASCIIToUTF16("sekrit"), passwords[0]->password_value); | |
2113 } | |
OLD | NEW |