summaryrefslogtreecommitdiffstats
path: root/chrome/browser/password_manager
diff options
context:
space:
mode:
authormdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-20 04:11:37 +0000
committermdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-09-20 04:11:37 +0000
commita5df176f277e700bf68d377b720bc1db2ed34c31 (patch)
tree952b1c4ce5f541ff130d666bc0ebbb84d585a235 /chrome/browser/password_manager
parenta0f5900848d6580bbfb0a242de097c3ef723eefd (diff)
downloadchromium_src-a5df176f277e700bf68d377b720bc1db2ed34c31.zip
chromium_src-a5df176f277e700bf68d377b720bc1db2ed34c31.tar.gz
chromium_src-a5df176f277e700bf68d377b720bc1db2ed34c31.tar.bz2
Linux: clean up a bit after getting rid of the last use of dbus-glib.
Also add unit tests for KWallet integration, now possible due to our mockable DBus client library. Review URL: http://codereview.chromium.org/7930019 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@101916 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/password_manager')
-rw-r--r--chrome/browser/password_manager/native_backend_gnome_x_unittest.cc63
-rw-r--r--chrome/browser/password_manager/native_backend_kwallet_x.cc95
-rw-r--r--chrome/browser/password_manager/native_backend_kwallet_x.h35
-rw-r--r--chrome/browser/password_manager/native_backend_kwallet_x_unittest.cc1000
4 files changed, 1097 insertions, 96 deletions
diff --git a/chrome/browser/password_manager/native_backend_gnome_x_unittest.cc b/chrome/browser/password_manager/native_backend_gnome_x_unittest.cc
index dc7e4fd..2aa8bb1 100644
--- a/chrome/browser/password_manager/native_backend_gnome_x_unittest.cc
+++ b/chrome/browser/password_manager/native_backend_gnome_x_unittest.cc
@@ -292,7 +292,6 @@ class NativeBackendGnomeTest : public testing::Test {
ASSERT_TRUE(db_thread_.Start());
MockGnomeKeyringLoader::LoadMockGnomeKeyring();
- profile_.reset(new TestingProfile());
form_google_.origin = GURL("http://www.google.com/");
form_google_.action = GURL("http://www.google.com/login");
@@ -389,7 +388,7 @@ class NativeBackendGnomeTest : public testing::Test {
BrowserThread ui_thread_;
BrowserThread db_thread_;
- scoped_ptr<TestingProfile> profile_;
+ TestingProfile profile_;
// Provide some test forms to avoid having to set them up in each test.
PasswordForm form_google_;
@@ -398,9 +397,9 @@ class NativeBackendGnomeTest : public testing::Test {
TEST_F(NativeBackendGnomeTest, BasicAddLogin) {
// Pretend that the migration has already taken place.
- profile_->GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
@@ -417,9 +416,9 @@ TEST_F(NativeBackendGnomeTest, BasicAddLogin) {
TEST_F(NativeBackendGnomeTest, BasicListLogins) {
// Pretend that the migration has already taken place.
- profile_->GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
@@ -446,9 +445,9 @@ TEST_F(NativeBackendGnomeTest, BasicListLogins) {
TEST_F(NativeBackendGnomeTest, BasicRemoveLogin) {
// Pretend that the migration has already taken place.
- profile_->GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
@@ -474,9 +473,9 @@ TEST_F(NativeBackendGnomeTest, BasicRemoveLogin) {
TEST_F(NativeBackendGnomeTest, RemoveNonexistentLogin) {
// Pretend that the migration has already taken place.
- profile_->GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
// First add an unrelated login.
@@ -517,9 +516,9 @@ TEST_F(NativeBackendGnomeTest, RemoveNonexistentLogin) {
TEST_F(NativeBackendGnomeTest, AddDuplicateLogin) {
// Pretend that the migration has already taken place.
- profile_->GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
@@ -540,9 +539,9 @@ TEST_F(NativeBackendGnomeTest, AddDuplicateLogin) {
TEST_F(NativeBackendGnomeTest, ListLoginsAppends) {
// Pretend that the migration has already taken place.
- profile_->GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
@@ -579,7 +578,7 @@ TEST_F(NativeBackendGnomeTest, MigrateOneLogin) {
mock_keyring_reject_local_ids = true;
{
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
@@ -609,7 +608,7 @@ TEST_F(NativeBackendGnomeTest, MigrateOneLogin) {
mock_keyring_reject_local_ids = false;
{
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
// This should not trigger migration because there will be no results.
@@ -633,10 +632,10 @@ TEST_F(NativeBackendGnomeTest, MigrateOneLogin) {
// Check that we haven't set the persistent preference.
EXPECT_FALSE(
- profile_->GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
+ profile_.GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
{
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
// Trigger the migration by looking something up.
@@ -661,7 +660,7 @@ TEST_F(NativeBackendGnomeTest, MigrateOneLogin) {
// Check that we have set the persistent preference.
EXPECT_TRUE(
- profile_->GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
+ profile_.GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
}
TEST_F(NativeBackendGnomeTest, MigrateToMultipleProfiles) {
@@ -669,7 +668,7 @@ TEST_F(NativeBackendGnomeTest, MigrateToMultipleProfiles) {
mock_keyring_reject_local_ids = true;
{
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
@@ -688,7 +687,7 @@ TEST_F(NativeBackendGnomeTest, MigrateToMultipleProfiles) {
mock_keyring_reject_local_ids = false;
{
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
// Trigger the migration by looking something up.
@@ -713,14 +712,14 @@ TEST_F(NativeBackendGnomeTest, MigrateToMultipleProfiles) {
// Check that we have set the persistent preference.
EXPECT_TRUE(
- profile_->GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
+ profile_.GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
// Normally we'd actually have a different profile. But in the test just reset
// the profile's persistent pref; we pass in the local profile id anyway.
- profile_->GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, false);
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, false);
{
- NativeBackendGnome backend(24, profile_->GetPrefs());
+ NativeBackendGnome backend(24, profile_.GetPrefs());
backend.Init();
// Trigger the migration by looking something up.
@@ -751,7 +750,7 @@ TEST_F(NativeBackendGnomeTest, NoMigrationWithPrefSet) {
mock_keyring_reject_local_ids = true;
{
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
@@ -768,10 +767,10 @@ TEST_F(NativeBackendGnomeTest, NoMigrationWithPrefSet) {
// Now allow migration, but also pretend that the it has already taken place.
mock_keyring_reject_local_ids = false;
- profile_->GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
{
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
// Trigger the migration by adding a new login.
@@ -808,7 +807,7 @@ TEST_F(NativeBackendGnomeTest, DeleteMigratedPasswordIsIsolated) {
mock_keyring_reject_local_ids = true;
{
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
@@ -827,7 +826,7 @@ TEST_F(NativeBackendGnomeTest, DeleteMigratedPasswordIsIsolated) {
mock_keyring_reject_local_ids = false;
{
- NativeBackendGnome backend(42, profile_->GetPrefs());
+ NativeBackendGnome backend(42, profile_.GetPrefs());
backend.Init();
// Trigger the migration by looking something up.
@@ -852,14 +851,14 @@ TEST_F(NativeBackendGnomeTest, DeleteMigratedPasswordIsIsolated) {
// Check that we have set the persistent preference.
EXPECT_TRUE(
- profile_->GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
+ profile_.GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
// Normally we'd actually have a different profile. But in the test just reset
// the profile's persistent pref; we pass in the local profile id anyway.
- profile_->GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, false);
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, false);
{
- NativeBackendGnome backend(24, profile_->GetPrefs());
+ NativeBackendGnome backend(24, profile_.GetPrefs());
backend.Init();
// Trigger the migration by looking something up.
diff --git a/chrome/browser/password_manager/native_backend_kwallet_x.cc b/chrome/browser/password_manager/native_backend_kwallet_x.cc
index 41d1edd..52f63df 100644
--- a/chrome/browser/password_manager/native_backend_kwallet_x.cc
+++ b/chrome/browser/password_manager/native_backend_kwallet_x.cc
@@ -6,6 +6,7 @@
#include <vector>
+#include "base/bind.h"
#include "base/logging.h"
#include "base/pickle.h"
#include "base/stl_util.h"
@@ -54,39 +55,35 @@ NativeBackendKWallet::~NativeBackendKWallet() {
// destroyed before that occurs, but that's OK.
if (session_bus_.get()) {
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
- NewRunnableMethod(
- session_bus_.get(),
- &dbus::Bus::ShutdownAndBlock));
+ base::Bind(&dbus::Bus::ShutdownAndBlock,
+ session_bus_.get()));
}
}
bool NativeBackendKWallet::Init() {
+ // Without the |optional_bus| parameter, a real bus will be instantiated.
+ return InitWithBus(scoped_refptr<dbus::Bus>());
+}
+
+bool NativeBackendKWallet::InitWithBus(scoped_refptr<dbus::Bus> optional_bus) {
// We must synchronously do a few DBus calls to figure out if initialization
// succeeds, but later, we'll want to do most work on the DB thread. So we
// have to do the initialization on the DB thread here too, and wait for it.
- scoped_refptr<dbus::Bus> optional_bus; // Will construct its own.
bool success = false;
base::WaitableEvent event(false, false);
+ // NativeBackendKWallet isn't reference counted, but we wait for InitWithBus
+ // to finish, so we can safely use base::Unretained here.
BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
- NewRunnableMethod(
- this, &NativeBackendKWallet::InitWithBus,
- optional_bus, &event, &success));
+ base::Bind(&NativeBackendKWallet::InitOnDBThread,
+ base::Unretained(this),
+ optional_bus, &event, &success));
event.Wait();
return success;
}
-// NativeBackendKWallet isn't reference counted, but the one place we post a
-// message to it (in InitWithBus below) waits for the task to run. So we can
-// disable needing reference counting safely here.
-template<>
-struct RunnableMethodTraits<NativeBackendKWallet> {
- void RetainCallee(NativeBackendKWallet*) {}
- void ReleaseCallee(NativeBackendKWallet*) {}
-};
-
-void NativeBackendKWallet::InitWithBus(scoped_refptr<dbus::Bus> optional_bus,
- base::WaitableEvent* event,
- bool* success) {
+void NativeBackendKWallet::InitOnDBThread(scoped_refptr<dbus::Bus> optional_bus,
+ base::WaitableEvent* event,
+ bool* success) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
DCHECK(!session_bus_.get());
if (optional_bus.get()) {
@@ -101,11 +98,13 @@ void NativeBackendKWallet::InitWithBus(scoped_refptr<dbus::Bus> optional_bus,
}
kwallet_proxy_ =
session_bus_->GetObjectProxy(kKWalletServiceName, kKWalletPath);
- // kwalletd may not be running. If it fails to initialize, try to start it
- // and then try to initialize it again. (Note the short-circuit evaluation.)
- *success = (InitWallet() || (StartKWalletd() && InitWallet()));
- if (event)
- event->Signal();
+ // kwalletd may not be running. If we get a temporary failure initializing it,
+ // try to start it and then try again. (Note the short-circuit evaluation.)
+ const InitResult result = InitWallet();
+ *success = (result == INIT_SUCCESS ||
+ (result == TEMPORARY_FAIL &&
+ StartKWalletd() && InitWallet() == INIT_SUCCESS));
+ event->Signal();
}
bool NativeBackendKWallet::StartKWalletd() {
@@ -118,14 +117,12 @@ bool NativeBackendKWallet::StartKWalletd() {
dbus::MethodCall method_call(kKLauncherInterface,
"start_service_by_desktop_name");
dbus::MessageWriter builder(&method_call);
- dbus::MessageWriter empty(&method_call);
- builder.AppendString("kwalletd"); // serviceName
- builder.OpenArray("s", &empty); // urls
- builder.CloseContainer(&empty);
- builder.OpenArray("s", &empty); // envs
- builder.CloseContainer(&empty);
- builder.AppendString(""); // startup_id
- builder.AppendBool(false); // blind
+ std::vector<std::string> empty;
+ builder.AppendString("kwalletd"); // serviceName
+ builder.AppendArrayOfStrings(empty); // urls
+ builder.AppendArrayOfStrings(empty); // envs
+ builder.AppendString(""); // startup_id
+ builder.AppendBool(false); // blind
scoped_ptr<dbus::Response> response(
klauncher->CallMethodAndBlock(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
@@ -153,7 +150,7 @@ bool NativeBackendKWallet::StartKWalletd() {
return true;
}
-bool NativeBackendKWallet::InitWallet() {
+NativeBackendKWallet::InitResult NativeBackendKWallet::InitWallet() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
{
// Check that KWallet is enabled.
@@ -163,19 +160,19 @@ bool NativeBackendKWallet::InitWallet() {
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
if (!response.get()) {
LOG(ERROR) << "Error contacting kwalletd (isEnabled)";
- return false;
+ return TEMPORARY_FAIL;
}
dbus::MessageReader reader(response.get());
bool enabled = false;
if (!reader.PopBool(&enabled)) {
LOG(ERROR) << "Error reading response from kwalletd (isEnabled): "
<< response->ToString();
- return false;
+ return PERMANENT_FAIL;
}
// Not enabled? Don't use KWallet. But also don't warn here.
if (!enabled) {
VLOG(1) << "kwalletd reports that KWallet is not enabled.";
- return false;
+ return PERMANENT_FAIL;
}
}
@@ -187,17 +184,17 @@ bool NativeBackendKWallet::InitWallet() {
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
if (!response.get()) {
LOG(ERROR) << "Error contacting kwalletd (networkWallet)";
- return false;
+ return TEMPORARY_FAIL;
}
dbus::MessageReader reader(response.get());
if (!reader.PopString(&wallet_name_)) {
LOG(ERROR) << "Error reading response from kwalletd (networkWallet): "
<< response->ToString();
- return false;
+ return PERMANENT_FAIL;
}
}
- return true;
+ return INIT_SUCCESS;
}
bool NativeBackendKWallet::AddLogin(const PasswordForm& form) {
@@ -524,21 +521,11 @@ bool NativeBackendKWallet::GetAllLogins(PasswordFormList* forms,
return false;
}
dbus::MessageReader reader(response.get());
- dbus::MessageReader array(response.get());
- if (!reader.PopArray(&array)) {
+ if (!reader.PopArrayOfStrings(&realm_list)) {
LOG(ERROR) << "Error reading response from kwalletd (entryList): "
<< response->ToString();
return false;
}
- while (array.HasMoreData()) {
- std::string realm;
- if (!array.PopString(&realm)) {
- LOG(ERROR) << "Error reading response from kwalletd (entryList): "
- << response->ToString();
- return false;
- }
- realm_list.push_back(realm);
- }
}
for (size_t i = 0; i < realm_list.size(); ++i) {
@@ -881,6 +868,14 @@ void NativeBackendKWallet::MigrateToProfileSpecificLogins() {
ok = false;
delete forms[i];
}
+ if (forms.empty()) {
+ // If there were no logins to migrate, we do an extra call to WalletHandle()
+ // for its side effect of attempting to create the profile-specific folder.
+ // This is not strictly necessary, but it's safe and helps in testing.
+ wallet_handle = WalletHandle();
+ if (wallet_handle == kInvalidKWalletHandle)
+ ok = false;
+ }
if (ok) {
// All good! Keep the new app string and set a persistent pref.
diff --git a/chrome/browser/password_manager/native_backend_kwallet_x.h b/chrome/browser/password_manager/native_backend_kwallet_x.h
index 82470e3..b40d3d8 100644
--- a/chrome/browser/password_manager/native_backend_kwallet_x.h
+++ b/chrome/browser/password_manager/native_backend_kwallet_x.h
@@ -47,22 +47,37 @@ class NativeBackendKWallet : public PasswordStoreX::NativeBackend {
const base::Time& delete_begin, const base::Time& delete_end) OVERRIDE;
virtual bool GetLogins(const webkit_glue::PasswordForm& form,
PasswordFormList* forms) OVERRIDE;
- virtual bool GetLoginsCreatedBetween(const base::Time& delete_begin,
- const base::Time& delete_end,
+ virtual bool GetLoginsCreatedBetween(const base::Time& get_begin,
+ const base::Time& get_end,
PasswordFormList* forms) OVERRIDE;
virtual bool GetAutofillableLogins(PasswordFormList* forms) OVERRIDE;
virtual bool GetBlacklistLogins(PasswordFormList* forms) OVERRIDE;
protected:
+ // Invalid handle returned by WalletHandle().
+ static const int kInvalidKWalletHandle = -1;
+
// Internally used by Init(), but also for testing to provide a mock bus.
- void InitWithBus(scoped_refptr<dbus::Bus> optional_bus,
- base::WaitableEvent* event,
- bool* success);
+ bool InitWithBus(scoped_refptr<dbus::Bus> optional_bus);
+
+ // Deserializes a list of PasswordForms from the wallet.
+ static void DeserializeValue(const std::string& signon_realm,
+ const Pickle& pickle,
+ PasswordFormList* forms);
private:
+ enum InitResult {
+ INIT_SUCCESS, // Init succeeded.
+ TEMPORARY_FAIL, // Init failed, but might succeed after StartKWalletd().
+ PERMANENT_FAIL // Init failed, and is not likely to work later either.
+ };
+
// Initialization.
bool StartKWalletd();
- bool InitWallet();
+ InitResult InitWallet();
+ void InitOnDBThread(scoped_refptr<dbus::Bus> optional_bus,
+ base::WaitableEvent* event,
+ bool* success);
// Reads PasswordForms from the wallet that match the given signon_realm.
bool GetLoginsList(PasswordFormList* forms,
@@ -110,11 +125,6 @@ class NativeBackendKWallet : public PasswordStoreX::NativeBackend {
static bool CheckSerializedValue(const uint8_t* byte_array, size_t length,
const std::string& realm);
- // Deserializes a list of PasswordForms from the wallet.
- static void DeserializeValue(const std::string& signon_realm,
- const Pickle& pickle,
- PasswordFormList* forms);
-
// Convenience function to read a GURL from a Pickle. Assumes the URL has
// been written as a std::string. Returns true on success.
static bool ReadGURL(const Pickle& pickle, void** iter, GURL* url);
@@ -134,9 +144,6 @@ class NativeBackendKWallet : public PasswordStoreX::NativeBackend {
static const char kKLauncherPath[];
static const char kKLauncherInterface[];
- // Invalid handle returned by WalletHandle().
- static const int kInvalidKWalletHandle = -1;
-
// Generates a profile-specific folder name based on profile_id_.
std::string GetProfileSpecificFolderName() const;
diff --git a/chrome/browser/password_manager/native_backend_kwallet_x_unittest.cc b/chrome/browser/password_manager/native_backend_kwallet_x_unittest.cc
new file mode 100644
index 0000000..f4b37f9
--- /dev/null
+++ b/chrome/browser/password_manager/native_backend_kwallet_x_unittest.cc
@@ -0,0 +1,1000 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/pickle.h"
+#include "base/stl_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/password_manager/native_backend_kwallet_x.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_profile.h"
+#include "dbus/message.h"
+#include "dbus/mock_bus.h"
+#include "dbus/mock_object_proxy.h"
+#include "dbus/object_proxy.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Invoke;
+using testing::Return;
+using webkit_glue::PasswordForm;
+
+namespace {
+
+// This class implements a very simple version of KWallet in memory.
+// We only provide the parts we actually use; the real version has more.
+class TestKWallet {
+ public:
+ typedef std::basic_string<uint8_t> Blob; // std::string is binary-safe.
+
+ TestKWallet() : reject_local_folders_(false) {}
+
+ void set_reject_local_folders(bool value) { reject_local_folders_ = value; }
+
+ // NOTE: The method names here are the same as the corresponding DBus
+ // methods, and therefore have names that don't match our style guide.
+
+ // Check for presence of a given password folder.
+ bool hasFolder(const std::string& folder) const {
+ return data_.find(folder) != data_.end();
+ }
+
+ // Check for presence of a given password in a given password folder.
+ bool hasEntry(const std::string& folder, const std::string& key) const {
+ Data::const_iterator it = data_.find(folder);
+ return it != data_.end() && it->second.find(key) != it->second.end();
+ }
+
+ // Get a list of password keys in a given password folder.
+ bool entryList(const std::string& folder,
+ std::vector<std::string>* entries) const {
+ Data::const_iterator it = data_.find(folder);
+ if (it == data_.end()) return false;
+ for (Folder::const_iterator fit = it->second.begin();
+ fit != it->second.end(); ++fit)
+ entries->push_back(fit->first);
+ return true;
+ }
+
+ // Read the password data for a given password in a given password folder.
+ bool readEntry(const std::string& folder, const std::string& key,
+ Blob* value) const {
+ Data::const_iterator it = data_.find(folder);
+ if (it == data_.end()) return false;
+ Folder::const_iterator fit = it->second.find(key);
+ if (fit == it->second.end()) return false;
+ *value = fit->second;
+ return true;
+ }
+
+ // Create the given password folder.
+ bool createFolder(const std::string& folder) {
+ if (reject_local_folders_ && folder.find('(') != std::string::npos)
+ return false;
+ return data_.insert(make_pair(folder, Folder())).second;
+ }
+
+ // Remove the given password from the given password folder.
+ bool removeEntry(const std::string& folder, const std::string& key) {
+ Data::iterator it = data_.find(folder);
+ if (it == data_.end()) return false;
+ return it->second.erase(key) > 0;
+ }
+
+ // Write the given password data to the given password folder.
+ bool writeEntry(const std::string& folder, const std::string& key,
+ const Blob& value) {
+ Data::iterator it = data_.find(folder);
+ if (it == data_.end()) return false;
+ it->second[key] = value;
+ return true;
+ }
+
+ private:
+ typedef std::map<std::string, Blob> Folder;
+ typedef std::map<std::string, Folder> Data;
+
+ Data data_;
+ // "Local" folders are folders containing local profile IDs in their names. We
+ // can reject attempts to create them in order to make it easier to create
+ // legacy shared passwords in these tests, for testing the migration code.
+ bool reject_local_folders_;
+
+ // No need to disallow copy and assign. This class is safe to copy and assign.
+};
+
+} // anonymous namespace
+
+// Obscure magic: we need to declare storage for this constant because we use it
+// in ways that require its address in this test, but not in the actual code.
+const int NativeBackendKWallet::kInvalidKWalletHandle;
+
+// Subclass NativeBackendKWallet to promote some members to public for testing.
+class NativeBackendKWalletStub : public NativeBackendKWallet {
+ public:
+ NativeBackendKWalletStub(LocalProfileId id, PrefService* pref_service)
+ : NativeBackendKWallet(id, pref_service) {
+ }
+ using NativeBackendKWallet::InitWithBus;
+ using NativeBackendKWallet::kInvalidKWalletHandle;
+ using NativeBackendKWallet::DeserializeValue;
+};
+
+class NativeBackendKWalletTest : public testing::Test {
+ protected:
+ NativeBackendKWalletTest()
+ : ui_thread_(BrowserThread::UI, &message_loop_),
+ db_thread_(BrowserThread::DB), klauncher_ret_(0),
+ klauncher_contacted_(false), kwallet_runnable_(true),
+ kwallet_running_(true), kwallet_enabled_(true) {
+ }
+
+ virtual void SetUp();
+ virtual void TearDown();
+
+ // Let the DB thread run to completion of all current tasks.
+ void RunDBThread() {
+ base::WaitableEvent event(false, false);
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ base::Bind(ThreadDone, &event));
+ event.Wait();
+ // Some of the tests may post messages to the UI thread, but we don't need
+ // to run those until after the DB thread is finished. So run it here.
+ message_loop_.RunAllPending();
+ }
+ static void ThreadDone(base::WaitableEvent* event) {
+ event->Signal();
+ }
+
+ // Utilities to help verify expectations.
+ typedef std::vector<
+ std::pair<std::string,
+ std::vector<const PasswordForm*> > > ExpectationArray;
+ void CheckPasswordForm(const PasswordForm& expected,
+ const PasswordForm& actual);
+ void CheckPasswordForms(const std::string& folder,
+ const ExpectationArray& sorted_expected);
+
+ MessageLoopForUI message_loop_;
+ BrowserThread ui_thread_;
+ BrowserThread db_thread_;
+ TestingProfile profile_;
+
+ scoped_refptr<dbus::MockBus> mock_session_bus_;
+ scoped_refptr<dbus::MockObjectProxy> mock_klauncher_proxy_;
+ scoped_refptr<dbus::MockObjectProxy> mock_kwallet_proxy_;
+
+ int klauncher_ret_;
+ std::string klauncher_error_;
+ bool klauncher_contacted_;
+
+ bool kwallet_runnable_;
+ bool kwallet_running_;
+ bool kwallet_enabled_;
+
+ TestKWallet wallet_;
+
+ // Provide some test forms to avoid having to set them up in each test.
+ PasswordForm form_google_;
+ PasswordForm form_isc_;
+
+ private:
+ dbus::Response* KLauncherMethodCall(
+ dbus::MethodCall* method_call, testing::Unused);
+
+ dbus::Response* KWalletMethodCall(
+ dbus::MethodCall* method_call, testing::Unused);
+};
+
+void NativeBackendKWalletTest::SetUp() {
+ ASSERT_TRUE(db_thread_.Start());
+
+ form_google_.origin = GURL("http://www.google.com/");
+ form_google_.action = GURL("http://www.google.com/login");
+ form_google_.username_element = UTF8ToUTF16("user");
+ form_google_.username_value = UTF8ToUTF16("joeschmoe");
+ form_google_.password_element = UTF8ToUTF16("pass");
+ form_google_.password_value = UTF8ToUTF16("seekrit");
+ form_google_.submit_element = UTF8ToUTF16("submit");
+ form_google_.signon_realm = "Google";
+
+ form_isc_.origin = GURL("http://www.isc.org/");
+ form_isc_.action = GURL("http://www.isc.org/auth");
+ form_isc_.username_element = UTF8ToUTF16("id");
+ form_isc_.username_value = UTF8ToUTF16("janedoe");
+ form_isc_.password_element = UTF8ToUTF16("passwd");
+ form_isc_.password_value = UTF8ToUTF16("ihazabukkit");
+ form_isc_.submit_element = UTF8ToUTF16("login");
+ form_isc_.signon_realm = "ISC";
+
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SESSION;
+ mock_session_bus_ = new dbus::MockBus(options);
+
+ mock_klauncher_proxy_ =
+ new dbus::MockObjectProxy(mock_session_bus_.get(),
+ "org.kde.klauncher",
+ "/KLauncher");
+ EXPECT_CALL(*mock_klauncher_proxy_,
+ CallMethodAndBlock(_, _))
+ .WillRepeatedly(Invoke(this,
+ &NativeBackendKWalletTest::KLauncherMethodCall));
+
+ mock_kwallet_proxy_ =
+ new dbus::MockObjectProxy(mock_session_bus_.get(),
+ "org.kde.kwalletd",
+ "/modules/kwalletd");
+ EXPECT_CALL(*mock_kwallet_proxy_,
+ CallMethodAndBlock(_, _))
+ .WillRepeatedly(Invoke(this,
+ &NativeBackendKWalletTest::KWalletMethodCall));
+
+ EXPECT_CALL(*mock_session_bus_, GetObjectProxy(
+ "org.kde.klauncher",
+ "/KLauncher"))
+ .WillRepeatedly(Return(mock_klauncher_proxy_.get()));
+ EXPECT_CALL(*mock_session_bus_, GetObjectProxy(
+ "org.kde.kwalletd",
+ "/modules/kwalletd"))
+ .WillRepeatedly(Return(mock_kwallet_proxy_.get()));
+
+ EXPECT_CALL(*mock_session_bus_,
+ ShutdownAndBlock()).WillOnce(Return()).WillRepeatedly(Return());
+}
+
+void NativeBackendKWalletTest::TearDown() {
+ MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask);
+ MessageLoop::current()->Run();
+ db_thread_.Stop();
+}
+
+dbus::Response* NativeBackendKWalletTest::KLauncherMethodCall(
+ dbus::MethodCall* method_call, testing::Unused) {
+ EXPECT_EQ("org.kde.KLauncher", method_call->GetInterface());
+ EXPECT_EQ("start_service_by_desktop_name", method_call->GetMember());
+
+ klauncher_contacted_ = true;
+
+ dbus::MessageReader reader(method_call);
+ std::string service_name;
+ std::vector<std::string> urls;
+ std::vector<std::string> envs;
+ std::string startup_id;
+ bool blind = false;
+
+ EXPECT_TRUE(reader.PopString(&service_name));
+ EXPECT_TRUE(reader.PopArrayOfStrings(&urls));
+ EXPECT_TRUE(reader.PopArrayOfStrings(&envs));
+ EXPECT_TRUE(reader.PopString(&startup_id));
+ EXPECT_TRUE(reader.PopBool(&blind));
+
+ EXPECT_EQ("kwalletd", service_name);
+ EXPECT_TRUE(urls.empty());
+ EXPECT_TRUE(envs.empty());
+ EXPECT_TRUE(startup_id.empty());
+ EXPECT_FALSE(blind);
+
+ if (kwallet_runnable_)
+ kwallet_running_ = true;
+
+ dbus::Response* response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response);
+ writer.AppendInt32(klauncher_ret_);
+ writer.AppendString(""); // dbus_name
+ writer.AppendString(klauncher_error_);
+ writer.AppendInt32(1234); // pid
+ return response;
+}
+
+dbus::Response* NativeBackendKWalletTest::KWalletMethodCall(
+ dbus::MethodCall* method_call, testing::Unused) {
+ if (!kwallet_running_)
+ return NULL;
+ EXPECT_EQ("org.kde.KWallet", method_call->GetInterface());
+
+ dbus::Response* response = NULL;
+ if (method_call->GetMember() == "isEnabled") {
+ response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response);
+ writer.AppendBool(kwallet_enabled_);
+ } else if (method_call->GetMember() == "networkWallet") {
+ response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response);
+ writer.AppendString("test_wallet"); // Should match |open| below.
+ } else if (method_call->GetMember() == "open") {
+ dbus::MessageReader reader(method_call);
+ std::string wallet_name;
+ int64_t wallet_id;
+ std::string app_name;
+ EXPECT_TRUE(reader.PopString(&wallet_name));
+ EXPECT_TRUE(reader.PopInt64(&wallet_id));
+ EXPECT_TRUE(reader.PopString(&app_name));
+ EXPECT_EQ("test_wallet", wallet_name); // Should match |networkWallet|.
+ response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response);
+ writer.AppendInt32(1); // Can be anything but kInvalidKWalletHandle.
+ } else if (method_call->GetMember() == "hasFolder" ||
+ method_call->GetMember() == "createFolder") {
+ dbus::MessageReader reader(method_call);
+ int handle = NativeBackendKWalletStub::kInvalidKWalletHandle;
+ std::string folder_name;
+ std::string app_name;
+ EXPECT_TRUE(reader.PopInt32(&handle));
+ EXPECT_TRUE(reader.PopString(&folder_name));
+ EXPECT_TRUE(reader.PopString(&app_name));
+ EXPECT_NE(NativeBackendKWalletStub::kInvalidKWalletHandle, handle);
+ response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response);
+ if (method_call->GetMember() == "hasFolder")
+ writer.AppendBool(wallet_.hasFolder(folder_name));
+ else
+ writer.AppendBool(wallet_.createFolder(folder_name));
+ } else if (method_call->GetMember() == "hasEntry" ||
+ method_call->GetMember() == "removeEntry") {
+ dbus::MessageReader reader(method_call);
+ int handle = NativeBackendKWalletStub::kInvalidKWalletHandle;
+ std::string folder_name;
+ std::string key;
+ std::string app_name;
+ EXPECT_TRUE(reader.PopInt32(&handle));
+ EXPECT_TRUE(reader.PopString(&folder_name));
+ EXPECT_TRUE(reader.PopString(&key));
+ EXPECT_TRUE(reader.PopString(&app_name));
+ EXPECT_NE(NativeBackendKWalletStub::kInvalidKWalletHandle, handle);
+ response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response);
+ if (method_call->GetMember() == "hasEntry")
+ writer.AppendBool(wallet_.hasEntry(folder_name, key));
+ else
+ writer.AppendInt32(wallet_.removeEntry(folder_name, key) ? 0 : 1);
+ } else if (method_call->GetMember() == "entryList") {
+ dbus::MessageReader reader(method_call);
+ int handle = NativeBackendKWalletStub::kInvalidKWalletHandle;
+ std::string folder_name;
+ std::string app_name;
+ EXPECT_TRUE(reader.PopInt32(&handle));
+ EXPECT_TRUE(reader.PopString(&folder_name));
+ EXPECT_TRUE(reader.PopString(&app_name));
+ EXPECT_NE(NativeBackendKWalletStub::kInvalidKWalletHandle, handle);
+ std::vector<std::string> entries;
+ if (wallet_.entryList(folder_name, &entries)) {
+ response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response);
+ writer.AppendArrayOfStrings(entries);
+ }
+ } else if (method_call->GetMember() == "readEntry") {
+ dbus::MessageReader reader(method_call);
+ int handle = NativeBackendKWalletStub::kInvalidKWalletHandle;
+ std::string folder_name;
+ std::string key;
+ std::string app_name;
+ EXPECT_TRUE(reader.PopInt32(&handle));
+ EXPECT_TRUE(reader.PopString(&folder_name));
+ EXPECT_TRUE(reader.PopString(&key));
+ EXPECT_TRUE(reader.PopString(&app_name));
+ EXPECT_NE(NativeBackendKWalletStub::kInvalidKWalletHandle, handle);
+ TestKWallet::Blob value;
+ if (wallet_.readEntry(folder_name, key, &value)) {
+ response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response);
+ writer.AppendArrayOfBytes(value.data(), value.size());
+ }
+ } else if (method_call->GetMember() == "writeEntry") {
+ dbus::MessageReader reader(method_call);
+ int handle = NativeBackendKWalletStub::kInvalidKWalletHandle;
+ std::string folder_name;
+ std::string key;
+ uint8_t* bytes = NULL;
+ size_t length = 0;
+ std::string app_name;
+ EXPECT_TRUE(reader.PopInt32(&handle));
+ EXPECT_TRUE(reader.PopString(&folder_name));
+ EXPECT_TRUE(reader.PopString(&key));
+ EXPECT_TRUE(reader.PopArrayOfBytes(&bytes, &length));
+ EXPECT_TRUE(reader.PopString(&app_name));
+ EXPECT_NE(NativeBackendKWalletStub::kInvalidKWalletHandle, handle);
+ response = dbus::Response::CreateEmpty();
+ dbus::MessageWriter writer(response);
+ writer.AppendInt32(
+ wallet_.writeEntry(folder_name, key,
+ TestKWallet::Blob(bytes, length)) ? 0 : 1);
+ }
+
+ EXPECT_FALSE(response == NULL);
+ return response;
+}
+
+void NativeBackendKWalletTest::CheckPasswordForm(const PasswordForm& expected,
+ const PasswordForm& actual) {
+ EXPECT_EQ(expected.origin, actual.origin);
+ EXPECT_EQ(expected.password_value, actual.password_value);
+ EXPECT_EQ(expected.action, actual.action);
+ EXPECT_EQ(expected.username_element, actual.username_element);
+ EXPECT_EQ(expected.username_value, actual.username_value);
+ EXPECT_EQ(expected.password_element, actual.password_element);
+ EXPECT_EQ(expected.submit_element, actual.submit_element);
+ EXPECT_EQ(expected.signon_realm, actual.signon_realm);
+ EXPECT_EQ(expected.ssl_valid, actual.ssl_valid);
+ EXPECT_EQ(expected.preferred, actual.preferred);
+ // We don't check the date created. It varies.
+ EXPECT_EQ(expected.blacklisted_by_user, actual.blacklisted_by_user);
+ EXPECT_EQ(expected.scheme, actual.scheme);
+}
+
+void NativeBackendKWalletTest::CheckPasswordForms(
+ const std::string& folder, const ExpectationArray& sorted_expected) {
+ EXPECT_TRUE(wallet_.hasFolder(folder));
+ std::vector<std::string> entries;
+ EXPECT_TRUE(wallet_.entryList(folder, &entries));
+ EXPECT_EQ(sorted_expected.size(), entries.size());
+ std::sort(entries.begin(), entries.end());
+ for (size_t i = 0; i < entries.size() && i < sorted_expected.size(); ++i) {
+ EXPECT_EQ(sorted_expected[i].first, entries[i]);
+ TestKWallet::Blob value;
+ EXPECT_TRUE(wallet_.readEntry(folder, entries[i], &value));
+ Pickle pickle(reinterpret_cast<const char*>(value.data()), value.size());
+ std::vector<PasswordForm*> forms;
+ NativeBackendKWalletStub::DeserializeValue(entries[i], pickle, &forms);
+ const std::vector<const PasswordForm*>& expect = sorted_expected[i].second;
+ EXPECT_EQ(expect.size(), forms.size());
+ for (size_t j = 0; j < forms.size() && j < expect.size(); ++j)
+ CheckPasswordForm(*expect[j], *forms[j]);
+ STLDeleteElements(&forms);
+ }
+}
+
+TEST_F(NativeBackendKWalletTest, NotEnabled) {
+ NativeBackendKWalletStub kwallet(42, profile_.GetPrefs());
+ kwallet_enabled_ = false;
+ EXPECT_FALSE(kwallet.InitWithBus(mock_session_bus_));
+ EXPECT_FALSE(klauncher_contacted_);
+}
+
+TEST_F(NativeBackendKWalletTest, NotRunnable) {
+ NativeBackendKWalletStub kwallet(42, profile_.GetPrefs());
+ kwallet_runnable_ = false;
+ kwallet_running_ = false;
+ EXPECT_FALSE(kwallet.InitWithBus(mock_session_bus_));
+ EXPECT_TRUE(klauncher_contacted_);
+}
+
+TEST_F(NativeBackendKWalletTest, NotRunningOrEnabled) {
+ NativeBackendKWalletStub kwallet(42, profile_.GetPrefs());
+ kwallet_running_ = false;
+ kwallet_enabled_ = false;
+ EXPECT_FALSE(kwallet.InitWithBus(mock_session_bus_));
+ EXPECT_TRUE(klauncher_contacted_);
+}
+
+TEST_F(NativeBackendKWalletTest, NotRunning) {
+ NativeBackendKWalletStub kwallet(42, profile_.GetPrefs());
+ kwallet_running_ = false;
+ EXPECT_TRUE(kwallet.InitWithBus(mock_session_bus_));
+ EXPECT_TRUE(klauncher_contacted_);
+}
+
+TEST_F(NativeBackendKWalletTest, BasicStartup) {
+ NativeBackendKWalletStub kwallet(42, profile_.GetPrefs());
+ EXPECT_TRUE(kwallet.InitWithBus(mock_session_bus_));
+ EXPECT_FALSE(klauncher_contacted_);
+}
+
+// NativeBackendKWallet isn't reference counted, but in these unit tests that
+// won't be a problem as it always outlives the threads we post tasks to.
+template<>
+struct RunnableMethodTraits<NativeBackendKWalletStub> {
+ void RetainCallee(NativeBackendKWalletStub*) {}
+ void ReleaseCallee(NativeBackendKWalletStub*) {}
+};
+
+TEST_F(NativeBackendKWalletTest, BasicAddLogin) {
+ // Pretend that the migration has already taken place.
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ // I'm not sure why base::Bind doesn't like NativeBackendKWallet[Stub], but
+ // it results in compile asserts in the templatized magic of base/callback.h.
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+
+ RunDBThread();
+
+ EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
+
+ std::vector<const PasswordForm*> forms;
+ forms.push_back(&form_google_);
+ ExpectationArray expected;
+ expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+}
+
+TEST_F(NativeBackendKWalletTest, BasicListLogins) {
+ // Pretend that the migration has already taken place.
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+
+ std::vector<PasswordForm*> form_list;
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+
+ RunDBThread();
+
+ // Quick check that we got something back.
+ EXPECT_EQ(1u, form_list.size());
+ STLDeleteElements(&form_list);
+
+ EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
+
+ std::vector<const PasswordForm*> forms;
+ forms.push_back(&form_google_);
+ ExpectationArray expected;
+ expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+}
+
+TEST_F(NativeBackendKWalletTest, BasicRemoveLogin) {
+ // Pretend that the migration has already taken place.
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+
+ RunDBThread();
+
+ EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
+
+ std::vector<const PasswordForm*> forms;
+ forms.push_back(&form_google_);
+ ExpectationArray expected;
+ expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::RemoveLogin,
+ form_google_));
+
+ RunDBThread();
+
+ expected.clear();
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+}
+
+TEST_F(NativeBackendKWalletTest, RemoveNonexistentLogin) {
+ // Pretend that the migration has already taken place.
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ // First add an unrelated login.
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+
+ RunDBThread();
+
+ EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
+
+ std::vector<const PasswordForm*> forms;
+ forms.push_back(&form_google_);
+ ExpectationArray expected;
+ expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+
+ // Attempt to remove a login that doesn't exist.
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::RemoveLogin,
+ form_isc_));
+
+ // Make sure we can still get the first form back.
+ std::vector<PasswordForm*> form_list;
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+
+ RunDBThread();
+
+ // Quick check that we got something back.
+ EXPECT_EQ(1u, form_list.size());
+ STLDeleteElements(&form_list);
+
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+}
+
+TEST_F(NativeBackendKWalletTest, AddDuplicateLogin) {
+ // Pretend that the migration has already taken place.
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+
+ RunDBThread();
+
+ EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
+
+ std::vector<const PasswordForm*> forms;
+ forms.push_back(&form_google_);
+ ExpectationArray expected;
+ expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+}
+
+TEST_F(NativeBackendKWalletTest, ListLoginsAppends) {
+ // Pretend that the migration has already taken place.
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+
+ // Send the same request twice with the same list both times.
+ std::vector<PasswordForm*> form_list;
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+
+ RunDBThread();
+
+ // Quick check that we got two results back.
+ EXPECT_EQ(2u, form_list.size());
+ STLDeleteElements(&form_list);
+
+ EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data"));
+
+ std::vector<const PasswordForm*> forms;
+ forms.push_back(&form_google_);
+ ExpectationArray expected;
+ expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+}
+
+// TODO(mdm): add more basic (i.e. non-migration) tests here at some point.
+// (For example tests for storing >1 password per realm pickle.)
+
+TEST_F(NativeBackendKWalletTest, MigrateOneLogin) {
+ // Reject attempts to migrate so we can populate the store.
+ wallet_.set_reject_local_folders(true);
+
+ {
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+
+ // Make sure we can get the form back even when migration is failing.
+ std::vector<PasswordForm*> form_list;
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+
+ RunDBThread();
+
+ // Quick check that we got something back.
+ EXPECT_EQ(1u, form_list.size());
+ STLDeleteElements(&form_list);
+ }
+
+ EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data (42)"));
+
+ std::vector<const PasswordForm*> forms;
+ forms.push_back(&form_google_);
+ ExpectationArray expected;
+ expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data", expected);
+
+ // Now allow the migration.
+ wallet_.set_reject_local_folders(false);
+
+ {
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ // Trigger the migration by looking something up.
+ std::vector<PasswordForm*> form_list;
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+
+ RunDBThread();
+
+ // Quick check that we got something back.
+ EXPECT_EQ(1u, form_list.size());
+ STLDeleteElements(&form_list);
+ }
+
+ CheckPasswordForms("Chrome Form Data", expected);
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+
+ // Check that we have set the persistent preference.
+ EXPECT_TRUE(
+ profile_.GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
+}
+
+TEST_F(NativeBackendKWalletTest, MigrateToMultipleProfiles) {
+ // Reject attempts to migrate so we can populate the store.
+ wallet_.set_reject_local_folders(true);
+
+ {
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+
+ RunDBThread();
+ }
+
+ EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data (42)"));
+
+ std::vector<const PasswordForm*> forms;
+ forms.push_back(&form_google_);
+ ExpectationArray expected;
+ expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data", expected);
+
+ // Now allow the migration.
+ wallet_.set_reject_local_folders(false);
+
+ {
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ // Trigger the migration by looking something up.
+ std::vector<PasswordForm*> form_list;
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+
+ RunDBThread();
+
+ // Quick check that we got something back.
+ EXPECT_EQ(1u, form_list.size());
+ STLDeleteElements(&form_list);
+ }
+
+ CheckPasswordForms("Chrome Form Data", expected);
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+
+ // Check that we have set the persistent preference.
+ EXPECT_TRUE(
+ profile_.GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
+
+ // Normally we'd actually have a different profile. But in the test just reset
+ // the profile's persistent pref; we pass in the local profile id anyway.
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, false);
+
+ {
+ NativeBackendKWalletStub backend(24, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ // Trigger the migration by looking something up.
+ std::vector<PasswordForm*> form_list;
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+
+ RunDBThread();
+
+ // Quick check that we got something back.
+ EXPECT_EQ(1u, form_list.size());
+ STLDeleteElements(&form_list);
+ }
+
+ CheckPasswordForms("Chrome Form Data", expected);
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+ CheckPasswordForms("Chrome Form Data (24)", expected);
+}
+
+TEST_F(NativeBackendKWalletTest, NoMigrationWithPrefSet) {
+ // Reject attempts to migrate so we can populate the store.
+ wallet_.set_reject_local_folders(true);
+
+ {
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+
+ RunDBThread();
+ }
+
+ EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data (42)"));
+
+ std::vector<const PasswordForm*> forms;
+ forms.push_back(&form_google_);
+ ExpectationArray expected;
+ expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data", expected);
+
+ // Now allow migration, but also pretend that the it has already taken place.
+ wallet_.set_reject_local_folders(false);
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, true);
+
+ {
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ // Trigger the migration by adding a new login.
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_isc_));
+
+ // Look up all logins; we expect only the one we added.
+ std::vector<PasswordForm*> form_list;
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+
+ RunDBThread();
+
+ // Quick check that we got the right thing back.
+ EXPECT_EQ(1u, form_list.size());
+ if (form_list.size() > 0)
+ EXPECT_EQ(form_isc_.signon_realm, form_list[0]->signon_realm);
+ STLDeleteElements(&form_list);
+ }
+
+ CheckPasswordForms("Chrome Form Data", expected);
+
+ forms[0] = &form_isc_;
+ expected.clear();
+ expected.push_back(make_pair(std::string(form_isc_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+}
+
+TEST_F(NativeBackendKWalletTest, DeleteMigratedPasswordIsIsolated) {
+ // Reject attempts to migrate so we can populate the store.
+ wallet_.set_reject_local_folders(true);
+
+ {
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::AddLogin,
+ form_google_));
+
+ RunDBThread();
+ }
+
+ EXPECT_FALSE(wallet_.hasFolder("Chrome Form Data (42)"));
+
+ std::vector<const PasswordForm*> forms;
+ forms.push_back(&form_google_);
+ ExpectationArray expected;
+ expected.push_back(make_pair(std::string(form_google_.signon_realm), forms));
+ CheckPasswordForms("Chrome Form Data", expected);
+
+ // Now allow the migration.
+ wallet_.set_reject_local_folders(false);
+
+ {
+ NativeBackendKWalletStub backend(42, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ // Trigger the migration by looking something up.
+ std::vector<PasswordForm*> form_list;
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+
+ RunDBThread();
+
+ // Quick check that we got something back.
+ EXPECT_EQ(1u, form_list.size());
+ STLDeleteElements(&form_list);
+ }
+
+ CheckPasswordForms("Chrome Form Data", expected);
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+
+ // Check that we have set the persistent preference.
+ EXPECT_TRUE(
+ profile_.GetPrefs()->GetBoolean(prefs::kPasswordsUseLocalProfileId));
+
+ // Normally we'd actually have a different profile. But in the test just reset
+ // the profile's persistent pref; we pass in the local profile id anyway.
+ profile_.GetPrefs()->SetBoolean(prefs::kPasswordsUseLocalProfileId, false);
+
+ {
+ NativeBackendKWalletStub backend(24, profile_.GetPrefs());
+ EXPECT_TRUE(backend.InitWithBus(mock_session_bus_));
+
+ // Trigger the migration by looking something up.
+ std::vector<PasswordForm*> form_list;
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::GetAutofillableLogins,
+ &form_list));
+
+ RunDBThread();
+
+ // Quick check that we got something back.
+ EXPECT_EQ(1u, form_list.size());
+ STLDeleteElements(&form_list);
+
+ // There should be three passwords now.
+ CheckPasswordForms("Chrome Form Data", expected);
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+ CheckPasswordForms("Chrome Form Data (24)", expected);
+
+ // Now delete the password from this second profile.
+ BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
+ NewRunnableMethod(&backend,
+ &NativeBackendKWalletStub::RemoveLogin,
+ form_google_));
+
+ RunDBThread();
+
+ // The other two copies of the password in different profiles should remain.
+ CheckPasswordForms("Chrome Form Data", expected);
+ CheckPasswordForms("Chrome Form Data (42)", expected);
+ expected.clear();
+ CheckPasswordForms("Chrome Form Data (24)", expected);
+ }
+}