1 // Copyright 2020 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 "components/password_manager/core/browser/ui/saved_passwords_presenter.h"
6
7 #include "base/memory/scoped_refptr.h"
8 #include "base/scoped_observation.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "base/test/metrics/histogram_tester.h"
11 #include "base/test/task_environment.h"
12 #include "components/password_manager/core/browser/password_form.h"
13 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
14 #include "components/password_manager/core/browser/test_password_store.h"
15 #include "testing/gmock/include/gmock/gmock.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17
18 namespace password_manager {
19
20 namespace {
21
22 using ::testing::ElementsAre;
23 using ::testing::ElementsAreArray;
24 using ::testing::IsEmpty;
25 using ::testing::Pair;
26 using ::testing::UnorderedElementsAre;
27
28 struct MockSavedPasswordsPresenterObserver : SavedPasswordsPresenter::Observer {
29 MOCK_METHOD(void, OnEdited, (const PasswordForm&), (override));
30 MOCK_METHOD(void,
31 OnSavedPasswordsChanged,
32 (SavedPasswordsPresenter::SavedPasswordsView),
33 (override));
34 };
35
36 using StrictMockSavedPasswordsPresenterObserver =
37 ::testing::StrictMock<MockSavedPasswordsPresenterObserver>;
38
39 class SavedPasswordsPresenterTest : public ::testing::Test {
40 protected:
SavedPasswordsPresenterTest()41 SavedPasswordsPresenterTest() { store_->Init(/*prefs=*/nullptr); }
42
~SavedPasswordsPresenterTest()43 ~SavedPasswordsPresenterTest() override {
44 store_->ShutdownOnUIThread();
45 task_env_.RunUntilIdle();
46 }
47
store()48 TestPasswordStore& store() { return *store_; }
presenter()49 SavedPasswordsPresenter& presenter() { return presenter_; }
50
RunUntilIdle()51 void RunUntilIdle() { task_env_.RunUntilIdle(); }
52
53 private:
54 base::test::SingleThreadTaskEnvironment task_env_;
55 scoped_refptr<TestPasswordStore> store_ =
56 base::MakeRefCounted<TestPasswordStore>();
57 SavedPasswordsPresenter presenter_{store_};
58 };
59
60 } // namespace
61
62 // Tests whether adding and removing an observer works as expected.
TEST_F(SavedPasswordsPresenterTest,NotifyObservers)63 TEST_F(SavedPasswordsPresenterTest, NotifyObservers) {
64 PasswordForm form;
65
66 StrictMockSavedPasswordsPresenterObserver observer;
67 presenter().AddObserver(&observer);
68
69 // Adding a credential should notify observers. Furthermore, the credential
70 // should be present of the list that is passed along.
71 EXPECT_CALL(observer, OnSavedPasswordsChanged(
72 ElementsAre(MatchesFormExceptStore(form))));
73 store().AddLogin(form);
74 RunUntilIdle();
75 EXPECT_FALSE(store().IsEmpty());
76
77 // Remove should notify, and observers should be passed an empty list.
78 EXPECT_CALL(observer, OnSavedPasswordsChanged(IsEmpty()));
79 store().RemoveLogin(form);
80 RunUntilIdle();
81 EXPECT_TRUE(store().IsEmpty());
82
83 // After an observer is removed it should no longer receive notifications.
84 presenter().RemoveObserver(&observer);
85 EXPECT_CALL(observer, OnSavedPasswordsChanged).Times(0);
86 store().AddLogin(form);
87 RunUntilIdle();
88 EXPECT_FALSE(store().IsEmpty());
89 }
90
91 // Tests whether adding federated credentials doesn't inform the observers.
TEST_F(SavedPasswordsPresenterTest,IgnoredCredentials)92 TEST_F(SavedPasswordsPresenterTest, IgnoredCredentials) {
93 PasswordForm federated_form;
94 federated_form.federation_origin =
95 url::Origin::Create(GURL("https://example.com"));
96
97 StrictMockSavedPasswordsPresenterObserver observer;
98 presenter().AddObserver(&observer);
99
100 // Adding a credential should notify observers. However, since federated
101 // credentials should be ignored it should not be passed a long.
102 EXPECT_CALL(observer, OnSavedPasswordsChanged(IsEmpty()));
103 store().AddLogin(federated_form);
104 RunUntilIdle();
105
106 PasswordForm blocked_form;
107 blocked_form.blocked_by_user = true;
108 EXPECT_CALL(observer, OnSavedPasswordsChanged(IsEmpty()));
109 store().AddLogin(blocked_form);
110 RunUntilIdle();
111
112 presenter().RemoveObserver(&observer);
113 }
114
115 // Tests whether editing a password works and results in the right
116 // notifications.
TEST_F(SavedPasswordsPresenterTest,EditPassword)117 TEST_F(SavedPasswordsPresenterTest, EditPassword) {
118 PasswordForm form;
119
120 StrictMockSavedPasswordsPresenterObserver observer;
121 presenter().AddObserver(&observer);
122
123 EXPECT_CALL(observer, OnSavedPasswordsChanged);
124 store().AddLogin(form);
125 RunUntilIdle();
126 EXPECT_FALSE(store().IsEmpty());
127
128 // When |form| is read back from the store, its |in_store| member will be set,
129 // and SavedPasswordsPresenter::EditPassword() actually depends on that. So
130 // set it here too.
131 form.in_store = PasswordForm::Store::kProfileStore;
132
133 const base::string16 new_password = base::ASCIIToUTF16("new_password");
134 PasswordForm updated = form;
135 updated.password_value = new_password;
136
137 // Verify that editing a password triggers the right notifications.
138 EXPECT_CALL(observer, OnEdited(updated));
139 EXPECT_CALL(observer, OnSavedPasswordsChanged(ElementsAre(updated)));
140 EXPECT_TRUE(presenter().EditPassword(form, new_password));
141 RunUntilIdle();
142 EXPECT_THAT(store().stored_passwords(),
143 ElementsAre(Pair(updated.signon_realm, ElementsAre(updated))));
144
145 // Verify that editing a password that does not exist does not triggers
146 // notifications.
147 form.username_value = base::ASCIIToUTF16("another_username");
148 EXPECT_CALL(observer, OnEdited).Times(0);
149 EXPECT_CALL(observer, OnSavedPasswordsChanged).Times(0);
150 EXPECT_FALSE(presenter().EditPassword(form, new_password));
151 RunUntilIdle();
152
153 presenter().RemoveObserver(&observer);
154 }
155
TEST_F(SavedPasswordsPresenterTest,EditOnlyUsername)156 TEST_F(SavedPasswordsPresenterTest, EditOnlyUsername) {
157 PasswordForm form;
158 form.signon_realm = "https://example.com";
159 form.username_value = base::ASCIIToUTF16("test@gmail.com");
160 form.password_value = base::ASCIIToUTF16("password");
161 form.in_store = PasswordForm::Store::kProfileStore;
162
163 StrictMockSavedPasswordsPresenterObserver observer;
164 presenter().AddObserver(&observer);
165
166 EXPECT_CALL(observer, OnSavedPasswordsChanged);
167 store().AddLogin(form);
168 RunUntilIdle();
169 EXPECT_FALSE(store().IsEmpty());
170
171 std::vector<PasswordForm> forms = {form};
172
173 const base::string16 new_username = base::ASCIIToUTF16("new_username");
174 PasswordForm updated_username = form;
175 updated_username.username_value = new_username;
176
177 // Verify that editing a username triggers the right notifications.
178 base::HistogramTester histogram_tester;
179
180 EXPECT_CALL(observer, OnEdited(updated_username));
181 EXPECT_CALL(observer, OnSavedPasswordsChanged(ElementsAre(updated_username)));
182 EXPECT_TRUE(
183 presenter().EditSavedPasswords(forms, new_username, form.password_value));
184 RunUntilIdle();
185 EXPECT_THAT(
186 store().stored_passwords(),
187 ElementsAre(Pair(form.signon_realm, ElementsAre(updated_username))));
188
189 histogram_tester.ExpectUniqueSample(
190 "PasswordManager.PasswordEditUpdatedValues",
191 metrics_util::PasswordEditUpdatedValues::kUsername, 1);
192
193 presenter().RemoveObserver(&observer);
194 }
195
TEST_F(SavedPasswordsPresenterTest,EditOnlyPassword)196 TEST_F(SavedPasswordsPresenterTest, EditOnlyPassword) {
197 PasswordForm form;
198 form.signon_realm = "https://example.com";
199 form.username_value = base::ASCIIToUTF16("test@gmail.com");
200 form.password_value = base::ASCIIToUTF16("password");
201 form.in_store = PasswordForm::Store::kProfileStore;
202
203 StrictMockSavedPasswordsPresenterObserver observer;
204 presenter().AddObserver(&observer);
205
206 EXPECT_CALL(observer, OnSavedPasswordsChanged);
207 store().AddLogin(form);
208 RunUntilIdle();
209 EXPECT_FALSE(store().IsEmpty());
210
211 std::vector<PasswordForm> forms = {form};
212
213 const base::string16 new_password = base::ASCIIToUTF16("new_password");
214 PasswordForm updated_password = form;
215 updated_password.password_value = new_password;
216
217 base::HistogramTester histogram_tester;
218 // Verify that editing a password triggers the right notifications.
219 EXPECT_CALL(observer, OnEdited(updated_password));
220 EXPECT_CALL(observer, OnSavedPasswordsChanged(ElementsAre(updated_password)));
221 EXPECT_TRUE(
222 presenter().EditSavedPasswords(forms, form.username_value, new_password));
223 RunUntilIdle();
224 EXPECT_THAT(
225 store().stored_passwords(),
226 ElementsAre(Pair(form.signon_realm, ElementsAre(updated_password))));
227 histogram_tester.ExpectUniqueSample(
228 "PasswordManager.PasswordEditUpdatedValues",
229 metrics_util::PasswordEditUpdatedValues::kPassword, 1);
230
231 presenter().RemoveObserver(&observer);
232 }
233
TEST_F(SavedPasswordsPresenterTest,EditUsernameAndPassword)234 TEST_F(SavedPasswordsPresenterTest, EditUsernameAndPassword) {
235 PasswordForm form;
236 form.signon_realm = "https://example.com";
237 form.username_value = base::ASCIIToUTF16("test@gmail.com");
238 form.password_value = base::ASCIIToUTF16("password");
239 form.in_store = PasswordForm::Store::kProfileStore;
240
241 StrictMockSavedPasswordsPresenterObserver observer;
242 presenter().AddObserver(&observer);
243
244 EXPECT_CALL(observer, OnSavedPasswordsChanged);
245 store().AddLogin(form);
246 RunUntilIdle();
247 EXPECT_FALSE(store().IsEmpty());
248
249 std::vector<PasswordForm> forms = {form};
250
251 const base::string16 new_username = base::ASCIIToUTF16("new_username");
252 const base::string16 new_password = base::ASCIIToUTF16("new_password");
253
254 PasswordForm updated_both = form;
255 updated_both.username_value = new_username;
256 updated_both.password_value = new_password;
257
258 base::HistogramTester histogram_tester;
259 // Verify that editing username and password triggers the right notifications.
260 EXPECT_CALL(observer, OnEdited(updated_both));
261 EXPECT_CALL(observer, OnSavedPasswordsChanged(ElementsAre(updated_both)));
262 EXPECT_TRUE(
263 presenter().EditSavedPasswords(forms, new_username, new_password));
264 RunUntilIdle();
265 EXPECT_THAT(store().stored_passwords(),
266 ElementsAre(Pair(form.signon_realm, ElementsAre(updated_both))));
267 histogram_tester.ExpectBucketCount(
268 "PasswordManager.PasswordEditUpdatedValues",
269 metrics_util::PasswordEditUpdatedValues::kBoth, 1);
270
271 presenter().RemoveObserver(&observer);
272 }
273
TEST_F(SavedPasswordsPresenterTest,EditPasswordFails)274 TEST_F(SavedPasswordsPresenterTest, EditPasswordFails) {
275 PasswordForm form1;
276 form1.signon_realm = "https://example.com";
277 form1.username_value = base::ASCIIToUTF16("test1@gmail.com");
278 form1.password_value = base::ASCIIToUTF16("password");
279 form1.in_store = PasswordForm::Store::kProfileStore;
280
281 PasswordForm form2;
282 form2.signon_realm = "https://example.com";
283 form2.username_value = base::ASCIIToUTF16("test2@gmail.com");
284 form2.password_value = base::ASCIIToUTF16("password");
285 form2.in_store = PasswordForm::Store::kProfileStore;
286
287 store().AddLogin(form1);
288 store().AddLogin(form2);
289 RunUntilIdle();
290 EXPECT_FALSE(store().IsEmpty());
291
292 std::vector<PasswordForm> forms{form1};
293
294 // Updating the form with the username which is already used for same website
295 // fails.
296 const base::string16 new_username = base::ASCIIToUTF16("test2@gmail.com");
297 EXPECT_FALSE(presenter().EditSavedPasswords(forms, new_username,
298 form1.password_value));
299 RunUntilIdle();
300 EXPECT_THAT(store().stored_passwords(),
301 ElementsAre(Pair(form1.signon_realm, ElementsAre(form1, form2))));
302
303 // Updating the form with the empty password fails.
304 EXPECT_FALSE(presenter().EditSavedPasswords(forms, form1.username_value,
305 base::string16()));
306 RunUntilIdle();
307 EXPECT_THAT(store().stored_passwords(),
308 ElementsAre(Pair(form1.signon_realm, ElementsAre(form1, form2))));
309 }
310
TEST_F(SavedPasswordsPresenterTest,EditPasswordWithoutChanges)311 TEST_F(SavedPasswordsPresenterTest, EditPasswordWithoutChanges) {
312 PasswordForm form;
313 form.signon_realm = "https://example.com";
314 form.username_value = base::ASCIIToUTF16("test1@gmail.com");
315 form.password_value = base::ASCIIToUTF16("password");
316 form.in_store = PasswordForm::Store::kProfileStore;
317
318 store().AddLogin(form);
319
320 RunUntilIdle();
321 StrictMockSavedPasswordsPresenterObserver observer;
322 presenter().AddObserver(&observer);
323
324 EXPECT_FALSE(store().IsEmpty());
325 // Verify that editing a form without changing the username or password does
326 // not triggers notifications.
327 base::HistogramTester histogram_tester;
328 EXPECT_CALL(observer, OnEdited).Times(0);
329 EXPECT_CALL(observer, OnSavedPasswordsChanged).Times(0);
330 std::vector<PasswordForm> forms = {form};
331 EXPECT_TRUE(presenter().EditSavedPasswords(forms, form.username_value,
332 form.password_value));
333 RunUntilIdle();
334 histogram_tester.ExpectBucketCount(
335 "PasswordManager.PasswordEditUpdatedValues",
336 metrics_util::PasswordEditUpdatedValues::kNone, 1);
337
338 presenter().RemoveObserver(&observer);
339 }
340
341 namespace {
342
343 class SavedPasswordsPresenterWithTwoStoresTest : public ::testing::Test {
344 protected:
SavedPasswordsPresenterWithTwoStoresTest()345 SavedPasswordsPresenterWithTwoStoresTest() {
346 profile_store_->Init(/*prefs=*/nullptr);
347 account_store_->Init(/*prefs=*/nullptr);
348 }
349
~SavedPasswordsPresenterWithTwoStoresTest()350 ~SavedPasswordsPresenterWithTwoStoresTest() override {
351 account_store_->ShutdownOnUIThread();
352 profile_store_->ShutdownOnUIThread();
353 task_env_.RunUntilIdle();
354 }
355
profile_store()356 TestPasswordStore& profile_store() { return *profile_store_; }
account_store()357 TestPasswordStore& account_store() { return *account_store_; }
presenter()358 SavedPasswordsPresenter& presenter() { return presenter_; }
359
RunUntilIdle()360 void RunUntilIdle() { task_env_.RunUntilIdle(); }
361
362 private:
363 base::test::SingleThreadTaskEnvironment task_env_;
364 scoped_refptr<TestPasswordStore> profile_store_ =
365 base::MakeRefCounted<TestPasswordStore>(IsAccountStore(false));
366 scoped_refptr<TestPasswordStore> account_store_ =
367 base::MakeRefCounted<TestPasswordStore>(IsAccountStore(true));
368 SavedPasswordsPresenter presenter_{profile_store_, account_store_};
369 };
370
371 } // namespace
372
373 // Tests whether adding credentials to profile or account store notifies
374 // observers with credentials in both stores.
TEST_F(SavedPasswordsPresenterWithTwoStoresTest,AddCredentialsToBothStores)375 TEST_F(SavedPasswordsPresenterWithTwoStoresTest, AddCredentialsToBothStores) {
376 PasswordForm profile_store_form;
377 profile_store_form.username_value = base::ASCIIToUTF16("profile@gmail.com");
378 profile_store_form.password_value = base::ASCIIToUTF16("profile_pass");
379 profile_store_form.in_store = PasswordForm::Store::kProfileStore;
380
381 PasswordForm account_store_form1;
382 account_store_form1.username_value = base::ASCIIToUTF16("account@gmail.com");
383 account_store_form1.password_value = base::ASCIIToUTF16("account_pass");
384 account_store_form1.in_store = PasswordForm::Store::kAccountStore;
385
386 PasswordForm account_store_form2 = account_store_form1;
387 account_store_form2.username_value = base::ASCIIToUTF16("account2@gmail.com");
388
389 StrictMockSavedPasswordsPresenterObserver observer;
390 presenter().AddObserver(&observer);
391
392 EXPECT_CALL(observer, OnSavedPasswordsChanged(
393 UnorderedElementsAre(profile_store_form)));
394 profile_store().AddLogin(profile_store_form);
395 RunUntilIdle();
396
397 EXPECT_CALL(observer, OnSavedPasswordsChanged(UnorderedElementsAre(
398 profile_store_form, account_store_form1)));
399 account_store().AddLogin(account_store_form1);
400 RunUntilIdle();
401
402 EXPECT_CALL(observer, OnSavedPasswordsChanged(UnorderedElementsAre(
403 profile_store_form, account_store_form1,
404 account_store_form2)));
405 account_store().AddLogin(account_store_form2);
406 RunUntilIdle();
407
408 EXPECT_CALL(observer, OnSavedPasswordsChanged(UnorderedElementsAre(
409 account_store_form1, account_store_form2)));
410 profile_store().RemoveLogin(profile_store_form);
411 RunUntilIdle();
412
413 EXPECT_CALL(observer, OnSavedPasswordsChanged(UnorderedElementsAre(
414 profile_store_form, account_store_form1,
415 account_store_form2)));
416 profile_store().AddLogin(profile_store_form);
417 RunUntilIdle();
418
419 presenter().RemoveObserver(&observer);
420 }
421
422 // This tests changing the username of a credentials stored in the profile store
423 // to be equal to a username of a credential stored in the account store for the
424 // same domain.
TEST_F(SavedPasswordsPresenterWithTwoStoresTest,EditUsername)425 TEST_F(SavedPasswordsPresenterWithTwoStoresTest, EditUsername) {
426 PasswordForm profile_store_form;
427 profile_store_form.username_value = base::ASCIIToUTF16("profile@gmail.com");
428 profile_store_form.password_value = base::ASCIIToUTF16("profile_pass");
429 profile_store_form.in_store = PasswordForm::Store::kProfileStore;
430
431 PasswordForm account_store_form;
432 account_store_form.username_value = base::ASCIIToUTF16("account@gmail.com");
433 account_store_form.password_value = base::ASCIIToUTF16("account_pass");
434 account_store_form.in_store = PasswordForm::Store::kAccountStore;
435
436 profile_store().AddLogin(profile_store_form);
437 account_store().AddLogin(account_store_form);
438 RunUntilIdle();
439
440 EXPECT_THAT(profile_store().stored_passwords(),
441 ElementsAre(Pair(profile_store_form.signon_realm,
442 ElementsAre(profile_store_form))));
443
444 auto new_username = account_store_form.username_value;
445 std::vector<PasswordForm> forms_to_edit{profile_store_form};
446 EXPECT_TRUE(presenter().EditSavedPasswords(
447 forms_to_edit, new_username, profile_store_form.password_value));
448 RunUntilIdle();
449 profile_store_form.username_value = new_username;
450 EXPECT_THAT(profile_store().stored_passwords(),
451 ElementsAre(Pair(profile_store_form.signon_realm,
452 ElementsAre(profile_store_form))));
453 }
454
455 } // namespace password_manager
456