| // 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_change_processor.h" |
| |
| #include <string> |
| |
| #include "base/location.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/password_manager/password_store.h" |
| #include "chrome/browser/password_manager/password_store_change.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/sync/glue/password_model_associator.h" |
| #include "chrome/browser/sync/profile_sync_service.h" |
| #include "components/autofill/core/common/password_form.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_source.h" |
| #include "sync/internal_api/public/change_record.h" |
| #include "sync/internal_api/public/read_node.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 { |
| |
| PasswordChangeProcessor::PasswordChangeProcessor( |
| PasswordModelAssociator* model_associator, |
| PasswordStore* password_store, |
| DataTypeErrorHandler* error_handler) |
| : ChangeProcessor(error_handler), |
| model_associator_(model_associator), |
| password_store_(password_store), |
| expected_loop_(base::MessageLoop::current()), |
| disconnected_(false) { |
| DCHECK(model_associator); |
| DCHECK(error_handler); |
| #if defined(OS_MACOSX) |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| #else |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); |
| #endif |
| } |
| |
| PasswordChangeProcessor::~PasswordChangeProcessor() { |
| DCHECK(expected_loop_ == base::MessageLoop::current()); |
| } |
| |
| void PasswordChangeProcessor::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK(expected_loop_ == base::MessageLoop::current()); |
| DCHECK(chrome::NOTIFICATION_LOGINS_CHANGED == type); |
| |
| base::AutoLock lock(disconnect_lock_); |
| if (disconnected_) |
| return; |
| |
| syncer::WriteTransaction trans(FROM_HERE, share_handle()); |
| |
| syncer::ReadNode password_root(&trans); |
| if (password_root.InitByTagLookup(kPasswordTag) != |
| syncer::BaseNode::INIT_OK) { |
| error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, |
| "Server did not create the top-level password node. " |
| "We might be running against an out-of-date server."); |
| return; |
| } |
| |
| PasswordStoreChangeList* changes = |
| content::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: { |
| syncer::WriteNode sync_node(&trans); |
| syncer::WriteNode::InitUniqueByCreationResult result = |
| sync_node.InitUniqueByCreation(syncer::PASSWORDS, password_root, |
| tag); |
| if (result == syncer::WriteNode::INIT_SUCCESS) { |
| PasswordModelAssociator::WriteToSyncNode(change->form(), &sync_node); |
| model_associator_->Associate(&tag, sync_node.GetId()); |
| break; |
| } else { |
| // Maybe this node already exists and we should update it. |
| // |
| // If the PasswordStore is told to add an entry but an entry with the |
| // same name already exists, it will overwrite it. It will report |
| // this change as an ADD rather than an UPDATE. Ideally, it would be |
| // able to tell us what action was actually taken, rather than what |
| // action was requested. If it did so, we wouldn't need to fall back |
| // to trying to update an existing password node here. |
| // |
| // TODO: Remove this. See crbug.com/87855. |
| int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); |
| if (syncer::kInvalidId == sync_id) { |
| error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, |
| "Unable to create or retrieve password node"); |
| LOG(ERROR) << "Invalid sync id."; |
| return; |
| } |
| if (sync_node.InitByIdLookup(sync_id) != |
| syncer::BaseNode::INIT_OK) { |
| error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, |
| "Password node lookup failed."); |
| LOG(ERROR) << "Password node lookup failed."; |
| return; |
| } |
| PasswordModelAssociator::WriteToSyncNode(change->form(), &sync_node); |
| break; |
| } |
| } |
| case PasswordStoreChange::UPDATE: { |
| syncer::WriteNode sync_node(&trans); |
| int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); |
| if (syncer::kInvalidId == sync_id) { |
| error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, |
| "Invalid sync id"); |
| LOG(ERROR) << "Invalid sync id."; |
| return; |
| } else { |
| if (sync_node.InitByIdLookup(sync_id) != |
| syncer::BaseNode::INIT_OK) { |
| error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, |
| "Password node lookup failed."); |
| LOG(ERROR) << "Password node lookup failed."; |
| return; |
| } |
| } |
| |
| PasswordModelAssociator::WriteToSyncNode(change->form(), &sync_node); |
| break; |
| } |
| case PasswordStoreChange::REMOVE: { |
| syncer::WriteNode sync_node(&trans); |
| int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); |
| if (syncer::kInvalidId == sync_id) { |
| // We've been asked to remove a password that we don't know about. |
| // That's weird, but apparently we were already in the requested |
| // state, so it's not really an unrecoverable error. Just return. |
| LOG(WARNING) << "Trying to delete nonexistent password sync node!"; |
| return; |
| } else { |
| if (sync_node.InitByIdLookup(sync_id) != |
| syncer::BaseNode::INIT_OK) { |
| error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, |
| "Password node lookup failed."); |
| return; |
| } |
| model_associator_->Disassociate(sync_node.GetId()); |
| sync_node.Tombstone(); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| void PasswordChangeProcessor::ApplyChangesFromSyncModel( |
| const syncer::BaseTransaction* trans, |
| int64 model_version, |
| const syncer::ImmutableChangeRecordList& changes) { |
| DCHECK(expected_loop_ == base::MessageLoop::current()); |
| base::AutoLock lock(disconnect_lock_); |
| if (disconnected_) |
| return; |
| |
| syncer::ReadNode password_root(trans); |
| if (password_root.InitByTagLookup(kPasswordTag) != |
| syncer::BaseNode::INIT_OK) { |
| error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, |
| "Password root node lookup failed."); |
| return; |
| } |
| |
| DCHECK(deleted_passwords_.empty() && new_passwords_.empty() && |
| updated_passwords_.empty()); |
| |
| for (syncer::ChangeRecordList::const_iterator it = |
| changes.Get().begin(); it != changes.Get().end(); ++it) { |
| if (syncer::ChangeRecord::ACTION_DELETE == |
| it->action) { |
| DCHECK(it->specifics.has_password()) |
| << "Password specifics data not present on delete!"; |
| DCHECK(it->extra.get()); |
| syncer::ExtraPasswordChangeRecordData* extra = |
| it->extra.get(); |
| const sync_pb::PasswordSpecificsData& password = extra->unencrypted(); |
| autofill::PasswordForm form; |
| PasswordModelAssociator::CopyPassword(password, &form); |
| deleted_passwords_.push_back(form); |
| model_associator_->Disassociate(it->id); |
| continue; |
| } |
| |
| syncer::ReadNode sync_node(trans); |
| if (sync_node.InitByIdLookup(it->id) != syncer::BaseNode::INIT_OK) { |
| error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, |
| "Password node lookup failed."); |
| return; |
| } |
| |
| // Check that the changed node is a child of the passwords folder. |
| DCHECK_EQ(password_root.GetId(), sync_node.GetParentId()); |
| DCHECK_EQ(syncer::PASSWORDS, sync_node.GetModelType()); |
| |
| const sync_pb::PasswordSpecificsData& password_data = |
| sync_node.GetPasswordSpecifics(); |
| autofill::PasswordForm password; |
| PasswordModelAssociator::CopyPassword(password_data, &password); |
| |
| if (syncer::ChangeRecord::ACTION_ADD == it->action) { |
| std::string tag(PasswordModelAssociator::MakeTag(password)); |
| model_associator_->Associate(&tag, sync_node.GetId()); |
| new_passwords_.push_back(password); |
| } else { |
| DCHECK_EQ(syncer::ChangeRecord::ACTION_UPDATE, it->action); |
| updated_passwords_.push_back(password); |
| } |
| } |
| } |
| |
| void PasswordChangeProcessor::CommitChangesFromSyncModel() { |
| DCHECK(expected_loop_ == base::MessageLoop::current()); |
| base::AutoLock lock(disconnect_lock_); |
| if (disconnected_) |
| return; |
| |
| ScopedStopObserving<PasswordChangeProcessor> stop_observing(this); |
| |
| syncer::SyncError error = model_associator_->WriteToPasswordStore( |
| &new_passwords_, |
| &updated_passwords_, |
| &deleted_passwords_); |
| if (error.IsSet()) { |
| error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, |
| "Error writing passwords"); |
| return; |
| } |
| |
| deleted_passwords_.clear(); |
| new_passwords_.clear(); |
| updated_passwords_.clear(); |
| } |
| |
| void PasswordChangeProcessor::Disconnect() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| base::AutoLock lock(disconnect_lock_); |
| disconnected_ = true; |
| password_store_ = NULL; |
| } |
| |
| void PasswordChangeProcessor::StartImpl(Profile* profile) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| password_store_->ScheduleTask( |
| base::Bind(&PasswordChangeProcessor::InitObserving, |
| base::Unretained(this))); |
| } |
| |
| void PasswordChangeProcessor::InitObserving() { |
| base::AutoLock lock(disconnect_lock_); |
| if (disconnected_) |
| return; |
| StartObserving(); |
| } |
| |
| void PasswordChangeProcessor::StartObserving() { |
| DCHECK(expected_loop_ == base::MessageLoop::current()); |
| disconnect_lock_.AssertAcquired(); |
| notification_registrar_.Add(this, |
| chrome::NOTIFICATION_LOGINS_CHANGED, |
| content::Source<PasswordStore>(password_store_)); |
| } |
| |
| void PasswordChangeProcessor::StopObserving() { |
| DCHECK(expected_loop_ == base::MessageLoop::current()); |
| disconnect_lock_.AssertAcquired(); |
| notification_registrar_.Remove( |
| this, |
| chrome::NOTIFICATION_LOGINS_CHANGED, |
| content::Source<PasswordStore>(password_store_)); |
| } |
| |
| } // namespace browser_sync |