summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorzork@chromium.org <zork@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-19 18:06:14 +0000
committerzork@chromium.org <zork@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-05-19 18:06:14 +0000
commit7b8c01166d60c3d075953638c27a0f465e1d5de8 (patch)
tree9f7f0922755d0fd12220de5ab886ed2a9eff264b
parentff5abd13cf1f892c98667f59df0bab5ca54aa382 (diff)
downloadchromium_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
-rw-r--r--chrome/browser/sync/engine/model_safe_worker.h3
-rw-r--r--chrome/browser/sync/engine/syncapi.h1
-rw-r--r--chrome/browser/sync/glue/data_type_manager_impl.cc1
-rw-r--r--chrome/browser/sync/glue/password_change_processor.cc213
-rw-r--r--chrome/browser/sync/glue/password_change_processor.h79
-rw-r--r--chrome/browser/sync/glue/password_data_type_controller.cc144
-rw-r--r--chrome/browser/sync/glue/password_data_type_controller.h89
-rw-r--r--chrome/browser/sync/glue/password_model_associator.cc402
-rw-r--r--chrome/browser/sync/glue/password_model_associator.h158
-rw-r--r--chrome/browser/sync/glue/password_model_worker.cc41
-rw-r--r--chrome/browser/sync/glue/password_model_worker.h43
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.cc10
-rw-r--r--chrome/browser/sync/glue/sync_backend_host.h1
-rw-r--r--chrome/browser/sync/profile_sync_factory.h9
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl.cc29
-rw-r--r--chrome/browser/sync/profile_sync_factory_impl.h5
-rw-r--r--chrome/browser/sync/profile_sync_factory_mock.h5
-rw-r--r--chrome/browser/sync/profile_sync_service.cc3
-rw-r--r--chrome/browser/sync/profile_sync_service_password_unittest.cc472
-rw-r--r--chrome/browser/sync/syncable/syncable.cc1
-rw-r--r--chrome/chrome_browser.gypi8
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/chrome_switches.cc6
-rw-r--r--chrome/common/chrome_switches.h2
-rw-r--r--chrome/common/pref_names.cc1
-rw-r--r--chrome/common/pref_names.h1
-rw-r--r--chrome/test/profile_mock.h1
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__