1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/sync/glue/password_change_processor.h" 6 7#include <string> 8 9#include "base/string_util.h" 10#include "base/utf_string_conversions.h" 11#include "chrome/browser/password_manager/password_store.h" 12#include "chrome/browser/password_manager/password_store_change.h" 13#include "chrome/browser/profiles/profile.h" 14#include "chrome/browser/sync/glue/password_model_associator.h" 15#include "chrome/browser/sync/profile_sync_service.h" 16#include "chrome/browser/sync/protocol/password_specifics.pb.h" 17#include "content/common/notification_details.h" 18#include "content/common/notification_source.h" 19#include "content/common/notification_type.h" 20#include "webkit/glue/password_form.h" 21 22namespace browser_sync { 23 24PasswordChangeProcessor::PasswordChangeProcessor( 25 PasswordModelAssociator* model_associator, 26 PasswordStore* password_store, 27 UnrecoverableErrorHandler* error_handler) 28 : ChangeProcessor(error_handler), 29 model_associator_(model_associator), 30 password_store_(password_store), 31 observing_(false), 32 expected_loop_(MessageLoop::current()) { 33 DCHECK(model_associator); 34 DCHECK(error_handler); 35#if defined(OS_MACOSX) 36 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); 37#else 38 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 39#endif 40 StartObserving(); 41} 42 43PasswordChangeProcessor::~PasswordChangeProcessor() { 44 DCHECK(expected_loop_ == MessageLoop::current()); 45} 46 47void PasswordChangeProcessor::Observe(NotificationType type, 48 const NotificationSource& source, 49 const NotificationDetails& details) { 50 DCHECK(expected_loop_ == MessageLoop::current()); 51 DCHECK(NotificationType::LOGINS_CHANGED == type); 52 if (!observing_) 53 return; 54 55 DCHECK(running()); 56 57 sync_api::WriteTransaction trans(share_handle()); 58 59 sync_api::ReadNode password_root(&trans); 60 if (!password_root.InitByTagLookup(kPasswordTag)) { 61 error_handler()->OnUnrecoverableError(FROM_HERE, 62 "Server did not create the top-level password node. " 63 "We might be running against an out-of-date server."); 64 return; 65 } 66 67 PasswordStoreChangeList* changes = 68 Details<PasswordStoreChangeList>(details).ptr(); 69 for (PasswordStoreChangeList::iterator change = changes->begin(); 70 change != changes->end(); ++change) { 71 std::string tag = PasswordModelAssociator::MakeTag(change->form()); 72 switch (change->type()) { 73 case PasswordStoreChange::ADD: { 74 sync_api::WriteNode sync_node(&trans); 75 if (!sync_node.InitUniqueByCreation(syncable::PASSWORDS, 76 password_root, tag)) { 77 error_handler()->OnUnrecoverableError(FROM_HERE, 78 "Failed to create password sync node."); 79 return; 80 } 81 82 PasswordModelAssociator::WriteToSyncNode(change->form(), &sync_node); 83 model_associator_->Associate(&tag, sync_node.GetId()); 84 break; 85 } 86 case PasswordStoreChange::UPDATE: { 87 sync_api::WriteNode sync_node(&trans); 88 int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); 89 if (sync_api::kInvalidId == sync_id) { 90 error_handler()->OnUnrecoverableError(FROM_HERE, 91 "Unexpected notification for: "); 92 return; 93 } else { 94 if (!sync_node.InitByIdLookup(sync_id)) { 95 error_handler()->OnUnrecoverableError(FROM_HERE, 96 "Password node lookup failed."); 97 return; 98 } 99 } 100 101 PasswordModelAssociator::WriteToSyncNode(change->form(), &sync_node); 102 break; 103 } 104 case PasswordStoreChange::REMOVE: { 105 sync_api::WriteNode sync_node(&trans); 106 int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); 107 if (sync_api::kInvalidId == sync_id) { 108 error_handler()->OnUnrecoverableError(FROM_HERE, 109 "Unexpected notification"); 110 return; 111 } else { 112 if (!sync_node.InitByIdLookup(sync_id)) { 113 error_handler()->OnUnrecoverableError(FROM_HERE, 114 "Password node lookup failed."); 115 return; 116 } 117 model_associator_->Disassociate(sync_node.GetId()); 118 sync_node.Remove(); 119 } 120 break; 121 } 122 } 123 } 124} 125 126void PasswordChangeProcessor::ApplyChangesFromSyncModel( 127 const sync_api::BaseTransaction* trans, 128 const sync_api::SyncManager::ChangeRecord* changes, 129 int change_count) { 130 DCHECK(expected_loop_ == MessageLoop::current()); 131 if (!running()) 132 return; 133 134 sync_api::ReadNode password_root(trans); 135 if (!password_root.InitByTagLookup(kPasswordTag)) { 136 error_handler()->OnUnrecoverableError(FROM_HERE, 137 "Password root node lookup failed."); 138 return; 139 } 140 141 DCHECK(deleted_passwords_.empty() && new_passwords_.empty() && 142 updated_passwords_.empty()); 143 144 for (int i = 0; i < change_count; ++i) { 145 if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == 146 changes[i].action) { 147 DCHECK(changes[i].specifics.HasExtension(sync_pb::password)) 148 << "Password specifics data not present on delete!"; 149 DCHECK(changes[i].extra.get()); 150 sync_api::SyncManager::ExtraPasswordChangeRecordData* extra = 151 changes[i].extra.get(); 152 const sync_pb::PasswordSpecificsData& password = extra->unencrypted(); 153 webkit_glue::PasswordForm form; 154 PasswordModelAssociator::CopyPassword(password, &form); 155 deleted_passwords_.push_back(form); 156 model_associator_->Disassociate(changes[i].id); 157 continue; 158 } 159 160 sync_api::ReadNode sync_node(trans); 161 if (!sync_node.InitByIdLookup(changes[i].id)) { 162 error_handler()->OnUnrecoverableError(FROM_HERE, 163 "Password node lookup failed."); 164 return; 165 } 166 167 // Check that the changed node is a child of the passwords folder. 168 DCHECK(password_root.GetId() == sync_node.GetParentId()); 169 DCHECK(syncable::PASSWORDS == sync_node.GetModelType()); 170 171 const sync_pb::PasswordSpecificsData& password_data = 172 sync_node.GetPasswordSpecifics(); 173 webkit_glue::PasswordForm password; 174 PasswordModelAssociator::CopyPassword(password_data, &password); 175 176 if (sync_api::SyncManager::ChangeRecord::ACTION_ADD == changes[i].action) { 177 std::string tag(PasswordModelAssociator::MakeTag(password)); 178 model_associator_->Associate(&tag, sync_node.GetId()); 179 new_passwords_.push_back(password); 180 } else { 181 DCHECK(sync_api::SyncManager::ChangeRecord::ACTION_UPDATE == 182 changes[i].action); 183 updated_passwords_.push_back(password); 184 } 185 } 186} 187 188void PasswordChangeProcessor::CommitChangesFromSyncModel() { 189 DCHECK(expected_loop_ == MessageLoop::current()); 190 if (!running()) 191 return; 192 StopObserving(); 193 194 if (!model_associator_->WriteToPasswordStore(&new_passwords_, 195 &updated_passwords_, 196 &deleted_passwords_)) { 197 error_handler()->OnUnrecoverableError(FROM_HERE, "Error writing passwords"); 198 return; 199 } 200 201 deleted_passwords_.clear(); 202 new_passwords_.clear(); 203 updated_passwords_.clear(); 204 205 StartObserving(); 206} 207 208void PasswordChangeProcessor::StartImpl(Profile* profile) { 209 DCHECK(expected_loop_ == MessageLoop::current()); 210 observing_ = true; 211} 212 213void PasswordChangeProcessor::StopImpl() { 214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 215 observing_ = false; 216} 217 218 219void PasswordChangeProcessor::StartObserving() { 220 DCHECK(expected_loop_ == MessageLoop::current()); 221 notification_registrar_.Add(this, 222 NotificationType::LOGINS_CHANGED, 223 Source<PasswordStore>(password_store_)); 224} 225 226void PasswordChangeProcessor::StopObserving() { 227 DCHECK(expected_loop_ == MessageLoop::current()); 228 notification_registrar_.Remove(this, 229 NotificationType::LOGINS_CHANGED, 230 Source<PasswordStore>(password_store_)); 231} 232 233} // namespace browser_sync 234