1// Copyright 2014 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/password_syncable_service.h"
6
7#include <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/basictypes.h"
12#include "base/location.h"
13#include "base/memory/ref_counted.h"
14#include "base/memory/scoped_ptr.h"
15#include "base/strings/utf_string_conversions.h"
16#include "components/password_manager/core/browser/mock_password_store.h"
17#include "sync/api/sync_change_processor.h"
18#include "sync/api/sync_error.h"
19#include "sync/api/sync_error_factory_mock.h"
20#include "testing/gmock/include/gmock/gmock.h"
21#include "testing/gtest/include/gtest/gtest.h"
22
23using syncer::SyncChange;
24using syncer::SyncData;
25using syncer::SyncDataList;
26using syncer::SyncError;
27using testing::AnyNumber;
28using testing::ElementsAre;
29using testing::Invoke;
30using testing::IsEmpty;
31using testing::Matches;
32using testing::Return;
33using testing::SetArgPointee;
34using testing::UnorderedElementsAre;
35using testing::_;
36
37namespace password_manager {
38
39// Defined in the implementation file corresponding to this test.
40syncer::SyncData SyncDataFromPassword(const autofill::PasswordForm& password);
41autofill::PasswordForm PasswordFromSpecifics(
42    const sync_pb::PasswordSpecificsData& password);
43std::string MakePasswordSyncTag(const sync_pb::PasswordSpecificsData& password);
44std::string MakePasswordSyncTag(const autofill::PasswordForm& password);
45
46namespace {
47
48// PasswordForm values for tests.
49const autofill::PasswordForm::Type kArbitraryType =
50    autofill::PasswordForm::TYPE_GENERATED;
51const char kAvatarUrl[] = "https://fb.com/Avatar";
52const char kDisplayName[] = "Agent Smith";
53const char kFederationUrl[] = "https://fb.com/federation_url";
54const char kSignonRealm[] = "abc";
55const char kSignonRealm2[] = "def";
56const char kSignonRealm3[] = "xyz";
57const int kTimesUsed = 5;
58
59typedef std::vector<SyncChange> SyncChangeList;
60
61const sync_pb::PasswordSpecificsData& GetPasswordSpecifics(
62    const syncer::SyncData& sync_data) {
63  return sync_data.GetSpecifics().password().client_only_encrypted_data();
64}
65
66MATCHER(HasDateSynced, "") {
67  return !arg.date_synced.is_null() && !arg.date_synced.is_max();
68}
69
70MATCHER_P(PasswordIs, form, "") {
71  sync_pb::PasswordSpecificsData actual_password =
72      GetPasswordSpecifics(SyncDataFromPassword(arg));
73  sync_pb::PasswordSpecificsData expected_password =
74      GetPasswordSpecifics(SyncDataFromPassword(form));
75  if (expected_password.scheme() == actual_password.scheme() &&
76      expected_password.signon_realm() == actual_password.signon_realm() &&
77      expected_password.origin() == actual_password.origin() &&
78      expected_password.action() == actual_password.action() &&
79      expected_password.username_element() ==
80          actual_password.username_element() &&
81      expected_password.password_element() ==
82          actual_password.password_element() &&
83      expected_password.username_value() == actual_password.username_value() &&
84      expected_password.password_value() == actual_password.password_value() &&
85      expected_password.ssl_valid() == actual_password.ssl_valid() &&
86      expected_password.preferred() == actual_password.preferred() &&
87      expected_password.date_created() == actual_password.date_created() &&
88      expected_password.blacklisted() == actual_password.blacklisted() &&
89      expected_password.type() == actual_password.type() &&
90      expected_password.times_used() == actual_password.times_used() &&
91      expected_password.display_name() == actual_password.display_name() &&
92      expected_password.avatar_url() == actual_password.avatar_url() &&
93      expected_password.federation_url() == actual_password.federation_url())
94    return true;
95
96  *result_listener << "Password protobuf does not match; expected:\n"
97                   << form << '\n'
98                   << "actual:" << '\n'
99                   << arg;
100  return false;
101}
102
103MATCHER_P2(SyncChangeIs, change_type, password, "") {
104  const SyncData& data = arg.sync_data();
105  autofill::PasswordForm form = PasswordFromSpecifics(
106      GetPasswordSpecifics(data));
107  return (arg.change_type() == change_type &&
108          syncer::SyncDataLocal(data).GetTag() ==
109              MakePasswordSyncTag(password) &&
110          (change_type == SyncChange::ACTION_DELETE ||
111           Matches(PasswordIs(password))(form)));
112}
113
114// The argument is std::vector<autofill::PasswordForm*>*. The caller is
115// responsible for the lifetime of all the password forms.
116ACTION_P(AppendForm, form) {
117  arg0->push_back(new autofill::PasswordForm(form));
118  return true;
119}
120
121// Creates a sync data consisting of password specifics. The sign on realm is
122// set to |signon_realm|.
123SyncData CreateSyncData(const std::string& signon_realm) {
124  sync_pb::EntitySpecifics password_data;
125  sync_pb::PasswordSpecificsData* password_specifics =
126      password_data.mutable_password()->mutable_client_only_encrypted_data();
127  password_specifics->set_signon_realm(signon_realm);
128  password_specifics->set_type(autofill::PasswordForm::TYPE_GENERATED);
129  password_specifics->set_times_used(3);
130  password_specifics->set_display_name("Mr. X");
131  password_specifics->set_avatar_url("https://accounts.google.com/Avatar");
132  password_specifics->set_federation_url("https://google.com/federation");
133
134  std::string tag = MakePasswordSyncTag(*password_specifics);
135  return syncer::SyncData::CreateLocalData(tag, tag, password_data);
136}
137
138SyncChange CreateSyncChange(const autofill::PasswordForm& password,
139                            SyncChange::SyncChangeType type) {
140  SyncData data = SyncDataFromPassword(password);
141  return SyncChange(FROM_HERE, type, data);
142}
143
144// A testable implementation of the |PasswordSyncableService| that mocks
145// out all interaction with the password database.
146class MockPasswordSyncableService : public PasswordSyncableService {
147 public:
148  explicit MockPasswordSyncableService(PasswordStoreSync* password_store)
149      : PasswordSyncableService(password_store) {}
150
151  MOCK_METHOD1(StartSyncFlare, void(syncer::ModelType));
152
153 private:
154  DISALLOW_COPY_AND_ASSIGN(MockPasswordSyncableService);
155};
156
157// Mock implementation of SyncChangeProcessor.
158class MockSyncChangeProcessor : public syncer::SyncChangeProcessor {
159 public:
160  MockSyncChangeProcessor() {}
161
162  MOCK_METHOD2(ProcessSyncChanges,
163               SyncError(const tracked_objects::Location&,
164                         const SyncChangeList& list));
165  virtual SyncDataList GetAllSyncData(syncer::ModelType type) const OVERRIDE {
166    NOTREACHED();
167    return SyncDataList();
168  }
169
170 private:
171  DISALLOW_COPY_AND_ASSIGN(MockSyncChangeProcessor);
172};
173
174// Convenience wrapper around a PasswordSyncableService and PasswordStore
175// pair.
176class PasswordSyncableServiceWrapper {
177 public:
178  PasswordSyncableServiceWrapper() {
179    password_store_ = new testing::StrictMock<MockPasswordStore>;
180    service_.reset(new MockPasswordSyncableService(
181        password_store_->GetSyncInterface()));
182    ON_CALL(*password_store_, AddLoginImpl(HasDateSynced()))
183        .WillByDefault(Return(PasswordStoreChangeList()));
184    ON_CALL(*password_store_, RemoveLoginImpl(_))
185        .WillByDefault(Return(PasswordStoreChangeList()));
186    ON_CALL(*password_store_, UpdateLoginImpl(HasDateSynced()))
187        .WillByDefault(Return(PasswordStoreChangeList()));
188    EXPECT_CALL(*password_store(), NotifyLoginsChanged(_)).Times(AnyNumber());
189  }
190
191  ~PasswordSyncableServiceWrapper() {
192    password_store_->Shutdown();
193  }
194
195  MockPasswordStore* password_store() { return password_store_.get(); }
196
197  MockPasswordSyncableService* service() { return service_.get(); }
198
199  // Returnes the scoped_ptr to |service_| thus NULLing out it.
200  scoped_ptr<syncer::SyncChangeProcessor> ReleaseSyncableService() {
201    return service_.PassAs<syncer::SyncChangeProcessor>();
202  }
203
204 private:
205  scoped_refptr<MockPasswordStore> password_store_;
206  scoped_ptr<MockPasswordSyncableService> service_;
207
208  DISALLOW_COPY_AND_ASSIGN(PasswordSyncableServiceWrapper);
209};
210
211class PasswordSyncableServiceTest : public testing::Test {
212 public:
213  PasswordSyncableServiceTest()
214      : processor_(new testing::StrictMock<MockSyncChangeProcessor>) {
215    ON_CALL(*processor_, ProcessSyncChanges(_, _))
216        .WillByDefault(Return(SyncError()));
217  }
218  MockPasswordStore* password_store() { return wrapper_.password_store(); }
219  MockPasswordSyncableService* service() { return wrapper_.service(); }
220
221 protected:
222  scoped_ptr<MockSyncChangeProcessor> processor_;
223
224 private:
225  PasswordSyncableServiceWrapper wrapper_;
226};
227
228
229// Both sync and password db have data that are not present in the other.
230TEST_F(PasswordSyncableServiceTest, AdditionsInBoth) {
231  autofill::PasswordForm form;
232  form.signon_realm = kSignonRealm;
233
234  SyncDataList list;
235  list.push_back(CreateSyncData(kSignonRealm2));
236  autofill::PasswordForm new_from_sync = PasswordFromSpecifics(
237      GetPasswordSpecifics(list.back()));
238
239  EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
240      .WillOnce(AppendForm(form));
241  EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
242      .WillOnce(Return(true));
243  EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(new_from_sync)));
244  EXPECT_CALL(*processor_, ProcessSyncChanges(_, ElementsAre(
245      SyncChangeIs(SyncChange::ACTION_ADD, form))));
246
247  service()->MergeDataAndStartSyncing(
248      syncer::PASSWORDS,
249      list,
250      processor_.PassAs<syncer::SyncChangeProcessor>(),
251      scoped_ptr<syncer::SyncErrorFactory>());
252}
253
254// Sync has data that is not present in the password db.
255TEST_F(PasswordSyncableServiceTest, AdditionOnlyInSync) {
256  SyncDataList list;
257  list.push_back(CreateSyncData(kSignonRealm));
258  autofill::PasswordForm new_from_sync = PasswordFromSpecifics(
259      GetPasswordSpecifics(list.back()));
260
261  EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
262      .WillOnce(Return(true));
263  EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
264      .WillOnce(Return(true));
265  EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(new_from_sync)));
266  EXPECT_CALL(*processor_, ProcessSyncChanges(_, IsEmpty()));
267
268  service()->MergeDataAndStartSyncing(
269      syncer::PASSWORDS,
270      list,
271      processor_.PassAs<syncer::SyncChangeProcessor>(),
272      scoped_ptr<syncer::SyncErrorFactory>());
273}
274
275// Passwords db has data that is not present in sync.
276TEST_F(PasswordSyncableServiceTest, AdditionOnlyInPasswordStore) {
277  autofill::PasswordForm form;
278  form.signon_realm = kSignonRealm;
279  form.times_used = kTimesUsed;
280  form.type = kArbitraryType;
281  form.display_name = base::ASCIIToUTF16(kDisplayName);
282  form.avatar_url = GURL(kAvatarUrl);
283  form.federation_url = GURL(kFederationUrl);
284  EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
285      .WillOnce(AppendForm(form));
286  EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
287      .WillOnce(Return(true));
288
289  EXPECT_CALL(*processor_, ProcessSyncChanges(_, ElementsAre(
290      SyncChangeIs(SyncChange::ACTION_ADD, form))));
291
292  service()->MergeDataAndStartSyncing(
293      syncer::PASSWORDS,
294      SyncDataList(),
295      processor_.PassAs<syncer::SyncChangeProcessor>(),
296      scoped_ptr<syncer::SyncErrorFactory>());
297}
298
299// Both passwords db and sync contain the same data.
300TEST_F(PasswordSyncableServiceTest, BothInSync) {
301  autofill::PasswordForm form;
302  form.signon_realm = kSignonRealm;
303  form.times_used = kTimesUsed;
304  form.type = kArbitraryType;
305  EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
306      .WillOnce(AppendForm(form));
307  EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
308      .WillOnce(Return(true));
309
310  EXPECT_CALL(*processor_, ProcessSyncChanges(_, IsEmpty()));
311
312  service()->MergeDataAndStartSyncing(
313      syncer::PASSWORDS,
314      SyncDataList(1, SyncDataFromPassword(form)),
315      processor_.PassAs<syncer::SyncChangeProcessor>(),
316      scoped_ptr<syncer::SyncErrorFactory>());
317}
318
319// Both passwords db and sync have the same data but they need to be merged
320// as some fields of the data differ.
321TEST_F(PasswordSyncableServiceTest, Merge) {
322  autofill::PasswordForm form1;
323  form1.signon_realm = kSignonRealm;
324  form1.action = GURL("http://pie.com");
325  form1.date_created = base::Time::Now();
326  form1.preferred = true;
327
328  autofill::PasswordForm form2(form1);
329  form2.preferred = false;
330  EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
331      .WillOnce(AppendForm(form1));
332  EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
333      .WillOnce(Return(true));
334  EXPECT_CALL(*password_store(), UpdateLoginImpl(PasswordIs(form2)));
335  EXPECT_CALL(*processor_, ProcessSyncChanges(_, IsEmpty()));
336
337  service()->MergeDataAndStartSyncing(
338      syncer::PASSWORDS,
339      SyncDataList(1, SyncDataFromPassword(form2)),
340      processor_.PassAs<syncer::SyncChangeProcessor>(),
341      scoped_ptr<syncer::SyncErrorFactory>());
342}
343
344// Initiate sync due to local DB changes.
345TEST_F(PasswordSyncableServiceTest, PasswordStoreChanges) {
346  // Save the reference to the processor because |processor_| is NULL after
347  // MergeDataAndStartSyncing().
348  MockSyncChangeProcessor& weak_processor = *processor_;
349  EXPECT_CALL(weak_processor, ProcessSyncChanges(_, IsEmpty()));
350  EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
351      .WillOnce(Return(true));
352  EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
353      .WillOnce(Return(true));
354  service()->MergeDataAndStartSyncing(
355      syncer::PASSWORDS,
356      SyncDataList(),
357      processor_.PassAs<syncer::SyncChangeProcessor>(),
358      scoped_ptr<syncer::SyncErrorFactory>());
359
360  autofill::PasswordForm form1;
361  form1.signon_realm = kSignonRealm;
362  autofill::PasswordForm form2;
363  form2.signon_realm = kSignonRealm2;
364  autofill::PasswordForm form3;
365  form3.signon_realm = kSignonRealm3;
366
367  SyncChangeList sync_list;
368  sync_list.push_back(CreateSyncChange(form1, SyncChange::ACTION_ADD));
369  sync_list.push_back(CreateSyncChange(form2, SyncChange::ACTION_UPDATE));
370  sync_list.push_back(CreateSyncChange(form3, SyncChange::ACTION_DELETE));
371
372  PasswordStoreChangeList list;
373  list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form1));
374  list.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE, form2));
375  list.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, form3));
376  EXPECT_CALL(weak_processor, ProcessSyncChanges(_, ElementsAre(
377      SyncChangeIs(SyncChange::ACTION_ADD, form1),
378      SyncChangeIs(SyncChange::ACTION_UPDATE, form2),
379      SyncChangeIs(SyncChange::ACTION_DELETE, form3))));
380  service()->ActOnPasswordStoreChanges(list);
381}
382
383// Process all types of changes from sync.
384TEST_F(PasswordSyncableServiceTest, ProcessSyncChanges) {
385  autofill::PasswordForm updated_form;
386  updated_form.signon_realm = kSignonRealm;
387  updated_form.action = GURL("http://foo.com");
388  updated_form.date_created = base::Time::Now();
389  autofill::PasswordForm deleted_form;
390  deleted_form.signon_realm = kSignonRealm2;
391  deleted_form.action = GURL("http://bar.com");
392  deleted_form.blacklisted_by_user = true;
393
394  SyncData add_data = CreateSyncData(kSignonRealm3);
395  autofill::PasswordForm new_from_sync = PasswordFromSpecifics(
396      GetPasswordSpecifics(add_data));
397
398  SyncChangeList list;
399  list.push_back(SyncChange(FROM_HERE,
400                            syncer::SyncChange::ACTION_ADD,
401                            add_data));
402  list.push_back(CreateSyncChange(updated_form,
403                                  syncer::SyncChange::ACTION_UPDATE));
404  list.push_back(CreateSyncChange(deleted_form,
405                                  syncer::SyncChange::ACTION_DELETE));
406  EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(new_from_sync)));
407  EXPECT_CALL(*password_store(), UpdateLoginImpl(PasswordIs(updated_form)));
408  EXPECT_CALL(*password_store(), RemoveLoginImpl(PasswordIs(deleted_form)));
409  service()->ProcessSyncChanges(FROM_HERE, list);
410}
411
412// Retrives sync data from the model.
413TEST_F(PasswordSyncableServiceTest, GetAllSyncData) {
414  autofill::PasswordForm form1;
415  form1.signon_realm = kSignonRealm;
416  form1.action = GURL("http://foo.com");
417  form1.times_used = kTimesUsed;
418  form1.type = kArbitraryType;
419  form1.display_name = base::ASCIIToUTF16(kDisplayName);
420  form1.avatar_url = GURL(kAvatarUrl);
421  form1.federation_url = GURL(kFederationUrl);
422  autofill::PasswordForm form2;
423  form2.signon_realm = kSignonRealm2;
424  form2.action = GURL("http://bar.com");
425  form2.blacklisted_by_user = true;
426  EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
427      .WillOnce(AppendForm(form1));
428  EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
429      .WillOnce(AppendForm(form2));
430
431  SyncDataList actual_list = service()->GetAllSyncData(syncer::PASSWORDS);
432  std::vector<autofill::PasswordForm> actual_form_list;
433  for (SyncDataList::iterator it = actual_list.begin();
434       it != actual_list.end(); ++it) {
435    actual_form_list.push_back(
436        PasswordFromSpecifics(GetPasswordSpecifics(*it)));
437  }
438  EXPECT_THAT(actual_form_list, UnorderedElementsAre(PasswordIs(form1),
439                                                     PasswordIs(form2)));
440}
441
442// Creates 2 PasswordSyncableService instances, merges the content of the first
443// one to the second one and back.
444TEST_F(PasswordSyncableServiceTest, MergeDataAndPushBack) {
445  autofill::PasswordForm form1;
446  form1.signon_realm = kSignonRealm;
447  form1.action = GURL("http://foo.com");
448
449  PasswordSyncableServiceWrapper other_service_wrapper;
450  autofill::PasswordForm form2;
451  form2.signon_realm = kSignonRealm2;
452  form2.action = GURL("http://bar.com");
453  EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
454      .WillOnce(AppendForm(form1));
455  EXPECT_CALL(*password_store(), FillBlacklistLogins(_))
456      .WillOnce(Return(true));
457  EXPECT_CALL(*other_service_wrapper.password_store(),
458              FillAutofillableLogins(_)).WillOnce(AppendForm(form2));
459  EXPECT_CALL(*other_service_wrapper.password_store(),
460              FillBlacklistLogins(_)).WillOnce(Return(true));
461
462  EXPECT_CALL(*password_store(), AddLoginImpl(PasswordIs(form2)));
463  EXPECT_CALL(*other_service_wrapper.password_store(),
464              AddLoginImpl(PasswordIs(form1)));
465
466  syncer::SyncDataList other_service_data =
467      other_service_wrapper.service()->GetAllSyncData(syncer::PASSWORDS);
468  service()->MergeDataAndStartSyncing(
469      syncer::PASSWORDS,
470      other_service_data,
471      other_service_wrapper.ReleaseSyncableService(),
472      scoped_ptr<syncer::SyncErrorFactory>());
473}
474
475// Calls ActOnPasswordStoreChanges without SyncChangeProcessor. StartSyncFlare
476// should be called.
477TEST_F(PasswordSyncableServiceTest, StartSyncFlare) {
478  autofill::PasswordForm form;
479  form.signon_realm = kSignonRealm;
480  PasswordStoreChangeList list;
481  list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
482
483  // No flare and no SyncChangeProcessor, the call shouldn't crash.
484  service()->ActOnPasswordStoreChanges(list);
485
486  // Set the flare. It should be called as there is no SyncChangeProcessor.
487  service()->InjectStartSyncFlare(
488      base::Bind(&MockPasswordSyncableService::StartSyncFlare,
489                 base::Unretained(service())));
490  EXPECT_CALL(*service(), StartSyncFlare(syncer::PASSWORDS));
491  service()->ActOnPasswordStoreChanges(list);
492}
493
494// Start syncing with an error. Subsequent password store updates shouldn't be
495// propagated to Sync.
496TEST_F(PasswordSyncableServiceTest, FailedReadFromPasswordStore) {
497  scoped_ptr<syncer::SyncErrorFactoryMock> error_factory(
498      new syncer::SyncErrorFactoryMock);
499  EXPECT_CALL(*password_store(), FillAutofillableLogins(_))
500      .WillOnce(Return(false));
501  EXPECT_CALL(*error_factory, CreateAndUploadError(_, _))
502      .WillOnce(Return(SyncError()));
503  // ActOnPasswordStoreChanges() below shouldn't generate any changes for Sync.
504  // |processor_| will be destroyed in MergeDataAndStartSyncing().
505  EXPECT_CALL(*processor_, ProcessSyncChanges(_, _)).Times(0);
506  service()->MergeDataAndStartSyncing(
507      syncer::PASSWORDS,
508      syncer::SyncDataList(),
509      processor_.PassAs<syncer::SyncChangeProcessor>(),
510      error_factory.PassAs<syncer::SyncErrorFactory>());
511
512  autofill::PasswordForm form;
513  form.signon_realm = kSignonRealm;
514  PasswordStoreChangeList list;
515  list.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form));
516  service()->ActOnPasswordStoreChanges(list);
517}
518
519}  // namespace
520
521}  // namespace password_manager
522