profile_sync_service_password_unittest.cc revision 4a5e2dc747d50c653511c68ccb2cfbfb740bd5a7
1// Copyright (c) 2010 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 <vector>
6
7#include "testing/gtest/include/gtest/gtest.h"
8
9#include "base/task.h"
10#include "base/time.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/password_manager/password_store.h"
13#include "chrome/browser/sync/abstract_profile_sync_service_test.h"
14#include "chrome/browser/sync/engine/syncapi.h"
15#include "chrome/browser/sync/glue/password_change_processor.h"
16#include "chrome/browser/sync/glue/password_data_type_controller.h"
17#include "chrome/browser/sync/glue/password_model_associator.h"
18#include "chrome/browser/sync/glue/sync_backend_host_mock.h"
19#include "chrome/browser/sync/profile_sync_factory.h"
20#include "chrome/browser/sync/profile_sync_factory_mock.h"
21#include "chrome/browser/sync/profile_sync_service.h"
22#include "chrome/browser/sync/profile_sync_test_util.h"
23#include "chrome/browser/sync/protocol/password_specifics.pb.h"
24#include "chrome/browser/sync/syncable/directory_manager.h"
25#include "chrome/browser/sync/syncable/syncable.h"
26#include "chrome/browser/sync/test_profile_sync_service.h"
27#include "chrome/common/net/gaia/gaia_constants.h"
28#include "chrome/common/notification_observer_mock.h"
29#include "chrome/common/notification_source.h"
30#include "chrome/common/notification_type.h"
31#include "chrome/common/pref_names.h"
32#include "chrome/test/sync/engine/test_id_factory.h"
33#include "chrome/test/profile_mock.h"
34#include "testing/gmock/include/gmock/gmock.h"
35#include "webkit/glue/password_form.h"
36
37using base::Time;
38using browser_sync::PasswordChangeProcessor;
39using browser_sync::PasswordDataTypeController;
40using browser_sync::PasswordModelAssociator;
41using browser_sync::SyncBackendHostMock;
42using browser_sync::TestIdFactory;
43using browser_sync::UnrecoverableErrorHandler;
44using sync_api::SyncManager;
45using sync_api::UserShare;
46using syncable::BASE_VERSION;
47using syncable::CREATE;
48using syncable::DirectoryManager;
49using syncable::IS_DEL;
50using syncable::IS_DIR;
51using syncable::IS_UNAPPLIED_UPDATE;
52using syncable::IS_UNSYNCED;
53using syncable::MutableEntry;
54using syncable::SERVER_IS_DIR;
55using syncable::SERVER_VERSION;
56using syncable::SPECIFICS;
57using syncable::ScopedDirLookup;
58using syncable::UNIQUE_SERVER_TAG;
59using syncable::UNITTEST;
60using syncable::WriteTransaction;
61using testing::_;
62using testing::DoAll;
63using testing::DoDefault;
64using testing::ElementsAre;
65using testing::Eq;
66using testing::Invoke;
67using testing::Return;
68using testing::SaveArg;
69using testing::SetArgumentPointee;
70using webkit_glue::PasswordForm;
71
72ACTION_P3(MakePasswordSyncComponents, service, ps, dtc) {
73  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
74  PasswordModelAssociator* model_associator =
75      new PasswordModelAssociator(service, ps);
76  PasswordChangeProcessor* change_processor =
77      new PasswordChangeProcessor(model_associator, ps, dtc);
78  return ProfileSyncFactory::SyncComponents(model_associator,
79                                            change_processor);
80}
81
82class MockPasswordStore : public PasswordStore {
83 public:
84  MOCK_METHOD1(RemoveLogin, void(const PasswordForm&));
85  MOCK_METHOD2(GetLogins, int(const PasswordForm&, PasswordStoreConsumer*));
86  MOCK_METHOD1(AddLogin, void(const PasswordForm&));
87  MOCK_METHOD1(UpdateLogin, void(const PasswordForm&));
88  MOCK_METHOD0(ReportMetrics, void());
89  MOCK_METHOD0(ReportMetricsImpl, void());
90  MOCK_METHOD1(AddLoginImpl, void(const PasswordForm&));
91  MOCK_METHOD1(UpdateLoginImpl, void(const PasswordForm&));
92  MOCK_METHOD1(RemoveLoginImpl, void(const PasswordForm&));
93  MOCK_METHOD2(RemoveLoginsCreatedBetweenImpl, void(const base::Time&,
94               const base::Time&));
95  MOCK_METHOD2(GetLoginsImpl, void(GetLoginsRequest*, const PasswordForm&));
96  MOCK_METHOD1(GetAutofillableLoginsImpl, void(GetLoginsRequest*));
97  MOCK_METHOD1(GetBlacklistLoginsImpl, void(GetLoginsRequest*));
98  MOCK_METHOD1(FillAutofillableLogins,
99      bool(std::vector<PasswordForm*>*));
100  MOCK_METHOD1(FillBlacklistLogins,
101      bool(std::vector<PasswordForm*>*));
102};
103
104class ProfileSyncServicePasswordTest : public AbstractProfileSyncServiceTest {
105 protected:
106  ProfileSyncServicePasswordTest()
107      : db_thread_(BrowserThread::DB) {
108  }
109
110  virtual void SetUp() {
111    password_store_ = new MockPasswordStore();
112    db_thread_.Start();
113
114    notification_service_ = new ThreadNotificationService(&db_thread_);
115    notification_service_->Init();
116    registrar_.Add(&observer_,
117        NotificationType::SYNC_PASSPHRASE_ACCEPTED,
118        NotificationService::AllSources());
119    registrar_.Add(&observer_,
120        NotificationType::SYNC_CONFIGURE_DONE,
121        NotificationService::AllSources());
122
123    // We shouldn't ever get this. Gmock will complain if we do.
124    registrar_.Add(&observer_,
125        NotificationType::SYNC_PASSPHRASE_REQUIRED,
126        NotificationService::AllSources());
127  }
128
129  virtual void TearDown() {
130    service_.reset();
131    notification_service_->TearDown();
132    db_thread_.Stop();
133    MessageLoop::current()->RunAllPending();
134  }
135
136  void StartSyncService(Task* root_task, Task* node_task) {
137    StartSyncService(root_task, node_task, 2, 2);
138  }
139
140  void StartSyncService(Task* root_task, Task* node_task,
141                        int num_resume_expectations,
142                        int num_pause_expectations) {
143    if (!service_.get()) {
144      service_.reset(new TestProfileSyncService(&factory_, &profile_,
145                                                "test_user", false, root_task));
146      service_->RegisterPreferences();
147      profile_.GetPrefs()->SetBoolean(prefs::kSyncPasswords, true);
148      service_->set_num_expected_resumes(num_resume_expectations);
149      service_->set_num_expected_pauses(num_pause_expectations);
150      PasswordDataTypeController* data_type_controller =
151          new PasswordDataTypeController(&factory_,
152                                         &profile_,
153                                         service_.get());
154
155      EXPECT_CALL(factory_, CreatePasswordSyncComponents(_, _, _)).
156          WillOnce(MakePasswordSyncComponents(service_.get(),
157                                              password_store_.get(),
158                                              data_type_controller));
159      EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).
160          WillOnce(ReturnNewDataTypeManager());
161
162      // We need tokens to get the tests going
163      token_service_.IssueAuthTokenForTest(
164          GaiaConstants::kSyncService, "token");
165
166      EXPECT_CALL(profile_, GetTokenService()).
167          WillRepeatedly(Return(&token_service_));
168
169      // Creating model safe workers will request the history service and
170      // password store.  I couldn't manage to convince gmock that splitting up
171      // the expectations to match the class responsibilities was a good thing,
172      // so we set them all together here.
173      EXPECT_CALL(profile_, GetHistoryService(_)).
174         WillOnce(Return(static_cast<HistoryService*>(NULL)));
175
176      EXPECT_CALL(profile_, GetPasswordStore(_)).
177          Times(2).
178          WillRepeatedly(Return(password_store_.get()));
179
180      EXPECT_CALL(observer_,
181          Observe(
182              NotificationType(NotificationType::SYNC_CONFIGURE_DONE),_,_));
183
184      service_->RegisterDataTypeController(data_type_controller);
185      service_->Initialize();
186      MessageLoop::current()->Run();
187
188      // Only set the passphrase if we actually created the password and nigori
189      // root nodes.
190      if (root_task) {
191        EXPECT_CALL(observer_,
192            Observe(
193                NotificationType(NotificationType::SYNC_PASSPHRASE_ACCEPTED),
194                    _,_)).
195            WillOnce(InvokeTask(node_task));
196        EXPECT_CALL(observer_,
197            Observe(
198                NotificationType(NotificationType::SYNC_CONFIGURE_DONE),
199                    _,_)).
200            WillOnce(QuitUIMessageLoop());
201        service_->SetPassphrase("foo", false);
202        MessageLoop::current()->Run();
203      }
204    }
205  }
206
207  void AddPasswordSyncNode(const PasswordForm& entry) {
208    sync_api::WriteTransaction trans(
209        service_->backend()->GetUserShareHandle());
210    sync_api::ReadNode password_root(&trans);
211    ASSERT_TRUE(password_root.InitByTagLookup(browser_sync::kPasswordTag));
212
213    sync_api::WriteNode node(&trans);
214    std::string tag = PasswordModelAssociator::MakeTag(entry);
215    ASSERT_TRUE(node.InitUniqueByCreation(syncable::PASSWORDS,
216                                          password_root,
217                                          tag));
218    PasswordModelAssociator::WriteToSyncNode(entry, &node);
219  }
220
221  void GetPasswordEntriesFromSyncDB(std::vector<PasswordForm>* entries) {
222    sync_api::ReadTransaction trans(service_->backend()->GetUserShareHandle());
223    sync_api::ReadNode password_root(&trans);
224    ASSERT_TRUE(password_root.InitByTagLookup(browser_sync::kPasswordTag));
225
226    int64 child_id = password_root.GetFirstChildId();
227    while (child_id != sync_api::kInvalidId) {
228      sync_api::ReadNode child_node(&trans);
229      ASSERT_TRUE(child_node.InitByIdLookup(child_id));
230
231      const sync_pb::PasswordSpecificsData& password =
232          child_node.GetPasswordSpecifics();
233
234      PasswordForm form;
235      PasswordModelAssociator::CopyPassword(password, &form);
236
237      entries->push_back(form);
238
239      child_id = child_node.GetSuccessorId();
240    }
241  }
242
243  bool ComparePasswords(const PasswordForm& lhs, const PasswordForm& rhs) {
244    return lhs.scheme == rhs.scheme &&
245           lhs.signon_realm == rhs.signon_realm &&
246           lhs.origin == rhs.origin &&
247           lhs.action == rhs.action &&
248           lhs.username_element == rhs.username_element &&
249           lhs.username_value == rhs.username_value &&
250           lhs.password_element == rhs.password_element &&
251           lhs.password_value == rhs.password_value &&
252           lhs.ssl_valid == rhs.ssl_valid &&
253           lhs.preferred == rhs.preferred &&
254           lhs.date_created == rhs.date_created &&
255           lhs.blacklisted_by_user == rhs.blacklisted_by_user;
256  }
257
258  void SetIdleChangeProcessorExpectations() {
259    EXPECT_CALL(*password_store_, AddLoginImpl(_)).Times(0);
260    EXPECT_CALL(*password_store_, UpdateLoginImpl(_)).Times(0);
261    EXPECT_CALL(*password_store_, RemoveLoginImpl(_)).Times(0);
262  }
263
264  friend class AddPasswordEntriesTask;
265
266  BrowserThread db_thread_;
267  scoped_refptr<ThreadNotificationService> notification_service_;
268  NotificationObserverMock observer_;
269  ProfileMock profile_;
270  scoped_refptr<MockPasswordStore> password_store_;
271  NotificationRegistrar registrar_;
272
273  TestIdFactory ids_;
274};
275
276class CreatePasswordRootTask : public Task {
277 public:
278  explicit CreatePasswordRootTask(AbstractProfileSyncServiceTest* test)
279      : test_(test) {
280  }
281
282  virtual void Run() {
283    test_->CreateRoot(syncable::NIGORI);
284    test_->CreateRoot(syncable::PASSWORDS);
285  }
286
287 private:
288  AbstractProfileSyncServiceTest* test_;
289};
290
291class AddPasswordEntriesTask : public Task {
292 public:
293  AddPasswordEntriesTask(ProfileSyncServicePasswordTest* test,
294                         const std::vector<PasswordForm>& entries)
295      : test_(test), entries_(entries) {
296  }
297
298  virtual void Run() {
299    for (size_t i = 0; i < entries_.size(); ++i) {
300      test_->AddPasswordSyncNode(entries_[i]);
301    }
302  }
303
304 private:
305  ProfileSyncServicePasswordTest* test_;
306  const std::vector<PasswordForm>& entries_;
307};
308
309TEST_F(ProfileSyncServicePasswordTest, FailModelAssociation) {
310  // Create the nigori root node so that password model association is
311  // attempted, but not the password root node so that it fails.
312  CreateRootTask task(this, syncable::NIGORI);
313  StartSyncService(&task, NULL, 1, 2);
314  EXPECT_TRUE(service_->unrecoverable_error_detected());
315}
316
317TEST_F(ProfileSyncServicePasswordTest, EmptyNativeEmptySync) {
318  EXPECT_CALL(*password_store_, FillAutofillableLogins(_))
319      .WillOnce(Return(true));
320  EXPECT_CALL(*password_store_, FillBlacklistLogins(_))
321      .WillOnce(Return(true));
322  SetIdleChangeProcessorExpectations();
323  CreatePasswordRootTask task(this);
324  StartSyncService(&task, NULL);
325  std::vector<PasswordForm> sync_entries;
326  GetPasswordEntriesFromSyncDB(&sync_entries);
327  EXPECT_EQ(0U, sync_entries.size());
328}
329
330TEST_F(ProfileSyncServicePasswordTest, HasNativeEntriesEmptySync) {
331  std::vector<PasswordForm*> forms;
332  std::vector<PasswordForm> expected_forms;
333  PasswordForm* new_form = new PasswordForm;
334  new_form->scheme = PasswordForm::SCHEME_HTML;
335  new_form->signon_realm = "pie";
336  new_form->origin = GURL("http://pie.com");
337  new_form->action = GURL("http://pie.com/submit");
338  new_form->username_element = UTF8ToUTF16("name");
339  new_form->username_value = UTF8ToUTF16("tom");
340  new_form->password_element = UTF8ToUTF16("cork");
341  new_form->password_value = UTF8ToUTF16("password1");
342  new_form->ssl_valid = true;
343  new_form->preferred = false;
344  new_form->date_created = base::Time::FromInternalValue(1234);
345  new_form->blacklisted_by_user = false;
346  forms.push_back(new_form);
347  expected_forms.push_back(*new_form);
348  EXPECT_CALL(*password_store_, FillAutofillableLogins(_))
349      .WillOnce(DoAll(SetArgumentPointee<0>(forms), Return(true)));
350  EXPECT_CALL(*password_store_, FillBlacklistLogins(_))
351      .WillOnce(Return(true));
352  SetIdleChangeProcessorExpectations();
353  CreatePasswordRootTask task(this);
354  StartSyncService(&task, NULL);
355  std::vector<PasswordForm> sync_forms;
356  GetPasswordEntriesFromSyncDB(&sync_forms);
357  ASSERT_EQ(1U, sync_forms.size());
358  EXPECT_TRUE(ComparePasswords(expected_forms[0], sync_forms[0]));
359}
360
361TEST_F(ProfileSyncServicePasswordTest, HasNativeEntriesEmptySyncSameUsername) {
362  std::vector<PasswordForm*> forms;
363  std::vector<PasswordForm> expected_forms;
364
365  {
366    PasswordForm* new_form = new PasswordForm;
367    new_form->scheme = PasswordForm::SCHEME_HTML;
368    new_form->signon_realm = "pie";
369    new_form->origin = GURL("http://pie.com");
370    new_form->action = GURL("http://pie.com/submit");
371    new_form->username_element = UTF8ToUTF16("name");
372    new_form->username_value = UTF8ToUTF16("tom");
373    new_form->password_element = UTF8ToUTF16("cork");
374    new_form->password_value = UTF8ToUTF16("password1");
375    new_form->ssl_valid = true;
376    new_form->preferred = false;
377    new_form->date_created = base::Time::FromInternalValue(1234);
378    new_form->blacklisted_by_user = false;
379    forms.push_back(new_form);
380    expected_forms.push_back(*new_form);
381  }
382  {
383    PasswordForm* new_form = new PasswordForm;
384    new_form->scheme = PasswordForm::SCHEME_HTML;
385    new_form->signon_realm = "pie";
386    new_form->origin = GURL("http://pie.com");
387    new_form->action = GURL("http://pie.com/submit");
388    new_form->username_element = UTF8ToUTF16("name");
389    new_form->username_value = UTF8ToUTF16("pete");
390    new_form->password_element = UTF8ToUTF16("cork");
391    new_form->password_value = UTF8ToUTF16("password2");
392    new_form->ssl_valid = true;
393    new_form->preferred = false;
394    new_form->date_created = base::Time::FromInternalValue(1234);
395    new_form->blacklisted_by_user = false;
396    forms.push_back(new_form);
397    expected_forms.push_back(*new_form);
398  }
399
400  EXPECT_CALL(*password_store_, FillAutofillableLogins(_))
401      .WillOnce(DoAll(SetArgumentPointee<0>(forms), Return(true)));
402  EXPECT_CALL(*password_store_, FillBlacklistLogins(_))
403      .WillOnce(Return(true));
404  SetIdleChangeProcessorExpectations();
405  CreatePasswordRootTask task(this);
406  StartSyncService(&task, NULL);
407  std::vector<PasswordForm> sync_forms;
408  GetPasswordEntriesFromSyncDB(&sync_forms);
409  ASSERT_EQ(2U, sync_forms.size());
410  EXPECT_TRUE(ComparePasswords(expected_forms[0], sync_forms[1]));
411  EXPECT_TRUE(ComparePasswords(expected_forms[1], sync_forms[0]));
412}
413
414TEST_F(ProfileSyncServicePasswordTest, HasNativeHasSyncNoMerge) {
415  std::vector<PasswordForm*> native_forms;
416  std::vector<PasswordForm> sync_forms;
417  std::vector<PasswordForm> expected_forms;
418  {
419    PasswordForm* new_form = new PasswordForm;
420    new_form->scheme = PasswordForm::SCHEME_HTML;
421    new_form->signon_realm = "pie";
422    new_form->origin = GURL("http://pie.com");
423    new_form->action = GURL("http://pie.com/submit");
424    new_form->username_element = UTF8ToUTF16("name");
425    new_form->username_value = UTF8ToUTF16("tom");
426    new_form->password_element = UTF8ToUTF16("cork");
427    new_form->password_value = UTF8ToUTF16("password1");
428    new_form->ssl_valid = true;
429    new_form->preferred = false;
430    new_form->date_created = base::Time::FromInternalValue(1234);
431    new_form->blacklisted_by_user = false;
432
433    native_forms.push_back(new_form);
434    expected_forms.push_back(*new_form);
435  }
436
437  {
438    PasswordForm new_form;
439    new_form.scheme = PasswordForm::SCHEME_HTML;
440    new_form.signon_realm = "pie2";
441    new_form.origin = GURL("http://pie2.com");
442    new_form.action = GURL("http://pie2.com/submit");
443    new_form.username_element = UTF8ToUTF16("name2");
444    new_form.username_value = UTF8ToUTF16("tom2");
445    new_form.password_element = UTF8ToUTF16("cork2");
446    new_form.password_value = UTF8ToUTF16("password12");
447    new_form.ssl_valid = false;
448    new_form.preferred = true;
449    new_form.date_created = base::Time::FromInternalValue(12345);
450    new_form.blacklisted_by_user = false;
451    sync_forms.push_back(new_form);
452    expected_forms.push_back(new_form);
453  }
454
455  EXPECT_CALL(*password_store_, FillAutofillableLogins(_))
456      .WillOnce(DoAll(SetArgumentPointee<0>(native_forms), Return(true)));
457  EXPECT_CALL(*password_store_, FillBlacklistLogins(_)).WillOnce(Return(true));
458  EXPECT_CALL(*password_store_, AddLoginImpl(_)).Times(1);
459
460  CreatePasswordRootTask root_task(this);
461  AddPasswordEntriesTask node_task(this, sync_forms);
462  StartSyncService(&root_task, &node_task);
463
464  std::vector<PasswordForm> new_sync_forms;
465  GetPasswordEntriesFromSyncDB(&new_sync_forms);
466
467  EXPECT_EQ(2U, new_sync_forms.size());
468  EXPECT_TRUE(ComparePasswords(expected_forms[0], new_sync_forms[0]));
469  EXPECT_TRUE(ComparePasswords(expected_forms[1], new_sync_forms[1]));
470}
471
472TEST_F(ProfileSyncServicePasswordTest, HasNativeHasSyncMergeEntry) {
473  std::vector<PasswordForm*> native_forms;
474  std::vector<PasswordForm> sync_forms;
475  std::vector<PasswordForm> expected_forms;
476  {
477    PasswordForm* new_form = new PasswordForm;
478    new_form->scheme = PasswordForm::SCHEME_HTML;
479    new_form->signon_realm = "pie";
480    new_form->origin = GURL("http://pie.com");
481    new_form->action = GURL("http://pie.com/submit");
482    new_form->username_element = UTF8ToUTF16("name");
483    new_form->username_value = UTF8ToUTF16("tom");
484    new_form->password_element = UTF8ToUTF16("cork");
485    new_form->password_value = UTF8ToUTF16("password1");
486    new_form->ssl_valid = true;
487    new_form->preferred = false;
488    new_form->date_created = base::Time::FromInternalValue(1234);
489    new_form->blacklisted_by_user = false;
490
491    native_forms.push_back(new_form);
492  }
493
494  {
495    PasswordForm new_form;
496    new_form.scheme = PasswordForm::SCHEME_HTML;
497    new_form.signon_realm = "pie";
498    new_form.origin = GURL("http://pie.com");
499    new_form.action = GURL("http://pie.com/submit");
500    new_form.username_element = UTF8ToUTF16("name");
501    new_form.username_value = UTF8ToUTF16("tom");
502    new_form.password_element = UTF8ToUTF16("cork");
503    new_form.password_value = UTF8ToUTF16("password12");
504    new_form.ssl_valid = false;
505    new_form.preferred = true;
506    new_form.date_created = base::Time::FromInternalValue(12345);
507    new_form.blacklisted_by_user = false;
508    sync_forms.push_back(new_form);
509  }
510
511  {
512    PasswordForm new_form;
513    new_form.scheme = PasswordForm::SCHEME_HTML;
514    new_form.signon_realm = "pie";
515    new_form.origin = GURL("http://pie.com");
516    new_form.action = GURL("http://pie.com/submit");
517    new_form.username_element = UTF8ToUTF16("name");
518    new_form.username_value = UTF8ToUTF16("tom");
519    new_form.password_element = UTF8ToUTF16("cork");
520    new_form.password_value = UTF8ToUTF16("password12");
521    new_form.ssl_valid = false;
522    new_form.preferred = true;
523    new_form.date_created = base::Time::FromInternalValue(12345);
524    new_form.blacklisted_by_user = false;
525    expected_forms.push_back(new_form);
526  }
527
528  EXPECT_CALL(*password_store_, FillAutofillableLogins(_))
529      .WillOnce(DoAll(SetArgumentPointee<0>(native_forms), Return(true)));
530  EXPECT_CALL(*password_store_, FillBlacklistLogins(_)).WillOnce(Return(true));
531  EXPECT_CALL(*password_store_, UpdateLoginImpl(_)).Times(1);
532
533  CreatePasswordRootTask root_task(this);
534  AddPasswordEntriesTask node_task(this, sync_forms);
535
536  StartSyncService(&root_task, &node_task);
537
538  std::vector<PasswordForm> new_sync_forms;
539  GetPasswordEntriesFromSyncDB(&new_sync_forms);
540
541  EXPECT_EQ(1U, new_sync_forms.size());
542  EXPECT_TRUE(ComparePasswords(expected_forms[0], new_sync_forms[0]));
543}
544