diff options
author | zork@chromium.org <zork@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-19 18:06:14 +0000 |
---|---|---|
committer | zork@chromium.org <zork@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-19 18:06:14 +0000 |
commit | 7b8c01166d60c3d075953638c27a0f465e1d5de8 (patch) | |
tree | 9f7f0922755d0fd12220de5ab886ed2a9eff264b | |
parent | ff5abd13cf1f892c98667f59df0bab5ca54aa382 (diff) | |
download | chromium_src-7b8c01166d60c3d075953638c27a0f465e1d5de8.zip chromium_src-7b8c01166d60c3d075953638c27a0f465e1d5de8.tar.gz chromium_src-7b8c01166d60c3d075953638c27a0f465e1d5de8.tar.bz2 |
Adding sync support for Passwords
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/1851004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47686 0039d316-1c4b-4281-b951-d872f2087c98
27 files changed, 1727 insertions, 2 deletions
diff --git a/chrome/browser/sync/engine/model_safe_worker.h b/chrome/browser/sync/engine/model_safe_worker.h index 7d7461a..a89be28 100644 --- a/chrome/browser/sync/engine/model_safe_worker.h +++ b/chrome/browser/sync/engine/model_safe_worker.h @@ -23,6 +23,9 @@ enum ModelSafeGroup { GROUP_DB, // Models that live on DB thread and are being synced. GROUP_HISTORY, // Models that live on history thread and are being // synced. + GROUP_PASSWORD, // Models that live on the password thread and are + // being synced. On windows and linux, this runs on the + // DB thread. MODEL_SAFE_GROUP_COUNT, }; diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h index 6dc9501..ed6b7dc 100644 --- a/chrome/browser/sync/engine/syncapi.h +++ b/chrome/browser/sync/engine/syncapi.h @@ -79,6 +79,7 @@ namespace sync_pb { class AutofillSpecifics; class BookmarkSpecifics; class EntitySpecifics; +class PasswordSpecifics; class PreferenceSpecifics; class PasswordSpecifics; class PasswordSpecificsData; diff --git a/chrome/browser/sync/glue/data_type_manager_impl.cc b/chrome/browser/sync/glue/data_type_manager_impl.cc index f875616..15ea2c6 100644 --- a/chrome/browser/sync/glue/data_type_manager_impl.cc +++ b/chrome/browser/sync/glue/data_type_manager_impl.cc @@ -25,6 +25,7 @@ static const syncable::ModelType kStartOrder[] = { syncable::AUTOFILL, syncable::THEMES, syncable::TYPED_URLS, + syncable::PASSWORD, }; // Comparator used when sorting data type controllers. diff --git a/chrome/browser/sync/glue/password_change_processor.cc b/chrome/browser/sync/glue/password_change_processor.cc new file mode 100644 index 0000000..57a377d --- /dev/null +++ b/chrome/browser/sync/glue/password_change_processor.cc @@ -0,0 +1,213 @@ +// 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/sync/glue/password_change_processor.h" + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/password_manager/password_store.h" +#include "chrome/browser/password_manager/password_store_change.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sync/glue/password_model_associator.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/protocol/password_specifics.pb.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_type.h" +#include "webkit/glue/password_form.h" + +namespace browser_sync { + +PasswordChangeProcessor::PasswordChangeProcessor( + PasswordModelAssociator* model_associator, + PasswordStore* password_store, + UnrecoverableErrorHandler* error_handler) + : ChangeProcessor(error_handler), + model_associator_(model_associator), + password_store_(password_store), + observing_(false), + expected_loop_(MessageLoop::current()) { + DCHECK(model_associator); + DCHECK(error_handler); +#if defined(OS_MACOSX) + DCHECK(!ChromeThread::CurrentlyOn(ChromeThread::UI)); +#else + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); +#endif + StartObserving(); +} + +PasswordChangeProcessor::~PasswordChangeProcessor() { + DCHECK(expected_loop_ == MessageLoop::current()); +} + +void PasswordChangeProcessor::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(expected_loop_ == MessageLoop::current()); + DCHECK(NotificationType::LOGINS_CHANGED == type); + if (!observing_) + return; + + DCHECK(running()); + + sync_api::WriteTransaction trans(share_handle()); + + sync_api::ReadNode password_root(&trans); + if (!password_root.InitByTagLookup(kPasswordTag)) { + error_handler()->OnUnrecoverableError(); + LOG(ERROR) << "Server did not create the top-level password node. " + << "We might be running against an out-of-date server."; + return; + } + + PasswordStoreChangeList* changes = + Details<PasswordStoreChangeList>(details).ptr(); + for (PasswordStoreChangeList::iterator change = changes->begin(); + change != changes->end(); ++change) { + std::string tag = PasswordModelAssociator::MakeTag(change->form()); + switch (change->type()) { + case PasswordStoreChange::ADD: { + sync_api::WriteNode sync_node(&trans); + if (!sync_node.InitUniqueByCreation(syncable::PASSWORD, + password_root, tag)) { + LOG(ERROR) << "Failed to create password sync node."; + error_handler()->OnUnrecoverableError(); + return; + } + + PasswordModelAssociator::WriteToSyncNode(change->form(), &sync_node); + model_associator_->Associate(&tag, sync_node.GetId()); + break; + } + case PasswordStoreChange::UPDATE: { + sync_api::WriteNode sync_node(&trans); + int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); + if (sync_api::kInvalidId == sync_id) { + LOG(ERROR) << "Unexpected notification for: " << tag; + error_handler()->OnUnrecoverableError(); + return; + } else { + if (!sync_node.InitByIdLookup(sync_id)) { + LOG(ERROR) << "Password node lookup failed."; + error_handler()->OnUnrecoverableError(); + return; + } + } + + PasswordModelAssociator::WriteToSyncNode(change->form(), &sync_node); + break; + } + case PasswordStoreChange::REMOVE: { + sync_api::WriteNode sync_node(&trans); + int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); + if (sync_api::kInvalidId == sync_id) { + LOG(ERROR) << "Unexpected notification"; + error_handler()->OnUnrecoverableError(); + return; + } else { + if (!sync_node.InitByIdLookup(sync_id)) { + LOG(ERROR) << "Password node lookup failed."; + error_handler()->OnUnrecoverableError(); + return; + } + model_associator_->Disassociate(sync_node.GetId()); + sync_node.Remove(); + } + break; + } + } + } +} + +void PasswordChangeProcessor::ApplyChangesFromSyncModel( + const sync_api::BaseTransaction* trans, + const sync_api::SyncManager::ChangeRecord* changes, + int change_count) { + DCHECK(expected_loop_ == MessageLoop::current()); + if (!running()) + return; + StopObserving(); + + sync_api::ReadNode password_root(trans); + if (!password_root.InitByTagLookup(kPasswordTag)) { + LOG(ERROR) << "Password root node lookup failed."; + error_handler()->OnUnrecoverableError(); + return; + } + + PasswordModelAssociator::PasswordVector new_passwords; + PasswordModelAssociator::PasswordVector updated_passwords; + PasswordModelAssociator::PasswordVector deleted_passwords; + + for (int i = 0; i < change_count; ++i) { + + sync_api::ReadNode sync_node(trans); + if (!sync_node.InitByIdLookup(changes[i].id)) { + LOG(ERROR) << "Password node lookup failed."; + error_handler()->OnUnrecoverableError(); + return; + } + + // Check that the changed node is a child of the passwords folder. + DCHECK(password_root.GetId() == sync_node.GetParentId()); + DCHECK(syncable::PASSWORD == sync_node.GetModelType()); + + sync_pb::PasswordSpecificsData password_data; + if (!sync_node.GetPasswordSpecifics(&password_data)) { + LOG(ERROR) << "Could not read password specifics"; + error_handler()->OnUnrecoverableError(); + return; + } + webkit_glue::PasswordForm password; + PasswordModelAssociator::CopyPassword(password_data, + &password); + + if (sync_api::SyncManager::ChangeRecord::ACTION_ADD == changes[i].action) { + new_passwords.push_back(password); + } else if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == + changes[i].action) { + deleted_passwords.push_back(password); + } else { + DCHECK(sync_api::SyncManager::ChangeRecord::ACTION_UPDATE == + changes[i].action); + updated_passwords.push_back(password); + } + } + if (!model_associator_->WriteToPasswordStore(&new_passwords, + &updated_passwords, + &deleted_passwords)) { + LOG(ERROR) << "Error writing passwords"; + error_handler()->OnUnrecoverableError(); + return; + } + + StartObserving(); +} + +void PasswordChangeProcessor::StartImpl(Profile* profile) { + DCHECK(expected_loop_ == MessageLoop::current()); + observing_ = true; +} + +void PasswordChangeProcessor::StopImpl() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + observing_ = false; +} + + +void PasswordChangeProcessor::StartObserving() { + DCHECK(expected_loop_ == MessageLoop::current()); + notification_registrar_.Add(this, + NotificationType::LOGINS_CHANGED, + NotificationService::AllSources()); +} + +void PasswordChangeProcessor::StopObserving() { + DCHECK(expected_loop_ == MessageLoop::current()); + notification_registrar_.Remove(this, + NotificationType::LOGINS_CHANGED, + NotificationService::AllSources()); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/password_change_processor.h b/chrome/browser/sync/glue/password_change_processor.h new file mode 100644 index 0000000..6ca3dbb --- /dev/null +++ b/chrome/browser/sync/glue/password_change_processor.h @@ -0,0 +1,79 @@ +// 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_SYNC_GLUE_PASSWORD_CHANGE_PROCESSOR_H_ +#define CHROME_BROWSER_SYNC_GLUE_PASSWORD_CHANGE_PROCESSOR_H_ + +#include "chrome/browser/sync/glue/change_processor.h" + +#include <vector> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/sync/glue/sync_backend_host.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_type.h" + +class PasswordStore; +class MessageLoop; +class NotificationService; + +namespace browser_sync { + +class PasswordModelAssociator; +class UnrecoverableErrorHandler; + +// This class is responsible for taking changes from the password backend and +// applying them to the sync_api 'syncable' model, and vice versa. All +// operations and use of this class are from the DB thread on Windows and Linux, +// or the password thread on Mac. +class PasswordChangeProcessor : public ChangeProcessor, + public NotificationObserver { + public: + PasswordChangeProcessor(PasswordModelAssociator* model_associator, + PasswordStore* password_store, + UnrecoverableErrorHandler* error_handler); + virtual ~PasswordChangeProcessor(); + + // NotificationObserver implementation. + // Passwords -> sync_api model change application. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // sync_api model -> WebDataService change application. + virtual void ApplyChangesFromSyncModel( + const sync_api::BaseTransaction* trans, + const sync_api::SyncManager::ChangeRecord* changes, + int change_count); + + protected: + virtual void StartImpl(Profile* profile); + virtual void StopImpl(); + + private: + void StartObserving(); + void StopObserving(); + + // The two models should be associated according to this ModelAssociator. + PasswordModelAssociator* model_associator_; + + // The model we are processing changes from. This is owned by the + // WebDataService which is kept alive by our data type controller + // holding a reference. + PasswordStore* password_store_; + + NotificationRegistrar notification_registrar_; + + bool observing_; + + MessageLoop* expected_loop_; + + DISALLOW_COPY_AND_ASSIGN(PasswordChangeProcessor); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_PASSWORD_CHANGE_PROCESSOR_H_ diff --git a/chrome/browser/sync/glue/password_data_type_controller.cc b/chrome/browser/sync/glue/password_data_type_controller.cc new file mode 100644 index 0000000..50f4e86 --- /dev/null +++ b/chrome/browser/sync/glue/password_data_type_controller.cc @@ -0,0 +1,144 @@ +// 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/histogram.h" +#include "base/logging.h" +#include "base/task.h" +#include "base/time.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/password_manager/password_store.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sync/glue/password_change_processor.h" +#include "chrome/browser/sync/glue/password_data_type_controller.h" +#include "chrome/browser/sync/glue/password_model_associator.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/profile_sync_factory.h" + +namespace browser_sync { + +PasswordDataTypeController::PasswordDataTypeController( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* sync_service) + : profile_sync_factory_(profile_sync_factory), + profile_(profile), + sync_service_(sync_service), + state_(NOT_RUNNING) { + DCHECK(profile_sync_factory); + DCHECK(profile); + DCHECK(sync_service); +} + +PasswordDataTypeController::~PasswordDataTypeController() { +} + +void PasswordDataTypeController::Start(StartCallback* start_callback) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + DCHECK(start_callback); + if (state_ != NOT_RUNNING) { + start_callback->Run(BUSY); + delete start_callback; + return; + } + + start_callback_.reset(start_callback); + + set_state(ASSOCIATING); + password_store_ = profile_->GetPasswordStore(Profile::EXPLICIT_ACCESS); + DCHECK(password_store_.get()); + password_store_->ScheduleTask( + NewRunnableMethod(this, &PasswordDataTypeController::StartImpl)); +} + +void PasswordDataTypeController::Stop() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + if (change_processor_ != NULL) + sync_service_->DeactivateDataType(this, change_processor_.get()); + + if (model_associator_ != NULL) + model_associator_->DisassociateModels(); + + set_state(NOT_RUNNING); + DCHECK(password_store_.get()); + password_store_->ScheduleTask( + NewRunnableMethod(this, &PasswordDataTypeController::StopImpl)); +} + +void PasswordDataTypeController::StartImpl() { + // No additional services need to be started before we can proceed + // with model association. + ProfileSyncFactory::SyncComponents sync_components = + profile_sync_factory_->CreatePasswordSyncComponents( + sync_service_, + password_store_.get(), + this); + model_associator_.reset(sync_components.model_associator); + change_processor_.reset(sync_components.change_processor); + + bool sync_has_nodes = false; + if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { + StartFailed(UNRECOVERABLE_ERROR); + return; + } + + base::TimeTicks start_time = base::TimeTicks::Now(); + bool merge_success = model_associator_->AssociateModels(); + UMA_HISTOGRAM_TIMES("Sync.PasswordAssociationTime", + base::TimeTicks::Now() - start_time); + if (!merge_success) { + StartFailed(ASSOCIATION_FAILED); + return; + } + + sync_service_->ActivateDataType(this, change_processor_.get()); + StartDone(!sync_has_nodes ? OK_FIRST_RUN : OK, RUNNING); +} + +void PasswordDataTypeController::StartDone( + DataTypeController::StartResult result, + DataTypeController::State new_state) { + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + this, + &PasswordDataTypeController::StartDoneImpl, + result, + new_state)); +} + +void PasswordDataTypeController::StartDoneImpl( + DataTypeController::StartResult result, + DataTypeController::State new_state) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + set_state(new_state); + start_callback_->Run(result); + start_callback_.reset(); +} + +void PasswordDataTypeController::StopImpl() { + change_processor_.reset(); + model_associator_.reset(); + + state_ = NOT_RUNNING; +} + +void PasswordDataTypeController::StartFailed(StartResult result) { + change_processor_.reset(); + model_associator_.reset(); + StartDone(result, NOT_RUNNING); +} + +void PasswordDataTypeController::OnUnrecoverableError() { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, + &PasswordDataTypeController::OnUnrecoverableErrorImpl)); +} + +void PasswordDataTypeController::OnUnrecoverableErrorImpl() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + sync_service_->OnUnrecoverableError(); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/password_data_type_controller.h b/chrome/browser/sync/glue/password_data_type_controller.h new file mode 100644 index 0000000..8d04b23 --- /dev/null +++ b/chrome/browser/sync/glue/password_data_type_controller.h @@ -0,0 +1,89 @@ +// 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_SYNC_GLUE_PASSWORD_DATA_TYPE_CONTROLLER_H__ +#define CHROME_BROWSER_SYNC_GLUE_PASSWORD_DATA_TYPE_CONTROLLER_H__ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/glue/data_type_controller.h" + +class Profile; +class ProfileSyncFactory; +class ProfileSyncService; + +namespace browser_sync { + +class AssociatorInterface; +class ChangeProcessor; +class ControlTask; + +// A class that manages the startup and shutdown of password sync. +class PasswordDataTypeController : public DataTypeController { + public: + PasswordDataTypeController( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* sync_service); + virtual ~PasswordDataTypeController(); + + // DataTypeController implementation + virtual void Start(StartCallback* start_callback); + + virtual void Stop(); + + virtual bool enabled() { + return true; + } + + virtual syncable::ModelType type() { + return syncable::PASSWORD; + } + + virtual browser_sync::ModelSafeGroup model_safe_group() { + return browser_sync::GROUP_PASSWORD; + } + + virtual const char* name() const { + // For logging only. + return "password"; + } + + virtual State state() { + return state_; + } + + // UnrecoverableHandler implementation + virtual void OnUnrecoverableError(); + + private: + void StartImpl(); + void StartDone(StartResult result, State state); + void StartDoneImpl(StartResult result, State state); + void StopImpl(); + void StartFailed(StartResult result); + void OnUnrecoverableErrorImpl(); + + void set_state(State state) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + state_ = state; + } + + ProfileSyncFactory* profile_sync_factory_; + Profile* profile_; + ProfileSyncService* sync_service_; + State state_; + + scoped_ptr<AssociatorInterface> model_associator_; + scoped_ptr<ChangeProcessor> change_processor_; + scoped_ptr<StartCallback> start_callback_; + scoped_refptr<PasswordStore> password_store_; + + DISALLOW_COPY_AND_ASSIGN(PasswordDataTypeController); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_PASSWORD_DATA_TYPE_CONTROLLER_H__ diff --git a/chrome/browser/sync/glue/password_model_associator.cc b/chrome/browser/sync/glue/password_model_associator.cc new file mode 100644 index 0000000..923ee7b --- /dev/null +++ b/chrome/browser/sync/glue/password_model_associator.cc @@ -0,0 +1,402 @@ +// 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/sync/glue/password_model_associator.h" + +#include <set> + +#include "base/stl_util-inl.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/password_manager/password_store.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/protocol/password_specifics.pb.h" +#include "net/base/escape.h" +#include "webkit/glue/password_form.h" + +namespace browser_sync { + +const char kPasswordTag[] = "google_chrome_passwords"; + +PasswordModelAssociator::PasswordModelAssociator( + ProfileSyncService* sync_service, + PasswordStore* password_store, + UnrecoverableErrorHandler* error_handler) + : sync_service_(sync_service), + password_store_(password_store), + error_handler_(error_handler), + password_node_id_(sync_api::kInvalidId), + abort_association_pending_(false), + expected_loop_(MessageLoop::current()) { + DCHECK(sync_service_); + DCHECK(password_store_); + DCHECK(error_handler_); +#if defined(OS_MACOSX) + DCHECK(!ChromeThread::CurrentlyOn(ChromeThread::UI)); +#else + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); +#endif +} + +bool PasswordModelAssociator::AssociateModels() { + DCHECK(expected_loop_ == MessageLoop::current()); + { + AutoLock lock(abort_association_pending_lock_); + abort_association_pending_ = false; + } + + sync_api::WriteTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + sync_api::ReadNode password_root(&trans); + if (!password_root.InitByTagLookup(kPasswordTag)) { + LOG(ERROR) << "Server did not create the top-level password node. We " + << "might be running against an out-of-date server."; + return false; + } + + std::vector<webkit_glue::PasswordForm*> passwords; + if (!password_store_->FillAutofillableLogins(&passwords) || + !password_store_->FillBlacklistLogins(&passwords)) { + STLDeleteElements(&passwords); + LOG(ERROR) << "Could not get the password entries."; + return false; + } + + std::set<std::string> current_passwords; + PasswordVector new_passwords; + PasswordVector updated_passwords; + + for (std::vector<webkit_glue::PasswordForm*>::iterator ix = passwords.begin(); + ix != passwords.end(); ++ix) { + if (IsAbortPending()) + return false; + std::string tag = MakeTag(**ix); + + sync_api::ReadNode node(&trans); + if (node.InitByClientTagLookup(syncable::PASSWORD, tag)) { + sync_pb::PasswordSpecificsData password; + if (!node.GetPasswordSpecifics(&password)) { + STLDeleteElements(&passwords); + LOG(ERROR) << "Failed to get password specifics from sync node."; + return false; + } + DCHECK_EQ(tag, MakeTag(password)); + + webkit_glue::PasswordForm new_password; + + if (MergePasswords(password, **ix, &new_password)) { + sync_api::WriteNode write_node(&trans); + if (!write_node.InitByClientTagLookup(syncable::PASSWORD, tag)) { + STLDeleteElements(&passwords); + LOG(ERROR) << "Failed to edit password sync node."; + return false; + } + WriteToSyncNode(new_password, &write_node); + updated_passwords.push_back(new_password); + } + + Associate(&tag, node.GetId()); + } else { + sync_api::WriteNode node(&trans); + if (!node.InitUniqueByCreation(syncable::PASSWORD, + password_root, tag)) { + STLDeleteElements(&passwords); + LOG(ERROR) << "Failed to create password sync node."; + return false; + } + + WriteToSyncNode(**ix, &node); + + Associate(&tag, node.GetId()); + } + + current_passwords.insert(tag); + } + + STLDeleteElements(&passwords); + + int64 sync_child_id = password_root.GetFirstChildId(); + while (sync_child_id != sync_api::kInvalidId) { + sync_api::ReadNode sync_child_node(&trans); + if (!sync_child_node.InitByIdLookup(sync_child_id)) { + LOG(ERROR) << "Failed to fetch child node."; + return false; + } + sync_pb::PasswordSpecificsData password; + if (!sync_child_node.GetPasswordSpecifics(&password)) { + LOG(ERROR) << "Failed to get specifics from password node."; + return false; + } + std::string tag = MakeTag(password); + + // The password only exists on the server. Add it to the local + // model. + if (current_passwords.find(tag) == current_passwords.end()) { + webkit_glue::PasswordForm new_password; + + CopyPassword(password, &new_password); + Associate(&tag, sync_child_node.GetId()); + new_passwords.push_back(new_password); + } + + sync_child_id = sync_child_node.GetSuccessorId(); + } + + if (!WriteToPasswordStore(&new_passwords, &updated_passwords, NULL)) { + LOG(ERROR) << "Failed to write passwords."; + return false; + } + + return true; +} + +bool PasswordModelAssociator::DeleteAllNodes( + sync_api::WriteTransaction* trans) { + DCHECK(expected_loop_ == MessageLoop::current()); + for (PasswordToSyncIdMap::iterator node_id = id_map_.begin(); + node_id != id_map_.end(); ++node_id) { + sync_api::WriteNode sync_node(trans); + if (!sync_node.InitByIdLookup(node_id->second)) { + LOG(ERROR) << "Typed url node lookup failed."; + return false; + } + sync_node.Remove(); + } + + id_map_.clear(); + id_map_inverse_.clear(); + return true; +} + +bool PasswordModelAssociator::DisassociateModels() { + id_map_.clear(); + id_map_inverse_.clear(); + return true; +} + +bool PasswordModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) { + DCHECK(has_nodes); + *has_nodes = false; + int64 password_sync_id; + if (!GetSyncIdForTaggedNode(kPasswordTag, &password_sync_id)) { + LOG(ERROR) << "Server did not create the top-level password node. We " + << "might be running against an out-of-date server."; + return false; + } + sync_api::ReadTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + + sync_api::ReadNode password_node(&trans); + if (!password_node.InitByIdLookup(password_sync_id)) { + LOG(ERROR) << "Server did not create the top-level password node. We " + << "might be running against an out-of-date server."; + return false; + } + + // The sync model has user created nodes if the password folder has any + // children. + *has_nodes = sync_api::kInvalidId != password_node.GetFirstChildId(); + return true; +} + +bool PasswordModelAssociator::ChromeModelHasUserCreatedNodes(bool* has_nodes) { + DCHECK(has_nodes); + std::vector<webkit_glue::PasswordForm*> passwords; + if (!password_store_->FillAutofillableLogins(&passwords) || + !password_store_->FillBlacklistLogins(&passwords)) { + STLDeleteElements(&passwords); + LOG(ERROR) << "Could not get the password entries."; + return false; + } + + *has_nodes = !passwords.empty(); + STLDeleteElements(&passwords); + return true; +} + +void PasswordModelAssociator::AbortAssociation() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + AutoLock lock(abort_association_pending_lock_); + abort_association_pending_ = true; +} + +bool PasswordModelAssociator::IsAbortPending() { + AutoLock lock(abort_association_pending_lock_); + return abort_association_pending_; +} + +int64 PasswordModelAssociator::GetSyncIdFromChromeId( + const std::string password) { + PasswordToSyncIdMap::const_iterator iter = id_map_.find(password); + return iter == id_map_.end() ? sync_api::kInvalidId : iter->second; +} + +void PasswordModelAssociator::Associate( + const std::string* password, int64 sync_id) { + DCHECK(expected_loop_ == MessageLoop::current()); + DCHECK_NE(sync_api::kInvalidId, sync_id); + DCHECK(id_map_.find(*password) == id_map_.end()); + DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end()); + id_map_[*password] = sync_id; + id_map_inverse_[sync_id] = *password; +} + +void PasswordModelAssociator::Disassociate(int64 sync_id) { + DCHECK(expected_loop_ == MessageLoop::current()); + SyncIdToPasswordMap::iterator iter = id_map_inverse_.find(sync_id); + if (iter == id_map_inverse_.end()) + return; + CHECK(id_map_.erase(iter->second)); + id_map_inverse_.erase(iter); +} + +bool PasswordModelAssociator::GetSyncIdForTaggedNode(const std::string& tag, + int64* sync_id) { + sync_api::ReadTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + sync_api::ReadNode sync_node(&trans); + if (!sync_node.InitByTagLookup(tag.c_str())) + return false; + *sync_id = sync_node.GetId(); + return true; +} + +bool PasswordModelAssociator::WriteToPasswordStore( + const PasswordVector* new_passwords, + const PasswordVector* updated_passwords, + const PasswordVector* deleted_passwords) { + if (new_passwords) { + for (PasswordVector::const_iterator password = new_passwords->begin(); + password != new_passwords->end(); ++password) { + password_store_->AddLoginImpl(*password); + } + } + + if (updated_passwords) { + for (PasswordVector::const_iterator password = updated_passwords->begin(); + password != updated_passwords->end(); ++password) { + password_store_->UpdateLoginImpl(*password); + } + } + + if (deleted_passwords) { + for (PasswordVector::const_iterator password = deleted_passwords->begin(); + password != deleted_passwords->end(); ++password) { + password_store_->RemoveLoginImpl(*password); + } + } + return true; +} + +// static +void PasswordModelAssociator::CopyPassword( + const sync_pb::PasswordSpecificsData& password, + webkit_glue::PasswordForm* new_password) { + new_password->scheme = + static_cast<webkit_glue::PasswordForm::Scheme>(password.scheme()); + new_password->signon_realm = password.signon_realm(); + new_password->origin = GURL(password.origin()); + new_password->action = GURL(password.action()); + new_password->username_element = + UTF8ToUTF16(password.username_element()); + new_password->password_element = + UTF8ToUTF16(password.password_element()); + new_password->username_value = + UTF8ToUTF16(password.username_value()); + new_password->password_value = + UTF8ToUTF16(password.password_value()); + new_password->ssl_valid = password.ssl_valid(); + new_password->preferred = password.preferred(); + new_password->date_created = + base::Time::FromInternalValue(password.date_created()); + new_password->blacklisted_by_user = + password.blacklisted(); +} + +// static +bool PasswordModelAssociator::MergePasswords( + const sync_pb::PasswordSpecificsData& password, + const webkit_glue::PasswordForm& password_form, + webkit_glue::PasswordForm* new_password) { + DCHECK(new_password); + + if (password.scheme() == password_form.scheme && + password_form.signon_realm == password.signon_realm() && + password_form.origin.spec() == password.origin() && + password_form.action.spec() == password.action() && + UTF16ToUTF8(password_form.username_element) == + password.username_element() && + UTF16ToUTF8(password_form.password_element) == + password.password_element() && + UTF16ToUTF8(password_form.username_value) == + password.username_value() && + UTF16ToUTF8(password_form.password_value) == + password.password_value() && + password.ssl_valid() == password_form.ssl_valid && + password.preferred() == password_form.preferred && + password.date_created() == password_form.date_created.ToInternalValue() && + password.blacklisted() == password_form.blacklisted_by_user) { + return false; + } + + // If the passwords differ, we take the one that was created more recently. + if (base::Time::FromInternalValue(password.date_created()) <= + password_form.date_created) { + *new_password = password_form; + } else { + CopyPassword(password, new_password); + } + + return true; +} + +// static +void PasswordModelAssociator::WriteToSyncNode( + const webkit_glue::PasswordForm& password_form, + sync_api::WriteNode* node) { + sync_pb::PasswordSpecificsData password; + password.set_scheme(password_form.scheme); + password.set_signon_realm(password_form.signon_realm); + password.set_origin(password_form.origin.spec()); + password.set_action(password_form.action.spec()); + password.set_username_element(UTF16ToUTF8(password_form.username_element)); + password.set_password_element(UTF16ToUTF8(password_form.password_element)); + password.set_username_value(UTF16ToUTF8(password_form.username_value)); + password.set_password_value(UTF16ToUTF8(password_form.password_value)); + password.set_ssl_valid(password_form.ssl_valid); + password.set_preferred(password_form.preferred); + password.set_date_created(password_form.date_created.ToInternalValue()); + password.set_blacklisted(password_form.blacklisted_by_user); + + node->SetPasswordSpecifics(password); +} + +// static +std::string PasswordModelAssociator::MakeTag( + const webkit_glue::PasswordForm& password) { + return MakeTag(password.signon_realm, + password.origin.spec(), + password.action.spec()); +} + +// static +std::string PasswordModelAssociator::MakeTag( + const sync_pb::PasswordSpecificsData& password) { + return MakeTag(password.signon_realm(), + password.origin(), + password.action()); +} + +// static +std::string PasswordModelAssociator::MakeTag( + const std::string& signon_realm, + const std::string& origin, + const std::string& action) { + return EscapePath(signon_realm) + "|" + + EscapePath(origin) + "|" + + EscapePath(action); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/password_model_associator.h b/chrome/browser/sync/glue/password_model_associator.h new file mode 100644 index 0000000..55ebce0 --- /dev/null +++ b/chrome/browser/sync/glue/password_model_associator.h @@ -0,0 +1,158 @@ +// 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_SYNC_GLUE_PASSWORD_MODEL_ASSOCIATOR_H_ +#define CHROME_BROWSER_SYNC_GLUE_PASSWORD_MODEL_ASSOCIATOR_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/lock.h" +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/sync/glue/model_associator.h" +#include "chrome/browser/sync/protocol/password_specifics.pb.h" + +class GURL; +class MessageLoop; +class PasswordStore; +class ProfileSyncService; + +namespace webkit_glue { +struct PasswordForm; +}; + +namespace sync_api { +class WriteNode; +class WriteTransaction; +}; + +namespace browser_sync { + +class PasswordChangeProcessor; +class UnrecoverableErrorHandler; + +extern const char kPasswordTag[]; + +// Contains all model association related logic: +// * Algorithm to associate password model and sync model. +// * Persisting model associations and loading them back. +// We do not check if we have local data before this runs; we always +// merge and sync. +class PasswordModelAssociator + : public PerDataTypeAssociatorInterface<std::string, std::string> { + public: + typedef std::vector<webkit_glue::PasswordForm> PasswordVector; + + static syncable::ModelType model_type() { return syncable::PASSWORD; } + PasswordModelAssociator(ProfileSyncService* sync_service, + PasswordStore* password_store, + UnrecoverableErrorHandler* error_handler); + virtual ~PasswordModelAssociator() { } + + // PerDataTypeAssociatorInterface implementation. + // + // Iterates through the sync model looking for matched pairs of items. + virtual bool AssociateModels(); + + // Delete all password nodes. + bool DeleteAllNodes(sync_api::WriteTransaction* trans); + + // Clears all associations. + virtual bool DisassociateModels(); + + // The has_nodes out param is true if the sync model has nodes other + // than the permanent tagged nodes. + virtual bool SyncModelHasUserCreatedNodes(bool* has_nodes); + + // The has_nodes out param is true if the autofill model has any + // user-defined password entries. + virtual bool ChromeModelHasUserCreatedNodes(bool* has_nodes); + + // See ModelAssociator interface. + virtual void AbortAssociation(); + + // Not implemented. + virtual const std::string* GetChromeNodeFromSyncId(int64 sync_id) { + return NULL; + } + + // Not implemented. + virtual bool InitSyncNodeFromChromeId(std::string node_id, + sync_api::BaseNode* sync_node) { + return false; + } + + // Returns the sync id for the given password name, or sync_api::kInvalidId + // if the password name is not associated to any sync id. + virtual int64 GetSyncIdFromChromeId(std::string node_id); + + // Associates the given password name with the given sync id. + virtual void Associate(const std::string* node, int64 sync_id); + + // Remove the association that corresponds to the given sync id. + virtual void Disassociate(int64 sync_id); + + // Returns whether a node with the given permanent tag was found and update + // |sync_id| with that node's id. + virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id); + + bool WriteToPasswordStore(const PasswordVector* new_passwords, + const PasswordVector* updated_passwords, + const PasswordVector* deleted_passwords); + + static std::string MakeTag(const webkit_glue::PasswordForm& password); + static std::string MakeTag(const sync_pb::PasswordSpecificsData& password); + static std::string MakeTag(const std::string& signon_realm, + const std::string& origin, + const std::string& action); + + static void CopyPassword(const sync_pb::PasswordSpecificsData& password, + webkit_glue::PasswordForm* new_password); + + static bool MergePasswords(const sync_pb::PasswordSpecificsData& password, + const webkit_glue::PasswordForm& password_form, + webkit_glue::PasswordForm* new_password); + static void WriteToSyncNode(const webkit_glue::PasswordForm& password_form, + sync_api::WriteNode* node); + + // Called at various points in model association to determine if the + // user requested an abort. + bool IsAbortPending(); + + protected: + // Returns sync service instance. + ProfileSyncService* sync_service() { return sync_service_; } + + private: + typedef std::map<std::string, int64> PasswordToSyncIdMap; + typedef std::map<int64, std::string> SyncIdToPasswordMap; + + ProfileSyncService* sync_service_; + PasswordStore* password_store_; + UnrecoverableErrorHandler* error_handler_; + int64 password_node_id_; + + // Abort association pending flag and lock. If this is set to true + // (via the AbortAssociation method), return from the + // AssociateModels method as soon as possible. + Lock abort_association_pending_lock_; + bool abort_association_pending_; + + MessageLoop* expected_loop_; + + PasswordToSyncIdMap id_map_; + SyncIdToPasswordMap id_map_inverse_; + + DISALLOW_COPY_AND_ASSIGN(PasswordModelAssociator); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_PASSWORD_MODEL_ASSOCIATOR_H_ diff --git a/chrome/browser/sync/glue/password_model_worker.cc b/chrome/browser/sync/glue/password_model_worker.cc new file mode 100644 index 0000000..be7531b --- /dev/null +++ b/chrome/browser/sync/glue/password_model_worker.cc @@ -0,0 +1,41 @@ +// 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/sync/glue/password_model_worker.h" + +#include "base/logging.h" +#include "base/ref_counted.h" +#include "base/task.h" +#include "base/waitable_event.h" +#include "chrome/browser/password_manager/password_store.h" +#include "chrome/browser/sync/util/closure.h" + +using base::WaitableEvent; + +namespace browser_sync { + +PasswordModelWorker::PasswordModelWorker(PasswordStore* password_store) + : password_store_(password_store) { +} + +void PasswordModelWorker::DoWorkAndWaitUntilDone(Closure* work) { + WaitableEvent done(false, false); + password_store_->ScheduleTask( + NewRunnableMethod(this, &PasswordModelWorker::CallDoWorkAndSignalTask, + work, &done)); + done.Wait(); +} + +void PasswordModelWorker::CallDoWorkAndSignalTask(Closure* work, + WaitableEvent* done) { + work->Run(); + done->Signal(); +} + +bool PasswordModelWorker::CurrentThreadIsWorkThread() { + // TODO(ncarter): How to determine this? + return true; +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/password_model_worker.h b/chrome/browser/sync/glue/password_model_worker.h new file mode 100644 index 0000000..e778441 --- /dev/null +++ b/chrome/browser/sync/glue/password_model_worker.h @@ -0,0 +1,43 @@ +// 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_SYNC_GLUE_PASSWORD_MODEL_WORKER_H_ +#define CHROME_BROWSER_SYNC_GLUE_PASSWORD_MODEL_WORKER_H_ + +#include "chrome/browser/sync/engine/model_safe_worker.h" + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "chrome/browser/sync/util/closure.h" + +class PasswordStore; + +namespace base { +class WaitableEvent; +} + +namespace browser_sync { + +// A ModelSafeWorker for password models that accepts requests +// from the syncapi that need to be fulfilled on the password thread, +// which is the DB thread on Linux and Windows. +class PasswordModelWorker : public browser_sync::ModelSafeWorker { + public: + explicit PasswordModelWorker(PasswordStore* password_store); + + // ModelSafeWorker implementation. Called on syncapi SyncerThread. + void DoWorkAndWaitUntilDone(Closure* work); + virtual ModelSafeGroup GetModelSafeGroup() { return GROUP_PASSWORD; } + virtual bool CurrentThreadIsWorkThread(); + + private: + void CallDoWorkAndSignalTask(Closure* work, base::WaitableEvent* done); + + scoped_refptr<PasswordStore> password_store_; + DISALLOW_COPY_AND_ASSIGN(PasswordModelWorker); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_PASSWORD_MODEL_WORKER_H_ diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc index 5ae9366..a058244 100644 --- a/chrome/browser/sync/glue/sync_backend_host.cc +++ b/chrome/browser/sync/glue/sync_backend_host.cc @@ -15,6 +15,7 @@ #include "chrome/browser/sync/glue/history_model_worker.h" #include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/glue/http_bridge.h" +#include "chrome/browser/sync/glue/password_model_worker.h" #include "chrome/browser/sync/sessions/session_state.h" #include "chrome/common/notification_service.h" #include "chrome/common/notification_type.h" @@ -84,10 +85,13 @@ void SyncBackendHost::Initialize( // need to update routing_info_. registrar_.workers[GROUP_DB] = new DatabaseModelWorker(); registrar_.workers[GROUP_HISTORY] = - new HistoryModelWorker( - profile_->GetHistoryService(Profile::IMPLICIT_ACCESS)); + new HistoryModelWorker( + profile_->GetHistoryService(Profile::IMPLICIT_ACCESS)); registrar_.workers[GROUP_UI] = new UIModelWorker(frontend_loop_); registrar_.workers[GROUP_PASSIVE] = new ModelSafeWorker(); + registrar_.workers[GROUP_PASSWORD] = + new PasswordModelWorker( + profile_->GetPasswordStore(Profile::IMPLICIT_ACCESS)); // Any datatypes that we want the syncer to pull down must // be in the routing_info map. We set them to group passive, meaning that @@ -153,10 +157,12 @@ void SyncBackendHost::Shutdown(bool sync_disabled) { registrar_.workers[GROUP_HISTORY] = NULL; registrar_.workers[GROUP_UI] = NULL; registrar_.workers[GROUP_PASSIVE] = NULL; + registrar_.workers[GROUP_PASSWORD] = NULL; registrar_.workers.erase(GROUP_DB); registrar_.workers.erase(GROUP_HISTORY); registrar_.workers.erase(GROUP_UI); registrar_.workers.erase(GROUP_PASSIVE); + registrar_.workers.erase(GROUP_PASSWORD); frontend_ = NULL; core_ = NULL; // Releases reference to core_. } diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h index 7294acd..91d0c9a 100644 --- a/chrome/browser/sync/glue/sync_backend_host.h +++ b/chrome/browser/sync/glue/sync_backend_host.h @@ -188,6 +188,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { registrar_.routing_info[syncable::AUTOFILL] = GROUP_PASSIVE; registrar_.routing_info[syncable::THEMES] = GROUP_PASSIVE; registrar_.routing_info[syncable::TYPED_URLS] = GROUP_PASSIVE; + registrar_.routing_info[syncable::PASSWORD] = GROUP_PASSIVE; core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(core_.get(), diff --git a/chrome/browser/sync/profile_sync_factory.h b/chrome/browser/sync/profile_sync_factory.h index 5a19f1f..d2cb6e5 100644 --- a/chrome/browser/sync/profile_sync_factory.h +++ b/chrome/browser/sync/profile_sync_factory.h @@ -13,6 +13,7 @@ #include "chrome/browser/sync/unrecoverable_error_handler.h" class PersonalDataManager; +class PasswordStore; class ProfileSyncService; class WebDatabase; @@ -72,6 +73,14 @@ class ProfileSyncFactory { browser_sync::UnrecoverableErrorHandler* error_handler) = 0; // Instantiates both a model associator and change processor for the + // password data type. The pointers in the return struct are + // owned by the caller. + virtual SyncComponents CreatePasswordSyncComponents( + ProfileSyncService* profile_sync_service, + PasswordStore* password_store, + browser_sync::UnrecoverableErrorHandler* error_handler) = 0; + + // Instantiates both a model associator and change processor for the // preference data type. The pointers in the return struct are // owned by the caller. virtual SyncComponents CreatePreferenceSyncComponents( diff --git a/chrome/browser/sync/profile_sync_factory_impl.cc b/chrome/browser/sync/profile_sync_factory_impl.cc index 3efbc76..e7b5e57 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.cc +++ b/chrome/browser/sync/profile_sync_factory_impl.cc @@ -13,6 +13,9 @@ #include "chrome/browser/sync/glue/bookmark_data_type_controller.h" #include "chrome/browser/sync/glue/bookmark_model_associator.h" #include "chrome/browser/sync/glue/data_type_manager_impl.h" +#include "chrome/browser/sync/glue/password_change_processor.h" +#include "chrome/browser/sync/glue/password_data_type_controller.h" +#include "chrome/browser/sync/glue/password_model_associator.h" #include "chrome/browser/sync/glue/preference_change_processor.h" #include "chrome/browser/sync/glue/preference_data_type_controller.h" #include "chrome/browser/sync/glue/preference_model_associator.h" @@ -37,6 +40,9 @@ using browser_sync::BookmarkModelAssociator; using browser_sync::DataTypeController; using browser_sync::DataTypeManager; using browser_sync::DataTypeManagerImpl; +using browser_sync::PasswordChangeProcessor; +using browser_sync::PasswordDataTypeController; +using browser_sync::PasswordModelAssociator; using browser_sync::PreferenceChangeProcessor; using browser_sync::PreferenceDataTypeController; using browser_sync::PreferenceModelAssociator; @@ -82,6 +88,13 @@ ProfileSyncService* ProfileSyncFactoryImpl::CreateProfileSyncService() { new BookmarkDataTypeController(this, profile_, pss)); } + // Password sync is disabled by default. Register only if + // explicitly enabled. + if (command_line_->HasSwitch(switches::kEnableSyncPasswords)) { + pss->RegisterDataTypeController( + new PasswordDataTypeController(this, profile_, pss)); + } + // Preference sync is enabled by default. Register unless explicitly // disabled. if (!command_line_->HasSwitch(switches::kDisableSyncPreferences)) { @@ -144,6 +157,22 @@ ProfileSyncFactoryImpl::CreateBookmarkSyncComponents( } ProfileSyncFactory::SyncComponents +ProfileSyncFactoryImpl::CreatePasswordSyncComponents( + ProfileSyncService* profile_sync_service, + PasswordStore* password_store, + UnrecoverableErrorHandler* error_handler) { + PasswordModelAssociator* model_associator = + new PasswordModelAssociator(profile_sync_service, + password_store, + error_handler); + PasswordChangeProcessor* change_processor = + new PasswordChangeProcessor(model_associator, + password_store, + error_handler); + return SyncComponents(model_associator, change_processor); +} + +ProfileSyncFactory::SyncComponents ProfileSyncFactoryImpl::CreatePreferenceSyncComponents( ProfileSyncService* profile_sync_service, UnrecoverableErrorHandler* error_handler) { diff --git a/chrome/browser/sync/profile_sync_factory_impl.h b/chrome/browser/sync/profile_sync_factory_impl.h index db8dba7..bbce500 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.h +++ b/chrome/browser/sync/profile_sync_factory_impl.h @@ -41,6 +41,11 @@ class ProfileSyncFactoryImpl : public ProfileSyncFactory { ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler); + virtual SyncComponents CreatePasswordSyncComponents( + ProfileSyncService* profile_sync_service, + PasswordStore* password_store, + browser_sync::UnrecoverableErrorHandler* error_handler); + virtual SyncComponents CreatePreferenceSyncComponents( ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler); diff --git a/chrome/browser/sync/profile_sync_factory_mock.h b/chrome/browser/sync/profile_sync_factory_mock.h index 4a4cf92..df760c5 100644 --- a/chrome/browser/sync/profile_sync_factory_mock.h +++ b/chrome/browser/sync/profile_sync_factory_mock.h @@ -37,6 +37,11 @@ class ProfileSyncFactoryMock : public ProfileSyncFactory { MOCK_METHOD2(CreateBookmarkSyncComponents, SyncComponents(ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler)); + MOCK_METHOD3(CreatePasswordSyncComponents, + SyncComponents( + ProfileSyncService* profile_sync_service, + PasswordStore* password_store, + browser_sync::UnrecoverableErrorHandler* error_handler)); MOCK_METHOD2(CreatePreferenceSyncComponents, SyncComponents(ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler)); diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc index 8c6bdc4..fac8b5a 100644 --- a/chrome/browser/sync/profile_sync_service.cc +++ b/chrome/browser/sync/profile_sync_service.cc @@ -206,6 +206,7 @@ void ProfileSyncService::RegisterPreferences() { #endif pref_service->RegisterBooleanPref(prefs::kSyncBookmarks, true); + pref_service->RegisterBooleanPref(prefs::kSyncPasswords, enable_by_default); pref_service->RegisterBooleanPref(prefs::kSyncPreferences, enable_by_default); pref_service->RegisterBooleanPref(prefs::kSyncAutofill, enable_by_default); pref_service->RegisterBooleanPref(prefs::kSyncThemes, enable_by_default); @@ -380,6 +381,8 @@ const wchar_t* ProfileSyncService::GetPrefNameForDataType( switch (data_type) { case syncable::BOOKMARKS: return prefs::kSyncBookmarks; + case syncable::PASSWORD: + return prefs::kSyncPasswords; case syncable::PREFERENCES: return prefs::kSyncPreferences; case syncable::AUTOFILL: diff --git a/chrome/browser/sync/profile_sync_service_password_unittest.cc b/chrome/browser/sync/profile_sync_service_password_unittest.cc new file mode 100644 index 0000000..4038f8e --- /dev/null +++ b/chrome/browser/sync/profile_sync_service_password_unittest.cc @@ -0,0 +1,472 @@ +// 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 <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/task.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "base/waitable_event.h" +#include "chrome/browser/password_manager/password_store.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/password_change_processor.h" +#include "chrome/browser/sync/glue/password_data_type_controller.h" +#include "chrome/browser/sync/glue/password_model_associator.h" +#include "chrome/browser/sync/glue/sync_backend_host_mock.h" +#include "chrome/browser/sync/profile_sync_factory.h" +#include "chrome/browser/sync/profile_sync_factory_mock.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/profile_sync_test_util.h" +#include "chrome/browser/sync/protocol/password_specifics.pb.h" +#include "chrome/browser/sync/syncable/directory_manager.h" +#include "chrome/browser/sync/syncable/syncable.h" +#include "chrome/browser/sync/test_profile_sync_service.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" +#include "chrome/test/sync/engine/test_id_factory.h" +#include "chrome/test/profile_mock.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "webkit/glue/password_form.h" + +using base::Time; +using base::WaitableEvent; +using browser_sync::PasswordChangeProcessor; +using browser_sync::PasswordDataTypeController; +using browser_sync::PasswordModelAssociator; +using browser_sync::SyncBackendHostMock; +using browser_sync::TestIdFactory; +using browser_sync::UnrecoverableErrorHandler; +using sync_api::SyncManager; +using sync_api::UserShare; +using syncable::BASE_VERSION; +using syncable::CREATE; +using syncable::DirectoryManager; +using syncable::ID; +using syncable::IS_DEL; +using syncable::IS_DIR; +using syncable::IS_UNAPPLIED_UPDATE; +using syncable::IS_UNSYNCED; +using syncable::MutableEntry; +using syncable::SERVER_IS_DIR; +using syncable::SERVER_VERSION; +using syncable::SPECIFICS; +using syncable::ScopedDirLookup; +using syncable::UNIQUE_SERVER_TAG; +using syncable::UNITTEST; +using syncable::WriteTransaction; +using testing::_; +using testing::DoAll; +using testing::DoDefault; +using testing::ElementsAre; +using testing::Eq; +using testing::Invoke; +using testing::Return; +using testing::SaveArg; +using testing::SetArgumentPointee; +using webkit_glue::PasswordForm; + +ACTION_P3(MakePasswordSyncComponents, service, ps, dtc) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + PasswordModelAssociator* model_associator = + new PasswordModelAssociator(service, ps, dtc); + PasswordChangeProcessor* change_processor = + new PasswordChangeProcessor(model_associator, ps, dtc); + return ProfileSyncFactory::SyncComponents(model_associator, + change_processor); +} + +class MockPasswordStore : public PasswordStore { + public: + MOCK_METHOD1(RemoveLogin, void(const PasswordForm&)); + MOCK_METHOD2(GetLogins, int(const PasswordForm&, PasswordStoreConsumer*)); + MOCK_METHOD1(AddLogin, void(const PasswordForm&)); + MOCK_METHOD1(UpdateLogin, void(const PasswordForm&)); + MOCK_METHOD1(AddLoginImpl, void(const PasswordForm&)); + MOCK_METHOD1(UpdateLoginImpl, void(const PasswordForm&)); + MOCK_METHOD1(RemoveLoginImpl, void(const PasswordForm&)); + MOCK_METHOD2(RemoveLoginsCreatedBetweenImpl, void(const base::Time&, + const base::Time&)); + MOCK_METHOD2(GetLoginsImpl, void(GetLoginsRequest*, const PasswordForm&)); + MOCK_METHOD1(GetAutofillableLoginsImpl, void(GetLoginsRequest*)); + MOCK_METHOD1(GetBlacklistLoginsImpl, void(GetLoginsRequest*)); + MOCK_METHOD1(FillAutofillableLogins, + bool(std::vector<PasswordForm*>*)); + MOCK_METHOD1(FillBlacklistLogins, + bool(std::vector<PasswordForm*>*)); +}; + +class ProfileSyncServicePasswordTest : public testing::Test { + protected: + ProfileSyncServicePasswordTest() + : ui_thread_(ChromeThread::UI, &message_loop_), + db_thread_(ChromeThread::DB) { + } + + virtual void SetUp() { + password_store_ = new MockPasswordStore(); + db_thread_.Start(); + + notification_service_ = new ThreadNotificationService(&db_thread_); + notification_service_->Init(); + } + + virtual void TearDown() { + service_.reset(); + notification_service_->TearDown(); + db_thread_.Stop(); + MessageLoop::current()->RunAllPending(); + } + + void StartSyncService(Task* task) { + if (!service_.get()) { + service_.reset(new TestProfileSyncService(&factory_, &profile_, + false, false)); + service_->AddObserver(&observer_); + PasswordDataTypeController* data_type_controller = + new PasswordDataTypeController(&factory_, + &profile_, + service_.get()); + + EXPECT_CALL(factory_, CreatePasswordSyncComponents(_, _, _)). + WillOnce(MakePasswordSyncComponents(service_.get(), + password_store_.get(), + data_type_controller)); + EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). + WillOnce(MakeDataTypeManager(&backend_)); + + EXPECT_CALL(profile_, GetPasswordStore(_)). + WillOnce(Return(password_store_.get())); + + // State changes once for the backend init and once for startup done. + EXPECT_CALL(observer_, OnStateChanged()). + WillOnce(InvokeTask(task)). + WillOnce(Return()). + WillOnce(QuitUIMessageLoop()); + service_->RegisterDataTypeController(data_type_controller); + service_->Initialize(); + MessageLoop::current()->Run(); + } + } + + void CreatePasswordRoot() { + UserShare* user_share = service_->backend()->GetUserShareHandle(); + DirectoryManager* dir_manager = user_share->dir_manager.get(); + + ScopedDirLookup dir(dir_manager, user_share->authenticated_name); + ASSERT_TRUE(dir.good()); + + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); + MutableEntry node(&wtrans, + CREATE, + wtrans.root_id(), + browser_sync::kPasswordTag); + node.Put(UNIQUE_SERVER_TAG, browser_sync::kPasswordTag); + node.Put(IS_DIR, true); + node.Put(SERVER_IS_DIR, false); + node.Put(IS_UNSYNCED, false); + node.Put(IS_UNAPPLIED_UPDATE, false); + node.Put(SERVER_VERSION, 20); + node.Put(BASE_VERSION, 20); + node.Put(IS_DEL, false); + node.Put(ID, ids_.MakeServer(browser_sync::kPasswordTag)); + sync_pb::EntitySpecifics specifics; + specifics.MutableExtension(sync_pb::password); + node.Put(SPECIFICS, specifics); + } + + void AddPasswordSyncNode(const PasswordForm& entry) { + sync_api::WriteTransaction trans( + service_->backend()->GetUserShareHandle()); + sync_api::ReadNode password_root(&trans); + ASSERT_TRUE(password_root.InitByTagLookup(browser_sync::kPasswordTag)); + + sync_api::WriteNode node(&trans); + std::string tag = PasswordModelAssociator::MakeTag(entry); + ASSERT_TRUE(node.InitUniqueByCreation(syncable::PASSWORD, + password_root, + tag)); + PasswordModelAssociator::WriteToSyncNode(entry, &node); + } + + void GetPasswordEntriesFromSyncDB(std::vector<PasswordForm>* entries) { + sync_api::ReadTransaction trans(service_->backend()->GetUserShareHandle()); + sync_api::ReadNode password_root(&trans); + ASSERT_TRUE(password_root.InitByTagLookup(browser_sync::kPasswordTag)); + + int64 child_id = password_root.GetFirstChildId(); + while (child_id != sync_api::kInvalidId) { + sync_api::ReadNode child_node(&trans); + ASSERT_TRUE(child_node.InitByIdLookup(child_id)); + + sync_pb::PasswordSpecificsData password; + ASSERT_TRUE(child_node.GetPasswordSpecifics(&password)); + + PasswordForm form; + PasswordModelAssociator::CopyPassword(password, &form); + + entries->push_back(form); + + child_id = child_node.GetSuccessorId(); + } + } + + bool ComparePasswords(const PasswordForm& lhs, const PasswordForm& rhs) { + return lhs.scheme == rhs.scheme && + lhs.signon_realm == rhs.signon_realm && + lhs.origin == rhs.origin && + lhs.action == rhs.action && + lhs.username_element == rhs.username_element && + lhs.username_value == rhs.username_value && + lhs.password_element == rhs.password_element && + lhs.password_value == rhs.password_value && + lhs.ssl_valid == rhs.ssl_valid && + lhs.preferred == rhs.preferred && + lhs.date_created == rhs.date_created && + lhs.blacklisted_by_user == rhs.blacklisted_by_user; + } + + void SetIdleChangeProcessorExpectations() { + EXPECT_CALL(*(password_store_.get()), AddLoginImpl(_)).Times(0); + EXPECT_CALL(*(password_store_.get()), UpdateLoginImpl(_)).Times(0); + EXPECT_CALL(*(password_store_.get()), RemoveLoginImpl(_)).Times(0); + } + + friend class CreatePasswordRootTask; + friend class AddPasswordEntriesTask; + + MessageLoopForUI message_loop_; + ChromeThread ui_thread_; + ChromeThread db_thread_; + scoped_refptr<ThreadNotificationService> notification_service_; + + scoped_ptr<TestProfileSyncService> service_; + ProfileMock profile_; + ProfileSyncFactoryMock factory_; + ProfileSyncServiceObserverMock observer_; + SyncBackendHostMock backend_; + scoped_refptr<MockPasswordStore> password_store_; + + TestIdFactory ids_; +}; + +class CreatePasswordRootTask : public Task { + public: + explicit CreatePasswordRootTask(ProfileSyncServicePasswordTest* test) + : test_(test) { + } + + virtual void Run() { + test_->CreatePasswordRoot(); + } + + private: + ProfileSyncServicePasswordTest* test_; +}; + +class AddPasswordEntriesTask : public Task { + public: + AddPasswordEntriesTask(ProfileSyncServicePasswordTest* test, + const std::vector<PasswordForm>& entries) + : test_(test), entries_(entries) { + } + + virtual void Run() { + test_->CreatePasswordRoot(); + for (size_t i = 0; i < entries_.size(); ++i) { + test_->AddPasswordSyncNode(entries_[i]); + } + } + + private: + ProfileSyncServicePasswordTest* test_; + const std::vector<PasswordForm>& entries_; +}; + +TEST_F(ProfileSyncServicePasswordTest, FailModelAssociation) { + // Backend will be paused but not resumed. + EXPECT_CALL(backend_, RequestPause()). + WillOnce(testing::DoAll(Notify(NotificationType::SYNC_PAUSED), + testing::Return(true))); + // Don't create the root password node so startup fails. + StartSyncService(NULL); + EXPECT_TRUE(service_->unrecoverable_error_detected()); +} + +TEST_F(ProfileSyncServicePasswordTest, EmptyNativeEmptySync) { + EXPECT_CALL(*(password_store_.get()), FillAutofillableLogins(_)) + .WillOnce(Return(true)); + EXPECT_CALL(*(password_store_.get()), FillBlacklistLogins(_)) + .WillOnce(Return(true)); + SetIdleChangeProcessorExpectations(); + CreatePasswordRootTask task(this); + StartSyncService(&task); + std::vector<PasswordForm> sync_entries; + GetPasswordEntriesFromSyncDB(&sync_entries); + EXPECT_EQ(0U, sync_entries.size()); +} + +TEST_F(ProfileSyncServicePasswordTest, HasNativeEntriesEmptySync) { + std::vector<PasswordForm*> forms; + std::vector<PasswordForm> expected_forms; + PasswordForm* new_form = new PasswordForm; + new_form->scheme = PasswordForm::SCHEME_HTML; + new_form->signon_realm = "pie"; + new_form->origin = GURL("http://pie.com"); + new_form->action = GURL("http://pie.com/submit"); + new_form->username_element = UTF8ToUTF16("name"); + new_form->username_value = UTF8ToUTF16("tom"); + new_form->password_element = UTF8ToUTF16("cork"); + new_form->password_value = UTF8ToUTF16("password1"); + new_form->ssl_valid = true; + new_form->preferred = false; + new_form->date_created = base::Time::FromInternalValue(1234); + new_form->blacklisted_by_user = false; + forms.push_back(new_form); + expected_forms.push_back(*new_form); + EXPECT_CALL(*(password_store_.get()), FillAutofillableLogins(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(forms), Return(true))); + EXPECT_CALL(*(password_store_.get()), FillBlacklistLogins(_)) + .WillOnce(Return(true)); + SetIdleChangeProcessorExpectations(); + CreatePasswordRootTask task(this); + StartSyncService(&task); + std::vector<PasswordForm> sync_forms; + GetPasswordEntriesFromSyncDB(&sync_forms); + ASSERT_EQ(1U, sync_forms.size()); + EXPECT_TRUE(ComparePasswords(expected_forms[0], sync_forms[0])); +} + +TEST_F(ProfileSyncServicePasswordTest, HasNativeHasSyncNoMerge) { + std::vector<PasswordForm*> native_forms; + std::vector<PasswordForm> sync_forms; + std::vector<PasswordForm> expected_forms; + { + PasswordForm* new_form = new PasswordForm; + new_form->scheme = PasswordForm::SCHEME_HTML; + new_form->signon_realm = "pie"; + new_form->origin = GURL("http://pie.com"); + new_form->action = GURL("http://pie.com/submit"); + new_form->username_element = UTF8ToUTF16("name"); + new_form->username_value = UTF8ToUTF16("tom"); + new_form->password_element = UTF8ToUTF16("cork"); + new_form->password_value = UTF8ToUTF16("password1"); + new_form->ssl_valid = true; + new_form->preferred = false; + new_form->date_created = base::Time::FromInternalValue(1234); + new_form->blacklisted_by_user = false; + + native_forms.push_back(new_form); + expected_forms.push_back(*new_form); + } + + { + PasswordForm new_form; + new_form.scheme = PasswordForm::SCHEME_HTML; + new_form.signon_realm = "pie2"; + new_form.origin = GURL("http://pie2.com"); + new_form.action = GURL("http://pie2.com/submit"); + new_form.username_element = UTF8ToUTF16("name2"); + new_form.username_value = UTF8ToUTF16("tom2"); + new_form.password_element = UTF8ToUTF16("cork2"); + new_form.password_value = UTF8ToUTF16("password12"); + new_form.ssl_valid = false; + new_form.preferred = true; + new_form.date_created = base::Time::FromInternalValue(12345); + new_form.blacklisted_by_user = false; + sync_forms.push_back(new_form); + expected_forms.push_back(new_form); + } + + EXPECT_CALL(*(password_store_.get()), FillAutofillableLogins(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(native_forms), Return(true))); + EXPECT_CALL(*(password_store_.get()), FillBlacklistLogins(_)) + .WillOnce(Return(true)); + + AddPasswordEntriesTask task(this, sync_forms); + + EXPECT_CALL(*(password_store_.get()), AddLoginImpl(_)).Times(1); + StartSyncService(&task); + + std::vector<PasswordForm> new_sync_forms; + GetPasswordEntriesFromSyncDB(&new_sync_forms); + + EXPECT_EQ(2U, new_sync_forms.size()); + EXPECT_TRUE(ComparePasswords(expected_forms[0], new_sync_forms[0])); + EXPECT_TRUE(ComparePasswords(expected_forms[1], new_sync_forms[1])); +} + +TEST_F(ProfileSyncServicePasswordTest, HasNativeHasSyncMergeEntry) { + std::vector<PasswordForm*> native_forms; + std::vector<PasswordForm> sync_forms; + std::vector<PasswordForm> expected_forms; + { + PasswordForm* new_form = new PasswordForm; + new_form->scheme = PasswordForm::SCHEME_HTML; + new_form->signon_realm = "pie"; + new_form->origin = GURL("http://pie.com"); + new_form->action = GURL("http://pie.com/submit"); + new_form->username_element = UTF8ToUTF16("name"); + new_form->username_value = UTF8ToUTF16("tom"); + new_form->password_element = UTF8ToUTF16("cork"); + new_form->password_value = UTF8ToUTF16("password1"); + new_form->ssl_valid = true; + new_form->preferred = false; + new_form->date_created = base::Time::FromInternalValue(1234); + new_form->blacklisted_by_user = false; + + native_forms.push_back(new_form); + } + + { + PasswordForm new_form; + new_form.scheme = PasswordForm::SCHEME_HTML; + new_form.signon_realm = "pie"; + new_form.origin = GURL("http://pie.com"); + new_form.action = GURL("http://pie.com/submit"); + new_form.username_element = UTF8ToUTF16("name2"); + new_form.username_value = UTF8ToUTF16("tom2"); + new_form.password_element = UTF8ToUTF16("cork2"); + new_form.password_value = UTF8ToUTF16("password12"); + new_form.ssl_valid = false; + new_form.preferred = true; + new_form.date_created = base::Time::FromInternalValue(12345); + new_form.blacklisted_by_user = false; + sync_forms.push_back(new_form); + } + + { + PasswordForm new_form; + new_form.scheme = PasswordForm::SCHEME_HTML; + new_form.signon_realm = "pie"; + new_form.origin = GURL("http://pie.com"); + new_form.action = GURL("http://pie.com/submit"); + new_form.username_element = UTF8ToUTF16("name2"); + new_form.username_value = UTF8ToUTF16("tom2"); + new_form.password_element = UTF8ToUTF16("cork2"); + new_form.password_value = UTF8ToUTF16("password12"); + new_form.ssl_valid = false; + new_form.preferred = true; + new_form.date_created = base::Time::FromInternalValue(12345); + new_form.blacklisted_by_user = false; + expected_forms.push_back(new_form); + } + + EXPECT_CALL(*(password_store_.get()), FillAutofillableLogins(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(native_forms), Return(true))); + EXPECT_CALL(*(password_store_.get()), FillBlacklistLogins(_)) + .WillOnce(Return(true)); + + AddPasswordEntriesTask task(this, sync_forms); + + EXPECT_CALL(*(password_store_.get()), UpdateLoginImpl(_)).Times(1); + StartSyncService(&task); + + std::vector<PasswordForm> new_sync_forms; + GetPasswordEntriesFromSyncDB(&new_sync_forms); + + EXPECT_EQ(1U, new_sync_forms.size()); + EXPECT_TRUE(ComparePasswords(expected_forms[0], new_sync_forms[0])); +} diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc index 4050fb9..01c2fc7 100644 --- a/chrome/browser/sync/syncable/syncable.cc +++ b/chrome/browser/sync/syncable/syncable.cc @@ -37,6 +37,7 @@ #include "chrome/browser/sync/engine/syncer_util.h" #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" +#include "chrome/browser/sync/protocol/password_specifics.pb.h" #include "chrome/browser/sync/protocol/preference_specifics.pb.h" #include "chrome/browser/sync/protocol/service_constants.h" #include "chrome/browser/sync/protocol/theme_specifics.pb.h" diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 2c75d38..523a30a 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2058,9 +2058,17 @@ 'browser/sync/glue/database_model_worker.h', 'browser/sync/glue/history_model_worker.cc', 'browser/sync/glue/history_model_worker.h', + 'browser/sync/glue/password_model_worker.cc', + 'browser/sync/glue/password_model_worker.h', 'browser/sync/glue/http_bridge.cc', 'browser/sync/glue/http_bridge.h', 'browser/sync/glue/model_associator.h', + 'browser/sync/glue/password_change_processor.cc', + 'browser/sync/glue/password_change_processor.h', + 'browser/sync/glue/password_data_type_controller.cc', + 'browser/sync/glue/password_data_type_controller.h', + 'browser/sync/glue/password_model_associator.cc', + 'browser/sync/glue/password_model_associator.h', 'browser/sync/glue/preference_change_processor.cc', 'browser/sync/glue/preference_change_processor.h', 'browser/sync/glue/preference_data_type_controller.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 3a46a7d..48796cd 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -930,6 +930,7 @@ 'browser/sync/profile_sync_factory_mock.h', 'browser/sync/profile_sync_service_autofill_unittest.cc', 'browser/sync/profile_sync_service_mock.h', + 'browser/sync/profile_sync_service_password_unittest.cc', 'browser/sync/profile_sync_service_preference_unittest.cc', 'browser/sync/profile_sync_service_startup_unittest.cc', 'browser/sync/profile_sync_service_typed_url_unittest.cc', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 5243163c..ffa3a378 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -212,6 +212,9 @@ const char kDisableSyncAutofill[] = "disable-sync-autofill"; // Disable syncing of bookmarks. const char kDisableSyncBookmarks[] = "disable-sync-bookmarks"; +// Disable syncing of passwords. +const char kDisableSyncPasswords[] = "disable-sync-passwords"; + // Disable syncing of preferences. const char kDisableSyncPreferences[] = "disable-sync-preferences"; @@ -351,6 +354,9 @@ const char kEnableSyncAutofill[] = "enable-sync-autofill"; // Enable syncing browser bookmarks. const char kEnableSyncBookmarks[] = "enable-sync-bookmarks"; +// Enable syncing browser passwords. +const char kEnableSyncPasswords[] = "enable-sync-passwords"; + // Enable syncing browser preferences. const char kEnableSyncPreferences[] = "enable-sync-preferences"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 62bc9f7..d880ad0 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -74,6 +74,7 @@ extern const char kDisableSiteSpecificQuirks[]; extern const char kDisableSync[]; extern const char kDisableSyncAutofill[]; extern const char kDisableSyncBookmarks[]; +extern const char kDisableSyncPasswords[]; extern const char kDisableSyncPreferences[]; extern const char kDisableSyncThemes[]; extern const char kDisableSyncTypedUrls[]; @@ -114,6 +115,7 @@ extern const char kEnableStatsTable[]; extern const char kEnableSync[]; extern const char kEnableSyncAutofill[]; extern const char kEnableSyncBookmarks[]; +extern const char kEnableSyncPasswords[]; extern const char kEnableSyncPreferences[]; extern const char kEnableSyncThemes[]; extern const char kEnableSyncTypedUrls[]; diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index b2e10ff..2d871ac 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -767,6 +767,7 @@ const wchar_t kSyncHasSetupCompleted[] = L"sync.has_setup_completed"; // Booleans specifying whether the user has selected to sync the following // datatypes. const wchar_t kSyncBookmarks[] = L"sync.bookmarks"; +const wchar_t kSyncPasswords[] = L"sync.passwords"; const wchar_t kSyncPreferences[] = L"sync.preferences"; const wchar_t kSyncAutofill[] = L"sync.autofill"; const wchar_t kSyncThemes[] = L"sync.themes"; diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index defee69..bbad183 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -276,6 +276,7 @@ extern const wchar_t kDevToolsSplitLocation[]; extern const wchar_t kSyncLastSyncedTime[]; extern const wchar_t kSyncHasSetupCompleted[]; extern const wchar_t kSyncBookmarks[]; +extern const wchar_t kSyncPasswords[]; extern const wchar_t kSyncPreferences[]; extern const wchar_t kSyncAutofill[]; extern const wchar_t kSyncThemes[]; diff --git a/chrome/test/profile_mock.h b/chrome/test/profile_mock.h index bfc4329..d7e564bf 100644 --- a/chrome/test/profile_mock.h +++ b/chrome/test/profile_mock.h @@ -16,6 +16,7 @@ class ProfileMock : public TestingProfile { MOCK_METHOD0(GetHistoryServiceWithoutCreating, HistoryService*()); MOCK_METHOD1(GetWebDataService, WebDataService*(ServiceAccessType access)); MOCK_METHOD0(GetPersonalDataManager, PersonalDataManager*()); + MOCK_METHOD1(GetPasswordStore, PasswordStore* (ServiceAccessType access)); }; #endif // CHROME_TEST_PROFILE_MOCK_H__ |