blob: 9abea4340599022a67a53bff34b8a02080f41ed8 [file] [log] [blame]
// Copyright (c) 2012 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/location.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/password_manager/password_store.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "components/autofill/core/common/password_form.h"
#include "net/base/escape.h"
#include "sync/api/sync_error.h"
#include "sync/internal_api/public/read_node.h"
#include "sync/internal_api/public/read_transaction.h"
#include "sync/internal_api/public/write_node.h"
#include "sync/internal_api/public/write_transaction.h"
#include "sync/protocol/password_specifics.pb.h"
using content::BrowserThread;
namespace browser_sync {
const char kPasswordTag[] = "google_chrome_passwords";
PasswordModelAssociator::PasswordModelAssociator(
ProfileSyncService* sync_service,
PasswordStore* password_store,
DataTypeErrorHandler* error_handler)
: sync_service_(sync_service),
password_store_(password_store),
password_node_id_(syncer::kInvalidId),
abort_association_requested_(false),
expected_loop_(base::MessageLoop::current()),
error_handler_(error_handler) {
DCHECK(sync_service_);
#if defined(OS_MACOSX)
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
#else
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
#endif
}
PasswordModelAssociator::~PasswordModelAssociator() {
DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
}
syncer::SyncError PasswordModelAssociator::AssociateModels(
syncer::SyncMergeResult* local_merge_result,
syncer::SyncMergeResult* syncer_merge_result) {
DCHECK(expected_loop_ == base::MessageLoop::current());
PasswordVector new_passwords;
PasswordVector updated_passwords;
{
base::AutoLock lock(association_lock_);
if (abort_association_requested_)
return syncer::SyncError();
CHECK(password_store_.get());
// We must not be holding a transaction when we interact with the password
// store, as it can post tasks to the UI thread which can itself be blocked
// on our transaction, resulting in deadlock. (http://crbug.com/70658)
std::vector<autofill::PasswordForm*> passwords;
if (!password_store_->FillAutofillableLogins(&passwords) ||
!password_store_->FillBlacklistLogins(&passwords)) {
STLDeleteElements(&passwords);
// Password store often fails to load passwords. Track failures with UMA.
// (http://crbug.com/249000)
UMA_HISTOGRAM_ENUMERATION("Sync.LocalDataFailedToLoad",
ModelTypeToHistogramInt(syncer::PASSWORDS),
syncer::MODEL_TYPE_COUNT);
return syncer::SyncError(FROM_HERE,
syncer::SyncError::DATATYPE_ERROR,
"Could not get the password entries.",
model_type());
}
std::set<std::string> current_passwords;
syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
syncer::ReadNode password_root(&trans);
if (password_root.InitByTagLookup(kPasswordTag) !=
syncer::BaseNode::INIT_OK) {
return error_handler_->CreateAndUploadError(
FROM_HERE,
"Server did not create the top-level password node. We "
"might be running against an out-of-date server.",
model_type());
}
for (std::vector<autofill::PasswordForm*>::iterator ix =
passwords.begin();
ix != passwords.end(); ++ix) {
std::string tag = MakeTag(**ix);
syncer::ReadNode node(&trans);
if (node.InitByClientTagLookup(syncer::PASSWORDS, tag) ==
syncer::BaseNode::INIT_OK) {
const sync_pb::PasswordSpecificsData& password =
node.GetPasswordSpecifics();
DCHECK_EQ(tag, MakeTag(password));
autofill::PasswordForm new_password;
if (MergePasswords(password, **ix, &new_password)) {
syncer::WriteNode write_node(&trans);
if (write_node.InitByClientTagLookup(syncer::PASSWORDS, tag) !=
syncer::BaseNode::INIT_OK) {
STLDeleteElements(&passwords);
return error_handler_->CreateAndUploadError(
FROM_HERE,
"Failed to edit password sync node.",
model_type());
}
WriteToSyncNode(new_password, &write_node);
updated_passwords.push_back(new_password);
}
Associate(&tag, node.GetId());
} else {
syncer::WriteNode node(&trans);
syncer::WriteNode::InitUniqueByCreationResult result =
node.InitUniqueByCreation(syncer::PASSWORDS, password_root, tag);
if (result != syncer::WriteNode::INIT_SUCCESS) {
STLDeleteElements(&passwords);
return error_handler_->CreateAndUploadError(
FROM_HERE,
"Failed to create password sync node.",
model_type());
}
WriteToSyncNode(**ix, &node);
Associate(&tag, node.GetId());
}
current_passwords.insert(tag);
}
STLDeleteElements(&passwords);
int64 sync_child_id = password_root.GetFirstChildId();
while (sync_child_id != syncer::kInvalidId) {
syncer::ReadNode sync_child_node(&trans);
if (sync_child_node.InitByIdLookup(sync_child_id) !=
syncer::BaseNode::INIT_OK) {
return error_handler_->CreateAndUploadError(
FROM_HERE,
"Failed to fetch child node.",
model_type());
}
const sync_pb::PasswordSpecificsData& password =
sync_child_node.GetPasswordSpecifics();
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()) {
autofill::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();
}
}
// We must not be holding a transaction when we interact with the password
// store, as it can post tasks to the UI thread which can itself be blocked
// on our transaction, resulting in deadlock. (http://crbug.com/70658)
return WriteToPasswordStore(&new_passwords,
&updated_passwords,
NULL);
}
bool PasswordModelAssociator::DeleteAllNodes(
syncer::WriteTransaction* trans) {
DCHECK(expected_loop_ == base::MessageLoop::current());
for (PasswordToSyncIdMap::iterator node_id = id_map_.begin();
node_id != id_map_.end(); ++node_id) {
syncer::WriteNode sync_node(trans);
if (sync_node.InitByIdLookup(node_id->second) !=
syncer::BaseNode::INIT_OK) {
LOG(ERROR) << "Typed url node lookup failed.";
return false;
}
sync_node.Tombstone();
}
id_map_.clear();
id_map_inverse_.clear();
return true;
}
syncer::SyncError PasswordModelAssociator::DisassociateModels() {
id_map_.clear();
id_map_inverse_.clear();
return syncer::SyncError();
}
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;
}
syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
syncer::ReadNode password_node(&trans);
if (password_node.InitByIdLookup(password_sync_id) !=
syncer::BaseNode::INIT_OK) {
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 = password_node.HasChildren();
return true;
}
void PasswordModelAssociator::AbortAssociation() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::AutoLock lock(association_lock_);
abort_association_requested_ = true;
password_store_ = NULL;
}
bool PasswordModelAssociator::CryptoReadyIfNecessary() {
// We only access the cryptographer while holding a transaction.
syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
// We always encrypt passwords, so no need to check if encryption is enabled.
return sync_service_->IsCryptographerReady(&trans);
}
const std::string* PasswordModelAssociator::GetChromeNodeFromSyncId(
int64 sync_id) {
return NULL;
}
bool PasswordModelAssociator::InitSyncNodeFromChromeId(
const std::string& node_id,
syncer::BaseNode* sync_node) {
return false;
}
int64 PasswordModelAssociator::GetSyncIdFromChromeId(
const std::string& password) {
PasswordToSyncIdMap::const_iterator iter = id_map_.find(password);
return iter == id_map_.end() ? syncer::kInvalidId : iter->second;
}
void PasswordModelAssociator::Associate(
const std::string* password, int64 sync_id) {
DCHECK(expected_loop_ == base::MessageLoop::current());
DCHECK_NE(syncer::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_ == base::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) {
syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
syncer::ReadNode sync_node(&trans);
if (sync_node.InitByTagLookup(tag.c_str()) != syncer::BaseNode::INIT_OK)
return false;
*sync_id = sync_node.GetId();
return true;
}
syncer::SyncError PasswordModelAssociator::WriteToPasswordStore(
const PasswordVector* new_passwords,
const PasswordVector* updated_passwords,
const PasswordVector* deleted_passwords) {
base::AutoLock lock(association_lock_);
if (abort_association_requested_)
return syncer::SyncError();
CHECK(password_store_.get());
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);
}
}
if (new_passwords || updated_passwords || deleted_passwords) {
// We have to notify password store observers of the change by hand since
// we use internal password store interfaces to make changes synchronously.
password_store_->PostNotifyLoginsChanged();
}
return syncer::SyncError();
}
// static
void PasswordModelAssociator::CopyPassword(
const sync_pb::PasswordSpecificsData& password,
autofill::PasswordForm* new_password) {
new_password->scheme =
static_cast<autofill::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 autofill::PasswordForm& password_form,
autofill::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 autofill::PasswordForm& password_form,
syncer::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 autofill::PasswordForm& password) {
return MakeTag(password.origin.spec(),
UTF16ToUTF8(password.username_element),
UTF16ToUTF8(password.username_value),
UTF16ToUTF8(password.password_element),
password.signon_realm);
}
// static
std::string PasswordModelAssociator::MakeTag(
const sync_pb::PasswordSpecificsData& password) {
return MakeTag(password.origin(),
password.username_element(),
password.username_value(),
password.password_element(),
password.signon_realm());
}
// static
std::string PasswordModelAssociator::MakeTag(
const std::string& origin_url,
const std::string& username_element,
const std::string& username_value,
const std::string& password_element,
const std::string& signon_realm) {
return net::EscapePath(origin_url) + "|" +
net::EscapePath(username_element) + "|" +
net::EscapePath(username_value) + "|" +
net::EscapePath(password_element) + "|" +
net::EscapePath(signon_realm);
}
} // namespace browser_sync