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