Index: chrome/browser/sync/glue/password_model_associator.cc |
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 0000000000000000000000000000000000000000..923ee7b16322a6b53bd0d4deca276e36a7d78075 |
--- /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 |