Home | History | Annotate | Download | only in glue
      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