1 // Copyright (c) 2012 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/location.h" 10 #include "base/strings/string_util.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "chrome/browser/chrome_notification_types.h" 13 #include "chrome/browser/password_manager/password_store.h" 14 #include "chrome/browser/password_manager/password_store_change.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/sync/glue/password_model_associator.h" 17 #include "chrome/browser/sync/profile_sync_service.h" 18 #include "content/public/browser/notification_details.h" 19 #include "content/public/browser/notification_source.h" 20 #include "content/public/common/password_form.h" 21 #include "sync/internal_api/public/change_record.h" 22 #include "sync/internal_api/public/read_node.h" 23 #include "sync/internal_api/public/write_node.h" 24 #include "sync/internal_api/public/write_transaction.h" 25 #include "sync/protocol/password_specifics.pb.h" 26 27 using content::BrowserThread; 28 29 namespace browser_sync { 30 31 PasswordChangeProcessor::PasswordChangeProcessor( 32 PasswordModelAssociator* model_associator, 33 PasswordStore* password_store, 34 DataTypeErrorHandler* error_handler) 35 : ChangeProcessor(error_handler), 36 model_associator_(model_associator), 37 password_store_(password_store), 38 expected_loop_(base::MessageLoop::current()), 39 disconnected_(false) { 40 DCHECK(model_associator); 41 DCHECK(error_handler); 42 #if defined(OS_MACOSX) 43 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI)); 44 #else 45 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); 46 #endif 47 } 48 49 PasswordChangeProcessor::~PasswordChangeProcessor() { 50 DCHECK(expected_loop_ == base::MessageLoop::current()); 51 } 52 53 void PasswordChangeProcessor::Observe( 54 int type, 55 const content::NotificationSource& source, 56 const content::NotificationDetails& details) { 57 DCHECK(expected_loop_ == base::MessageLoop::current()); 58 DCHECK(chrome::NOTIFICATION_LOGINS_CHANGED == type); 59 60 base::AutoLock lock(disconnect_lock_); 61 if (disconnected_) 62 return; 63 64 syncer::WriteTransaction trans(FROM_HERE, share_handle()); 65 66 syncer::ReadNode password_root(&trans); 67 if (password_root.InitByTagLookup(kPasswordTag) != 68 syncer::BaseNode::INIT_OK) { 69 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, 70 "Server did not create the top-level password node. " 71 "We might be running against an out-of-date server."); 72 return; 73 } 74 75 PasswordStoreChangeList* changes = 76 content::Details<PasswordStoreChangeList>(details).ptr(); 77 for (PasswordStoreChangeList::iterator change = changes->begin(); 78 change != changes->end(); ++change) { 79 std::string tag = PasswordModelAssociator::MakeTag(change->form()); 80 switch (change->type()) { 81 case PasswordStoreChange::ADD: { 82 syncer::WriteNode sync_node(&trans); 83 syncer::WriteNode::InitUniqueByCreationResult result = 84 sync_node.InitUniqueByCreation(syncer::PASSWORDS, password_root, 85 tag); 86 if (result == syncer::WriteNode::INIT_SUCCESS) { 87 PasswordModelAssociator::WriteToSyncNode(change->form(), &sync_node); 88 model_associator_->Associate(&tag, sync_node.GetId()); 89 break; 90 } else { 91 // Maybe this node already exists and we should update it. 92 // 93 // If the PasswordStore is told to add an entry but an entry with the 94 // same name already exists, it will overwrite it. It will report 95 // this change as an ADD rather than an UPDATE. Ideally, it would be 96 // able to tell us what action was actually taken, rather than what 97 // action was requested. If it did so, we wouldn't need to fall back 98 // to trying to update an existing password node here. 99 // 100 // TODO: Remove this. See crbug.com/87855. 101 int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); 102 if (syncer::kInvalidId == sync_id) { 103 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, 104 "Unable to create or retrieve password node"); 105 LOG(ERROR) << "Invalid sync id."; 106 return; 107 } 108 if (sync_node.InitByIdLookup(sync_id) != 109 syncer::BaseNode::INIT_OK) { 110 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, 111 "Password node lookup failed."); 112 LOG(ERROR) << "Password node lookup failed."; 113 return; 114 } 115 PasswordModelAssociator::WriteToSyncNode(change->form(), &sync_node); 116 break; 117 } 118 } 119 case PasswordStoreChange::UPDATE: { 120 syncer::WriteNode sync_node(&trans); 121 int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); 122 if (syncer::kInvalidId == sync_id) { 123 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, 124 "Invalid sync id"); 125 LOG(ERROR) << "Invalid sync id."; 126 return; 127 } else { 128 if (sync_node.InitByIdLookup(sync_id) != 129 syncer::BaseNode::INIT_OK) { 130 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, 131 "Password node lookup failed."); 132 LOG(ERROR) << "Password node lookup failed."; 133 return; 134 } 135 } 136 137 PasswordModelAssociator::WriteToSyncNode(change->form(), &sync_node); 138 break; 139 } 140 case PasswordStoreChange::REMOVE: { 141 syncer::WriteNode sync_node(&trans); 142 int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); 143 if (syncer::kInvalidId == sync_id) { 144 // We've been asked to remove a password that we don't know about. 145 // That's weird, but apparently we were already in the requested 146 // state, so it's not really an unrecoverable error. Just return. 147 LOG(WARNING) << "Trying to delete nonexistent password sync node!"; 148 return; 149 } else { 150 if (sync_node.InitByIdLookup(sync_id) != 151 syncer::BaseNode::INIT_OK) { 152 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, 153 "Password node lookup failed."); 154 return; 155 } 156 model_associator_->Disassociate(sync_node.GetId()); 157 sync_node.Tombstone(); 158 } 159 break; 160 } 161 } 162 } 163 } 164 165 void PasswordChangeProcessor::ApplyChangesFromSyncModel( 166 const syncer::BaseTransaction* trans, 167 int64 model_version, 168 const syncer::ImmutableChangeRecordList& changes) { 169 DCHECK(expected_loop_ == base::MessageLoop::current()); 170 base::AutoLock lock(disconnect_lock_); 171 if (disconnected_) 172 return; 173 174 syncer::ReadNode password_root(trans); 175 if (password_root.InitByTagLookup(kPasswordTag) != 176 syncer::BaseNode::INIT_OK) { 177 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, 178 "Password root node lookup failed."); 179 return; 180 } 181 182 DCHECK(deleted_passwords_.empty() && new_passwords_.empty() && 183 updated_passwords_.empty()); 184 185 for (syncer::ChangeRecordList::const_iterator it = 186 changes.Get().begin(); it != changes.Get().end(); ++it) { 187 if (syncer::ChangeRecord::ACTION_DELETE == 188 it->action) { 189 DCHECK(it->specifics.has_password()) 190 << "Password specifics data not present on delete!"; 191 DCHECK(it->extra.get()); 192 syncer::ExtraPasswordChangeRecordData* extra = 193 it->extra.get(); 194 const sync_pb::PasswordSpecificsData& password = extra->unencrypted(); 195 content::PasswordForm form; 196 PasswordModelAssociator::CopyPassword(password, &form); 197 deleted_passwords_.push_back(form); 198 model_associator_->Disassociate(it->id); 199 continue; 200 } 201 202 syncer::ReadNode sync_node(trans); 203 if (sync_node.InitByIdLookup(it->id) != syncer::BaseNode::INIT_OK) { 204 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, 205 "Password node lookup failed."); 206 return; 207 } 208 209 // Check that the changed node is a child of the passwords folder. 210 DCHECK_EQ(password_root.GetId(), sync_node.GetParentId()); 211 DCHECK_EQ(syncer::PASSWORDS, sync_node.GetModelType()); 212 213 const sync_pb::PasswordSpecificsData& password_data = 214 sync_node.GetPasswordSpecifics(); 215 content::PasswordForm password; 216 PasswordModelAssociator::CopyPassword(password_data, &password); 217 218 if (syncer::ChangeRecord::ACTION_ADD == it->action) { 219 std::string tag(PasswordModelAssociator::MakeTag(password)); 220 model_associator_->Associate(&tag, sync_node.GetId()); 221 new_passwords_.push_back(password); 222 } else { 223 DCHECK_EQ(syncer::ChangeRecord::ACTION_UPDATE, it->action); 224 updated_passwords_.push_back(password); 225 } 226 } 227 } 228 229 void PasswordChangeProcessor::CommitChangesFromSyncModel() { 230 DCHECK(expected_loop_ == base::MessageLoop::current()); 231 base::AutoLock lock(disconnect_lock_); 232 if (disconnected_) 233 return; 234 235 ScopedStopObserving<PasswordChangeProcessor> stop_observing(this); 236 237 syncer::SyncError error = model_associator_->WriteToPasswordStore( 238 &new_passwords_, 239 &updated_passwords_, 240 &deleted_passwords_); 241 if (error.IsSet()) { 242 error_handler()->OnSingleDatatypeUnrecoverableError(FROM_HERE, 243 "Error writing passwords"); 244 return; 245 } 246 247 deleted_passwords_.clear(); 248 new_passwords_.clear(); 249 updated_passwords_.clear(); 250 } 251 252 void PasswordChangeProcessor::Disconnect() { 253 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 254 base::AutoLock lock(disconnect_lock_); 255 disconnected_ = true; 256 } 257 258 void PasswordChangeProcessor::StartImpl(Profile* profile) { 259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 260 password_store_->ScheduleTask( 261 base::Bind(&PasswordChangeProcessor::StartObserving, 262 base::Unretained(this))); 263 } 264 265 void PasswordChangeProcessor::StartObserving() { 266 DCHECK(expected_loop_ == base::MessageLoop::current()); 267 notification_registrar_.Add(this, 268 chrome::NOTIFICATION_LOGINS_CHANGED, 269 content::Source<PasswordStore>(password_store_)); 270 } 271 272 void PasswordChangeProcessor::StopObserving() { 273 DCHECK(expected_loop_ == base::MessageLoop::current()); 274 notification_registrar_.Remove( 275 this, 276 chrome::NOTIFICATION_LOGINS_CHANGED, 277 content::Source<PasswordStore>(password_store_)); 278 } 279 280 } // namespace browser_sync 281