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 <algorithm>
6#include <map>
7#include <string>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/pickle.h"
12#include "base/prefs/pref_service.h"
13#include "base/stl_util.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/synchronization/waitable_event.h"
16#include "chrome/browser/password_manager/native_backend_kwallet_x.h"
17#include "chrome/common/pref_names.h"
18#include "chrome/test/base/testing_profile.h"
19#include "components/autofill/core/common/password_form.h"
20#include "content/public/test/test_browser_thread.h"
21#include "dbus/message.h"
22#include "dbus/mock_bus.h"
23#include "dbus/mock_object_proxy.h"
24#include "dbus/object_path.h"
25#include "dbus/object_proxy.h"
26#include "testing/gmock/include/gmock/gmock.h"
27#include "testing/gtest/include/gtest/gtest.h"
28
29using autofill::PasswordForm;
30using content::BrowserThread;
31using testing::_;
32using testing::Invoke;
33using testing::Return;
34
35namespace {
36
37// This class implements a very simple version of KWallet in memory.
38// We only provide the parts we actually use; the real version has more.
39class TestKWallet {
40 public:
41  typedef std::basic_string<uint8_t> Blob;  // std::string is binary-safe.
42
43  TestKWallet() : reject_local_folders_(false) {}
44
45  void set_reject_local_folders(bool value) { reject_local_folders_ = value; }
46
47  // NOTE: The method names here are the same as the corresponding DBus
48  // methods, and therefore have names that don't match our style guide.
49
50  // Check for presence of a given password folder.
51  bool hasFolder(const std::string& folder) const {
52    return data_.find(folder) != data_.end();
53  }
54
55  // Check for presence of a given password in a given password folder.
56  bool hasEntry(const std::string& folder, const std::string& key) const {
57    Data::const_iterator it = data_.find(folder);
58    return it != data_.end() && it->second.find(key) != it->second.end();
59  }
60
61  // Get a list of password keys in a given password folder.
62  bool entryList(const std::string& folder,
63                 std::vector<std::string>* entries) const {
64    Data::const_iterator it = data_.find(folder);
65    if (it == data_.end()) return false;
66    for (Folder::const_iterator fit = it->second.begin();
67         fit != it->second.end(); ++fit)
68      entries->push_back(fit->first);
69    return true;
70  }
71
72  // Read the password data for a given password in a given password folder.
73  bool readEntry(const std::string& folder, const std::string& key,
74                 Blob* value) const {
75    Data::const_iterator it = data_.find(folder);
76    if (it == data_.end()) return false;
77    Folder::const_iterator fit = it->second.find(key);
78    if (fit == it->second.end()) return false;
79    *value = fit->second;
80    return true;
81  }
82
83  // Create the given password folder.
84  bool createFolder(const std::string& folder) {
85    if (reject_local_folders_ && folder.find('(') != std::string::npos)
86      return false;
87    return data_.insert(make_pair(folder, Folder())).second;
88  }
89
90  // Remove the given password from the given password folder.
91  bool removeEntry(const std::string& folder, const std::string& key) {
92    Data::iterator it = data_.find(folder);
93    if (it == data_.end()) return false;
94    return it->second.erase(key) > 0;
95  }
96
97  // Write the given password data to the given password folder.
98  bool writeEntry(const std::string& folder, const std::string& key,
99                  const Blob& value) {
100    Data::iterator it = data_.find(folder);
101    if (it == data_.end()) return false;
102    it->second[key] = value;
103    return true;
104  }
105
106 private:
107  typedef std::map<std::string, Blob> Folder;
108  typedef std::map<std::string, Folder> Data;
109
110  Data data_;
111  // "Local" folders are folders containing local profile IDs in their names. We
112  // can reject attempts to create them in order to make it easier to create
113  // legacy shared passwords in these tests, for testing the migration code.
114  bool reject_local_folders_;
115
116  // No need to disallow copy and assign. This class is safe to copy and assign.
117};
118
119}  // anonymous namespace
120
121// Obscure magic: we need to declare storage for this constant because we use it
122// in ways that require its address in this test, but not in the actual code.
123const int NativeBackendKWallet::kInvalidKWalletHandle;
124
125// Subclass NativeBackendKWallet to promote some members to public for testing.
126class NativeBackendKWalletStub : public NativeBackendKWallet {
127 public:
128  NativeBackendKWalletStub(LocalProfileId id, PrefService* pref_service)
129      :  NativeBackendKWallet(id, pref_service) {
130  }
131  using NativeBackendKWallet::InitWithBus;
132  using NativeBackendKWallet::kInvalidKWalletHandle;
133  using NativeBackendKWallet::DeserializeValue;
134};
135
136// Provide some test forms to avoid having to set them up in each test.
137class NativeBackendKWalletTestBase : public testing::Test {
138 protected:
139  NativeBackendKWalletTestBase() {
140    form_google_.origin = GURL("http://www.google.com/");
141    form_google_.action = GURL("http://www.google.com/login");
142    form_google_.username_element = UTF8ToUTF16("user");
143    form_google_.username_value = UTF8ToUTF16("joeschmoe");
144    form_google_.password_element = UTF8ToUTF16("pass");
145    form_google_.password_value = UTF8ToUTF16("seekrit");
146    form_google_.submit_element = UTF8ToUTF16("submit");
147    form_google_.signon_realm = "Google";
148
149    form_isc_.origin = GURL("http://www.isc.org/");
150    form_isc_.action = GURL("http://www.isc.org/auth");
151    form_isc_.username_element = UTF8ToUTF16("id");
152    form_isc_.username_value = UTF8ToUTF16("janedoe");
153    form_isc_.password_element = UTF8ToUTF16("passwd");
154    form_isc_.password_value = UTF8ToUTF16("ihazabukkit");
155    form_isc_.submit_element = UTF8ToUTF16("login");
156    form_isc_.signon_realm = "ISC";
157  }
158
159  void CheckPasswordForm(const PasswordForm& expected,
160                         const PasswordForm& actual);
161
162  PasswordForm form_google_;
163  PasswordForm form_isc_;
164};
165
166void NativeBackendKWalletTestBase::CheckPasswordForm(
167    const PasswordForm& expected, const PasswordForm& actual) {
168  EXPECT_EQ(expected.origin, actual.origin);
169  EXPECT_EQ(expected.password_value, actual.password_value);
170  EXPECT_EQ(expected.action, actual.action);
171  EXPECT_EQ(expected.username_element, actual.username_element);
172  EXPECT_EQ(expected.username_value, actual.username_value);
173  EXPECT_EQ(expected.password_element, actual.password_element);
174  EXPECT_EQ(expected.submit_element, actual.submit_element);
175  EXPECT_EQ(expected.signon_realm, actual.signon_realm);
176  EXPECT_EQ(expected.ssl_valid, actual.ssl_valid);
177  EXPECT_EQ(expected.preferred, actual.preferred);
178  // We don't check the date created. It varies.
179  EXPECT_EQ(expected.blacklisted_by_user, actual.blacklisted_by_user);
180  EXPECT_EQ(expected.scheme, actual.scheme);
181}
182
183class NativeBackendKWalletTest : public NativeBackendKWalletTestBase {
184 protected:
185  NativeBackendKWalletTest()
186      : ui_thread_(BrowserThread::UI, &message_loop_),
187        db_thread_(BrowserThread::DB), klauncher_ret_(0),
188        klauncher_contacted_(false), kwallet_runnable_(true),
189        kwallet_running_(true), kwallet_enabled_(true) {
190  }
191
192  virtual void SetUp();
193  virtual void TearDown();
194
195  // Let the DB thread run to completion of all current tasks.
196  void RunDBThread() {
197    base::WaitableEvent event(false, false);
198    BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
199                            base::Bind(ThreadDone, &event));
200    event.Wait();
201    // Some of the tests may post messages to the UI thread, but we don't need
202    // to run those until after the DB thread is finished. So run it here.
203    message_loop_.RunUntilIdle();
204  }
205  static void ThreadDone(base::WaitableEvent* event) {
206    event->Signal();
207  }
208
209  // Utilities to help verify sets of expectations.
210  typedef std::vector<
211              std::pair<std::string,
212                        std::vector<const PasswordForm*> > > ExpectationArray;
213  void CheckPasswordForms(const std::string& folder,
214                          const ExpectationArray& sorted_expected);
215
216  base::MessageLoopForUI message_loop_;
217  content::TestBrowserThread ui_thread_;
218  content::TestBrowserThread db_thread_;
219  TestingProfile profile_;
220
221  scoped_refptr<dbus::MockBus> mock_session_bus_;
222  scoped_refptr<dbus::MockObjectProxy> mock_klauncher_proxy_;
223  scoped_refptr<dbus::MockObjectProxy> mock_kwallet_proxy_;
224
225  int klauncher_ret_;
226  std::string klauncher_error_;
227  bool klauncher_contacted_;
228
229  bool kwallet_runnable_;
230  bool kwallet_running_;
231  bool kwallet_enabled_;
232
233  TestKWallet wallet_;
234
235 private:
236  dbus::Response* KLauncherMethodCall(
237      dbus::MethodCall* method_call, testing::Unused);
238
239  dbus::Response* KWalletMethodCall(
240      dbus::MethodCall* method_call, testing::Unused);
241};
242
243void NativeBackendKWalletTest::SetUp() {
244  ASSERT_TRUE(db_thread_.Start());
245
246  dbus::Bus::Options options;
247  options.bus_type = dbus::Bus::SESSION;
248  mock_session_bus_ = new dbus::MockBus(options);
249
250  mock_klauncher_proxy_ =
251      new dbus::MockObjectProxy(mock_session_bus_.get(),
252                                "org.kde.klauncher",
253                                dbus::ObjectPath("/KLauncher"));
254  EXPECT_CALL(*mock_klauncher_proxy_.get(), MockCallMethodAndBlock(_, _))
255      .WillRepeatedly(
256           Invoke(this, &NativeBackendKWalletTest::KLauncherMethodCall));
257
258  mock_kwallet_proxy_ =
259      new dbus::MockObjectProxy(mock_session_bus_.get(),
260                                "org.kde.kwalletd",
261                                dbus::ObjectPath("/modules/kwalletd"));
262  EXPECT_CALL(*mock_kwallet_proxy_.get(), MockCallMethodAndBlock(_, _))
263      .WillRepeatedly(
264           Invoke(this, &NativeBackendKWalletTest::KWalletMethodCall));
265
266  EXPECT_CALL(
267      *mock_session_bus_.get(),
268      GetObjectProxy("org.kde.klauncher", dbus::ObjectPath("/KLauncher")))
269      .WillRepeatedly(Return(mock_klauncher_proxy_.get()));
270  EXPECT_CALL(
271      *mock_session_bus_.get(),
272      GetObjectProxy("org.kde.kwalletd", dbus::ObjectPath("/modules/kwalletd")))
273      .WillRepeatedly(Return(mock_kwallet_proxy_.get()));
274
275  EXPECT_CALL(*mock_session_bus_.get(), ShutdownAndBlock()).WillOnce(Return())
276      .WillRepeatedly(Return());
277}
278
279void NativeBackendKWalletTest::TearDown() {
280  base::MessageLoop::current()->PostTask(FROM_HERE,
281                                         base::MessageLoop::QuitClosure());
282  base::MessageLoop::current()->Run();
283  db_thread_.Stop();
284}
285
286dbus::Response* NativeBackendKWalletTest::KLauncherMethodCall(
287    dbus::MethodCall* method_call, testing::Unused) {
288  EXPECT_EQ("org.kde.KLauncher", method_call->GetInterface());
289  EXPECT_EQ("start_service_by_desktop_name", method_call->GetMember());
290
291  klauncher_contacted_ = true;
292
293  dbus::MessageReader reader(method_call);
294  std::string service_name;
295  std::vector<std::string> urls;
296  std::vector<std::string> envs;
297  std::string startup_id;
298  bool blind = false;
299
300  EXPECT_TRUE(reader.PopString(&service_name));
301  EXPECT_TRUE(reader.PopArrayOfStrings(&urls));
302  EXPECT_TRUE(reader.PopArrayOfStrings(&envs));
303  EXPECT_TRUE(reader.PopString(&startup_id));
304  EXPECT_TRUE(reader.PopBool(&blind));
305
306  EXPECT_EQ("kwalletd", service_name);
307  EXPECT_TRUE(urls.empty());
308  EXPECT_TRUE(envs.empty());
309  EXPECT_TRUE(startup_id.empty());
310  EXPECT_FALSE(blind);
311
312  if (kwallet_runnable_)
313    kwallet_running_ = true;
314
315  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
316  dbus::MessageWriter writer(response.get());
317  writer.AppendInt32(klauncher_ret_);
318  writer.AppendString(std::string());  // dbus_name
319  writer.AppendString(klauncher_error_);
320  writer.AppendInt32(1234);  // pid
321  return response.release();
322}
323
324dbus::Response* NativeBackendKWalletTest::KWalletMethodCall(
325    dbus::MethodCall* method_call, testing::Unused) {
326  if (!kwallet_running_)
327    return NULL;
328  EXPECT_EQ("org.kde.KWallet", method_call->GetInterface());
329
330  scoped_ptr<dbus::Response> response;
331  if (method_call->GetMember() == "isEnabled") {
332    response = dbus::Response::CreateEmpty();
333    dbus::MessageWriter writer(response.get());
334    writer.AppendBool(kwallet_enabled_);
335  } else if (method_call->GetMember() == "networkWallet") {
336    response = dbus::Response::CreateEmpty();
337    dbus::MessageWriter writer(response.get());
338    writer.AppendString("test_wallet");  // Should match |open| below.
339  } else if (method_call->GetMember() == "open") {
340    dbus::MessageReader reader(method_call);
341    std::string wallet_name;
342    int64_t wallet_id;
343    std::string app_name;
344    EXPECT_TRUE(reader.PopString(&wallet_name));
345    EXPECT_TRUE(reader.PopInt64(&wallet_id));
346    EXPECT_TRUE(reader.PopString(&app_name));
347    EXPECT_EQ("test_wallet", wallet_name);  // Should match |networkWallet|.
348    response = dbus::Response::CreateEmpty();
349    dbus::MessageWriter writer(response.get());
350    writer.AppendInt32(1);  // Can be anything but kInvalidKWalletHandle.
351  } else if (method_call->GetMember() == "hasFolder" ||
352             method_call->GetMember() == "createFolder") {
353    dbus::MessageReader reader(method_call);
354    int handle = NativeBackendKWalletStub::kInvalidKWalletHandle;
355    std::string folder_name;
356    std::string app_name;
357    EXPECT_TRUE(reader.PopInt32(&handle));
358    EXPECT_TRUE(reader.PopString(&folder_name));
359    EXPECT_TRUE(reader.PopString(&app_name));
360    EXPECT_NE(NativeBackendKWalletStub::kInvalidKWalletHandle, handle);
361    response = dbus::Response::CreateEmpty();
362    dbus::MessageWriter writer(response.get());
363    if (method_call->GetMember() == "hasFolder")
364      writer.AppendBool(wallet_.hasFolder(folder_name));
365    else
366      writer.AppendBool(wallet_.createFolder(folder_name));
367  } else if (method_call->GetMember() == "hasEntry" ||
368             method_call->GetMember() == "removeEntry") {
369    dbus::MessageReader reader(method_call);
370    int handle = NativeBackendKWalletStub::kInvalidKWalletHandle;
371    std::string folder_name;
372    std::string key;
373    std::string app_name;
374    EXPECT_TRUE(reader.PopInt32(&handle));
375    EXPECT_TRUE(reader.PopString(&folder_name));
376    EXPECT_TRUE(reader.PopString(&key));
377    EXPECT_TRUE(reader.PopString(&app_name));
378    EXPECT_NE(NativeBackendKWalletStub::kInvalidKWalletHandle, handle);
379    response = dbus::Response::CreateEmpty();
380    dbus::MessageWriter writer(response.get());
381    if (method_call->GetMember() == "hasEntry")
382      writer.AppendBool(wallet_.hasEntry(folder_name, key));
383    else
384      writer.AppendInt32(wallet_.removeEntry(folder_name, key) ? 0 : 1);
385  } else if (method_call->GetMember() == "entryList") {
386    dbus::MessageReader reader(method_call);
387    int handle = NativeBackendKWalletStub::kInvalidKWalletHandle;
388    std::string folder_name;
389    std::string app_name;
390    EXPECT_TRUE(reader.PopInt32(&handle));
391    EXPECT_TRUE(reader.PopString(&folder_name));
392    EXPECT_TRUE(reader.PopString(&app_name));
393    EXPECT_NE(NativeBackendKWalletStub::kInvalidKWalletHandle, handle);
394    std::vector<std::string> entries;
395    if (wallet_.entryList(folder_name, &entries)) {
396      response = dbus::Response::CreateEmpty();
397      dbus::MessageWriter writer(response.get());
398      writer.AppendArrayOfStrings(entries);
399    }
400  } else if (method_call->GetMember() == "readEntry") {
401    dbus::MessageReader reader(method_call);
402    int handle = NativeBackendKWalletStub::kInvalidKWalletHandle;
403    std::string folder_name;
404    std::string key;
405    std::string app_name;
406    EXPECT_TRUE(reader.PopInt32(&handle));
407    EXPECT_TRUE(reader.PopString(&folder_name));
408    EXPECT_TRUE(reader.PopString(&key));
409    EXPECT_TRUE(reader.PopString(&app_name));
410    EXPECT_NE(NativeBackendKWalletStub::kInvalidKWalletHandle, handle);
411    TestKWallet::Blob value;
412    if (wallet_.readEntry(folder_name, key, &value)) {
413      response = dbus::Response::CreateEmpty();
414      dbus::MessageWriter writer(response.get());
415      writer.AppendArrayOfBytes(value.data(), value.size());
416    }
417  } else if (method_call->GetMember() == "writeEntry") {
418    dbus::MessageReader reader(method_call);
419    int handle = NativeBackendKWalletStub::kInvalidKWalletHandle;
420    std::string folder_name;
421    std::string key;
422    uint8_t* bytes = NULL;
423    size_t length = 0;
424    std::string app_name;
425    EXPECT_TRUE(reader.PopInt32(&handle));
426    EXPECT_TRUE(reader.PopString(&folder_name));
427    EXPECT_TRUE(reader.PopString(&key));
428    EXPECT_TRUE(reader.PopArrayOfBytes(&bytes, &length));
429    EXPECT_TRUE(reader.PopString(&app_name));
430    EXPECT_NE(NativeBackendKWalletStub::kInvalidKWalletHandle, handle);
431    response = dbus::Response::CreateEmpty();
432    dbus::MessageWriter writer(response.get());
433    writer.AppendInt32(
434        wallet_.writeEntry(folder_name, key,
435                           TestKWallet::Blob(bytes, length)) ? 0 : 1);
436  }
437
438  EXPECT_FALSE(response.get() == NULL);
439  return response.release();
440}
441
442void NativeBackendKWalletTest::CheckPasswordForms(
443    const std::string& folder, const ExpectationArray& sorted_expected) {
444  EXPECT_TRUE(wallet_.hasFolder(folder));
445  std::vector<std::string> entries;
446  EXPECT_TRUE(wallet_.entryList(folder, &entries));
447  EXPECT_EQ(sorted_expected.size(), entries.size());
448  std::sort(entries.begin(), entries.end());
449  for (size_t i = 0; i < entries.size() && i < sorted_expected.size(); ++i) {
450    EXPECT_EQ(sorted_expected[i].first, entries[i]);
451    TestKWallet::Blob value;
452    EXPECT_TRUE(wallet_.readEntry(folder, entries[i], &value));
453    Pickle pickle(reinterpret_cast<const char*>(value.data()), value.size());
454    std::vector<PasswordForm*> forms;
455    NativeBackendKWalletStub::DeserializeValue(entries[i], pickle, &forms);
456    const std::vector<const PasswordForm*>& expect = sorted_expected[i].second;
457    EXPECT_EQ(expect.size(), forms.size());
458    for (size_t j = 0; j < forms.size() && j < expect.size(); ++j)
459      CheckPasswordForm(*expect[j], *forms[j]);
460    STLDeleteElements(&forms);
461  }
462}
463
464TEST_F(NativeBackendKWalletTest, NotEnabled) {
465  NativeBackendKWalletStub kwallet(42, profile_.GetPrefs());
466  kwallet_enabled_ = false;
467  EXPECT_FALSE(kwallet.InitWithBus(mock_session_bus_));
468  EXPECT_FALSE(klauncher_contacted_);
469}
470
471TEST_F(NativeBackendKWalletTest, NotRunnable) {
472  NativeBackendKWalletStub kwallet(42, profile_.GetPrefs());
473  kwallet_runnable_ = false;
474  kwallet_running_ = false;
475  EXPECT_FALSE(kwallet.InitWithBus(mock_session_bus_));
476  EXPECT_TRUE(klauncher_contacted_);
477}
478
479TEST_F(NativeBackendKWalletTest, NotRunningOrEnabled) {
480  NativeBackendKWalletStub kwallet(42, profile_.GetPrefs());
481  kwallet_running_ = false;
482  kwallet_enabled_ = false;
483  EXPECT_FALSE(kwallet.InitWithBus(mock_session_bus_));
484  EXPECT_TRUE(klauncher_contacted_);
485}
486
487TEST_F(NativeBackendKWalletTest, NotRunning) {
488  NativeBackendKWalletStub kwallet(42, profile_.GetPrefs());
489  kwallet_running_ = false;
490  EXPECT_TRUE(kwallet.InitWithBus(mock_session_bus_));
491  EXPECT_TRUE(klauncher_contacted_);
492}
493
494TEST_F(NativeBackendKWalletTest, BasicStartup) {
495  NativeBackendKWalletStub kwallet(42, profile_.GetPrefs());
496  EXPECT_TRUE(kwallet.InitWithBus(mock_session_bus_));
497  EXPECT_FALSE(klauncher_contacted_);
498}
499
500TEST_F(NativeBackendKWalletTest, BasicAddLogin) {
501  // Pretend that the migration has already taken place.
502  profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
503
504  NativeBackendKWalletStub backend(42, profile_.GetPrefs());
505  EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
506
507  BrowserThread::PostTask(
508      BrowserThread::DB, FROM_HERE,
509      base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
510                 base::Unretained(&backend), form_google_));
511
512  RunDBThread();
513
514  EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
515
516  std::vector<const PasswordForm*> forms;
517  forms.push_back(&form_google_);
518  ExpectationArray expected;
519  expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
520  CheckPasswordForms("Chrome Form Data (42)", expected);
521}
522
523TEST_F(NativeBackendKWalletTest, BasicListLogins) {
524  // Pretend that the migration has already taken place.
525  profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
526
527  NativeBackendKWalletStub backend(42, profile_.GetPrefs());
528  EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
529
530  BrowserThread::PostTask(
531      BrowserThread::DB, FROM_HERE,
532      base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
533                 base::Unretained(&backend), form_google_));
534
535  std::vector<PasswordForm*> form_list;
536  BrowserThread::PostTask(
537      BrowserThread::DB, FROM_HERE,
538      base::Bind(
539          base::IgnoreResult(&NativeBackendKWalletStub::GetAutofillableLogins),
540          base::Unretained(&backend), &form_list));
541
542  RunDBThread();
543
544  // Quick check that we got something back.
545  EXPECT_EQ(1u, form_list.size());
546  STLDeleteElements(&form_list);
547
548  EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
549
550  std::vector<const PasswordForm*> forms;
551  forms.push_back(&form_google_);
552  ExpectationArray expected;
553  expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
554  CheckPasswordForms("Chrome Form Data (42)", expected);
555}
556
557TEST_F(NativeBackendKWalletTest, BasicRemoveLogin) {
558  // Pretend that the migration has already taken place.
559  profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
560
561  NativeBackendKWalletStub backend(42, profile_.GetPrefs());
562  EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
563
564  BrowserThread::PostTask(
565      BrowserThread::DB, FROM_HERE,
566      base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
567                 base::Unretained(&backend), form_google_));
568
569  RunDBThread();
570
571  EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
572
573  std::vector<const PasswordForm*> forms;
574  forms.push_back(&form_google_);
575  ExpectationArray expected;
576  expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
577  CheckPasswordForms("Chrome Form Data (42)", expected);
578
579  BrowserThread::PostTask(
580      BrowserThread::DB, FROM_HERE,
581      base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::RemoveLogin),
582                 base::Unretained(&backend), form_google_));
583
584  RunDBThread();
585
586  expected.clear();
587  CheckPasswordForms("Chrome Form Data (42)", expected);
588}
589
590TEST_F(NativeBackendKWalletTest, RemoveNonexistentLogin) {
591  // Pretend that the migration has already taken place.
592  profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
593
594  NativeBackendKWalletStub backend(42, profile_.GetPrefs());
595  EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
596
597  // First add an unrelated login.
598  BrowserThread::PostTask(
599      BrowserThread::DB, FROM_HERE,
600      base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
601                 base::Unretained(&backend), form_google_));
602
603  RunDBThread();
604
605  EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
606
607  std::vector<const PasswordForm*> forms;
608  forms.push_back(&form_google_);
609  ExpectationArray expected;
610  expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
611  CheckPasswordForms("Chrome Form Data (42)", expected);
612
613  // Attempt to remove a login that doesn't exist.
614  BrowserThread::PostTask(
615      BrowserThread::DB, FROM_HERE,
616      base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::RemoveLogin),
617                 base::Unretained(&backend), form_isc_));
618
619  // Make sure we can still get the first form back.
620  std::vector<PasswordForm*> form_list;
621  BrowserThread::PostTask(
622      BrowserThread::DB, FROM_HERE,
623      base::Bind(
624          base::IgnoreResult(&NativeBackendKWalletStub::GetAutofillableLogins),
625          base::Unretained(&backend), &form_list));
626
627  RunDBThread();
628
629  // Quick check that we got something back.
630  EXPECT_EQ(1u, form_list.size());
631  STLDeleteElements(&form_list);
632
633  CheckPasswordForms("Chrome Form Data (42)", expected);
634}
635
636TEST_F(NativeBackendKWalletTest, AddDuplicateLogin) {
637  // Pretend that the migration has already taken place.
638  profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
639
640  NativeBackendKWalletStub backend(42, profile_.GetPrefs());
641  EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
642
643  BrowserThread::PostTask(
644      BrowserThread::DB, FROM_HERE,
645      base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
646                 base::Unretained(&backend), form_google_));
647  BrowserThread::PostTask(
648      BrowserThread::DB, FROM_HERE,
649      base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
650                 base::Unretained(&backend), form_google_));
651
652  RunDBThread();
653
654  EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
655
656  std::vector<const PasswordForm*> forms;
657  forms.push_back(&form_google_);
658  ExpectationArray expected;
659  expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
660  CheckPasswordForms("Chrome Form Data (42)", expected);
661}
662
663TEST_F(NativeBackendKWalletTest, ListLoginsAppends) {
664  // Pretend that the migration has already taken place.
665  profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
666
667  NativeBackendKWalletStub backend(42, profile_.GetPrefs());
668  EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
669
670  BrowserThread::PostTask(
671      BrowserThread::DB, FROM_HERE,
672      base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
673                 base::Unretained(&backend), form_google_));
674
675  // Send the same request twice with the same list both times.
676  std::vector<PasswordForm*> form_list;
677  BrowserThread::PostTask(
678      BrowserThread::DB, FROM_HERE,
679      base::Bind(
680          base::IgnoreResult(&NativeBackendKWalletStub::GetAutofillableLogins),
681          base::Unretained(&backend), &form_list));
682  BrowserThread::PostTask(
683      BrowserThread::DB, FROM_HERE,
684      base::Bind(
685          base::IgnoreResult(&NativeBackendKWalletStub::GetAutofillableLogins),
686          base::Unretained(&backend), &form_list));
687
688  RunDBThread();
689
690  // Quick check that we got two results back.
691  EXPECT_EQ(2u, form_list.size());
692  STLDeleteElements(&form_list);
693
694  EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
695
696  std::vector<const PasswordForm*> forms;
697  forms.push_back(&form_google_);
698  ExpectationArray expected;
699  expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
700  CheckPasswordForms("Chrome Form Data (42)", expected);
701}
702
703// TODO(mdm): add more basic (i.e. non-migration) tests here at some point.
704// (For example tests for storing >1 password per realm pickle.)
705
706TEST_F(NativeBackendKWalletTest, DISABLED_MigrateOneLogin) {
707  // Reject attempts to migrate so we can populate the store.
708  wallet_.set_reject_local_folders(true);
709
710  {
711    NativeBackendKWalletStub backend(42, profile_.GetPrefs());
712    EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
713
714    BrowserThread::PostTask(
715        BrowserThread::DB, FROM_HERE,
716        base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
717                   base::Unretained(&backend), form_google_));
718
719    // Make sure we can get the form back even when migration is failing.
720    std::vector<PasswordForm*> form_list;
721    BrowserThread::PostTask(
722        BrowserThread::DB, FROM_HERE,
723        base::Bind(
724            base::IgnoreResult(
725                &NativeBackendKWalletStub::GetAutofillableLogins),
726            base::Unretained(&backend), &form_list));
727
728    RunDBThread();
729
730    // Quick check that we got something back.
731    EXPECT_EQ(1u, form_list.size());
732    STLDeleteElements(&form_list);
733  }
734
735  EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data (42)"));
736
737  std::vector<const PasswordForm*> forms;
738  forms.push_back(&form_google_);
739  ExpectationArray expected;
740  expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
741  CheckPasswordForms("Chrome Form Data", expected);
742
743  // Now allow the migration.
744  wallet_.set_reject_local_folders(false);
745
746  {
747    NativeBackendKWalletStub backend(42, profile_.GetPrefs());
748    EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
749
750    // Trigger the migration by looking something up.
751    std::vector<PasswordForm*> form_list;
752    BrowserThread::PostTask(
753        BrowserThread::DB, FROM_HERE,
754        base::Bind(
755            base::IgnoreResult(
756                &NativeBackendKWalletStub::GetAutofillableLogins),
757            base::Unretained(&backend), &form_list));
758
759    RunDBThread();
760
761    // Quick check that we got something back.
762    EXPECT_EQ(1u, form_list.size());
763    STLDeleteElements(&form_list);
764  }
765
766  CheckPasswordForms("Chrome Form Data", expected);
767  CheckPasswordForms("Chrome Form Data (42)", expected);
768
769  // Check that we have set the persistent preference.
770  EXPECT_TRUE(
771      profile_.GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
772}
773
774TEST_F(NativeBackendKWalletTest, DISABLED_MigrateToMultipleProfiles) {
775  // Reject attempts to migrate so we can populate the store.
776  wallet_.set_reject_local_folders(true);
777
778  {
779    NativeBackendKWalletStub backend(42, profile_.GetPrefs());
780    EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
781
782    BrowserThread::PostTask(
783        BrowserThread::DB, FROM_HERE,
784        base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
785                   base::Unretained(&backend), form_google_));
786
787    RunDBThread();
788  }
789
790  EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data (42)"));
791
792  std::vector<const PasswordForm*> forms;
793  forms.push_back(&form_google_);
794  ExpectationArray expected;
795  expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
796  CheckPasswordForms("Chrome Form Data", expected);
797
798  // Now allow the migration.
799  wallet_.set_reject_local_folders(false);
800
801  {
802    NativeBackendKWalletStub backend(42, profile_.GetPrefs());
803    EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
804
805    // Trigger the migration by looking something up.
806    std::vector<PasswordForm*> form_list;
807    BrowserThread::PostTask(
808        BrowserThread::DB, FROM_HERE,
809        base::Bind(
810            base::IgnoreResult(
811                &NativeBackendKWalletStub::GetAutofillableLogins),
812            base::Unretained(&backend), &form_list));
813
814    RunDBThread();
815
816    // Quick check that we got something back.
817    EXPECT_EQ(1u, form_list.size());
818    STLDeleteElements(&form_list);
819  }
820
821  CheckPasswordForms("Chrome Form Data", expected);
822  CheckPasswordForms("Chrome Form Data (42)", expected);
823
824  // Check that we have set the persistent preference.
825  EXPECT_TRUE(
826      profile_.GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
827
828  // Normally we'd actually have a different profile. But in the test just reset
829  // the profile's persistent pref; we pass in the local profile id anyway.
830  profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, false);
831
832  {
833    NativeBackendKWalletStub backend(24, profile_.GetPrefs());
834    EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
835
836    // Trigger the migration by looking something up.
837    std::vector<PasswordForm*> form_list;
838    BrowserThread::PostTask(
839        BrowserThread::DB, FROM_HERE,
840        base::Bind(
841            base::IgnoreResult(
842                &NativeBackendKWalletStub::GetAutofillableLogins),
843            base::Unretained(&backend), &form_list));
844
845    RunDBThread();
846
847    // Quick check that we got something back.
848    EXPECT_EQ(1u, form_list.size());
849    STLDeleteElements(&form_list);
850  }
851
852  CheckPasswordForms("Chrome Form Data", expected);
853  CheckPasswordForms("Chrome Form Data (42)", expected);
854  CheckPasswordForms("Chrome Form Data (24)", expected);
855}
856
857TEST_F(NativeBackendKWalletTest, DISABLED_NoMigrationWithPrefSet) {
858  // Reject attempts to migrate so we can populate the store.
859  wallet_.set_reject_local_folders(true);
860
861  {
862    NativeBackendKWalletStub backend(42, profile_.GetPrefs());
863    EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
864
865    BrowserThread::PostTask(
866        BrowserThread::DB, FROM_HERE,
867        base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
868                   base::Unretained(&backend), form_google_));
869
870    RunDBThread();
871  }
872
873  EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data (42)"));
874
875  std::vector<const PasswordForm*> forms;
876  forms.push_back(&form_google_);
877  ExpectationArray expected;
878  expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
879  CheckPasswordForms("Chrome Form Data", expected);
880
881  // Now allow migration, but also pretend that the it has already taken place.
882  wallet_.set_reject_local_folders(false);
883  profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
884
885  {
886    NativeBackendKWalletStub backend(42, profile_.GetPrefs());
887    EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
888
889    // Trigger the migration by adding a new login.
890    BrowserThread::PostTask(
891        BrowserThread::DB, FROM_HERE,
892        base::Bind(base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
893                   base::Unretained(&backend), form_isc_));
894
895    // Look up all logins; we expect only the one we added.
896    std::vector<PasswordForm*> form_list;
897    BrowserThread::PostTask(
898        BrowserThread::DB, FROM_HERE,
899        base::Bind(
900            base::IgnoreResult(
901                &NativeBackendKWalletStub::GetAutofillableLogins),
902            base::Unretained(&backend), &form_list));
903
904    RunDBThread();
905
906    // Quick check that we got the right thing back.
907    EXPECT_EQ(1u, form_list.size());
908    if (form_list.size() > 0)
909      EXPECT_EQ(form_isc_.signon_realm, form_list[0]->signon_realm);
910    STLDeleteElements(&form_list);
911  }
912
913  CheckPasswordForms("Chrome Form Data", expected);
914
915  forms[0] = &form_isc_;
916  expected.clear();
917  expected.push_back(make_pair(std::string(form_isc_.signon_realm), forms));
918  CheckPasswordForms("Chrome Form Data (42)", expected);
919}
920
921TEST_F(NativeBackendKWalletTest, DISABLED_DeleteMigratedPasswordIsIsolated) {
922  // Reject attempts to migrate so we can populate the store.
923  wallet_.set_reject_local_folders(true);
924
925  {
926    NativeBackendKWalletStub backend(42, profile_.GetPrefs());
927    EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
928
929    BrowserThread::PostTask(
930        BrowserThread::DB, FROM_HERE,
931        base::Bind(
932            base::IgnoreResult(&NativeBackendKWalletStub::AddLogin),
933            base::Unretained(&backend), form_google_));
934
935    RunDBThread();
936  }
937
938  EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data (42)"));
939
940  std::vector<const PasswordForm*> forms;
941  forms.push_back(&form_google_);
942  ExpectationArray expected;
943  expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
944  CheckPasswordForms("Chrome Form Data", expected);
945
946  // Now allow the migration.
947  wallet_.set_reject_local_folders(false);
948
949  {
950    NativeBackendKWalletStub backend(42, profile_.GetPrefs());
951    EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
952
953    // Trigger the migration by looking something up.
954    std::vector<PasswordForm*> form_list;
955    BrowserThread::PostTask(
956        BrowserThread::DB, FROM_HERE,
957        base::Bind(
958            base::IgnoreResult(
959                &NativeBackendKWalletStub::GetAutofillableLogins),
960            base::Unretained(&backend), &form_list));
961
962    RunDBThread();
963
964    // Quick check that we got something back.
965    EXPECT_EQ(1u, form_list.size());
966    STLDeleteElements(&form_list);
967  }
968
969  CheckPasswordForms("Chrome Form Data", expected);
970  CheckPasswordForms("Chrome Form Data (42)", expected);
971
972  // Check that we have set the persistent preference.
973  EXPECT_TRUE(
974      profile_.GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
975
976  // Normally we'd actually have a different profile. But in the test just reset
977  // the profile's persistent pref; we pass in the local profile id anyway.
978  profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, false);
979
980  {
981    NativeBackendKWalletStub backend(24, profile_.GetPrefs());
982    EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
983
984    // Trigger the migration by looking something up.
985    std::vector<PasswordForm*> form_list;
986    BrowserThread::PostTask(
987        BrowserThread::DB, FROM_HERE,
988        base::Bind(
989            base::IgnoreResult(
990                &NativeBackendKWalletStub::GetAutofillableLogins),
991            base::Unretained(&backend), &form_list));
992
993    RunDBThread();
994
995    // Quick check that we got something back.
996    EXPECT_EQ(1u, form_list.size());
997    STLDeleteElements(&form_list);
998
999    // There should be three passwords now.
1000    CheckPasswordForms("Chrome Form Data", expected);
1001    CheckPasswordForms("Chrome Form Data (42)", expected);
1002    CheckPasswordForms("Chrome Form Data (24)", expected);
1003
1004    // Now delete the password from this second profile.
1005    BrowserThread::PostTask(
1006        BrowserThread::DB, FROM_HERE,
1007        base::Bind(
1008            base::IgnoreResult(&NativeBackendKWalletStub::RemoveLogin),
1009            base::Unretained(&backend), form_google_));
1010
1011    RunDBThread();
1012
1013    // The other two copies of the password in different profiles should remain.
1014    CheckPasswordForms("Chrome Form Data", expected);
1015    CheckPasswordForms("Chrome Form Data (42)", expected);
1016    expected.clear();
1017    CheckPasswordForms("Chrome Form Data (24)", expected);
1018  }
1019}
1020
1021class NativeBackendKWalletPickleTest : public NativeBackendKWalletTestBase {
1022 protected:
1023  void CreateVersion0Pickle(bool size_32,
1024                            const PasswordForm& form,
1025                            Pickle* pickle);
1026  void CheckVersion0Pickle(bool size_32, PasswordForm::Scheme scheme);
1027};
1028
1029void NativeBackendKWalletPickleTest::CreateVersion0Pickle(
1030    bool size_32, const PasswordForm& form, Pickle* pickle) {
1031  const int kPickleVersion0 = 0;
1032  pickle->WriteInt(kPickleVersion0);
1033  if (size_32)
1034    pickle->WriteUInt32(1);  // Size of form list. 32 bits.
1035  else
1036    pickle->WriteUInt64(1);  // Size of form list. 64 bits.
1037  pickle->WriteInt(form.scheme);
1038  pickle->WriteString(form.origin.spec());
1039  pickle->WriteString(form.action.spec());
1040  pickle->WriteString16(form.username_element);
1041  pickle->WriteString16(form.username_value);
1042  pickle->WriteString16(form.password_element);
1043  pickle->WriteString16(form.password_value);
1044  pickle->WriteString16(form.submit_element);
1045  pickle->WriteBool(form.ssl_valid);
1046  pickle->WriteBool(form.preferred);
1047  pickle->WriteBool(form.blacklisted_by_user);
1048  pickle->WriteInt64(form.date_created.ToTimeT());
1049}
1050
1051void NativeBackendKWalletPickleTest::CheckVersion0Pickle(
1052    bool size_32, PasswordForm::Scheme scheme) {
1053  Pickle pickle;
1054  PasswordForm form = form_google_;
1055  form.scheme = scheme;
1056  CreateVersion0Pickle(size_32, form, &pickle);
1057  std::vector<PasswordForm*> form_list;
1058  NativeBackendKWalletStub::DeserializeValue(form.signon_realm,
1059                                             pickle, &form_list);
1060  EXPECT_EQ(1u, form_list.size());
1061  if (form_list.size() > 0)
1062    CheckPasswordForm(form, *form_list[0]);
1063  STLDeleteElements(&form_list);
1064}
1065
1066// We try both SCHEME_HTML and SCHEME_BASIC since the scheme is stored right
1067// after the size in the pickle, so it's what gets read as part of the count
1068// when reading 32-bit pickles on 64-bit systems. SCHEME_HTML is 0 (so we'll
1069// detect errors later) while SCHEME_BASIC is 1 (so we'll detect it then). We
1070// try both 32-bit and 64-bit pickles since only one will be the "other" size
1071// for whatever architecture we're running on, but we want to make sure we can
1072// read all combinations in any event.
1073
1074TEST_F(NativeBackendKWalletPickleTest, ReadsOld32BitHTMLPickles) {
1075  CheckVersion0Pickle(true, PasswordForm::SCHEME_HTML);
1076}
1077
1078TEST_F(NativeBackendKWalletPickleTest, ReadsOld32BitHTTPPickles) {
1079  CheckVersion0Pickle(true, PasswordForm::SCHEME_BASIC);
1080}
1081
1082TEST_F(NativeBackendKWalletPickleTest, ReadsOld64BitHTMLPickles) {
1083  CheckVersion0Pickle(false, PasswordForm::SCHEME_HTML);
1084}
1085
1086TEST_F(NativeBackendKWalletPickleTest, ReadsOld64BitHTTPPickles) {
1087  CheckVersion0Pickle(false, PasswordForm::SCHEME_BASIC);
1088}
1089