diff options
author | mdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-16 21:33:16 +0000 |
---|---|---|
committer | mdm@chromium.org <mdm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-16 21:33:16 +0000 |
commit | 7f58b02555f53658403cc2639a944a5b25b28fb8 (patch) | |
tree | 2d8e637dcd64064edb4a0ce2f410d47c9db108bc | |
parent | 2307f147f598b97e3aa615738c7c74228d21efed (diff) | |
download | chromium_src-7f58b02555f53658403cc2639a944a5b25b28fb8.zip chromium_src-7f58b02555f53658403cc2639a944a5b25b28fb8.tar.gz chromium_src-7f58b02555f53658403cc2639a944a5b25b28fb8.tar.bz2 |
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
-rw-r--r-- | chrome/browser/password_manager/native_backend_gnome_x.cc (renamed from chrome/browser/password_manager/password_store_gnome.cc) | 253 | ||||
-rw-r--r-- | chrome/browser/password_manager/native_backend_gnome_x.h | 62 | ||||
-rw-r--r-- | chrome/browser/password_manager/native_backend_kwallet_x.cc (renamed from chrome/browser/password_manager/password_store_kwallet.cc) | 283 | ||||
-rw-r--r-- | chrome/browser/password_manager/native_backend_kwallet_x.h (renamed from chrome/browser/password_manager/password_store_kwallet.h) | 92 | ||||
-rw-r--r-- | chrome/browser/password_manager/password_store_default.cc | 4 | ||||
-rw-r--r-- | chrome/browser/password_manager/password_store_gnome.h | 69 | ||||
-rw-r--r-- | chrome/browser/password_manager/password_store_x.cc | 231 | ||||
-rw-r--r-- | chrome/browser/password_manager/password_store_x.h | 107 | ||||
-rw-r--r-- | chrome/browser/password_manager/password_store_x_unittest.cc | 771 | ||||
-rwxr-xr-x | chrome/chrome_browser.gypi | 40 | ||||
-rwxr-xr-x | chrome/chrome_tests.gypi | 1 |
11 files changed, 1524 insertions, 389 deletions
diff --git a/chrome/browser/password_manager/password_store_gnome.cc b/chrome/browser/password_manager/native_backend_gnome_x.cc index 7495815..29febc0 100644 --- a/chrome/browser/password_manager/password_store_gnome.cc +++ b/chrome/browser/password_manager/native_backend_gnome_x.cc @@ -2,7 +2,7 @@ // 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" +#include "chrome/browser/password_manager/native_backend_gnome_x.h" #if defined(DLOPEN_GNOME_KEYRING) #include <dlfcn.h> @@ -13,13 +13,12 @@ #include "base/logging.h" #include "base/string_util.h" -#include "base/task.h" #include "base/time.h" #include "base/utf_string_conversions.h" +#include "chrome/browser/chrome_thread.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 @@ -136,7 +135,7 @@ bool LoadGnomeKeyring() { #define GNOME_KEYRING_APPLICATION_CHROME "chrome" // Schema is analagous to the fields in PasswordForm. -const GnomeKeyringPasswordSchema PasswordStoreGnome::kGnomeSchema = { +const GnomeKeyringPasswordSchema NativeBackendGnome::kGnomeSchema = { GNOME_KEYRING_ITEM_GENERIC_SECRET, { { "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, { "action_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING }, @@ -156,26 +155,47 @@ const GnomeKeyringPasswordSchema PasswordStoreGnome::kGnomeSchema = { } }; -PasswordStoreGnome::PasswordStoreGnome(LoginDatabase* login_db, - Profile* profile, - WebDataService* web_data_service) { +NativeBackendGnome::NativeBackendGnome() { } -PasswordStoreGnome::~PasswordStoreGnome() { +NativeBackendGnome::~NativeBackendGnome() { } -bool PasswordStoreGnome::Init() { - return PasswordStore::Init() && - LoadGnomeKeyring() && - gnome_keyring_is_available(); +bool NativeBackendGnome::Init() { + return LoadGnomeKeyring() && gnome_keyring_is_available(); } -void PasswordStoreGnome::AddLoginImpl(const PasswordForm& form) { +bool NativeBackendGnome::AddLogin(const PasswordForm& form) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); - AddLoginHelper(form, base::Time::Now()); + 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; } -void PasswordStoreGnome::UpdateLoginImpl(const PasswordForm& form) { +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 @@ -201,31 +221,35 @@ void PasswordStoreGnome::UpdateLoginImpl(const PasswordForm& form) { "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, GNOME_KEYRING_APPLICATION_CHROME, NULL); - vector<PasswordForm*> 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 { + 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; } -void PasswordStoreGnome::RemoveLoginImpl(const PasswordForm& form) { +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( @@ -241,43 +265,35 @@ void PasswordStoreGnome::RemoveLoginImpl(const PasswordForm& form) { if (result != GNOME_KEYRING_RESULT_OK) { LOG(ERROR) << "Keyring delete failed: " << gnome_keyring_result_to_message(result); + return false; } + return true; } -void PasswordStoreGnome::RemoveLoginsCreatedBetweenImpl( +bool NativeBackendGnome::RemoveLoginsCreatedBetween( 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<PasswordForm*> 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]; + 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; } - } else if (result != GNOME_KEYRING_RESULT_NO_MATCH) { - LOG(ERROR) << "Keyring find failed: " - << gnome_keyring_result_to_message(result); + delete forms[i]; } + return ok; } -void PasswordStoreGnome::GetLoginsImpl(GetLoginsRequest* request, - const PasswordForm& form) { +bool NativeBackendGnome::GetLogins(const PasswordForm& form, + PasswordFormList* forms) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); GList* found = NULL; // Search gnome keyring for matching passwords. @@ -289,97 +305,96 @@ void PasswordStoreGnome::GetLoginsImpl(GetLoginsRequest* request, "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING, GNOME_KEYRING_APPLICATION_CHROME, NULL); - vector<PasswordForm*> forms; - if (result == GNOME_KEYRING_RESULT_OK) { - FillFormVector(found, &forms); - } else if (result != GNOME_KEYRING_RESULT_NO_MATCH) { + 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; } - NotifyConsumer(request, forms); + ConvertFormList(found, forms); + return true; } -void PasswordStoreGnome::GetAutofillableLoginsImpl( - GetLoginsRequest* request) { - std::vector<PasswordForm*> forms; - FillAutofillableLogins(&forms); - NotifyConsumer(request, forms); -} +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]; + } + } -void PasswordStoreGnome::GetBlacklistLoginsImpl( - GetLoginsRequest* request) { - std::vector<PasswordForm*> forms; - FillBlacklistLogins(&forms); - NotifyConsumer(request, forms); + return true; } -bool PasswordStoreGnome::FillAutofillableLogins( - std::vector<PasswordForm*>* forms) { - return FillSomeLogins(true, forms); +bool NativeBackendGnome::GetAutofillableLogins(PasswordFormList* forms) { + return GetLoginsList(forms, true); } -bool PasswordStoreGnome::FillBlacklistLogins( - std::vector<PasswordForm*>* forms) { - return FillSomeLogins(false, forms); +bool NativeBackendGnome::GetBlacklistLogins(PasswordFormList* forms) { + return GetLoginsList(forms, false); } -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, +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 save failed: " + LOG(ERROR) << "Keyring find failed: " << gnome_keyring_result_to_message(result); return false; } + ConvertFormList(found, forms); return true; } -bool PasswordStoreGnome::FillSomeLogins( - bool autofillable, - std::vector<PasswordForm*>* forms) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); +bool NativeBackendGnome::GetAllLogins(PasswordFormList* forms) { GList* found = NULL; - uint32_t blacklisted_by_user = !autofillable; - // Search gnome keyring for matching passwords. + // 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, - "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) { + 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 PasswordStoreGnome::FillFormVector(GList* found, - std::vector<PasswordForm*>* forms) { +void NativeBackendGnome::ConvertFormList(GList* found, + PasswordFormList* forms) { GList* element = g_list_first(found); while (element != NULL) { GnomeKeyringFound* data = static_cast<GnomeKeyringFound*>(element->data); 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 <gnome-keyring.h> +} + +#include <vector> + +#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/password_store_kwallet.cc b/chrome/browser/password_manager/native_backend_kwallet_x.cc index 26f0ab9..501ce1f 100644 --- a/chrome/browser/password_manager/password_store_kwallet.cc +++ b/chrome/browser/password_manager/native_backend_kwallet_x.cc @@ -2,16 +2,15 @@ // 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 "chrome/browser/password_manager/native_backend_kwallet_x.h" #include <sstream> #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" +#include "chrome/browser/chrome_thread.h" using std::string; using std::vector; @@ -19,31 +18,28 @@ 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) +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) { } -PasswordStoreKWallet::~PasswordStoreKWallet() { - if (proxy_) { +NativeBackendKWallet::~NativeBackendKWallet() { + if (proxy_) g_object_unref(proxy_); - } } -bool PasswordStoreKWallet::Init() { +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()) @@ -64,7 +60,7 @@ bool PasswordStoreKWallet::Init() { return true; } -bool PasswordStoreKWallet::StartKWalletd() { +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 = @@ -100,7 +96,7 @@ bool PasswordStoreKWallet::StartKWalletd() { return true; } -bool PasswordStoreKWallet::InitWallet() { +bool NativeBackendKWallet::InitWallet() { // Make a proxy to KWallet. proxy_ = dbus_g_proxy_new_for_name(connection_, kKWalletServiceName, kKWalletPath, kKWalletInterface); @@ -129,26 +125,25 @@ bool PasswordStoreKWallet::InitWallet() { return true; } -void PasswordStoreKWallet::AddLoginImpl(const PasswordForm& form) { - AutoLock l(kwallet_lock_); +bool NativeBackendKWallet::AddLogin(const PasswordForm& form) { int wallet_handle = WalletHandle(); if (wallet_handle == kInvalidKWalletHandle) - return; + return false; PasswordFormList forms; GetLoginsList(&forms, form.signon_realm, wallet_handle); forms.push_back(new PasswordForm(form)); - SetLoginsList(forms, form.signon_realm, wallet_handle); + bool ok = SetLoginsList(forms, form.signon_realm, wallet_handle); STLDeleteElements(&forms); + return ok; } -void PasswordStoreKWallet::UpdateLoginImpl(const PasswordForm& form) { - AutoLock l(kwallet_lock_); +bool NativeBackendKWallet::UpdateLogin(const PasswordForm& form) { int wallet_handle = WalletHandle(); if (wallet_handle == kInvalidKWalletHandle) - return; + return false; PasswordFormList forms; GetLoginsList(&forms, form.signon_realm, wallet_handle); @@ -158,16 +153,16 @@ void PasswordStoreKWallet::UpdateLoginImpl(const PasswordForm& form) { *forms[i] = form; } - SetLoginsList(forms, form.signon_realm, wallet_handle); + bool ok = SetLoginsList(forms, form.signon_realm, wallet_handle); STLDeleteElements(&forms); + return ok; } -void PasswordStoreKWallet::RemoveLoginImpl(const PasswordForm& form) { - AutoLock l(kwallet_lock_); +bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) { int wallet_handle = WalletHandle(); if (wallet_handle == kInvalidKWalletHandle) - return; + return false; PasswordFormList all_forms; GetLoginsList(&all_forms, form.signon_realm, wallet_handle); @@ -181,18 +176,19 @@ void PasswordStoreKWallet::RemoveLoginImpl(const PasswordForm& form) { kept_forms.push_back(all_forms[i]); } - // Update the entry in the wallet. - SetLoginsList(kept_forms, form.signon_realm, wallet_handle); + // Update the entry in the wallet, possibly deleting it. + bool ok = SetLoginsList(kept_forms, form.signon_realm, wallet_handle); + STLDeleteElements(&kept_forms); + return ok; } -void PasswordStoreKWallet::RemoveLoginsCreatedBetweenImpl( +bool NativeBackendKWallet::RemoveLoginsCreatedBetween( const base::Time& delete_begin, const base::Time& delete_end) { - AutoLock l(kwallet_lock_); int wallet_handle = WalletHandle(); if (wallet_handle == kInvalidKWalletHandle) - return; + return false; // We could probably also use readEntryList here. char** realm_list = NULL; @@ -204,8 +200,9 @@ void PasswordStoreKWallet::RemoveLoginsCreatedBetweenImpl( G_TYPE_STRV, &realm_list, G_TYPE_INVALID); if (CheckError()) - return; + return false; + bool ok = true; for (char** realm = realm_list; *realm; ++realm) { GArray* byte_array = NULL; dbus_g_proxy_call(proxy_, "readEntry", &error_, @@ -237,49 +234,46 @@ void PasswordStoreKWallet::RemoveLoginsCreatedBetweenImpl( } } - SetLoginsList(kept_forms, signon_realm, wallet_handle); + if (!SetLoginsList(kept_forms, signon_realm, wallet_handle)) + ok = false; STLDeleteElements(&kept_forms); } g_strfreev(realm_list); + return ok; } -void PasswordStoreKWallet::GetLoginsImpl(GetLoginsRequest* request, - const PasswordForm& form) { - PasswordFormList forms; - - AutoLock l(kwallet_lock_); +bool NativeBackendKWallet::GetLogins(const PasswordForm& form, + PasswordFormList* forms) { 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<PasswordForm*> forms; - FillAutofillableLogins(&forms); - NotifyConsumer(request, forms); + if (wallet_handle == kInvalidKWalletHandle) + return false; + return GetLoginsList(forms, form.signon_realm, wallet_handle); } -void PasswordStoreKWallet::GetBlacklistLoginsImpl( - GetLoginsRequest* request) { - std::vector<PasswordForm*> forms; - FillBlacklistLogins(&forms); - NotifyConsumer(request, forms); +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 PasswordStoreKWallet::FillAutofillableLogins( - std::vector<PasswordForm*>* forms) { - return FillSomeLogins(true, forms); +bool NativeBackendKWallet::GetAutofillableLogins(PasswordFormList* forms) { + int wallet_handle = WalletHandle(); + if (wallet_handle == kInvalidKWalletHandle) + return false; + return GetLoginsList(forms, true, wallet_handle); } -bool PasswordStoreKWallet::FillBlacklistLogins( - std::vector<PasswordForm*>* forms) { - return FillSomeLogins(false, forms); +bool NativeBackendKWallet::GetBlacklistLogins(PasswordFormList* forms) { + int wallet_handle = WalletHandle(); + if (wallet_handle == kInvalidKWalletHandle) + return false; + return GetLoginsList(forms, false, wallet_handle); } -void PasswordStoreKWallet::GetLoginsList(PasswordFormList* forms, +bool NativeBackendKWallet::GetLoginsList(PasswordFormList* forms, const string& signon_realm, int wallet_handle) { // Is there an entry in the wallet? @@ -294,7 +288,7 @@ void PasswordStoreKWallet::GetLoginsList(PasswordFormList* forms, G_TYPE_INVALID); if (CheckError() || !has_entry) - return; + return false; GArray* byte_array = NULL; dbus_g_proxy_call(proxy_, "readEntry", &error_, @@ -307,14 +301,93 @@ void PasswordStoreKWallet::GetLoginsList(PasswordFormList* forms, G_TYPE_INVALID); if (CheckError() || !byte_array || !byte_array->len) - return; + return false; Pickle pickle(byte_array->data, byte_array->len); DeserializeValue(signon_realm, pickle, forms); g_array_free(byte_array, true); + + return true; } -void PasswordStoreKWallet::SetLoginsList(const PasswordFormList& forms, +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()) { @@ -331,7 +404,7 @@ void PasswordStoreKWallet::SetLoginsList(const PasswordFormList& forms, CheckError(); if (ret != 0) LOG(ERROR) << "Bad return code " << ret << " from kwallet removeEntry"; - return; + return ret == 0; } Pickle value; @@ -358,61 +431,10 @@ void PasswordStoreKWallet::SetLoginsList(const PasswordFormList& forms, CheckError(); if (ret != 0) LOG(ERROR) << "Bad return code " << ret << " from kwallet writeEntry"; + return ret == 0; } -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, +bool NativeBackendKWallet::CompareForms(const PasswordForm& a, const PasswordForm& b, bool update_check) { // An update check doesn't care about the submit element. @@ -425,7 +447,7 @@ bool PasswordStoreKWallet::CompareForms(const PasswordForm& a, a.username_value == b.username_value; } -void PasswordStoreKWallet::SerializeValue(const PasswordFormList& forms, +void NativeBackendKWallet::SerializeValue(const PasswordFormList& forms, Pickle* pickle) { pickle->WriteInt(kPickleVersion); pickle->WriteSize(forms.size()); @@ -447,7 +469,7 @@ void PasswordStoreKWallet::SerializeValue(const PasswordFormList& forms, } } -void PasswordStoreKWallet::DeserializeValue(const string& signon_realm, +void NativeBackendKWallet::DeserializeValue(const string& signon_realm, const Pickle& pickle, PasswordFormList* forms) { void* iter = NULL; @@ -487,14 +509,14 @@ void PasswordStoreKWallet::DeserializeValue(const string& signon_realm, } } -void PasswordStoreKWallet::ReadGURL(const Pickle& pickle, void** iter, +void NativeBackendKWallet::ReadGURL(const Pickle& pickle, void** iter, GURL* url) { string url_string; pickle.ReadString(iter, &url_string); *url = GURL(url_string); } -bool PasswordStoreKWallet::CheckError() { +bool NativeBackendKWallet::CheckError() { if (error_) { LOG(ERROR) << "Failed to complete KWallet call: " << error_->message; g_error_free(error_); @@ -504,7 +526,8 @@ bool PasswordStoreKWallet::CheckError() { return false; } -int PasswordStoreKWallet::WalletHandle() { +int NativeBackendKWallet::WalletHandle() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); // Open the wallet. int handle = kInvalidKWalletHandle; dbus_g_proxy_call(proxy_, "open", &error_, diff --git a/chrome/browser/password_manager/password_store_kwallet.h b/chrome/browser/password_manager/native_backend_kwallet_x.h index f7835fe..ad2b367 100644 --- a/chrome/browser/password_manager/password_store_kwallet.h +++ b/chrome/browser/password_manager/native_backend_kwallet_x.h @@ -2,72 +2,75 @@ // 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_ +#ifndef CHROME_BROWSER_PASSWORD_MANAGER_NATIVE_BACKEND_KWALLET_X_H_ +#define CHROME_BROWSER_PASSWORD_MANAGER_NATIVE_BACKEND_KWALLET_X_H_ #include <dbus/dbus-glib.h> #include <glib.h> #include <string> -#include <vector> -#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 "base/basictypes.h" +#include "base/time.h" +#include "chrome/browser/password_manager/password_store_x.h" #include "webkit/glue/password_form.h" class Pickle; -class Profile; -class Task; -class PasswordStoreKWallet : public PasswordStore { +// NativeBackend implementation using KWallet. +class NativeBackendKWallet : public PasswordStoreX::NativeBackend { public: - PasswordStoreKWallet(LoginDatabase* login_db, - Profile* profile, - WebDataService* web_data_service); - - bool Init(); + 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: - typedef std::vector<webkit_glue::PasswordForm*> 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<webkit_glue::PasswordForm*>* forms); - virtual bool FillBlacklistLogins( - std::vector<webkit_glue::PasswordForm*>* forms); - // Initialization. bool StartKWalletd(); bool InitWallet(); - // Reads a list of PasswordForms from the wallet that match the signon_realm. - void GetLoginsList(PasswordFormList* forms, + // 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. - void SetLoginsList(const PasswordFormList& forms, + // |forms| is empty. Returns true on success. + bool 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. @@ -117,9 +120,6 @@ class PasswordStoreKWallet : public PasswordStore { // 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_; @@ -131,7 +131,7 @@ class PasswordStoreKWallet : public PasswordStore { // The name of the wallet we've opened. Set during Init(). std::string wallet_name_; - DISALLOW_COPY_AND_ASSIGN(PasswordStoreKWallet); + DISALLOW_COPY_AND_ASSIGN(NativeBackendKWallet); }; -#endif // CHROME_BROWSER_PASSWORD_MANAGER_PASSWORD_STORE_KWALLET_H_ +#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 <vector> + #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<const WDResult<PasswordForms>*>(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.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 <gnome-keyring.h> -} - -#include <vector> - -#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<webkit_glue::PasswordForm*>* forms); - virtual bool FillBlacklistLogins( - std::vector<webkit_glue::PasswordForm*>* 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<webkit_glue::PasswordForm*>* forms); - - // Parse all the results from the given GList into a - // vector<PasswordForm*>, and free the GList. PasswordForms are - // allocated on the heap, and should be deleted by the consumer. - void FillFormVector(GList* found, - std::vector<webkit_glue::PasswordForm*>* 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_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 <map> +#include <vector> + +#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<PasswordStoreChangeList>(&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<PasswordStoreChangeList>(&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<PasswordStoreChangeList>(&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<PasswordForm*> forms; + if (use_native_backend() && + backend_->GetLoginsCreatedBetween(delete_begin, delete_end, &forms) && + backend_->RemoveLoginsCreatedBetween(delete_begin, delete_end)) { + PasswordStoreChangeList changes; + for (vector<PasswordForm*>::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<PasswordStoreChangeList>(&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<PasswordForm*> 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<PasswordForm*> 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<PasswordForm*> 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<PasswordForm*>* 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<PasswordForm*>* 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<PasswordForm*> 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 <vector> + +#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<webkit_glue::PasswordForm*> 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<webkit_glue::PasswordForm*>* forms); + virtual bool FillBlacklistLogins( + std::vector<webkit_glue::PasswordForm*>* 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<NativeBackend> 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<PasswordForm*>&)); +}; + +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<DBThreadObserverHelper, + ChromeThread::DeleteOnDBThread> { + 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<DBThreadObserverHelper>; + + 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<PasswordForm> all_forms_; +}; + +class MockLoginDatabaseReturn { + public: + MOCK_METHOD1(OnLoginDatabaseQueryDone, + void(const std::vector<PasswordForm*>&)); +}; + +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<PasswordForm*> 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<PasswordForm*> 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<LoginDatabase> { + void RetainCallee(LoginDatabase*) {} + void ReleaseCallee(LoginDatabase*) {} +}; + +enum BackendType { + NO_BACKEND, + FAILING_BACKEND, + WORKING_BACKEND +}; + +class PasswordStoreXTest : public testing::TestWithParam<BackendType> { + 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<LoginDatabase> login_db_; + scoped_ptr<TestingProfile> profile_; + scoped_refptr<WebDataService> 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<const WDResult<std::vector<PasswordForm*> >*>( + 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<PasswordStoreX> 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<PasswordStoreX> 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<PasswordStoreX> 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<PasswordForm> form(CreatePasswordFormFromData(form_data)); + + scoped_refptr<DBThreadObserverHelper> 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<const PasswordStoreChangeList>::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<const PasswordStoreChangeList>::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<const PasswordStoreChangeList>::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<PasswordStoreX> 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)); diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index f297108..0e0ad22 100755 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1805,17 +1805,21 @@ 'browser/parsers/metadata_parser_manager.cc', 'browser/parsers/metadata_parser_manager.h', 'browser/parsers/metadata_parser.cc', + 'browser/password_manager/encryptor.h', 'browser/password_manager/encryptor_linux.cc', 'browser/password_manager/encryptor_mac.mm', 'browser/password_manager/encryptor_win.cc', - 'browser/password_manager/encryptor.h', 'browser/password_manager/ie7_password.cc', 'browser/password_manager/ie7_password.h', + 'browser/password_manager/login_database.cc', + 'browser/password_manager/login_database.h', 'browser/password_manager/login_database_mac.cc', 'browser/password_manager/login_database_posix.cc', 'browser/password_manager/login_database_win.cc', - 'browser/password_manager/login_database.cc', - 'browser/password_manager/login_database.h', + 'browser/password_manager/native_backend_gnome_x.cc', + 'browser/password_manager/native_backend_gnome_x.h', + 'browser/password_manager/native_backend_kwallet_x.cc', + 'browser/password_manager/native_backend_kwallet_x.h', 'browser/password_manager/password_form_manager.cc', 'browser/password_manager/password_form_manager.h', 'browser/password_manager/password_manager.cc', @@ -1824,15 +1828,13 @@ 'browser/password_manager/password_store.h', 'browser/password_manager/password_store_default.cc', 'browser/password_manager/password_store_default.h', - 'browser/password_manager/password_store_gnome.h', - 'browser/password_manager/password_store_gnome.cc', - 'browser/password_manager/password_store_kwallet.h', - 'browser/password_manager/password_store_kwallet.cc', - 'browser/password_manager/password_store_mac_internal.h', - 'browser/password_manager/password_store_mac.h', 'browser/password_manager/password_store_mac.cc', - 'browser/password_manager/password_store_win.h', + 'browser/password_manager/password_store_mac.h', + 'browser/password_manager/password_store_mac_internal.h', 'browser/password_manager/password_store_win.cc', + 'browser/password_manager/password_store_win.h', + 'browser/password_manager/password_store_x.cc', + 'browser/password_manager/password_store_x.h', 'browser/platform_util.h', 'browser/platform_util_linux.cc', 'browser/platform_util_chromeos.cc', @@ -2686,10 +2688,10 @@ }], ['chromeos==1', { 'sources!': [ - 'browser/password_manager/password_store_gnome.h', - 'browser/password_manager/password_store_gnome.cc', - 'browser/password_manager/password_store_kwallet.h', - 'browser/password_manager/password_store_kwallet.cc', + 'browser/password_manager/native_backend_gnome.h', + 'browser/password_manager/native_backend_gnome.cc', + 'browser/password_manager/native_backend_kwallet.h', + 'browser/password_manager/native_backend_kwallet.cc', 'browser/platform_util_linux.cc', ], 'link_settings': { @@ -2782,12 +2784,6 @@ 'browser/importer/nss_decryptor_system_nss.cc', 'browser/importer/nss_decryptor_system_nss.h', 'browser/jankometer.cc', - 'browser/password_manager/password_store_gnome.h', - 'browser/password_manager/password_store_gnome.cc', - 'browser/password_manager/password_store_kwallet.h', - 'browser/password_manager/password_store_kwallet.cc', - 'browser/password_manager/password_store_win.cc', - 'browser/password_manager/password_store_win.h', 'browser/renderer_host/backing_store_proxy.cc', 'browser/renderer_host/backing_store_proxy.h', 'browser/renderer_host/gpu_view_host.cc', @@ -2960,10 +2956,6 @@ 'browser/history/history_publisher_none.cc', 'browser/importer/nss_decryptor_system_nss.cc', 'browser/importer/nss_decryptor_system_nss.h', - 'browser/password_manager/password_store_gnome.h', - 'browser/password_manager/password_store_gnome.cc', - 'browser/password_manager/password_store_kwallet.h', - 'browser/password_manager/password_store_kwallet.cc', 'browser/power_save_blocker_stub.cc', 'browser/views/select_file_dialog.cc', ], diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 282b234..c9a60d9 100755 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -873,6 +873,7 @@ 'browser/password_manager/password_store_default_unittest.cc', 'browser/password_manager/password_store_mac_unittest.cc', 'browser/password_manager/password_store_win_unittest.cc', + 'browser/password_manager/password_store_x_unittest.cc', 'browser/pref_member_unittest.cc', 'browser/pref_service_unittest.cc', 'browser/preferences_mock_mac.cc', |