From 7f58b02555f53658403cc2639a944a5b25b28fb8 Mon Sep 17 00:00:00 2001 From: "mdm@chromium.org" Date: Wed, 16 Jun 2010 21:33:16 +0000 Subject: Linux: refactor GNOME Keyring and KWallet integration to allow migration from the default store, and add unit tests. Still disabled. BUG=12351, 25404 TEST=unit tests work Review URL: http://codereview.chromium.org/2806002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50034 0039d316-1c4b-4281-b951-d872f2087c98 --- .../password_manager/native_backend_gnome_x.cc | 448 ++++++++++++ .../password_manager/native_backend_gnome_x.h | 62 ++ .../password_manager/native_backend_kwallet_x.cc | 570 +++++++++++++++ .../password_manager/native_backend_kwallet_x.h | 137 ++++ .../password_manager/password_store_default.cc | 4 +- .../password_manager/password_store_gnome.cc | 433 ------------ .../password_manager/password_store_gnome.h | 69 -- .../password_manager/password_store_kwallet.cc | 547 --------------- .../password_manager/password_store_kwallet.h | 137 ---- .../browser/password_manager/password_store_x.cc | 231 ++++++ chrome/browser/password_manager/password_store_x.h | 107 +++ .../password_manager/password_store_x_unittest.cc | 771 +++++++++++++++++++++ 12 files changed, 2329 insertions(+), 1187 deletions(-) create mode 100644 chrome/browser/password_manager/native_backend_gnome_x.cc create mode 100644 chrome/browser/password_manager/native_backend_gnome_x.h create mode 100644 chrome/browser/password_manager/native_backend_kwallet_x.cc create mode 100644 chrome/browser/password_manager/native_backend_kwallet_x.h delete mode 100644 chrome/browser/password_manager/password_store_gnome.cc delete mode 100644 chrome/browser/password_manager/password_store_gnome.h delete mode 100644 chrome/browser/password_manager/password_store_kwallet.cc delete mode 100644 chrome/browser/password_manager/password_store_kwallet.h create mode 100644 chrome/browser/password_manager/password_store_x.cc create mode 100644 chrome/browser/password_manager/password_store_x.h create mode 100644 chrome/browser/password_manager/password_store_x_unittest.cc (limited to 'chrome/browser/password_manager') diff --git a/chrome/browser/password_manager/native_backend_gnome_x.cc b/chrome/browser/password_manager/native_backend_gnome_x.cc new file mode 100644 index 0000000..29febc0 --- /dev/null +++ b/chrome/browser/password_manager/native_backend_gnome_x.cc @@ -0,0 +1,448 @@ +// Copyright (c) 2010 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 "chrome/browser/password_manager/native_backend_gnome_x.h" + +#if defined(DLOPEN_GNOME_KEYRING) +#include +#endif + +#include +#include + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/chrome_thread.h" + +using std::map; +using std::string; +using webkit_glue::PasswordForm; + +/* Many of the gnome_keyring_* functions use variable arguments, which makes + * them difficult if not impossible to wrap in C. Therefore, we want the + * actual uses below to either call the functions directly (if we are linking + * against libgnome-keyring), or call them via appropriately-typed function + * pointers (if we are dynamically loading libgnome-keyring). + * + * Thus, instead of making a wrapper class with two implementations, we use + * the preprocessor to rename the calls below in the dynamic load case, and + * provide a function to initialize a set of function pointers that have the + * alternate names. We also make sure the types are correct, since otherwise + * dynamic loading like this would leave us vulnerable to signature changes. */ + +#if defined(DLOPEN_GNOME_KEYRING) + +namespace { + +gboolean (*wrap_gnome_keyring_is_available)(); +GnomeKeyringResult (*wrap_gnome_keyring_store_password_sync)( // NOLINT + const GnomeKeyringPasswordSchema* schema, const gchar* keyring, + const gchar* display_name, const gchar* password, ...); +GnomeKeyringResult (*wrap_gnome_keyring_delete_password_sync)( // NOLINT + const GnomeKeyringPasswordSchema* schema, ...); +GnomeKeyringResult (*wrap_gnome_keyring_find_itemsv_sync)( // NOLINT + GnomeKeyringItemType type, GList** found, ...); +const gchar* (*wrap_gnome_keyring_result_to_message)(GnomeKeyringResult res); +void (*wrap_gnome_keyring_found_list_free)(GList* found_list); + +/* Cause the compiler to complain if the types of the above function pointers + * do not correspond to the types of the actual gnome_keyring_* functions. */ +#define GNOME_KEYRING_VERIFY_TYPE(name) \ + typeof(&gnome_keyring_##name) name = wrap_gnome_keyring_##name; name = name + +inline void VerifyGnomeKeyringTypes() { + GNOME_KEYRING_VERIFY_TYPE(is_available); + GNOME_KEYRING_VERIFY_TYPE(store_password_sync); + GNOME_KEYRING_VERIFY_TYPE(delete_password_sync); + GNOME_KEYRING_VERIFY_TYPE(find_itemsv_sync); + GNOME_KEYRING_VERIFY_TYPE(result_to_message); + GNOME_KEYRING_VERIFY_TYPE(found_list_free); +} +#undef GNOME_KEYRING_VERIFY_TYPE + +/* Make it easy to initialize the function pointers above with a loop below. */ +#define GNOME_KEYRING_FUNCTION(name) \ + {#name, reinterpret_cast(&wrap_##name)} +const struct { + const char* name; + void** pointer; +} gnome_keyring_functions[] = { + GNOME_KEYRING_FUNCTION(gnome_keyring_is_available), + GNOME_KEYRING_FUNCTION(gnome_keyring_store_password_sync), + GNOME_KEYRING_FUNCTION(gnome_keyring_delete_password_sync), + GNOME_KEYRING_FUNCTION(gnome_keyring_find_itemsv_sync), + GNOME_KEYRING_FUNCTION(gnome_keyring_result_to_message), + GNOME_KEYRING_FUNCTION(gnome_keyring_found_list_free), + {NULL, NULL} +}; +#undef GNOME_KEYRING_FUNCTION + +/* Allow application code below to use the normal function names, but actually + * end up using the function pointers above instead. */ +#define gnome_keyring_is_available \ + wrap_gnome_keyring_is_available +#define gnome_keyring_store_password_sync \ + wrap_gnome_keyring_store_password_sync +#define gnome_keyring_delete_password_sync \ + wrap_gnome_keyring_delete_password_sync +#define gnome_keyring_find_itemsv_sync \ + wrap_gnome_keyring_find_itemsv_sync +#define gnome_keyring_result_to_message \ + wrap_gnome_keyring_result_to_message +#define gnome_keyring_found_list_free \ + wrap_gnome_keyring_found_list_free + +/* Load the library and initialize the function pointers. */ +bool LoadGnomeKeyring() { + void* handle = dlopen("libgnome-keyring.so.0", RTLD_NOW | RTLD_GLOBAL); + if (!handle) { + LOG(INFO) << "Could not find libgnome-keyring.so.0"; + return false; + } + for (size_t i = 0; gnome_keyring_functions[i].name; ++i) { + dlerror(); + *gnome_keyring_functions[i].pointer = + dlsym(handle, gnome_keyring_functions[i].name); + const char* error = dlerror(); + if (error) { + LOG(ERROR) << "Unable to load symbol " << + gnome_keyring_functions[i].name << ": " << error; + dlclose(handle); + return false; + } + } + // We leak the library handle. That's OK: this function is called only once. + return true; +} + +} // namespace + +#else // DLOPEN_GNOME_KEYRING + +namespace { + +bool LoadGnomeKeyring() { + return true; +} + +} // namespace + +#endif // DLOPEN_GNOME_KEYRING + +#define GNOME_KEYRING_APPLICATION_CHROME "chrome" + +// Schema is analagous to the fields in PasswordForm. +const GnomeKeyringPasswordSchema NativeBackendGnome::kGnomeSchema = { + GNOME_KEYRING_ITEM_GENERIC_SECRET, { + { "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, + { "action_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, + { "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, + { "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, + { "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, + { "submit_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, + { "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, + { "ssl_valid", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, + { "preferred", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, + { "date_created", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, + { "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, + { "scheme", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, + // This field is always "chrome" so that we can search for it. + { "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, + { NULL } + } +}; + +NativeBackendGnome::NativeBackendGnome() { +} + +NativeBackendGnome::~NativeBackendGnome() { +} + +bool NativeBackendGnome::Init() { + return LoadGnomeKeyring() && gnome_keyring_is_available(); +} + +bool NativeBackendGnome::AddLogin(const PasswordForm& form) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + GnomeKeyringResult result = gnome_keyring_store_password_sync( + &kGnomeSchema, + NULL, // Default keyring. + form.origin.spec().c_str(), // Display name. + UTF16ToUTF8(form.password_value).c_str(), + "origin_url", form.origin.spec().c_str(), + "action_url", form.action.spec().c_str(), + "username_element", UTF16ToUTF8(form.username_element).c_str(), + "username_value", UTF16ToUTF8(form.username_value).c_str(), + "password_element", UTF16ToUTF8(form.password_element).c_str(), + "submit_element", UTF16ToUTF8(form.submit_element).c_str(), + "signon_realm", form.signon_realm.c_str(), + "ssl_valid", form.ssl_valid, + "preferred", form.preferred, + "date_created", Int64ToString(form.date_created.ToTimeT()).c_str(), + "blacklisted_by_user", form.blacklisted_by_user, + "scheme", form.scheme, + "application", GNOME_KEYRING_APPLICATION_CHROME, + NULL); + + if (result != GNOME_KEYRING_RESULT_OK) { + LOG(ERROR) << "Keyring save failed: " + << gnome_keyring_result_to_message(result); + return false; + } + return true; +} + +bool NativeBackendGnome::UpdateLogin(const PasswordForm& form) { + // Based on LoginDatabase::UpdateLogin(), we search for forms to update by + // origin_url, username_element, username_value, password_element, and + // signon_realm. We then compare the result to the updated form. If they + // differ in any of the action, password_value, ssl_valid, or preferred + // fields, then we add a new login with those fields updated and only delete + // the original on success. + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + GList* found = NULL; + // Search gnome keyring for matching passwords. + GnomeKeyringResult result = gnome_keyring_find_itemsv_sync( + GNOME_KEYRING_ITEM_GENERIC_SECRET, + &found, + "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + form.origin.spec().c_str(), + "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + UTF16ToUTF8(form.username_element).c_str(), + "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + UTF16ToUTF8(form.username_value).c_str(), + "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + UTF16ToUTF8(form.password_element).c_str(), + "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + form.signon_realm.c_str(), + "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + GNOME_KEYRING_APPLICATION_CHROME, + NULL); + if (result != GNOME_KEYRING_RESULT_OK) { + LOG(ERROR) << "Keyring find failed: " + << gnome_keyring_result_to_message(result); + return false; + } + bool ok = true; + PasswordFormList forms; + ConvertFormList(found, &forms); + for (size_t i = 0; i < forms.size(); ++i) { + if (forms[i]->action != form.action || + forms[i]->password_value != form.password_value || + forms[i]->ssl_valid != form.ssl_valid || + forms[i]->preferred != form.preferred) { + PasswordForm updated = *forms[i]; + updated.action = form.action; + updated.password_value = form.password_value; + updated.ssl_valid = form.ssl_valid; + updated.preferred = form.preferred; + if (AddLogin(updated)) + RemoveLogin(*forms[i]); + else + ok = false; + } + delete forms[i]; + } + return ok; +} + +bool NativeBackendGnome::RemoveLogin(const PasswordForm& form) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + // We find forms using the same fields as LoginDatabase::RemoveLogin(). + GnomeKeyringResult result = gnome_keyring_delete_password_sync( + &kGnomeSchema, + "origin_url", form.origin.spec().c_str(), + "action_url", form.action.spec().c_str(), + "username_element", UTF16ToUTF8(form.username_element).c_str(), + "username_value", UTF16ToUTF8(form.username_value).c_str(), + "password_element", UTF16ToUTF8(form.password_element).c_str(), + "submit_element", UTF16ToUTF8(form.submit_element).c_str(), + "signon_realm", form.signon_realm.c_str(), + NULL); + if (result != GNOME_KEYRING_RESULT_OK) { + LOG(ERROR) << "Keyring delete failed: " + << gnome_keyring_result_to_message(result); + return false; + } + return true; +} + +bool NativeBackendGnome::RemoveLoginsCreatedBetween( + const base::Time& delete_begin, + const base::Time& delete_end) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + bool ok = true; + // We could walk the list and delete items as we find them, but it is much + // easier to build the list and use RemoveLogin() to delete them. + PasswordFormList forms; + if (!GetAllLogins(&forms)) + return false; + + for (size_t i = 0; i < forms.size(); ++i) { + if (delete_begin <= forms[i]->date_created && + (delete_end.is_null() || forms[i]->date_created < delete_end)) { + if (!RemoveLogin(*forms[i])) + ok = false; + } + delete forms[i]; + } + return ok; +} + +bool NativeBackendGnome::GetLogins(const PasswordForm& form, + PasswordFormList* forms) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + GList* found = NULL; + // Search gnome keyring for matching passwords. + GnomeKeyringResult result = gnome_keyring_find_itemsv_sync( + GNOME_KEYRING_ITEM_GENERIC_SECRET, + &found, + "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + form.signon_realm.c_str(), + "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + GNOME_KEYRING_APPLICATION_CHROME, + NULL); + if (result == GNOME_KEYRING_RESULT_NO_MATCH) + return true; + if (result != GNOME_KEYRING_RESULT_OK) { + LOG(ERROR) << "Keyring find failed: " + << gnome_keyring_result_to_message(result); + return false; + } + ConvertFormList(found, forms); + return true; +} + +bool NativeBackendGnome::GetLoginsCreatedBetween(const base::Time& get_begin, + const base::Time& get_end, + PasswordFormList* forms) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + // We could walk the list and add items as we find them, but it is much + // easier to build the list and then filter the results. + PasswordFormList all_forms; + if (!GetAllLogins(&all_forms)) + return false; + + forms->reserve(forms->size() + all_forms.size()); + for (size_t i = 0; i < all_forms.size(); ++i) { + if (get_begin <= all_forms[i]->date_created && + (get_end.is_null() || all_forms[i]->date_created < get_end)) { + forms->push_back(all_forms[i]); + } else { + delete all_forms[i]; + } + } + + return true; +} + +bool NativeBackendGnome::GetAutofillableLogins(PasswordFormList* forms) { + return GetLoginsList(forms, true); +} + +bool NativeBackendGnome::GetBlacklistLogins(PasswordFormList* forms) { + return GetLoginsList(forms, false); +} + +bool NativeBackendGnome::GetLoginsList(PasswordFormList* forms, + bool autofillable) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + GList* found = NULL; + uint32_t blacklisted_by_user = !autofillable; + // Search gnome keyring for matching passwords. + GnomeKeyringResult result = gnome_keyring_find_itemsv_sync( + GNOME_KEYRING_ITEM_GENERIC_SECRET, + &found, + "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32, + blacklisted_by_user, + "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + GNOME_KEYRING_APPLICATION_CHROME, + NULL); + if (result == GNOME_KEYRING_RESULT_NO_MATCH) + return true; + if (result != GNOME_KEYRING_RESULT_OK) { + LOG(ERROR) << "Keyring find failed: " + << gnome_keyring_result_to_message(result); + return false; + } + ConvertFormList(found, forms); + return true; +} + +bool NativeBackendGnome::GetAllLogins(PasswordFormList* forms) { + GList* found = NULL; + // We need to search for something, otherwise we get no results - so we search + // for the fixed application string. + GnomeKeyringResult result = gnome_keyring_find_itemsv_sync( + GNOME_KEYRING_ITEM_GENERIC_SECRET, + &found, + "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, + GNOME_KEYRING_APPLICATION_CHROME, + NULL); + if (result == GNOME_KEYRING_RESULT_NO_MATCH) + return true; + if (result != GNOME_KEYRING_RESULT_OK) { + LOG(ERROR) << "Keyring find failed: " + << gnome_keyring_result_to_message(result); + return false; + } + ConvertFormList(found, forms); + return true; +} + +void NativeBackendGnome::ConvertFormList(GList* found, + PasswordFormList* forms) { + GList* element = g_list_first(found); + while (element != NULL) { + GnomeKeyringFound* data = static_cast(element->data); + char* password = data->secret; + + GnomeKeyringAttributeList* attributes = data->attributes; + // Read the string and int attributes into the appropriate map. + map string_attribute_map; + map uint_attribute_map; + for (unsigned int i = 0; i < attributes->len; ++i) { + GnomeKeyringAttribute attribute = + gnome_keyring_attribute_list_index(attributes, i); + if (attribute.type == GNOME_KEYRING_ATTRIBUTE_TYPE_STRING) { + string_attribute_map[string(attribute.name)] = + string(attribute.value.string); + } else if (attribute.type == GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32) { + uint_attribute_map[string(attribute.name)] = attribute.value.integer; + } + } + + PasswordForm* form = new PasswordForm(); + form->origin = GURL(string_attribute_map["origin_url"]); + form->action = GURL(string_attribute_map["action_url"]); + form->username_element = + UTF8ToUTF16(string(string_attribute_map["username_element"])); + form->username_value = + UTF8ToUTF16(string(string_attribute_map["username_value"])); + form->password_element = + UTF8ToUTF16(string(string_attribute_map["password_element"])); + form->password_value = UTF8ToUTF16(string(password)); + form->submit_element = + UTF8ToUTF16(string(string_attribute_map["submit_element"])); + form->signon_realm = string_attribute_map["signon_realm"]; + form->ssl_valid = uint_attribute_map["ssl_valid"]; + form->preferred = uint_attribute_map["preferred"]; + string date = string_attribute_map["date_created"]; + int64 date_created = 0; + bool date_ok = StringToInt64(date, &date_created); + DCHECK(date_ok); + DCHECK_NE(date_created, 0); + form->date_created = base::Time::FromTimeT(date_created); + form->blacklisted_by_user = uint_attribute_map["blacklisted_by_user"]; + form->scheme = static_cast( + uint_attribute_map["scheme"]); + + forms->push_back(form); + + element = g_list_next(element); + } + gnome_keyring_found_list_free(found); +} diff --git a/chrome/browser/password_manager/native_backend_gnome_x.h b/chrome/browser/password_manager/native_backend_gnome_x.h new file mode 100644 index 0000000..aa554be --- /dev/null +++ b/chrome/browser/password_manager/native_backend_gnome_x.h @@ -0,0 +1,62 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_PASSWORD_MANAGER_NATIVE_BACKEND_GNOME_X_H_ +#define CHROME_BROWSER_PASSWORD_MANAGER_NATIVE_BACKEND_GNOME_X_H_ + +extern "C" { +#include +} + +#include + +#include "base/basictypes.h" +#include "base/time.h" +#include "chrome/browser/password_manager/password_store_x.h" + +namespace webkit_glue { +struct PasswordForm; +} + +// NativeBackend implementation using GNOME Keyring. +class NativeBackendGnome : public PasswordStoreX::NativeBackend { + public: + NativeBackendGnome(); + + virtual ~NativeBackendGnome(); + + virtual bool Init(); + + // Implements NativeBackend interface. + virtual bool AddLogin(const webkit_glue::PasswordForm& form); + virtual bool UpdateLogin(const webkit_glue::PasswordForm& form); + virtual bool RemoveLogin(const webkit_glue::PasswordForm& form); + virtual bool RemoveLoginsCreatedBetween(const base::Time& delete_begin, + const base::Time& delete_end); + virtual bool GetLogins(const webkit_glue::PasswordForm& form, + PasswordFormList* forms); + virtual bool GetLoginsCreatedBetween(const base::Time& get_begin, + const base::Time& get_end, + PasswordFormList* forms); + virtual bool GetAutofillableLogins(PasswordFormList* forms); + virtual bool GetBlacklistLogins(PasswordFormList* forms); + + private: + // Reads PasswordForms from the keyring with the given autofillability state. + bool GetLoginsList(PasswordFormList* forms, bool autofillable); + + // Helper for GetLoginsCreatedBetween(). + bool GetAllLogins(PasswordFormList* forms); + + // Parse all the results from the given GList into a PasswordFormList, and + // free the GList. PasswordForms are allocated on the heap, and should be + // deleted by the consumer. + void ConvertFormList(GList* found, PasswordFormList* forms); + + static const GnomeKeyringPasswordSchema kGnomeSchema; + + DISALLOW_COPY_AND_ASSIGN(NativeBackendGnome); +}; + +#endif // CHROME_BROWSER_PASSWORD_MANAGER_NATIVE_BACKEND_GNOME_X_H_ diff --git a/chrome/browser/password_manager/native_backend_kwallet_x.cc b/chrome/browser/password_manager/native_backend_kwallet_x.cc new file mode 100644 index 0000000..501ce1f --- /dev/null +++ b/chrome/browser/password_manager/native_backend_kwallet_x.cc @@ -0,0 +1,570 @@ +// Copyright (c) 2010 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 "chrome/browser/password_manager/native_backend_kwallet_x.h" + +#include + +#include "base/logging.h" +#include "base/pickle.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "chrome/browser/chrome_thread.h" + +using std::string; +using std::vector; +using webkit_glue::PasswordForm; + +// We could localize these strings, but then changing your locale would cause +// you to lose access to all your stored passwords. Maybe best not to do that. +const char* NativeBackendKWallet::kAppId = "Chrome"; +const char* NativeBackendKWallet::kKWalletFolder = "Chrome Form Data"; + +const char* NativeBackendKWallet::kKWalletServiceName = "org.kde.kwalletd"; +const char* NativeBackendKWallet::kKWalletPath = "/modules/kwalletd"; +const char* NativeBackendKWallet::kKWalletInterface = "org.kde.KWallet"; +const char* NativeBackendKWallet::kKLauncherServiceName = "org.kde.klauncher"; +const char* NativeBackendKWallet::kKLauncherPath = "/KLauncher"; +const char* NativeBackendKWallet::kKLauncherInterface = "org.kde.KLauncher"; + +NativeBackendKWallet::NativeBackendKWallet() + : error_(NULL), + connection_(NULL), + proxy_(NULL) { +} + +NativeBackendKWallet::~NativeBackendKWallet() { + if (proxy_) + g_object_unref(proxy_); +} + +bool NativeBackendKWallet::Init() { + // Initialize threading in dbus-glib - it should be fine for + // dbus_g_thread_init to be called multiple times. + if (!g_thread_supported()) + g_thread_init(NULL); + dbus_g_thread_init(); + + // Get a connection to the session bus. + connection_ = dbus_g_bus_get(DBUS_BUS_SESSION, &error_); + if (CheckError()) + return false; + + if (!InitWallet()) { + // kwalletd may not be running. Try to start it and try again. + if (!StartKWalletd() || !InitWallet()) + return false; + } + + return true; +} + +bool NativeBackendKWallet::StartKWalletd() { + // Sadly kwalletd doesn't use DBUS activation, so we have to make a call to + // klauncher to start it. + DBusGProxy* klauncher_proxy = + dbus_g_proxy_new_for_name(connection_, kKLauncherServiceName, + kKLauncherPath, kKLauncherInterface); + + char* empty_string_list = NULL; + int ret = 1; + char* error = NULL; + dbus_g_proxy_call(klauncher_proxy, "start_service_by_desktop_name", &error_, + G_TYPE_STRING, "kwalletd", // serviceName + G_TYPE_STRV, &empty_string_list, // urls + G_TYPE_STRV, &empty_string_list, // envs + G_TYPE_STRING, "", // startup_id + G_TYPE_BOOLEAN, (gboolean) false, // blind + G_TYPE_INVALID, + G_TYPE_INT, &ret, // result + G_TYPE_STRING, NULL, // dubsName + G_TYPE_STRING, &error, // error + G_TYPE_INT, NULL, // pid + G_TYPE_INVALID); + + if (error && *error) { + LOG(ERROR) << "Error launching kwalletd: " << error; + ret = 1; // Make sure we return false after freeing. + } + + g_free(error); + g_object_unref(klauncher_proxy); + + if (CheckError() || ret != 0) + return false; + return true; +} + +bool NativeBackendKWallet::InitWallet() { + // Make a proxy to KWallet. + proxy_ = dbus_g_proxy_new_for_name(connection_, kKWalletServiceName, + kKWalletPath, kKWalletInterface); + + // Check KWallet is enabled. + gboolean is_enabled = false; + dbus_g_proxy_call(proxy_, "isEnabled", &error_, + G_TYPE_INVALID, + G_TYPE_BOOLEAN, &is_enabled, + G_TYPE_INVALID); + if (CheckError() || !is_enabled) + return false; + + // Get the wallet name. + char* wallet_name = NULL; + dbus_g_proxy_call(proxy_, "networkWallet", &error_, + G_TYPE_INVALID, + G_TYPE_STRING, &wallet_name, + G_TYPE_INVALID); + if (CheckError() || !wallet_name) + return false; + + wallet_name_.assign(wallet_name); + g_free(wallet_name); + + return true; +} + +bool NativeBackendKWallet::AddLogin(const PasswordForm& form) { + int wallet_handle = WalletHandle(); + if (wallet_handle == kInvalidKWalletHandle) + return false; + + PasswordFormList forms; + GetLoginsList(&forms, form.signon_realm, wallet_handle); + + forms.push_back(new PasswordForm(form)); + bool ok = SetLoginsList(forms, form.signon_realm, wallet_handle); + + STLDeleteElements(&forms); + return ok; +} + +bool NativeBackendKWallet::UpdateLogin(const PasswordForm& form) { + int wallet_handle = WalletHandle(); + if (wallet_handle == kInvalidKWalletHandle) + return false; + + PasswordFormList forms; + GetLoginsList(&forms, form.signon_realm, wallet_handle); + + for (size_t i = 0; i < forms.size(); ++i) { + if (CompareForms(form, *forms[i], true)) + *forms[i] = form; + } + + bool ok = SetLoginsList(forms, form.signon_realm, wallet_handle); + + STLDeleteElements(&forms); + return ok; +} + +bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) { + int wallet_handle = WalletHandle(); + if (wallet_handle == kInvalidKWalletHandle) + return false; + + PasswordFormList all_forms; + GetLoginsList(&all_forms, form.signon_realm, wallet_handle); + + PasswordFormList kept_forms; + kept_forms.reserve(all_forms.size()); + for (size_t i = 0; i < all_forms.size(); ++i) { + if (CompareForms(form, *all_forms[i], false)) + delete all_forms[i]; + else + kept_forms.push_back(all_forms[i]); + } + + // Update the entry in the wallet, possibly deleting it. + bool ok = SetLoginsList(kept_forms, form.signon_realm, wallet_handle); + + STLDeleteElements(&kept_forms); + return ok; +} + +bool NativeBackendKWallet::RemoveLoginsCreatedBetween( + const base::Time& delete_begin, + const base::Time& delete_end) { + int wallet_handle = WalletHandle(); + if (wallet_handle == kInvalidKWalletHandle) + return false; + + // We could probably also use readEntryList here. + char** realm_list = NULL; + dbus_g_proxy_call(proxy_, "entryList", &error_, + G_TYPE_INT, wallet_handle, // handle + G_TYPE_STRING, kKWalletFolder, // folder + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + G_TYPE_STRV, &realm_list, + G_TYPE_INVALID); + if (CheckError()) + return false; + + bool ok = true; + for (char** realm = realm_list; *realm; ++realm) { + GArray* byte_array = NULL; + dbus_g_proxy_call(proxy_, "readEntry", &error_, + G_TYPE_INT, wallet_handle, // handle + G_TYPE_STRING, kKWalletFolder, // folder + G_TYPE_STRING, *realm, // key + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + DBUS_TYPE_G_UCHAR_ARRAY, &byte_array, + G_TYPE_INVALID); + + if (CheckError() || !byte_array || !byte_array->len) + continue; + + string signon_realm(*realm); + Pickle pickle(byte_array->data, byte_array->len); + PasswordFormList all_forms; + DeserializeValue(signon_realm, pickle, &all_forms); + g_array_free(byte_array, true); + + PasswordFormList kept_forms; + kept_forms.reserve(all_forms.size()); + for (size_t i = 0; i < all_forms.size(); ++i) { + if (delete_begin <= all_forms[i]->date_created && + (delete_end.is_null() || all_forms[i]->date_created < delete_end)) { + delete all_forms[i]; + } else { + kept_forms.push_back(all_forms[i]); + } + } + + if (!SetLoginsList(kept_forms, signon_realm, wallet_handle)) + ok = false; + STLDeleteElements(&kept_forms); + } + g_strfreev(realm_list); + return ok; +} + +bool NativeBackendKWallet::GetLogins(const PasswordForm& form, + PasswordFormList* forms) { + int wallet_handle = WalletHandle(); + if (wallet_handle == kInvalidKWalletHandle) + return false; + return GetLoginsList(forms, form.signon_realm, wallet_handle); +} + +bool NativeBackendKWallet::GetLoginsCreatedBetween(const base::Time& get_begin, + const base::Time& get_end, + PasswordFormList* forms) { + int wallet_handle = WalletHandle(); + if (wallet_handle == kInvalidKWalletHandle) + return false; + return GetLoginsList(forms, get_begin, get_end, wallet_handle); +} + +bool NativeBackendKWallet::GetAutofillableLogins(PasswordFormList* forms) { + int wallet_handle = WalletHandle(); + if (wallet_handle == kInvalidKWalletHandle) + return false; + return GetLoginsList(forms, true, wallet_handle); +} + +bool NativeBackendKWallet::GetBlacklistLogins(PasswordFormList* forms) { + int wallet_handle = WalletHandle(); + if (wallet_handle == kInvalidKWalletHandle) + return false; + return GetLoginsList(forms, false, wallet_handle); +} + +bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms, + const string& signon_realm, + int wallet_handle) { + // Is there an entry in the wallet? + gboolean has_entry = false; + dbus_g_proxy_call(proxy_, "hasEntry", &error_, + G_TYPE_INT, wallet_handle, // handle + G_TYPE_STRING, kKWalletFolder, // folder + G_TYPE_STRING, signon_realm.c_str(), // key + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + G_TYPE_BOOLEAN, &has_entry, + G_TYPE_INVALID); + + if (CheckError() || !has_entry) + return false; + + GArray* byte_array = NULL; + dbus_g_proxy_call(proxy_, "readEntry", &error_, + G_TYPE_INT, wallet_handle, // handle + G_TYPE_STRING, kKWalletFolder, // folder + G_TYPE_STRING, signon_realm.c_str(), // key + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + DBUS_TYPE_G_UCHAR_ARRAY, &byte_array, + G_TYPE_INVALID); + + if (CheckError() || !byte_array || !byte_array->len) + return false; + + Pickle pickle(byte_array->data, byte_array->len); + DeserializeValue(signon_realm, pickle, forms); + g_array_free(byte_array, true); + + return true; +} + +bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms, + bool autofillable, + int wallet_handle) { + PasswordFormList all_forms; + if (!GetAllLogins(&all_forms, wallet_handle)) + return false; + + // We have to read all the entries, and then filter them here. + forms->reserve(forms->size() + all_forms.size()); + for (size_t i = 0; i < all_forms.size(); ++i) { + if (all_forms[i]->blacklisted_by_user == !autofillable) + forms->push_back(all_forms[i]); + else + delete all_forms[i]; + } + + return true; +} + +bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms, + const base::Time& begin, + const base::Time& end, + int wallet_handle) { + PasswordFormList all_forms; + if (!GetAllLogins(&all_forms, wallet_handle)) + return false; + + // We have to read all the entries, and then filter them here. + forms->reserve(forms->size() + all_forms.size()); + for (size_t i = 0; i < all_forms.size(); ++i) { + if (begin <= all_forms[i]->date_created && + (end.is_null() || all_forms[i]->date_created < end)) { + forms->push_back(all_forms[i]); + } else { + delete all_forms[i]; + } + } + + return true; +} + +bool NativeBackendKWallet::GetAllLogins(PasswordFormList* forms, + int wallet_handle) { + // We could probably also use readEntryList here. + char** realm_list = NULL; + dbus_g_proxy_call(proxy_, "entryList", &error_, + G_TYPE_INT, wallet_handle, // handle + G_TYPE_STRING, kKWalletFolder, // folder + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + G_TYPE_STRV, &realm_list, + G_TYPE_INVALID); + if (CheckError()) + return false; + + for (char** realm = realm_list; *realm; ++realm) { + GArray* byte_array = NULL; + dbus_g_proxy_call(proxy_, "readEntry", &error_, + G_TYPE_INT, wallet_handle, // handle + G_TYPE_STRING, kKWalletFolder, // folder + G_TYPE_STRING, *realm, // key + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + DBUS_TYPE_G_UCHAR_ARRAY, &byte_array, + G_TYPE_INVALID); + + if (CheckError() || !byte_array || !byte_array->len) + continue; + + Pickle pickle(byte_array->data, byte_array->len); + DeserializeValue(*realm, pickle, forms); + g_array_free(byte_array, true); + } + g_strfreev(realm_list); + return true; +} + +bool NativeBackendKWallet::SetLoginsList(const PasswordFormList& forms, + const string& signon_realm, + int wallet_handle) { + if (forms.empty()) { + // No items left? Remove the entry from the wallet. + int ret = 0; + dbus_g_proxy_call(proxy_, "removeEntry", &error_, + G_TYPE_INT, wallet_handle, // handle + G_TYPE_STRING, kKWalletFolder, // folder + G_TYPE_STRING, signon_realm.c_str(), // key + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + G_TYPE_INT, &ret, + G_TYPE_INVALID); + CheckError(); + if (ret != 0) + LOG(ERROR) << "Bad return code " << ret << " from kwallet removeEntry"; + return ret == 0; + } + + Pickle value; + SerializeValue(forms, &value); + + // Convert the pickled bytes to a GByteArray. + GArray* byte_array = g_array_sized_new(false, false, sizeof(char), + value.size()); + g_array_append_vals(byte_array, value.data(), value.size()); + + // Make the call. + int ret = 0; + dbus_g_proxy_call(proxy_, "writeEntry", &error_, + G_TYPE_INT, wallet_handle, // handle + G_TYPE_STRING, kKWalletFolder, // folder + G_TYPE_STRING, signon_realm.c_str(), // key + DBUS_TYPE_G_UCHAR_ARRAY, byte_array, // value + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + G_TYPE_INT, &ret, + G_TYPE_INVALID); + g_array_free(byte_array, true); + + CheckError(); + if (ret != 0) + LOG(ERROR) << "Bad return code " << ret << " from kwallet writeEntry"; + return ret == 0; +} + +bool NativeBackendKWallet::CompareForms(const PasswordForm& a, + const PasswordForm& b, + bool update_check) { + // An update check doesn't care about the submit element. + if (!update_check && a.submit_element != b.submit_element) + return false; + return a.origin == b.origin && + a.password_element == b.password_element && + a.signon_realm == b.signon_realm && + a.username_element == b.username_element && + a.username_value == b.username_value; +} + +void NativeBackendKWallet::SerializeValue(const PasswordFormList& forms, + Pickle* pickle) { + pickle->WriteInt(kPickleVersion); + pickle->WriteSize(forms.size()); + for (PasswordFormList::const_iterator it = forms.begin() ; + it != forms.end() ; ++it) { + const PasswordForm* form = *it; + pickle->WriteInt(form->scheme); + pickle->WriteString(form->origin.spec()); + pickle->WriteString(form->action.spec()); + pickle->WriteString16(form->username_element); + pickle->WriteString16(form->username_value); + pickle->WriteString16(form->password_element); + pickle->WriteString16(form->password_value); + pickle->WriteString16(form->submit_element); + pickle->WriteBool(form->ssl_valid); + pickle->WriteBool(form->preferred); + pickle->WriteBool(form->blacklisted_by_user); + pickle->WriteInt64(form->date_created.ToTimeT()); + } +} + +void NativeBackendKWallet::DeserializeValue(const string& signon_realm, + const Pickle& pickle, + PasswordFormList* forms) { + void* iter = NULL; + + int version = -1; + pickle.ReadInt(&iter, &version); + if (version != kPickleVersion) { + // This is the only version so far, so anything else is an error. + return; + } + + size_t count = 0; + pickle.ReadSize(&iter, &count); + + forms->reserve(forms->size() + count); + for (size_t i = 0; i < count; ++i) { + PasswordForm* form = new PasswordForm(); + form->signon_realm.assign(signon_realm); + + int scheme = 0; + pickle.ReadInt(&iter, &scheme); + form->scheme = static_cast(scheme); + ReadGURL(pickle, &iter, &form->origin); + ReadGURL(pickle, &iter, &form->action); + pickle.ReadString16(&iter, &form->username_element); + pickle.ReadString16(&iter, &form->username_value); + pickle.ReadString16(&iter, &form->password_element); + pickle.ReadString16(&iter, &form->password_value); + pickle.ReadString16(&iter, &form->submit_element); + pickle.ReadBool(&iter, &form->ssl_valid); + pickle.ReadBool(&iter, &form->preferred); + pickle.ReadBool(&iter, &form->blacklisted_by_user); + int64 date_created = 0; + pickle.ReadInt64(&iter, &date_created); + form->date_created = base::Time::FromTimeT(date_created); + forms->push_back(form); + } +} + +void NativeBackendKWallet::ReadGURL(const Pickle& pickle, void** iter, + GURL* url) { + string url_string; + pickle.ReadString(iter, &url_string); + *url = GURL(url_string); +} + +bool NativeBackendKWallet::CheckError() { + if (error_) { + LOG(ERROR) << "Failed to complete KWallet call: " << error_->message; + g_error_free(error_); + error_ = NULL; + return true; + } + return false; +} + +int NativeBackendKWallet::WalletHandle() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + // Open the wallet. + int handle = kInvalidKWalletHandle; + dbus_g_proxy_call(proxy_, "open", &error_, + G_TYPE_STRING, wallet_name_.c_str(), // wallet + G_TYPE_INT64, 0LL, // wid + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + G_TYPE_INT, &handle, + G_TYPE_INVALID); + if (CheckError() || handle == kInvalidKWalletHandle) + return kInvalidKWalletHandle; + + // Check if our folder exists. + gboolean has_folder = false; + dbus_g_proxy_call(proxy_, "hasFolder", &error_, + G_TYPE_INT, handle, // handle + G_TYPE_STRING, kKWalletFolder, // folder + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + G_TYPE_BOOLEAN, &has_folder, + G_TYPE_INVALID); + if (CheckError()) + return kInvalidKWalletHandle; + + // Create it if it didn't. + if (!has_folder) { + gboolean success = false; + dbus_g_proxy_call(proxy_, "createFolder", &error_, + G_TYPE_INT, handle, // handle + G_TYPE_STRING, kKWalletFolder, // folder + G_TYPE_STRING, kAppId, // appid + G_TYPE_INVALID, + G_TYPE_BOOLEAN, &success, + G_TYPE_INVALID); + if (CheckError() || !success) + return kInvalidKWalletHandle; + } + + return handle; +} diff --git a/chrome/browser/password_manager/native_backend_kwallet_x.h b/chrome/browser/password_manager/native_backend_kwallet_x.h new file mode 100644 index 0000000..ad2b367 --- /dev/null +++ b/chrome/browser/password_manager/native_backend_kwallet_x.h @@ -0,0 +1,137 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_PASSWORD_MANAGER_NATIVE_BACKEND_KWALLET_X_H_ +#define CHROME_BROWSER_PASSWORD_MANAGER_NATIVE_BACKEND_KWALLET_X_H_ + +#include +#include + +#include + +#include "base/basictypes.h" +#include "base/time.h" +#include "chrome/browser/password_manager/password_store_x.h" +#include "webkit/glue/password_form.h" + +class Pickle; + +// NativeBackend implementation using KWallet. +class NativeBackendKWallet : public PasswordStoreX::NativeBackend { + public: + NativeBackendKWallet(); + + virtual ~NativeBackendKWallet(); + + virtual bool Init(); + + // Implements NativeBackend interface. + virtual bool AddLogin(const webkit_glue::PasswordForm& form); + virtual bool UpdateLogin(const webkit_glue::PasswordForm& form); + virtual bool RemoveLogin(const webkit_glue::PasswordForm& form); + virtual bool RemoveLoginsCreatedBetween(const base::Time& delete_begin, + const base::Time& delete_end); + virtual bool GetLogins(const webkit_glue::PasswordForm& form, + PasswordFormList* forms); + virtual bool GetLoginsCreatedBetween(const base::Time& delete_begin, + const base::Time& delete_end, + PasswordFormList* forms); + virtual bool GetAutofillableLogins(PasswordFormList* forms); + virtual bool GetBlacklistLogins(PasswordFormList* forms); + + private: + // Initialization. + bool StartKWalletd(); + bool InitWallet(); + + // Reads PasswordForms from the wallet that match the given signon_realm. + bool GetLoginsList(PasswordFormList* forms, + const std::string& signon_realm, + int wallet_handle); + + // Reads PasswordForms from the wallet with the given autofillability state. + bool GetLoginsList(PasswordFormList* forms, + bool autofillable, + int wallet_handle); + + // Reads PasswordForms from the wallet created in the given time range. + bool GetLoginsList(PasswordFormList* forms, + const base::Time& begin, + const base::Time& end, + int wallet_handle); + + // Helper for some of the above GetLoginsList() methods. + bool GetAllLogins(PasswordFormList* forms, int wallet_handle); + + // Writes a list of PasswordForms to the wallet with the given signon_realm. + // Overwrites any existing list for this signon_realm. Removes the entry if + // |forms| is empty. Returns true on success. + bool SetLoginsList(const PasswordFormList& forms, + const std::string& signon_realm, + int wallet_handle); + + // Checks if the last DBus call returned an error. If it did, logs the error + // message, frees it and returns true. + // This must be called after every DBus call. + bool CheckError(); + + // Opens the wallet and ensures that the "Chrome Form Data" folder exists. + // Returns kInvalidWalletHandle on error. + int WalletHandle(); + + // Compares two PasswordForms and returns true if they are the same. + // If |update_check| is false, we only check the fields that are checked by + // LoginDatabase::UpdateLogin() when updating logins; otherwise, we check the + // fields that are checked by LoginDatabase::RemoveLogin() for removing them. + static bool CompareForms(const webkit_glue::PasswordForm& a, + const webkit_glue::PasswordForm& b, + bool update_check); + + // Serializes a list of PasswordForms to be stored in the wallet. + static void SerializeValue(const PasswordFormList& forms, Pickle* pickle); + + // 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. + static void ReadGURL(const Pickle& pickle, void** iter, GURL* url); + + // In case the fields in the pickle ever change, version them so we can try to + // read old pickles. (Note: do not eat old pickles past the expiration date.) + static const int kPickleVersion = 0; + + // Name of the application - will appear in kwallet's dialogs. + static const char* kAppId; + // Name of the folder to store passwords in. + static const char* kKWalletFolder; + + // DBus stuff. + static const char* kKWalletServiceName; + static const char* kKWalletPath; + static const char* kKWalletInterface; + static const char* kKLauncherServiceName; + static const char* kKLauncherPath; + static const char* kKLauncherInterface; + + // Invalid handle returned by WalletHandle(). + static const int kInvalidKWalletHandle = -1; + + // Error from the last DBus call. NULL when there's no error. Freed and + // cleared by CheckError(). + GError* error_; + // Connection to the DBus session bus. + DBusGConnection* connection_; + // Proxy to the kwallet DBus service. + DBusGProxy* proxy_; + + // The name of the wallet we've opened. Set during Init(). + std::string wallet_name_; + + DISALLOW_COPY_AND_ASSIGN(NativeBackendKWallet); +}; + +#endif // CHROME_BROWSER_PASSWORD_MANAGER_NATIVE_BACKEND_KWALLET_X_H_ diff --git a/chrome/browser/password_manager/password_store_default.cc b/chrome/browser/password_manager/password_store_default.cc index 8d3ff96..dc58f56 100644 --- a/chrome/browser/password_manager/password_store_default.cc +++ b/chrome/browser/password_manager/password_store_default.cc @@ -4,6 +4,8 @@ #include "chrome/browser/password_manager/password_store_default.h" +#include + #include "chrome/browser/chrome_thread.h" #include "chrome/browser/password_manager/password_store_change.h" #include "chrome/browser/pref_service.h" @@ -145,7 +147,7 @@ void PasswordStoreDefault::OnWebDataServiceRequestDone( static_cast*>(result)->GetValue(); for (PasswordForms::const_iterator it = forms.begin(); it != forms.end(); ++it) { - AddLoginImpl(**it); + AddLogin(**it); web_data_service_->RemoveLogin(**it); delete *it; } diff --git a/chrome/browser/password_manager/password_store_gnome.cc b/chrome/browser/password_manager/password_store_gnome.cc deleted file mode 100644 index 7495815..0000000 --- a/chrome/browser/password_manager/password_store_gnome.cc +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright (c) 2010 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 "chrome/browser/password_manager/password_store_gnome.h" - -#if defined(DLOPEN_GNOME_KEYRING) -#include -#endif - -#include -#include - -#include "base/logging.h" -#include "base/string_util.h" -#include "base/task.h" -#include "base/time.h" -#include "base/utf_string_conversions.h" - -using std::map; -using std::string; -using std::vector; -using webkit_glue::PasswordForm; - -/* Many of the gnome_keyring_* functions use variable arguments, which makes - * them difficult if not impossible to wrap in C. Therefore, we want the - * actual uses below to either call the functions directly (if we are linking - * against libgnome-keyring), or call them via appropriately-typed function - * pointers (if we are dynamically loading libgnome-keyring). - * - * Thus, instead of making a wrapper class with two implementations, we use - * the preprocessor to rename the calls below in the dynamic load case, and - * provide a function to initialize a set of function pointers that have the - * alternate names. We also make sure the types are correct, since otherwise - * dynamic loading like this would leave us vulnerable to signature changes. */ - -#if defined(DLOPEN_GNOME_KEYRING) - -namespace { - -gboolean (*wrap_gnome_keyring_is_available)(); -GnomeKeyringResult (*wrap_gnome_keyring_store_password_sync)( // NOLINT - const GnomeKeyringPasswordSchema* schema, const gchar* keyring, - const gchar* display_name, const gchar* password, ...); -GnomeKeyringResult (*wrap_gnome_keyring_delete_password_sync)( // NOLINT - const GnomeKeyringPasswordSchema* schema, ...); -GnomeKeyringResult (*wrap_gnome_keyring_find_itemsv_sync)( // NOLINT - GnomeKeyringItemType type, GList** found, ...); -const gchar* (*wrap_gnome_keyring_result_to_message)(GnomeKeyringResult res); -void (*wrap_gnome_keyring_found_list_free)(GList* found_list); - -/* Cause the compiler to complain if the types of the above function pointers - * do not correspond to the types of the actual gnome_keyring_* functions. */ -#define GNOME_KEYRING_VERIFY_TYPE(name) \ - typeof(&gnome_keyring_##name) name = wrap_gnome_keyring_##name; name = name - -inline void VerifyGnomeKeyringTypes() { - GNOME_KEYRING_VERIFY_TYPE(is_available); - GNOME_KEYRING_VERIFY_TYPE(store_password_sync); - GNOME_KEYRING_VERIFY_TYPE(delete_password_sync); - GNOME_KEYRING_VERIFY_TYPE(find_itemsv_sync); - GNOME_KEYRING_VERIFY_TYPE(result_to_message); - GNOME_KEYRING_VERIFY_TYPE(found_list_free); -} -#undef GNOME_KEYRING_VERIFY_TYPE - -/* Make it easy to initialize the function pointers above with a loop below. */ -#define GNOME_KEYRING_FUNCTION(name) \ - {#name, reinterpret_cast(&wrap_##name)} -const struct { - const char* name; - void** pointer; -} gnome_keyring_functions[] = { - GNOME_KEYRING_FUNCTION(gnome_keyring_is_available), - GNOME_KEYRING_FUNCTION(gnome_keyring_store_password_sync), - GNOME_KEYRING_FUNCTION(gnome_keyring_delete_password_sync), - GNOME_KEYRING_FUNCTION(gnome_keyring_find_itemsv_sync), - GNOME_KEYRING_FUNCTION(gnome_keyring_result_to_message), - GNOME_KEYRING_FUNCTION(gnome_keyring_found_list_free), - {NULL, NULL} -}; -#undef GNOME_KEYRING_FUNCTION - -/* Allow application code below to use the normal function names, but actually - * end up using the function pointers above instead. */ -#define gnome_keyring_is_available \ - wrap_gnome_keyring_is_available -#define gnome_keyring_store_password_sync \ - wrap_gnome_keyring_store_password_sync -#define gnome_keyring_delete_password_sync \ - wrap_gnome_keyring_delete_password_sync -#define gnome_keyring_find_itemsv_sync \ - wrap_gnome_keyring_find_itemsv_sync -#define gnome_keyring_result_to_message \ - wrap_gnome_keyring_result_to_message -#define gnome_keyring_found_list_free \ - wrap_gnome_keyring_found_list_free - -/* Load the library and initialize the function pointers. */ -bool LoadGnomeKeyring() { - void* handle = dlopen("libgnome-keyring.so.0", RTLD_NOW | RTLD_GLOBAL); - if (!handle) { - LOG(INFO) << "Could not find libgnome-keyring.so.0"; - return false; - } - for (size_t i = 0; gnome_keyring_functions[i].name; ++i) { - dlerror(); - *gnome_keyring_functions[i].pointer = - dlsym(handle, gnome_keyring_functions[i].name); - const char* error = dlerror(); - if (error) { - LOG(ERROR) << "Unable to load symbol " << - gnome_keyring_functions[i].name << ": " << error; - dlclose(handle); - return false; - } - } - // We leak the library handle. That's OK: this function is called only once. - return true; -} - -} // namespace - -#else // DLOPEN_GNOME_KEYRING - -namespace { - -bool LoadGnomeKeyring() { - return true; -} - -} // namespace - -#endif // DLOPEN_GNOME_KEYRING - -#define GNOME_KEYRING_APPLICATION_CHROME "chrome" - -// Schema is analagous to the fields in PasswordForm. -const GnomeKeyringPasswordSchema PasswordStoreGnome::kGnomeSchema = { - GNOME_KEYRING_ITEM_GENERIC_SECRET, { - { "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, - { "action_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, - { "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, - { "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, - { "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, - { "submit_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, - { "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, - { "ssl_valid", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, - { "preferred", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, - { "date_created", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, - { "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, - { "scheme", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32 }, - // This field is always "chrome" so that we can search for it. - { "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, - { NULL } - } -}; - -PasswordStoreGnome::PasswordStoreGnome(LoginDatabase* login_db, - Profile* profile, - WebDataService* web_data_service) { -} - -PasswordStoreGnome::~PasswordStoreGnome() { -} - -bool PasswordStoreGnome::Init() { - return PasswordStore::Init() && - LoadGnomeKeyring() && - gnome_keyring_is_available(); -} - -void PasswordStoreGnome::AddLoginImpl(const PasswordForm& form) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); - AddLoginHelper(form, base::Time::Now()); -} - -void PasswordStoreGnome::UpdateLoginImpl(const PasswordForm& form) { - // Based on LoginDatabase::UpdateLogin(), we search for forms to update by - // origin_url, username_element, username_value, password_element, and - // signon_realm. We then compare the result to the updated form. If they - // differ in any of the action, password_value, ssl_valid, or preferred - // fields, then we add a new login with those fields updated and only delete - // the original on success. - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); - GList* found = NULL; - // Search gnome keyring for matching passwords. - GnomeKeyringResult result = gnome_keyring_find_itemsv_sync( - GNOME_KEYRING_ITEM_GENERIC_SECRET, - &found, - "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, - form.origin.spec().c_str(), - "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, - UTF16ToUTF8(form.username_element).c_str(), - "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, - UTF16ToUTF8(form.username_value).c_str(), - "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, - UTF16ToUTF8(form.password_element).c_str(), - "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, - form.signon_realm.c_str(), - "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, - GNOME_KEYRING_APPLICATION_CHROME, - NULL); - vector forms; - if (result == GNOME_KEYRING_RESULT_OK) { - FillFormVector(found, &forms); - for (size_t i = 0; i < forms.size(); ++i) { - if (forms[i]->action != form.action || - forms[i]->password_value != form.password_value || - forms[i]->ssl_valid != form.ssl_valid || - forms[i]->preferred != form.preferred) { - PasswordForm updated = *forms[i]; - updated.action = form.action; - updated.password_value = form.password_value; - updated.ssl_valid = form.ssl_valid; - updated.preferred = form.preferred; - if (AddLoginHelper(updated, updated.date_created)) - RemoveLoginImpl(*forms[i]); - } - delete forms[i]; - } - } else { - LOG(ERROR) << "Keyring find failed: " - << gnome_keyring_result_to_message(result); - } -} - -void PasswordStoreGnome::RemoveLoginImpl(const PasswordForm& form) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); - // We find forms using the same fields as LoginDatabase::RemoveLogin(). - GnomeKeyringResult result = gnome_keyring_delete_password_sync( - &kGnomeSchema, - "origin_url", form.origin.spec().c_str(), - "action_url", form.action.spec().c_str(), - "username_element", UTF16ToUTF8(form.username_element).c_str(), - "username_value", UTF16ToUTF8(form.username_value).c_str(), - "password_element", UTF16ToUTF8(form.password_element).c_str(), - "submit_element", UTF16ToUTF8(form.submit_element).c_str(), - "signon_realm", form.signon_realm.c_str(), - NULL); - if (result != GNOME_KEYRING_RESULT_OK) { - LOG(ERROR) << "Keyring delete failed: " - << gnome_keyring_result_to_message(result); - } -} - -void PasswordStoreGnome::RemoveLoginsCreatedBetweenImpl( - const base::Time& delete_begin, - const base::Time& delete_end) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); - GList* found = NULL; - // Search GNOME keyring for all passwords, then delete the ones in the range. - // We need to search for something, otherwise we get no results - so we search - // for the fixed application string. - GnomeKeyringResult result = gnome_keyring_find_itemsv_sync( - GNOME_KEYRING_ITEM_GENERIC_SECRET, - &found, - "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, - GNOME_KEYRING_APPLICATION_CHROME, - NULL); - if (result == GNOME_KEYRING_RESULT_OK) { - // We could walk the list and delete items as we find them, but it is much - // easier to build the vector and use RemoveLoginImpl() to delete them. - vector forms; - FillFormVector(found, &forms); - for (size_t i = 0; i < forms.size(); ++i) { - if (delete_begin <= forms[i]->date_created && - (delete_end.is_null() || forms[i]->date_created < delete_end)) { - RemoveLoginImpl(*forms[i]); - } - delete forms[i]; - } - } else if (result != GNOME_KEYRING_RESULT_NO_MATCH) { - LOG(ERROR) << "Keyring find failed: " - << gnome_keyring_result_to_message(result); - } -} - -void PasswordStoreGnome::GetLoginsImpl(GetLoginsRequest* request, - const PasswordForm& form) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); - GList* found = NULL; - // Search gnome keyring for matching passwords. - GnomeKeyringResult result = gnome_keyring_find_itemsv_sync( - GNOME_KEYRING_ITEM_GENERIC_SECRET, - &found, - "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, - form.signon_realm.c_str(), - "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, - GNOME_KEYRING_APPLICATION_CHROME, - NULL); - vector forms; - if (result == GNOME_KEYRING_RESULT_OK) { - FillFormVector(found, &forms); - } else if (result != GNOME_KEYRING_RESULT_NO_MATCH) { - LOG(ERROR) << "Keyring find failed: " - << gnome_keyring_result_to_message(result); - } - NotifyConsumer(request, forms); -} - -void PasswordStoreGnome::GetAutofillableLoginsImpl( - GetLoginsRequest* request) { - std::vector forms; - FillAutofillableLogins(&forms); - NotifyConsumer(request, forms); -} - -void PasswordStoreGnome::GetBlacklistLoginsImpl( - GetLoginsRequest* request) { - std::vector forms; - FillBlacklistLogins(&forms); - NotifyConsumer(request, forms); -} - -bool PasswordStoreGnome::FillAutofillableLogins( - std::vector* forms) { - return FillSomeLogins(true, forms); -} - -bool PasswordStoreGnome::FillBlacklistLogins( - std::vector* forms) { - return FillSomeLogins(false, forms); -} - -bool PasswordStoreGnome::AddLoginHelper(const PasswordForm& form, - const base::Time& date_created) { - GnomeKeyringResult result = gnome_keyring_store_password_sync( - &kGnomeSchema, - NULL, // Default keyring. - form.origin.spec().c_str(), // Display name. - UTF16ToUTF8(form.password_value).c_str(), - "origin_url", form.origin.spec().c_str(), - "action_url", form.action.spec().c_str(), - "username_element", UTF16ToUTF8(form.username_element).c_str(), - "username_value", UTF16ToUTF8(form.username_value).c_str(), - "password_element", UTF16ToUTF8(form.password_element).c_str(), - "submit_element", UTF16ToUTF8(form.submit_element).c_str(), - "signon_realm", form.signon_realm.c_str(), - "ssl_valid", form.ssl_valid, - "preferred", form.preferred, - "date_created", Int64ToString(date_created.ToTimeT()).c_str(), - "blacklisted_by_user", form.blacklisted_by_user, - "scheme", form.scheme, - "application", GNOME_KEYRING_APPLICATION_CHROME, - NULL); - - if (result != GNOME_KEYRING_RESULT_OK) { - LOG(ERROR) << "Keyring save failed: " - << gnome_keyring_result_to_message(result); - return false; - } - return true; -} - -bool PasswordStoreGnome::FillSomeLogins( - bool autofillable, - std::vector* forms) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); - GList* found = NULL; - uint32_t blacklisted_by_user = !autofillable; - // Search gnome keyring for matching passwords. - GnomeKeyringResult result = gnome_keyring_find_itemsv_sync( - GNOME_KEYRING_ITEM_GENERIC_SECRET, - &found, - "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32, - blacklisted_by_user, - "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, - GNOME_KEYRING_APPLICATION_CHROME, - NULL); - if (result == GNOME_KEYRING_RESULT_OK) { - FillFormVector(found, forms); - } else if (result != GNOME_KEYRING_RESULT_NO_MATCH) { - LOG(ERROR) << "Keyring find failed: " - << gnome_keyring_result_to_message(result); - return false; - } - return true; -} - -void PasswordStoreGnome::FillFormVector(GList* found, - std::vector* forms) { - GList* element = g_list_first(found); - while (element != NULL) { - GnomeKeyringFound* data = static_cast(element->data); - char* password = data->secret; - - GnomeKeyringAttributeList* attributes = data->attributes; - // Read the string and int attributes into the appropriate map. - map string_attribute_map; - map uint_attribute_map; - for (unsigned int i = 0; i < attributes->len; ++i) { - GnomeKeyringAttribute attribute = - gnome_keyring_attribute_list_index(attributes, i); - if (attribute.type == GNOME_KEYRING_ATTRIBUTE_TYPE_STRING) { - string_attribute_map[string(attribute.name)] = - string(attribute.value.string); - } else if (attribute.type == GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32) { - uint_attribute_map[string(attribute.name)] = attribute.value.integer; - } - } - - PasswordForm* form = new PasswordForm(); - form->origin = GURL(string_attribute_map["origin_url"]); - form->action = GURL(string_attribute_map["action_url"]); - form->username_element = - UTF8ToUTF16(string(string_attribute_map["username_element"])); - form->username_value = - UTF8ToUTF16(string(string_attribute_map["username_value"])); - form->password_element = - UTF8ToUTF16(string(string_attribute_map["password_element"])); - form->password_value = UTF8ToUTF16(string(password)); - form->submit_element = - UTF8ToUTF16(string(string_attribute_map["submit_element"])); - form->signon_realm = string_attribute_map["signon_realm"]; - form->ssl_valid = uint_attribute_map["ssl_valid"]; - form->preferred = uint_attribute_map["preferred"]; - string date = string_attribute_map["date_created"]; - int64 date_created = 0; - bool date_ok = StringToInt64(date, &date_created); - DCHECK(date_ok); - DCHECK_NE(date_created, 0); - form->date_created = base::Time::FromTimeT(date_created); - form->blacklisted_by_user = uint_attribute_map["blacklisted_by_user"]; - form->scheme = static_cast( - uint_attribute_map["scheme"]); - - forms->push_back(form); - - element = g_list_next(element); - } - gnome_keyring_found_list_free(found); -} diff --git a/chrome/browser/password_manager/password_store_gnome.h b/chrome/browser/password_manager/password_store_gnome.h deleted file mode 100644 index 8927ba3..0000000 --- a/chrome/browser/password_manager/password_store_gnome.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2010 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. - -#ifndef CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_GNOME_H_ -#define CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_GNOME_H_ - -extern "C" { -#include -} - -#include - -#include "base/lock.h" -#include "chrome/browser/password_manager/login_database.h" -#include "chrome/browser/password_manager/password_store.h" -#include "chrome/browser/profile.h" -#include "chrome/browser/webdata/web_data_service.h" - -class Profile; -class Task; - -// PasswordStore implementation using GNOME Keyring. -class PasswordStoreGnome : public PasswordStore { - public: - PasswordStoreGnome(LoginDatabase* login_db, - Profile* profile, - WebDataService* web_data_service); - - virtual bool Init(); - - private: - virtual ~PasswordStoreGnome(); - - // Implements PasswordStore interface. - virtual void AddLoginImpl(const webkit_glue::PasswordForm& form); - virtual void UpdateLoginImpl(const webkit_glue::PasswordForm& form); - virtual void RemoveLoginImpl(const webkit_glue::PasswordForm& form); - virtual void RemoveLoginsCreatedBetweenImpl(const base::Time& delete_begin, - const base::Time& delete_end); - virtual void GetLoginsImpl(GetLoginsRequest* request, - const webkit_glue::PasswordForm& form); - virtual void GetAutofillableLoginsImpl(GetLoginsRequest* request); - virtual void GetBlacklistLoginsImpl(GetLoginsRequest* request); - virtual bool FillAutofillableLogins( - std::vector* forms); - virtual bool FillBlacklistLogins( - std::vector* forms); - - // Helper for AddLoginImpl() and UpdateLoginImpl() with a success status. - bool AddLoginHelper(const webkit_glue::PasswordForm& form, - const base::Time& date_created); - - // Helper for FillAutofillableLogins() and FillBlacklistLogins(). - bool FillSomeLogins(bool autofillable, - std::vector* forms); - - // Parse all the results from the given GList into a - // vector, and free the GList. PasswordForms are - // allocated on the heap, and should be deleted by the consumer. - void FillFormVector(GList* found, - std::vector* forms); - - static const GnomeKeyringPasswordSchema kGnomeSchema; - - DISALLOW_COPY_AND_ASSIGN(PasswordStoreGnome); -}; - -#endif // CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_GNOME_H_ diff --git a/chrome/browser/password_manager/password_store_kwallet.cc b/chrome/browser/password_manager/password_store_kwallet.cc deleted file mode 100644 index 26f0ab9..0000000 --- a/chrome/browser/password_manager/password_store_kwallet.cc +++ /dev/null @@ -1,547 +0,0 @@ -// Copyright (c) 2010 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 "chrome/browser/password_manager/password_store_kwallet.h" - -#include - -#include "base/logging.h" -#include "base/md5.h" -#include "base/pickle.h" -#include "base/stl_util-inl.h" -#include "base/string_util.h" -#include "base/task.h" - -using std::string; -using std::vector; -using webkit_glue::PasswordForm; - -// We could localize these strings, but then changing your locale would cause -// you to lose access to all your stored passwords. Maybe best not to do that. -const char* PasswordStoreKWallet::kAppId = "Chrome"; -const char* PasswordStoreKWallet::kKWalletFolder = "Chrome Form Data"; - -const char* PasswordStoreKWallet::kKWalletServiceName = "org.kde.kwalletd"; -const char* PasswordStoreKWallet::kKWalletPath = "/modules/kwalletd"; -const char* PasswordStoreKWallet::kKWalletInterface = "org.kde.KWallet"; -const char* PasswordStoreKWallet::kKLauncherServiceName = "org.kde.klauncher"; -const char* PasswordStoreKWallet::kKLauncherPath = "/KLauncher"; -const char* PasswordStoreKWallet::kKLauncherInterface = "org.kde.KLauncher"; - -PasswordStoreKWallet::PasswordStoreKWallet(LoginDatabase* login_db, - Profile* profile, - WebDataService* web_data_service) - : error_(NULL), - connection_(NULL), - proxy_(NULL) { -} - -PasswordStoreKWallet::~PasswordStoreKWallet() { - if (proxy_) { - g_object_unref(proxy_); - } -} - -bool PasswordStoreKWallet::Init() { - // Initialize threading in dbus-glib - it should be fine for - // dbus_g_thread_init to be called multiple times. - if (!g_thread_supported()) - g_thread_init(NULL); - dbus_g_thread_init(); - - // Get a connection to the session bus. - connection_ = dbus_g_bus_get(DBUS_BUS_SESSION, &error_); - if (CheckError()) - return false; - - if (!InitWallet()) { - // kwalletd may not be running. Try to start it and try again. - if (!StartKWalletd() || !InitWallet()) - return false; - } - - return true; -} - -bool PasswordStoreKWallet::StartKWalletd() { - // Sadly kwalletd doesn't use DBUS activation, so we have to make a call to - // klauncher to start it. - DBusGProxy* klauncher_proxy = - dbus_g_proxy_new_for_name(connection_, kKLauncherServiceName, - kKLauncherPath, kKLauncherInterface); - - char* empty_string_list = NULL; - int ret = 1; - char* error = NULL; - dbus_g_proxy_call(klauncher_proxy, "start_service_by_desktop_name", &error_, - G_TYPE_STRING, "kwalletd", // serviceName - G_TYPE_STRV, &empty_string_list, // urls - G_TYPE_STRV, &empty_string_list, // envs - G_TYPE_STRING, "", // startup_id - G_TYPE_BOOLEAN, (gboolean) false, // blind - G_TYPE_INVALID, - G_TYPE_INT, &ret, // result - G_TYPE_STRING, NULL, // dubsName - G_TYPE_STRING, &error, // error - G_TYPE_INT, NULL, // pid - G_TYPE_INVALID); - - if (error && *error) { - LOG(ERROR) << "Error launching kwalletd: " << error; - ret = 1; // Make sure we return false after freeing. - } - - g_free(error); - g_object_unref(klauncher_proxy); - - if (CheckError() || ret != 0) - return false; - return true; -} - -bool PasswordStoreKWallet::InitWallet() { - // Make a proxy to KWallet. - proxy_ = dbus_g_proxy_new_for_name(connection_, kKWalletServiceName, - kKWalletPath, kKWalletInterface); - - // Check KWallet is enabled. - gboolean is_enabled = false; - dbus_g_proxy_call(proxy_, "isEnabled", &error_, - G_TYPE_INVALID, - G_TYPE_BOOLEAN, &is_enabled, - G_TYPE_INVALID); - if (CheckError() || !is_enabled) - return false; - - // Get the wallet name. - char* wallet_name = NULL; - dbus_g_proxy_call(proxy_, "networkWallet", &error_, - G_TYPE_INVALID, - G_TYPE_STRING, &wallet_name, - G_TYPE_INVALID); - if (CheckError() || !wallet_name) - return false; - - wallet_name_.assign(wallet_name); - g_free(wallet_name); - - return true; -} - -void PasswordStoreKWallet::AddLoginImpl(const PasswordForm& form) { - AutoLock l(kwallet_lock_); - int wallet_handle = WalletHandle(); - if (wallet_handle == kInvalidKWalletHandle) - return; - - PasswordFormList forms; - GetLoginsList(&forms, form.signon_realm, wallet_handle); - - forms.push_back(new PasswordForm(form)); - SetLoginsList(forms, form.signon_realm, wallet_handle); - - STLDeleteElements(&forms); -} - -void PasswordStoreKWallet::UpdateLoginImpl(const PasswordForm& form) { - AutoLock l(kwallet_lock_); - int wallet_handle = WalletHandle(); - if (wallet_handle == kInvalidKWalletHandle) - return; - - PasswordFormList forms; - GetLoginsList(&forms, form.signon_realm, wallet_handle); - - for (size_t i = 0; i < forms.size(); ++i) { - if (CompareForms(form, *forms[i], true)) - *forms[i] = form; - } - - SetLoginsList(forms, form.signon_realm, wallet_handle); - - STLDeleteElements(&forms); -} - -void PasswordStoreKWallet::RemoveLoginImpl(const PasswordForm& form) { - AutoLock l(kwallet_lock_); - int wallet_handle = WalletHandle(); - if (wallet_handle == kInvalidKWalletHandle) - return; - - PasswordFormList all_forms; - GetLoginsList(&all_forms, form.signon_realm, wallet_handle); - - PasswordFormList kept_forms; - kept_forms.reserve(all_forms.size()); - for (size_t i = 0; i < all_forms.size(); ++i) { - if (CompareForms(form, *all_forms[i], false)) - delete all_forms[i]; - else - kept_forms.push_back(all_forms[i]); - } - - // Update the entry in the wallet. - SetLoginsList(kept_forms, form.signon_realm, wallet_handle); - STLDeleteElements(&kept_forms); -} - -void PasswordStoreKWallet::RemoveLoginsCreatedBetweenImpl( - const base::Time& delete_begin, - const base::Time& delete_end) { - AutoLock l(kwallet_lock_); - int wallet_handle = WalletHandle(); - if (wallet_handle == kInvalidKWalletHandle) - return; - - // We could probably also use readEntryList here. - char** realm_list = NULL; - dbus_g_proxy_call(proxy_, "entryList", &error_, - G_TYPE_INT, wallet_handle, // handle - G_TYPE_STRING, kKWalletFolder, // folder - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - G_TYPE_STRV, &realm_list, - G_TYPE_INVALID); - if (CheckError()) - return; - - for (char** realm = realm_list; *realm; ++realm) { - GArray* byte_array = NULL; - dbus_g_proxy_call(proxy_, "readEntry", &error_, - G_TYPE_INT, wallet_handle, // handle - G_TYPE_STRING, kKWalletFolder, // folder - G_TYPE_STRING, *realm, // key - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - DBUS_TYPE_G_UCHAR_ARRAY, &byte_array, - G_TYPE_INVALID); - - if (CheckError() || !byte_array || !byte_array->len) - continue; - - string signon_realm(*realm); - Pickle pickle(byte_array->data, byte_array->len); - PasswordFormList all_forms; - DeserializeValue(signon_realm, pickle, &all_forms); - g_array_free(byte_array, true); - - PasswordFormList kept_forms; - kept_forms.reserve(all_forms.size()); - for (size_t i = 0; i < all_forms.size(); ++i) { - if (delete_begin <= all_forms[i]->date_created && - (delete_end.is_null() || all_forms[i]->date_created < delete_end)) { - delete all_forms[i]; - } else { - kept_forms.push_back(all_forms[i]); - } - } - - SetLoginsList(kept_forms, signon_realm, wallet_handle); - STLDeleteElements(&kept_forms); - } - g_strfreev(realm_list); -} - -void PasswordStoreKWallet::GetLoginsImpl(GetLoginsRequest* request, - const PasswordForm& form) { - PasswordFormList forms; - - AutoLock l(kwallet_lock_); - int wallet_handle = WalletHandle(); - if (wallet_handle != kInvalidKWalletHandle) - GetLoginsList(&forms, form.signon_realm, wallet_handle); - - NotifyConsumer(request, forms); -} - -void PasswordStoreKWallet::GetAutofillableLoginsImpl( - GetLoginsRequest* request) { - std::vector forms; - FillAutofillableLogins(&forms); - NotifyConsumer(request, forms); -} - -void PasswordStoreKWallet::GetBlacklistLoginsImpl( - GetLoginsRequest* request) { - std::vector forms; - FillBlacklistLogins(&forms); - NotifyConsumer(request, forms); -} - -bool PasswordStoreKWallet::FillAutofillableLogins( - std::vector* forms) { - return FillSomeLogins(true, forms); -} - -bool PasswordStoreKWallet::FillBlacklistLogins( - std::vector* forms) { - return FillSomeLogins(false, forms); -} - -void PasswordStoreKWallet::GetLoginsList(PasswordFormList* forms, - const string& signon_realm, - int wallet_handle) { - // Is there an entry in the wallet? - gboolean has_entry = false; - dbus_g_proxy_call(proxy_, "hasEntry", &error_, - G_TYPE_INT, wallet_handle, // handle - G_TYPE_STRING, kKWalletFolder, // folder - G_TYPE_STRING, signon_realm.c_str(), // key - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - G_TYPE_BOOLEAN, &has_entry, - G_TYPE_INVALID); - - if (CheckError() || !has_entry) - return; - - GArray* byte_array = NULL; - dbus_g_proxy_call(proxy_, "readEntry", &error_, - G_TYPE_INT, wallet_handle, // handle - G_TYPE_STRING, kKWalletFolder, // folder - G_TYPE_STRING, signon_realm.c_str(), // key - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - DBUS_TYPE_G_UCHAR_ARRAY, &byte_array, - G_TYPE_INVALID); - - if (CheckError() || !byte_array || !byte_array->len) - return; - - Pickle pickle(byte_array->data, byte_array->len); - DeserializeValue(signon_realm, pickle, forms); - g_array_free(byte_array, true); -} - -void PasswordStoreKWallet::SetLoginsList(const PasswordFormList& forms, - const string& signon_realm, - int wallet_handle) { - if (forms.empty()) { - // No items left? Remove the entry from the wallet. - int ret = 0; - dbus_g_proxy_call(proxy_, "removeEntry", &error_, - G_TYPE_INT, wallet_handle, // handle - G_TYPE_STRING, kKWalletFolder, // folder - G_TYPE_STRING, signon_realm.c_str(), // key - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - G_TYPE_INT, &ret, - G_TYPE_INVALID); - CheckError(); - if (ret != 0) - LOG(ERROR) << "Bad return code " << ret << " from kwallet removeEntry"; - return; - } - - Pickle value; - SerializeValue(forms, &value); - - // Convert the pickled bytes to a GByteArray. - GArray* byte_array = g_array_sized_new(false, false, sizeof(char), - value.size()); - g_array_append_vals(byte_array, value.data(), value.size()); - - // Make the call. - int ret = 0; - dbus_g_proxy_call(proxy_, "writeEntry", &error_, - G_TYPE_INT, wallet_handle, // handle - G_TYPE_STRING, kKWalletFolder, // folder - G_TYPE_STRING, signon_realm.c_str(), // key - DBUS_TYPE_G_UCHAR_ARRAY, byte_array, // value - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - G_TYPE_INT, &ret, - G_TYPE_INVALID); - g_array_free(byte_array, true); - - CheckError(); - if (ret != 0) - LOG(ERROR) << "Bad return code " << ret << " from kwallet writeEntry"; -} - -bool PasswordStoreKWallet::FillSomeLogins(bool autofillable, - PasswordFormList* forms) { - AutoLock l(kwallet_lock_); - int wallet_handle = WalletHandle(); - if (wallet_handle == kInvalidKWalletHandle) - return false; - - // We could probably also use readEntryList here. - char** realm_list = NULL; - dbus_g_proxy_call(proxy_, "entryList", &error_, - G_TYPE_INT, wallet_handle, // handle - G_TYPE_STRING, kKWalletFolder, // folder - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - G_TYPE_STRV, &realm_list, - G_TYPE_INVALID); - if (CheckError()) - return false; - - PasswordFormList all_forms; - for (char** realm = realm_list; *realm; ++realm) { - GArray* byte_array = NULL; - dbus_g_proxy_call(proxy_, "readEntry", &error_, - G_TYPE_INT, wallet_handle, // handle - G_TYPE_STRING, kKWalletFolder, // folder - G_TYPE_STRING, *realm, // key - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - DBUS_TYPE_G_UCHAR_ARRAY, &byte_array, - G_TYPE_INVALID); - - if (CheckError() || !byte_array || !byte_array->len) - continue; - - Pickle pickle(byte_array->data, byte_array->len); - DeserializeValue(*realm, pickle, &all_forms); - g_array_free(byte_array, true); - } - g_strfreev(realm_list); - - // We have to read all the entries, and then filter them here. - forms->reserve(forms->size() + all_forms.size()); - for (size_t i = 0; i < all_forms.size(); ++i) { - if (all_forms[i]->blacklisted_by_user == !autofillable) - forms->push_back(all_forms[i]); - else - delete all_forms[i]; - } - - return true; -} - -bool PasswordStoreKWallet::CompareForms(const PasswordForm& a, - const PasswordForm& b, - bool update_check) { - // An update check doesn't care about the submit element. - if (!update_check && a.submit_element != b.submit_element) - return false; - return a.origin == b.origin && - a.password_element == b.password_element && - a.signon_realm == b.signon_realm && - a.username_element == b.username_element && - a.username_value == b.username_value; -} - -void PasswordStoreKWallet::SerializeValue(const PasswordFormList& forms, - Pickle* pickle) { - pickle->WriteInt(kPickleVersion); - pickle->WriteSize(forms.size()); - for (PasswordFormList::const_iterator it = forms.begin() ; - it != forms.end() ; ++it) { - const PasswordForm* form = *it; - pickle->WriteInt(form->scheme); - pickle->WriteString(form->origin.spec()); - pickle->WriteString(form->action.spec()); - pickle->WriteString16(form->username_element); - pickle->WriteString16(form->username_value); - pickle->WriteString16(form->password_element); - pickle->WriteString16(form->password_value); - pickle->WriteString16(form->submit_element); - pickle->WriteBool(form->ssl_valid); - pickle->WriteBool(form->preferred); - pickle->WriteBool(form->blacklisted_by_user); - pickle->WriteInt64(form->date_created.ToTimeT()); - } -} - -void PasswordStoreKWallet::DeserializeValue(const string& signon_realm, - const Pickle& pickle, - PasswordFormList* forms) { - void* iter = NULL; - - int version = -1; - pickle.ReadInt(&iter, &version); - if (version != kPickleVersion) { - // This is the only version so far, so anything else is an error. - return; - } - - size_t count = 0; - pickle.ReadSize(&iter, &count); - - forms->reserve(forms->size() + count); - for (size_t i = 0; i < count; ++i) { - PasswordForm* form = new PasswordForm(); - form->signon_realm.assign(signon_realm); - - int scheme = 0; - pickle.ReadInt(&iter, &scheme); - form->scheme = static_cast(scheme); - ReadGURL(pickle, &iter, &form->origin); - ReadGURL(pickle, &iter, &form->action); - pickle.ReadString16(&iter, &form->username_element); - pickle.ReadString16(&iter, &form->username_value); - pickle.ReadString16(&iter, &form->password_element); - pickle.ReadString16(&iter, &form->password_value); - pickle.ReadString16(&iter, &form->submit_element); - pickle.ReadBool(&iter, &form->ssl_valid); - pickle.ReadBool(&iter, &form->preferred); - pickle.ReadBool(&iter, &form->blacklisted_by_user); - int64 date_created = 0; - pickle.ReadInt64(&iter, &date_created); - form->date_created = base::Time::FromTimeT(date_created); - forms->push_back(form); - } -} - -void PasswordStoreKWallet::ReadGURL(const Pickle& pickle, void** iter, - GURL* url) { - string url_string; - pickle.ReadString(iter, &url_string); - *url = GURL(url_string); -} - -bool PasswordStoreKWallet::CheckError() { - if (error_) { - LOG(ERROR) << "Failed to complete KWallet call: " << error_->message; - g_error_free(error_); - error_ = NULL; - return true; - } - return false; -} - -int PasswordStoreKWallet::WalletHandle() { - // Open the wallet. - int handle = kInvalidKWalletHandle; - dbus_g_proxy_call(proxy_, "open", &error_, - G_TYPE_STRING, wallet_name_.c_str(), // wallet - G_TYPE_INT64, 0LL, // wid - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - G_TYPE_INT, &handle, - G_TYPE_INVALID); - if (CheckError() || handle == kInvalidKWalletHandle) - return kInvalidKWalletHandle; - - // Check if our folder exists. - gboolean has_folder = false; - dbus_g_proxy_call(proxy_, "hasFolder", &error_, - G_TYPE_INT, handle, // handle - G_TYPE_STRING, kKWalletFolder, // folder - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - G_TYPE_BOOLEAN, &has_folder, - G_TYPE_INVALID); - if (CheckError()) - return kInvalidKWalletHandle; - - // Create it if it didn't. - if (!has_folder) { - gboolean success = false; - dbus_g_proxy_call(proxy_, "createFolder", &error_, - G_TYPE_INT, handle, // handle - G_TYPE_STRING, kKWalletFolder, // folder - G_TYPE_STRING, kAppId, // appid - G_TYPE_INVALID, - G_TYPE_BOOLEAN, &success, - G_TYPE_INVALID); - if (CheckError() || !success) - return kInvalidKWalletHandle; - } - - return handle; -} diff --git a/chrome/browser/password_manager/password_store_kwallet.h b/chrome/browser/password_manager/password_store_kwallet.h deleted file mode 100644 index f7835fe..0000000 --- a/chrome/browser/password_manager/password_store_kwallet.h +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (c) 2010 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. - -#ifndef CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_KWALLET_H_ -#define CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_KWALLET_H_ - -#include -#include - -#include -#include - -#include "base/lock.h" -#include "chrome/browser/password_manager/login_database.h" -#include "chrome/browser/password_manager/password_store.h" -#include "chrome/browser/webdata/web_data_service.h" -#include "webkit/glue/password_form.h" - -class Pickle; -class Profile; -class Task; - -class PasswordStoreKWallet : public PasswordStore { - public: - PasswordStoreKWallet(LoginDatabase* login_db, - Profile* profile, - WebDataService* web_data_service); - - bool Init(); - - private: - typedef std::vector PasswordFormList; - - virtual ~PasswordStoreKWallet(); - - // Implements PasswordStore interface. - virtual void AddLoginImpl(const webkit_glue::PasswordForm& form); - virtual void UpdateLoginImpl(const webkit_glue::PasswordForm& form); - virtual void RemoveLoginImpl(const webkit_glue::PasswordForm& form); - virtual void RemoveLoginsCreatedBetweenImpl(const base::Time& delete_begin, - const base::Time& delete_end); - virtual void GetLoginsImpl(GetLoginsRequest* request, - const webkit_glue::PasswordForm& form); - virtual void GetAutofillableLoginsImpl(GetLoginsRequest* request); - virtual void GetBlacklistLoginsImpl(GetLoginsRequest* request); - virtual bool FillAutofillableLogins( - std::vector* forms); - virtual bool FillBlacklistLogins( - std::vector* forms); - - // Initialization. - bool StartKWalletd(); - bool InitWallet(); - - // Reads a list of PasswordForms from the wallet that match the signon_realm. - void GetLoginsList(PasswordFormList* forms, - const std::string& signon_realm, - int wallet_handle); - - // Writes a list of PasswordForms to the wallet with the given signon_realm. - // Overwrites any existing list for this signon_realm. Removes the entry if - // |forms| is empty. - void SetLoginsList(const PasswordFormList& forms, - const std::string& signon_realm, - int wallet_handle); - - // Helper for FillAutofillableLogins() and FillBlacklistLogins(). - bool FillSomeLogins(bool autofillable, PasswordFormList* forms); - - // Checks if the last DBus call returned an error. If it did, logs the error - // message, frees it and returns true. - // This must be called after every DBus call. - bool CheckError(); - - // Opens the wallet and ensures that the "Chrome Form Data" folder exists. - // Returns kInvalidWalletHandle on error. - int WalletHandle(); - - // Compares two PasswordForms and returns true if they are the same. - // If |update_check| is false, we only check the fields that are checked by - // LoginDatabase::UpdateLogin() when updating logins; otherwise, we check the - // fields that are checked by LoginDatabase::RemoveLogin() for removing them. - static bool CompareForms(const webkit_glue::PasswordForm& a, - const webkit_glue::PasswordForm& b, - bool update_check); - - // Serializes a list of PasswordForms to be stored in the wallet. - static void SerializeValue(const PasswordFormList& forms, Pickle* pickle); - - // 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. - static void ReadGURL(const Pickle& pickle, void** iter, GURL* url); - - // In case the fields in the pickle ever change, version them so we can try to - // read old pickles. (Note: do not eat old pickles past the expiration date.) - static const int kPickleVersion = 0; - - // Name of the application - will appear in kwallet's dialogs. - static const char* kAppId; - // Name of the folder to store passwords in. - static const char* kKWalletFolder; - - // DBus stuff. - static const char* kKWalletServiceName; - static const char* kKWalletPath; - static const char* kKWalletInterface; - static const char* kKLauncherServiceName; - static const char* kKLauncherPath; - static const char* kKLauncherInterface; - - // Invalid handle returned by WalletHandle(). - static const int kInvalidKWalletHandle = -1; - - // Controls all access to kwallet DBus calls. - Lock kwallet_lock_; - - // Error from the last DBus call. NULL when there's no error. Freed and - // cleared by CheckError(). - GError* error_; - // Connection to the DBus session bus. - DBusGConnection* connection_; - // Proxy to the kwallet DBus service. - DBusGProxy* proxy_; - - // The name of the wallet we've opened. Set during Init(). - std::string wallet_name_; - - DISALLOW_COPY_AND_ASSIGN(PasswordStoreKWallet); -}; - -#endif // CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_KWALLET_H_ diff --git a/chrome/browser/password_manager/password_store_x.cc b/chrome/browser/password_manager/password_store_x.cc new file mode 100644 index 0000000..a9f7021 --- /dev/null +++ b/chrome/browser/password_manager/password_store_x.cc @@ -0,0 +1,231 @@ +// Copyright (c) 2010 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 "chrome/browser/password_manager/password_store_x.h" + +#include +#include + +#include "base/logging.h" +#include "base/stl_util-inl.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/password_manager/password_store_change.h" +#include "chrome/common/notification_service.h" + +using std::vector; +using webkit_glue::PasswordForm; + +PasswordStoreX::PasswordStoreX(LoginDatabase* login_db, + Profile* profile, + WebDataService* web_data_service, + NativeBackend* backend) + : PasswordStoreDefault(login_db, profile, web_data_service), + backend_(backend), migration_checked_(!backend), allow_fallback_(false) { +} + +PasswordStoreX::~PasswordStoreX() { +} + +void PasswordStoreX::AddLoginImpl(const PasswordForm& form) { + CheckMigration(); + if (use_native_backend() && backend_->AddLogin(form)) { + PasswordStoreChangeList changes; + changes.push_back(PasswordStoreChange(PasswordStoreChange::ADD, form)); + NotificationService::current()->Notify( + NotificationType::LOGINS_CHANGED, + NotificationService::AllSources(), + Details(&changes)); + allow_fallback_ = false; + } else if (allow_default_store()) { + PasswordStoreDefault::AddLoginImpl(form); + } +} + +void PasswordStoreX::UpdateLoginImpl(const PasswordForm& form) { + CheckMigration(); + if (use_native_backend() && backend_->UpdateLogin(form)) { + PasswordStoreChangeList changes; + changes.push_back(PasswordStoreChange(PasswordStoreChange::UPDATE, form)); + NotificationService::current()->Notify( + NotificationType::LOGINS_CHANGED, + NotificationService::AllSources(), + Details(&changes)); + allow_fallback_ = false; + } else if (allow_default_store()) { + PasswordStoreDefault::UpdateLoginImpl(form); + } +} + +void PasswordStoreX::RemoveLoginImpl(const PasswordForm& form) { + CheckMigration(); + if (use_native_backend() && backend_->RemoveLogin(form)) { + PasswordStoreChangeList changes; + changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, form)); + NotificationService::current()->Notify( + NotificationType::LOGINS_CHANGED, + NotificationService::AllSources(), + Details(&changes)); + allow_fallback_ = false; + } else if (allow_default_store()) { + PasswordStoreDefault::RemoveLoginImpl(form); + } +} + +void PasswordStoreX::RemoveLoginsCreatedBetweenImpl( + const base::Time& delete_begin, + const base::Time& delete_end) { + CheckMigration(); + vector forms; + if (use_native_backend() && + backend_->GetLoginsCreatedBetween(delete_begin, delete_end, &forms) && + backend_->RemoveLoginsCreatedBetween(delete_begin, delete_end)) { + PasswordStoreChangeList changes; + for (vector::const_iterator it = forms.begin(); + it != forms.end(); ++it) { + changes.push_back(PasswordStoreChange(PasswordStoreChange::REMOVE, + **it)); + } + NotificationService::current()->Notify( + NotificationType::LOGINS_CHANGED, + NotificationService::AllSources(), + Details(&changes)); + allow_fallback_ = false; + } else if (allow_default_store()) { + PasswordStoreDefault::RemoveLoginsCreatedBetweenImpl(delete_begin, + delete_end); + } + STLDeleteElements(&forms); +} + +void PasswordStoreX::GetLoginsImpl(GetLoginsRequest* request, + const PasswordForm& form) { + CheckMigration(); + vector forms; + if (use_native_backend() && backend_->GetLogins(form, &forms)) { + NotifyConsumer(request, forms); + allow_fallback_ = false; + } else if (allow_default_store()) { + PasswordStoreDefault::GetLoginsImpl(request, form); + } else { + // The consumer will be left hanging unless we reply. + NotifyConsumer(request, forms); + } +} + +void PasswordStoreX::GetAutofillableLoginsImpl(GetLoginsRequest* request) { + CheckMigration(); + vector forms; + if (use_native_backend() && backend_->GetAutofillableLogins(&forms)) { + NotifyConsumer(request, forms); + allow_fallback_ = false; + } else if (allow_default_store()) { + PasswordStoreDefault::GetAutofillableLoginsImpl(request); + } else { + // The consumer will be left hanging unless we reply. + NotifyConsumer(request, forms); + } +} + +void PasswordStoreX::GetBlacklistLoginsImpl(GetLoginsRequest* request) { + CheckMigration(); + vector forms; + if (use_native_backend() && backend_->GetBlacklistLogins(&forms)) { + NotifyConsumer(request, forms); + allow_fallback_ = false; + } else if (allow_default_store()) { + PasswordStoreDefault::GetBlacklistLoginsImpl(request); + } else { + // The consumer will be left hanging unless we reply. + NotifyConsumer(request, forms); + } +} + +bool PasswordStoreX::FillAutofillableLogins(vector* forms) { + CheckMigration(); + if (use_native_backend() && backend_->GetAutofillableLogins(forms)) { + allow_fallback_ = false; + return true; + } + if (allow_default_store()) + return PasswordStoreDefault::FillAutofillableLogins(forms); + return false; +} + +bool PasswordStoreX::FillBlacklistLogins(vector* forms) { + CheckMigration(); + if (use_native_backend() && backend_->GetBlacklistLogins(forms)) { + allow_fallback_ = false; + return true; + } + if (allow_default_store()) + return PasswordStoreDefault::FillBlacklistLogins(forms); + return false; +} + +void PasswordStoreX::CheckMigration() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + if (migration_checked_ || !backend_.get()) + return; + migration_checked_ = true; + ssize_t migrated = MigrateLogins(); + if (migrated > 0) { + LOG(INFO) << "Migrated " << migrated << " passwords to native store."; + } else if (migrated == 0) { + // As long as we are able to migrate some passwords, we know the native + // store is working. But if there is nothing to migrate, the "migration" + // can succeed even when the native store would fail. In this case we + // allow a later fallback to the default store. Once any later operation + // succeeds on the native store, we will no longer allow it. + allow_fallback_ = true; + } else { + LOG(WARNING) << "Native password store migration failed! " << + "Falling back on default (unencrypted) store."; + backend_.reset(NULL); + } +} + +bool PasswordStoreX::allow_default_store() { + if (allow_fallback_) { + LOG(WARNING) << "Native password store failed! " << + "Falling back on default (unencrypted) store."; + backend_.reset(NULL); + // Don't warn again. We'll use the default store because backend_ is NULL. + allow_fallback_ = false; + } + return !backend_.get(); +} + +ssize_t PasswordStoreX::MigrateLogins() { + DCHECK(backend_.get()); + vector forms; + bool ok = PasswordStoreDefault::FillAutofillableLogins(&forms) && + PasswordStoreDefault::FillBlacklistLogins(&forms); + if (ok) { + // We add all the passwords (and blacklist entries) to the native backend + // before attempting to remove any from the login database, to make sure we + // don't somehow end up with some of the passwords in one store and some in + // another. We'll always have at least one intact store this way. + for (size_t i = 0; i < forms.size(); ++i) { + if (!backend_->AddLogin(*forms[i])) { + ok = false; + break; + } + } + if (ok) { + for (size_t i = 0; i < forms.size(); ++i) { + // If even one of these calls to RemoveLoginImpl() succeeds, then we + // should prefer the native backend to the now-incomplete login + // database. Thus we want to return a success status even in the case + // where some fail. The only real problem with this is that we might + // leave passwords in the login database and never come back to clean + // them out if any of these calls do fail. + // TODO(mdm): Really we should just delete the login database file. + PasswordStoreDefault::RemoveLoginImpl(*forms[i]); + } + } + } + ssize_t result = ok ? forms.size() : -1; + STLDeleteElements(&forms); + return result; +} diff --git a/chrome/browser/password_manager/password_store_x.h b/chrome/browser/password_manager/password_store_x.h new file mode 100644 index 0000000..fd3fe42 --- /dev/null +++ b/chrome/browser/password_manager/password_store_x.h @@ -0,0 +1,107 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_X_H_ +#define CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_X_H_ + +#include + +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "chrome/browser/password_manager/password_store_default.h" + +class LoginDatabase; +class Profile; +class WebDataService; + +// PasswordStoreX is used on Linux and other non-Windows, non-Mac OS X +// operating systems. It uses a "native backend" to actually store the password +// data when such a backend is available, and otherwise falls back to using the +// login database like PasswordStoreDefault. It also handles automatically +// migrating password data to a native backend from the login database. +// +// There are currently native backends for GNOME Keyring and KWallet. +class PasswordStoreX : public PasswordStoreDefault { + public: + // NativeBackends more or less implement the PaswordStore interface, but + // with return values rather than implicit consumer notification. + class NativeBackend { + public: + typedef std::vector PasswordFormList; + + virtual ~NativeBackend() {} + + virtual bool Init() = 0; + + virtual bool AddLogin(const webkit_glue::PasswordForm& form) = 0; + virtual bool UpdateLogin(const webkit_glue::PasswordForm& form) = 0; + virtual bool RemoveLogin(const webkit_glue::PasswordForm& form) = 0; + virtual bool RemoveLoginsCreatedBetween(const base::Time& delete_begin, + const base::Time& delete_end) = 0; + virtual bool GetLogins(const webkit_glue::PasswordForm& form, + PasswordFormList* forms) = 0; + virtual bool GetLoginsCreatedBetween(const base::Time& get_begin, + const base::Time& get_end, + PasswordFormList* forms) = 0; + virtual bool GetAutofillableLogins(PasswordFormList* forms) = 0; + virtual bool GetBlacklistLogins(PasswordFormList* forms) = 0; + }; + + // Takes ownership of |login_db| and |backend|. |backend| may be NULL in which + // case this PasswordStoreX will act the same as PasswordStoreDefault. + PasswordStoreX(LoginDatabase* login_db, + Profile* profile, + WebDataService* web_data_service, + NativeBackend* backend); + + private: + friend class PasswordStoreXTest; + + virtual ~PasswordStoreX(); + + // Implements PasswordStore interface. + virtual void AddLoginImpl(const webkit_glue::PasswordForm& form); + virtual void UpdateLoginImpl(const webkit_glue::PasswordForm& form); + virtual void RemoveLoginImpl(const webkit_glue::PasswordForm& form); + virtual void RemoveLoginsCreatedBetweenImpl(const base::Time& delete_begin, + const base::Time& delete_end); + virtual void GetLoginsImpl(GetLoginsRequest* request, + const webkit_glue::PasswordForm& form); + virtual void GetAutofillableLoginsImpl(GetLoginsRequest* request); + virtual void GetBlacklistLoginsImpl(GetLoginsRequest* request); + virtual bool FillAutofillableLogins( + std::vector* forms); + virtual bool FillBlacklistLogins( + std::vector* forms); + + // Check to see whether migration is necessary, and perform it if so. + void CheckMigration(); + + // Return true if we should try using the native backend. + bool use_native_backend() { return !!backend_.get(); } + + // Return true if we can fall back on the default store, warning the first + // time we call it when falling back is necessary. See |allow_fallback_|. + bool allow_default_store(); + + // Synchronously migrates all the passwords stored in the login database to + // the native backend. If successful, the login database will be left with no + // stored passwords, and the number of passwords migrated will be returned. + // (This might be 0 if migration was not necessary.) Returns < 0 on failure. + ssize_t MigrateLogins(); + + // The native backend in use, or NULL if none. + scoped_ptr backend_; + // Whether we have already attempted migration to the native store. + bool migration_checked_; + // Whether we should allow falling back to the default store. If there is + // nothing to migrate, then the first attempt to use the native store will + // be the first time we try to use it and we should allow falling back. If + // we have migrated successfully, then we do not allow falling back. + bool allow_fallback_; + + DISALLOW_COPY_AND_ASSIGN(PasswordStoreX); +}; + +#endif // CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_X_H_ diff --git a/chrome/browser/password_manager/password_store_x_unittest.cc b/chrome/browser/password_manager/password_store_x_unittest.cc new file mode 100644 index 0000000..1b4708c --- /dev/null +++ b/chrome/browser/password_manager/password_store_x_unittest.cc @@ -0,0 +1,771 @@ +// Copyright (c) 2010 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 "base/basictypes.h" +#include "base/scoped_temp_dir.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "base/time.h" +#include "base/waitable_event.h" +#include "chrome/browser/password_manager/password_form_data.h" +#include "chrome/browser/password_manager/password_store_change.h" +#include "chrome/browser/password_manager/password_store_x.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/testing_profile.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::WaitableEvent; +using testing::_; +using testing::DoAll; +using testing::ElementsAreArray; +using testing::Pointee; +using testing::Property; +using testing::WithArg; +using webkit_glue::PasswordForm; + +namespace { + +class MockPasswordStoreConsumer : public PasswordStoreConsumer { + public: + MOCK_METHOD2(OnPasswordStoreRequestDone, + void(int, const std::vector&)); +}; + +class MockWebDataServiceConsumer : public WebDataServiceConsumer { + public: + MOCK_METHOD2(OnWebDataServiceRequestDone, void(WebDataService::Handle, + const WDTypedResult*)); +}; + +class SignalingTask : public Task { + public: + explicit SignalingTask(WaitableEvent* event) : event_(event) { + } + virtual void Run() { + event_->Signal(); + } + private: + WaitableEvent* event_; +}; + +class MockNotificationObserver : public NotificationObserver { + public: + MOCK_METHOD3(Observe, void(NotificationType, + const NotificationSource&, + const NotificationDetails&)); +}; + +// This class will add and remove a mock notification observer from +// the DB thread. +class DBThreadObserverHelper + : public base::RefCountedThreadSafe { + public: + DBThreadObserverHelper() : done_event_(true, false) {} + + void Init() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + ChromeThread::PostTask( + ChromeThread::DB, + FROM_HERE, + NewRunnableMethod(this, &DBThreadObserverHelper::AddObserverTask)); + done_event_.Wait(); + } + + virtual ~DBThreadObserverHelper() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + registrar_.RemoveAll(); + } + + MockNotificationObserver& observer() { + return observer_; + } + + protected: + friend class base::RefCountedThreadSafe; + + void AddObserverTask() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + registrar_.Add(&observer_, + NotificationType::LOGINS_CHANGED, + NotificationService::AllSources()); + done_event_.Signal(); + } + + WaitableEvent done_event_; + NotificationRegistrar registrar_; + MockNotificationObserver observer_; +}; + +class FailingBackend : public PasswordStoreX::NativeBackend { + public: + virtual bool Init() { return true; } + + virtual bool AddLogin(const PasswordForm& form) { return false; } + virtual bool UpdateLogin(const PasswordForm& form) { return false; } + virtual bool RemoveLogin(const PasswordForm& form) { return false; } + + virtual bool RemoveLoginsCreatedBetween(const base::Time& delete_begin, + const base::Time& delete_end) { + return false; + } + + virtual bool GetLogins(const PasswordForm& form, PasswordFormList* forms) { + return false; + } + + virtual bool GetLoginsCreatedBetween(const base::Time& get_begin, + const base::Time& get_end, + PasswordFormList* forms) { + return false; + } + + virtual bool GetAutofillableLogins(PasswordFormList* forms) { return false; } + virtual bool GetBlacklistLogins(PasswordFormList* forms) { return false; } +}; + +class MockBackend : public PasswordStoreX::NativeBackend { + public: + virtual bool Init() { return true; } + + virtual bool AddLogin(const PasswordForm& form) { + all_forms_.push_back(form); + return true; + } + + virtual bool UpdateLogin(const PasswordForm& form) { + for (size_t i = 0; i < all_forms_.size(); ++i) + if (CompareForms(all_forms_[i], form, true)) + all_forms_[i] = form; + return true; + } + + virtual bool RemoveLogin(const PasswordForm& form) { + for (size_t i = 0; i < all_forms_.size(); ++i) + if (CompareForms(all_forms_[i], form, false)) + erase(i--); + return true; + } + + virtual bool RemoveLoginsCreatedBetween(const base::Time& delete_begin, + const base::Time& delete_end) { + for (size_t i = 0; i < all_forms_.size(); ++i) { + if (delete_begin <= all_forms_[i].date_created && + (delete_end.is_null() || all_forms_[i].date_created < delete_end)) + erase(i--); + } + return true; + } + + virtual bool GetLogins(const PasswordForm& form, PasswordFormList* forms) { + for (size_t i = 0; i < all_forms_.size(); ++i) + if (all_forms_[i].signon_realm == form.signon_realm) + forms->push_back(new PasswordForm(all_forms_[i])); + return true; + } + + virtual bool GetLoginsCreatedBetween(const base::Time& get_begin, + const base::Time& get_end, + PasswordFormList* forms) { + for (size_t i = 0; i < all_forms_.size(); ++i) + if (get_begin <= all_forms_[i].date_created && + (get_end.is_null() || all_forms_[i].date_created < get_end)) + forms->push_back(new PasswordForm(all_forms_[i])); + return true; + } + + virtual bool GetAutofillableLogins(PasswordFormList* forms) { + for (size_t i = 0; i < all_forms_.size(); ++i) + if (!all_forms_[i].blacklisted_by_user) + forms->push_back(new PasswordForm(all_forms_[i])); + return true; + } + + virtual bool GetBlacklistLogins(PasswordFormList* forms) { + for (size_t i = 0; i < all_forms_.size(); ++i) + if (all_forms_[i].blacklisted_by_user) + forms->push_back(new PasswordForm(all_forms_[i])); + return true; + } + + private: + void erase(size_t index) { + if (index < all_forms_.size() - 1) + all_forms_[index] = all_forms_[all_forms_.size() - 1]; + all_forms_.pop_back(); + } + + bool CompareForms(const PasswordForm& a, const PasswordForm& b, bool update) { + // An update check doesn't care about the submit element. + if (!update && a.submit_element != b.submit_element) + return false; + return a.origin == b.origin && + a.password_element == b.password_element && + a.signon_realm == b.signon_realm && + a.username_element == b.username_element && + a.username_value == b.username_value; + } + + std::vector all_forms_; +}; + +class MockLoginDatabaseReturn { + public: + MOCK_METHOD1(OnLoginDatabaseQueryDone, + void(const std::vector&)); +}; + +class LoginDatabaseQueryTask : public Task { + public: + LoginDatabaseQueryTask(LoginDatabase* login_db, + bool autofillable, + MockLoginDatabaseReturn* mock_return) + : login_db_(login_db), autofillable_(autofillable), + mock_return_(mock_return) { + } + + virtual void Run() { + std::vector forms; + if (autofillable_) + login_db_->GetAutofillableLogins(&forms); + else + login_db_->GetBlacklistLogins(&forms); + mock_return_->OnLoginDatabaseQueryDone(forms); + } + + private: + LoginDatabase* login_db_; + bool autofillable_; + MockLoginDatabaseReturn* mock_return_; +}; + +const PasswordFormData g_autofillable_data[] = { + { PasswordForm::SCHEME_HTML, + "http://foo.example.com", + "http://foo.example.com/origin", + "http://foo.example.com/action", + L"submit_element", + L"username_element", + L"password_element", + L"username_value", + L"password_value", + true, false, 1 }, + { PasswordForm::SCHEME_HTML, + "http://bar.example.com", + "http://bar.example.com/origin", + "http://bar.example.com/action", + L"submit_element", + L"username_element", + L"password_element", + L"username_value", + L"password_value", + true, false, 2 }, + { PasswordForm::SCHEME_HTML, + "http://baz.example.com", + "http://baz.example.com/origin", + "http://baz.example.com/action", + L"submit_element", + L"username_element", + L"password_element", + L"username_value", + L"password_value", + true, false, 3 }, +}; +const PasswordFormData g_blacklisted_data[] = { + { PasswordForm::SCHEME_HTML, + "http://blacklisted.example.com", + "http://blacklisted.example.com/origin", + "http://blacklisted.example.com/action", + L"submit_element", + L"username_element", + L"password_element", + NULL, + NULL, + false, false, 1 }, + { PasswordForm::SCHEME_HTML, + "http://blacklisted2.example.com", + "http://blacklisted2.example.com/origin", + "http://blacklisted2.example.com/action", + L"submit_element", + L"username_element", + L"password_element", + NULL, + NULL, + false, false, 2 }, +}; + +} // anonymous namespace + +typedef std::vector VectorOfForms; + +// LoginDatabase 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 { + void RetainCallee(LoginDatabase*) {} + void ReleaseCallee(LoginDatabase*) {} +}; + +enum BackendType { + NO_BACKEND, + FAILING_BACKEND, + WORKING_BACKEND +}; + +class PasswordStoreXTest : public testing::TestWithParam { + protected: + PasswordStoreXTest() + : ui_thread_(ChromeThread::UI, &message_loop_), + db_thread_(ChromeThread::DB) { + } + + virtual void SetUp() { + ASSERT_TRUE(db_thread_.Start()); + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + profile_.reset(new TestingProfile()); + + login_db_.reset(new LoginDatabase()); + ASSERT_TRUE(login_db_->Init(temp_dir_.path().Append( + FILE_PATH_LITERAL("login_test")))); + + wds_ = new WebDataService(); + ASSERT_TRUE(wds_->Init(temp_dir_.path())); + } + + virtual void TearDown() { + wds_->Shutdown(); + MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask); + MessageLoop::current()->Run(); + db_thread_.Stop(); + } + + PasswordStoreX::NativeBackend* GetBackend() { + switch (GetParam()) { + case FAILING_BACKEND: + return new FailingBackend(); + case WORKING_BACKEND: + return new MockBackend(); + default: + return NULL; + } + } + + MessageLoopForUI message_loop_; + ChromeThread ui_thread_; + ChromeThread db_thread_; // PasswordStore, WDS schedule work on this thread. + + scoped_ptr login_db_; + scoped_ptr profile_; + scoped_refptr wds_; + ScopedTempDir temp_dir_; +}; + +ACTION(STLDeleteElements0) { + STLDeleteContainerPointers(arg0.begin(), arg0.end()); +} + +ACTION(QuitUIMessageLoop) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + MessageLoop::current()->Quit(); +} + +MATCHER(EmptyWDResult, "") { + return static_cast >*>( + arg)->GetValue().empty(); +} + +TEST_P(PasswordStoreXTest, WDSMigration) { + VectorOfForms expected_autofillable; + for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(g_autofillable_data); ++i) { + expected_autofillable.push_back( + CreatePasswordFormFromData(g_autofillable_data[i])); + } + + VectorOfForms expected_blacklisted; + for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(g_blacklisted_data); ++i) { + expected_blacklisted.push_back( + CreatePasswordFormFromData(g_blacklisted_data[i])); + } + + // Populate the WDS with logins that should be migrated. + for (VectorOfForms::iterator it = expected_autofillable.begin(); + it != expected_autofillable.end(); ++it) { + wds_->AddLogin(**it); + } + for (VectorOfForms::iterator it = expected_blacklisted.begin(); + it != expected_blacklisted.end(); ++it) { + wds_->AddLogin(**it); + } + + // The WDS schedules tasks to run on the DB thread so we schedule yet another + // task to notify us that it's safe to carry on with the test. + WaitableEvent done(false, false); + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); + + // Initializing the PasswordStore should trigger a migration. + scoped_refptr store( + new PasswordStoreX(login_db_.release(), + profile_.get(), + wds_.get(), + GetBackend())); + store->Init(); + + // Check that the migration preference has not been initialized. + ASSERT_TRUE(NULL == profile_->GetPrefs()->FindPreference( + prefs::kLoginDatabaseMigrated)); + + // Again, the WDS schedules tasks to run on the DB thread, so schedule a task + // to signal us when it is safe to continue. + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); + + // Let the WDS callbacks proceed so the logins can be migrated. + MessageLoop::current()->RunAllPending(); + + MockPasswordStoreConsumer consumer; + + // Make sure we quit the MessageLoop even if the test fails. + ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)) + .WillByDefault(QuitUIMessageLoop()); + + // The autofillable forms should have been migrated from the WDS to the login + // database. + EXPECT_CALL(consumer, + OnPasswordStoreRequestDone(_, + ContainsAllPasswordForms(expected_autofillable))) + .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); + + store->GetAutofillableLogins(&consumer); + MessageLoop::current()->Run(); + + // The blacklisted forms should have been migrated from the WDS to the login + // database. + EXPECT_CALL(consumer, + OnPasswordStoreRequestDone(_, + ContainsAllPasswordForms(expected_blacklisted))) + .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); + + store->GetBlacklistLogins(&consumer); + MessageLoop::current()->Run(); + + // Check that the migration updated the migrated preference. + ASSERT_TRUE(profile_->GetPrefs()->GetBoolean(prefs::kLoginDatabaseMigrated)); + + MockWebDataServiceConsumer wds_consumer; + + // No autofillable logins should be left in the WDS. + EXPECT_CALL(wds_consumer, + OnWebDataServiceRequestDone(_, EmptyWDResult())); + + wds_->GetAutofillableLogins(&wds_consumer); + + // Wait for the WDS methods to execute on the DB thread. + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); + + // Handle the callback from the WDS. + MessageLoop::current()->RunAllPending(); + + // Likewise, no blacklisted logins should be left in the WDS. + EXPECT_CALL(wds_consumer, + OnWebDataServiceRequestDone(_, EmptyWDResult())); + + wds_->GetBlacklistLogins(&wds_consumer); + + // Wait for the WDS methods to execute on the DB thread. + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); + + // Handle the callback from the WDS. + MessageLoop::current()->RunAllPending(); + + STLDeleteElements(&expected_autofillable); + STLDeleteElements(&expected_blacklisted); +} + +TEST_P(PasswordStoreXTest, WDSMigrationAlreadyDone) { + PasswordFormData wds_data[] = { + { PasswordForm::SCHEME_HTML, + "http://bar.example.com", + "http://bar.example.com/origin", + "http://bar.example.com/action", + L"submit_element", + L"username_element", + L"password_element", + L"username_value", + L"password_value", + true, false, 1 }, + }; + + VectorOfForms unexpected_autofillable; + for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(wds_data); ++i) { + unexpected_autofillable.push_back( + CreatePasswordFormFromData(wds_data[i])); + } + + // Populate the WDS with logins that should be migrated. + for (VectorOfForms::iterator it = unexpected_autofillable.begin(); + it != unexpected_autofillable.end(); ++it) { + wds_->AddLogin(**it); + } + + // The WDS schedules tasks to run on the DB thread so we schedule yet another + // task to notify us that it's safe to carry on with the test. + WaitableEvent done(false, false); + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); + + // Prentend that the migration has already taken place. + profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated, + true); + + // Initializing the PasswordStore shouldn't trigger a migration. + scoped_refptr store( + new PasswordStoreX(login_db_.release(), + profile_.get(), + wds_.get(), + GetBackend())); + store->Init(); + + MockPasswordStoreConsumer consumer; + // Make sure we quit the MessageLoop even if the test fails. + ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)) + .WillByDefault(QuitUIMessageLoop()); + + // No forms should be migrated. + VectorOfForms empty; + EXPECT_CALL(consumer, + OnPasswordStoreRequestDone(_, + ContainsAllPasswordForms(empty))) + .WillOnce(QuitUIMessageLoop()); + + store->GetAutofillableLogins(&consumer); + MessageLoop::current()->Run(); + + STLDeleteElements(&unexpected_autofillable); +} + +TEST_P(PasswordStoreXTest, Notifications) { + // Pretend that the migration has already taken place. + profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated, + true); + + // Initializing the PasswordStore shouldn't trigger a migration. + scoped_refptr store( + new PasswordStoreX(login_db_.release(), + profile_.get(), + wds_.get(), + GetBackend())); + store->Init(); + + PasswordFormData form_data = + { PasswordForm::SCHEME_HTML, + "http://bar.example.com", + "http://bar.example.com/origin", + "http://bar.example.com/action", + L"submit_element", + L"username_element", + L"password_element", + L"username_value", + L"password_value", + true, false, 1 }; + scoped_ptr form(CreatePasswordFormFromData(form_data)); + + scoped_refptr helper = new DBThreadObserverHelper; + helper->Init(); + + const PasswordStoreChange expected_add_changes[] = { + PasswordStoreChange(PasswordStoreChange::ADD, *form), + }; + + EXPECT_CALL(helper->observer(), + Observe(NotificationType(NotificationType::LOGINS_CHANGED), + NotificationService::AllSources(), + Property(&Details::ptr, + Pointee(ElementsAreArray( + expected_add_changes))))); + + // Adding a login should trigger a notification. + store->AddLogin(*form); + + // The PasswordStore schedules tasks to run on the DB thread so we schedule + // yet another task to notify us that it's safe to carry on with the test. + WaitableEvent done(false, false); + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); + + // Change the password. + form->password_value = WideToUTF16(L"a different password"); + + const PasswordStoreChange expected_update_changes[] = { + PasswordStoreChange(PasswordStoreChange::UPDATE, *form), + }; + + EXPECT_CALL(helper->observer(), + Observe(NotificationType(NotificationType::LOGINS_CHANGED), + NotificationService::AllSources(), + Property(&Details::ptr, + Pointee(ElementsAreArray( + expected_update_changes))))); + + // Updating the login with the new password should trigger a notification. + store->UpdateLogin(*form); + + // Wait for PasswordStore to send the notification. + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); + + const PasswordStoreChange expected_delete_changes[] = { + PasswordStoreChange(PasswordStoreChange::REMOVE, *form), + }; + + EXPECT_CALL(helper->observer(), + Observe(NotificationType(NotificationType::LOGINS_CHANGED), + NotificationService::AllSources(), + Property(&Details::ptr, + Pointee(ElementsAreArray( + expected_delete_changes))))); + + // Deleting the login should trigger a notification. + store->RemoveLogin(*form); + + // Wait for PasswordStore to send the notification. + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); +} + +TEST_P(PasswordStoreXTest, NativeMigration) { + VectorOfForms expected_autofillable; + for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(g_autofillable_data); ++i) { + expected_autofillable.push_back( + CreatePasswordFormFromData(g_autofillable_data[i])); + } + + VectorOfForms expected_blacklisted; + for (unsigned int i = 0; i < ARRAYSIZE_UNSAFE(g_blacklisted_data); ++i) { + expected_blacklisted.push_back( + CreatePasswordFormFromData(g_blacklisted_data[i])); + } + + LoginDatabase* login_db = login_db_.get(); + + // Populate the login DB with logins that should be migrated. + for (VectorOfForms::iterator it = expected_autofillable.begin(); + it != expected_autofillable.end(); ++it) { + ChromeThread::PostTask(ChromeThread::DB, + FROM_HERE, + NewRunnableMethod(login_db, + &LoginDatabase::AddLogin, + **it)); + } + for (VectorOfForms::iterator it = expected_blacklisted.begin(); + it != expected_blacklisted.end(); ++it) { + ChromeThread::PostTask(ChromeThread::DB, + FROM_HERE, + NewRunnableMethod(login_db, + &LoginDatabase::AddLogin, + **it)); + } + + // Schedule another task on the DB thread to notify us that it's safe to + // carry on with the test. + WaitableEvent done(false, false); + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); + + // Pretend that the WDS migration has already taken place. + profile_->GetPrefs()->RegisterBooleanPref(prefs::kLoginDatabaseMigrated, + true); + + // Initializing the PasswordStore shouldn't trigger a native migration (yet). + scoped_refptr store( + new PasswordStoreX(login_db_.release(), + profile_.get(), + wds_.get(), + GetBackend())); + store->Init(); + + MockPasswordStoreConsumer consumer; + + // Make sure we quit the MessageLoop even if the test fails. + ON_CALL(consumer, OnPasswordStoreRequestDone(_, _)) + .WillByDefault(QuitUIMessageLoop()); + + // The autofillable forms should have been migrated to the native backend. + EXPECT_CALL(consumer, + OnPasswordStoreRequestDone(_, + ContainsAllPasswordForms(expected_autofillable))) + .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); + + store->GetAutofillableLogins(&consumer); + MessageLoop::current()->Run(); + + // The blacklisted forms should have been migrated to the native backend. + EXPECT_CALL(consumer, + OnPasswordStoreRequestDone(_, + ContainsAllPasswordForms(expected_blacklisted))) + .WillOnce(DoAll(WithArg<1>(STLDeleteElements0()), QuitUIMessageLoop())); + + store->GetBlacklistLogins(&consumer); + MessageLoop::current()->Run(); + + VectorOfForms empty; + MockLoginDatabaseReturn ld_return; + + if (GetParam() == WORKING_BACKEND) { + // No autofillable logins should be left in the login DB. + EXPECT_CALL(ld_return, + OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty))); + } else { + // The autofillable logins should still be in the login DB. + EXPECT_CALL(ld_return, + OnLoginDatabaseQueryDone( + ContainsAllPasswordForms(expected_autofillable))) + .WillOnce(WithArg<0>(STLDeleteElements0())); + } + + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, + new LoginDatabaseQueryTask(login_db, true, &ld_return)); + + // Wait for the login DB methods to execute on the DB thread. + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); + + if (GetParam() == WORKING_BACKEND) { + // Likewise, no blacklisted logins should be left in the login DB. + EXPECT_CALL(ld_return, + OnLoginDatabaseQueryDone(ContainsAllPasswordForms(empty))); + } else { + // The blacklisted logins should still be in the login DB. + EXPECT_CALL(ld_return, + OnLoginDatabaseQueryDone( + ContainsAllPasswordForms(expected_blacklisted))) + .WillOnce(WithArg<0>(STLDeleteElements0())); + } + + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, + new LoginDatabaseQueryTask(login_db, false, &ld_return)); + + // Wait for the login DB methods to execute on the DB thread. + ChromeThread::PostTask(ChromeThread::DB, FROM_HERE, new SignalingTask(&done)); + done.Wait(); + + STLDeleteElements(&expected_autofillable); + STLDeleteElements(&expected_blacklisted); +} + +INSTANTIATE_TEST_CASE_P(NoBackend, + PasswordStoreXTest, + testing::Values(NO_BACKEND)); +INSTANTIATE_TEST_CASE_P(FailingBackend, + PasswordStoreXTest, + testing::Values(FAILING_BACKEND)); +INSTANTIATE_TEST_CASE_P(WorkingBackend, + PasswordStoreXTest, + testing::Values(WORKING_BACKEND)); -- cgit v1.1