password_model_associator.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 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_model_associator.h"
6
7#include <set>
8
9#include "base/stl_util-inl.h"
10#include "base/utf_string_conversions.h"
11#include "chrome/browser/password_manager/password_store.h"
12#include "chrome/browser/sync/engine/syncapi.h"
13#include "chrome/browser/sync/profile_sync_service.h"
14#include "chrome/browser/sync/protocol/password_specifics.pb.h"
15#include "net/base/escape.h"
16#include "webkit/glue/password_form.h"
17
18namespace browser_sync {
19
20const char kPasswordTag[] = "google_chrome_passwords";
21
22PasswordModelAssociator::PasswordModelAssociator(
23    ProfileSyncService* sync_service,
24    PasswordStore* password_store)
25    : sync_service_(sync_service),
26      password_store_(password_store),
27      password_node_id_(sync_api::kInvalidId),
28      abort_association_pending_(false),
29      expected_loop_(MessageLoop::current()) {
30  DCHECK(sync_service_);
31  DCHECK(password_store_);
32#if defined(OS_MACOSX)
33  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
34#else
35  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
36#endif
37}
38
39PasswordModelAssociator::~PasswordModelAssociator() {}
40
41bool PasswordModelAssociator::AssociateModels() {
42  DCHECK(expected_loop_ == MessageLoop::current());
43  {
44    AutoLock lock(abort_association_pending_lock_);
45    abort_association_pending_ = false;
46  }
47
48  sync_api::WriteTransaction trans(
49      sync_service_->backend()->GetUserShareHandle());
50  sync_api::ReadNode password_root(&trans);
51  if (!password_root.InitByTagLookup(kPasswordTag)) {
52    LOG(ERROR) << "Server did not create the top-level password node. We "
53               << "might be running against an out-of-date server.";
54    return false;
55  }
56
57  std::vector<webkit_glue::PasswordForm*> passwords;
58  if (!password_store_->FillAutofillableLogins(&passwords) ||
59      !password_store_->FillBlacklistLogins(&passwords)) {
60    STLDeleteElements(&passwords);
61    LOG(ERROR) << "Could not get the password entries.";
62    return false;
63  }
64
65  std::set<std::string> current_passwords;
66  PasswordVector new_passwords;
67  PasswordVector updated_passwords;
68
69  for (std::vector<webkit_glue::PasswordForm*>::iterator ix = passwords.begin();
70       ix != passwords.end(); ++ix) {
71    if (IsAbortPending())
72      return false;
73    std::string tag = MakeTag(**ix);
74
75    sync_api::ReadNode node(&trans);
76    if (node.InitByClientTagLookup(syncable::PASSWORDS, tag)) {
77      const sync_pb::PasswordSpecificsData& password =
78          node.GetPasswordSpecifics();
79      DCHECK_EQ(tag, MakeTag(password));
80
81      webkit_glue::PasswordForm new_password;
82
83      if (MergePasswords(password, **ix, &new_password)) {
84        sync_api::WriteNode write_node(&trans);
85        if (!write_node.InitByClientTagLookup(syncable::PASSWORDS, tag)) {
86          STLDeleteElements(&passwords);
87          LOG(ERROR) << "Failed to edit password sync node.";
88          return false;
89        }
90        WriteToSyncNode(new_password, &write_node);
91        updated_passwords.push_back(new_password);
92      }
93
94      Associate(&tag, node.GetId());
95    } else {
96      sync_api::WriteNode node(&trans);
97      if (!node.InitUniqueByCreation(syncable::PASSWORDS,
98                                     password_root, tag)) {
99        STLDeleteElements(&passwords);
100        LOG(ERROR) << "Failed to create password sync node.";
101        return false;
102      }
103
104      WriteToSyncNode(**ix, &node);
105
106      Associate(&tag, node.GetId());
107    }
108
109    current_passwords.insert(tag);
110  }
111
112  STLDeleteElements(&passwords);
113
114  int64 sync_child_id = password_root.GetFirstChildId();
115  while (sync_child_id != sync_api::kInvalidId) {
116    sync_api::ReadNode sync_child_node(&trans);
117    if (!sync_child_node.InitByIdLookup(sync_child_id)) {
118      LOG(ERROR) << "Failed to fetch child node.";
119      return false;
120    }
121    const sync_pb::PasswordSpecificsData& password =
122        sync_child_node.GetPasswordSpecifics();
123    std::string tag = MakeTag(password);
124
125    // The password only exists on the server.  Add it to the local
126    // model.
127    if (current_passwords.find(tag) == current_passwords.end()) {
128      webkit_glue::PasswordForm new_password;
129
130      CopyPassword(password, &new_password);
131      Associate(&tag, sync_child_node.GetId());
132      new_passwords.push_back(new_password);
133    }
134
135    sync_child_id = sync_child_node.GetSuccessorId();
136  }
137
138  if (!WriteToPasswordStore(&new_passwords, &updated_passwords, NULL)) {
139    LOG(ERROR) << "Failed to write passwords.";
140    return false;
141  }
142
143  return true;
144}
145
146bool PasswordModelAssociator::DeleteAllNodes(
147    sync_api::WriteTransaction* trans) {
148  DCHECK(expected_loop_ == MessageLoop::current());
149  for (PasswordToSyncIdMap::iterator node_id = id_map_.begin();
150       node_id != id_map_.end(); ++node_id) {
151    sync_api::WriteNode sync_node(trans);
152    if (!sync_node.InitByIdLookup(node_id->second)) {
153      LOG(ERROR) << "Typed url node lookup failed.";
154      return false;
155    }
156    sync_node.Remove();
157  }
158
159  id_map_.clear();
160  id_map_inverse_.clear();
161  return true;
162}
163
164bool PasswordModelAssociator::DisassociateModels() {
165  id_map_.clear();
166  id_map_inverse_.clear();
167  return true;
168}
169
170bool PasswordModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
171  DCHECK(has_nodes);
172  *has_nodes = false;
173  int64 password_sync_id;
174  if (!GetSyncIdForTaggedNode(kPasswordTag, &password_sync_id)) {
175    LOG(ERROR) << "Server did not create the top-level password node. We "
176               << "might be running against an out-of-date server.";
177    return false;
178  }
179  sync_api::ReadTransaction trans(
180      sync_service_->backend()->GetUserShareHandle());
181
182  sync_api::ReadNode password_node(&trans);
183  if (!password_node.InitByIdLookup(password_sync_id)) {
184    LOG(ERROR) << "Server did not create the top-level password node. We "
185               << "might be running against an out-of-date server.";
186    return false;
187  }
188
189  // The sync model has user created nodes if the password folder has any
190  // children.
191  *has_nodes = sync_api::kInvalidId != password_node.GetFirstChildId();
192  return true;
193}
194
195void PasswordModelAssociator::AbortAssociation() {
196  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
197  AutoLock lock(abort_association_pending_lock_);
198  abort_association_pending_ = true;
199}
200
201const std::string* PasswordModelAssociator::GetChromeNodeFromSyncId(
202    int64 sync_id) {
203  return NULL;
204}
205
206bool PasswordModelAssociator::InitSyncNodeFromChromeId(
207    const std::string& node_id,
208    sync_api::BaseNode* sync_node) {
209  return false;
210}
211
212bool PasswordModelAssociator::IsAbortPending() {
213  AutoLock lock(abort_association_pending_lock_);
214  return abort_association_pending_;
215}
216
217int64 PasswordModelAssociator::GetSyncIdFromChromeId(
218    const std::string& password) {
219  PasswordToSyncIdMap::const_iterator iter = id_map_.find(password);
220  return iter == id_map_.end() ? sync_api::kInvalidId : iter->second;
221}
222
223void PasswordModelAssociator::Associate(
224    const std::string* password, int64 sync_id) {
225  DCHECK(expected_loop_ == MessageLoop::current());
226  DCHECK_NE(sync_api::kInvalidId, sync_id);
227  DCHECK(id_map_.find(*password) == id_map_.end());
228  DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end());
229  id_map_[*password] = sync_id;
230  id_map_inverse_[sync_id] = *password;
231}
232
233void PasswordModelAssociator::Disassociate(int64 sync_id) {
234  DCHECK(expected_loop_ == MessageLoop::current());
235  SyncIdToPasswordMap::iterator iter = id_map_inverse_.find(sync_id);
236  if (iter == id_map_inverse_.end())
237    return;
238  CHECK(id_map_.erase(iter->second));
239  id_map_inverse_.erase(iter);
240}
241
242bool PasswordModelAssociator::GetSyncIdForTaggedNode(const std::string& tag,
243                                                     int64* sync_id) {
244  sync_api::ReadTransaction trans(
245      sync_service_->backend()->GetUserShareHandle());
246  sync_api::ReadNode sync_node(&trans);
247  if (!sync_node.InitByTagLookup(tag.c_str()))
248    return false;
249  *sync_id = sync_node.GetId();
250  return true;
251}
252
253bool PasswordModelAssociator::WriteToPasswordStore(
254         const PasswordVector* new_passwords,
255         const PasswordVector* updated_passwords,
256         const PasswordVector* deleted_passwords) {
257  if (new_passwords) {
258    for (PasswordVector::const_iterator password = new_passwords->begin();
259         password != new_passwords->end(); ++password) {
260      password_store_->AddLoginImpl(*password);
261    }
262  }
263
264  if (updated_passwords) {
265    for (PasswordVector::const_iterator password = updated_passwords->begin();
266         password != updated_passwords->end(); ++password) {
267      password_store_->UpdateLoginImpl(*password);
268    }
269  }
270
271  if (deleted_passwords) {
272    for (PasswordVector::const_iterator password = deleted_passwords->begin();
273         password != deleted_passwords->end(); ++password) {
274      password_store_->RemoveLoginImpl(*password);
275    }
276  }
277  return true;
278}
279
280// static
281void PasswordModelAssociator::CopyPassword(
282        const sync_pb::PasswordSpecificsData& password,
283        webkit_glue::PasswordForm* new_password) {
284  new_password->scheme =
285      static_cast<webkit_glue::PasswordForm::Scheme>(password.scheme());
286  new_password->signon_realm = password.signon_realm();
287  new_password->origin = GURL(password.origin());
288  new_password->action = GURL(password.action());
289  new_password->username_element =
290      UTF8ToUTF16(password.username_element());
291  new_password->password_element =
292      UTF8ToUTF16(password.password_element());
293  new_password->username_value =
294      UTF8ToUTF16(password.username_value());
295  new_password->password_value =
296      UTF8ToUTF16(password.password_value());
297  new_password->ssl_valid = password.ssl_valid();
298  new_password->preferred = password.preferred();
299  new_password->date_created =
300      base::Time::FromInternalValue(password.date_created());
301  new_password->blacklisted_by_user =
302      password.blacklisted();
303}
304
305// static
306bool PasswordModelAssociator::MergePasswords(
307        const sync_pb::PasswordSpecificsData& password,
308        const webkit_glue::PasswordForm& password_form,
309        webkit_glue::PasswordForm* new_password) {
310  DCHECK(new_password);
311
312  if (password.scheme() == password_form.scheme &&
313      password_form.signon_realm == password.signon_realm() &&
314      password_form.origin.spec() == password.origin() &&
315      password_form.action.spec() == password.action() &&
316      UTF16ToUTF8(password_form.username_element) ==
317          password.username_element() &&
318      UTF16ToUTF8(password_form.password_element) ==
319          password.password_element() &&
320      UTF16ToUTF8(password_form.username_value) ==
321          password.username_value() &&
322      UTF16ToUTF8(password_form.password_value) ==
323          password.password_value() &&
324      password.ssl_valid() == password_form.ssl_valid &&
325      password.preferred() == password_form.preferred &&
326      password.date_created() == password_form.date_created.ToInternalValue() &&
327      password.blacklisted() == password_form.blacklisted_by_user) {
328    return false;
329  }
330
331  // If the passwords differ, we take the one that was created more recently.
332  if (base::Time::FromInternalValue(password.date_created()) <=
333      password_form.date_created) {
334    *new_password = password_form;
335  } else {
336    CopyPassword(password, new_password);
337  }
338
339  return true;
340}
341
342// static
343void PasswordModelAssociator::WriteToSyncNode(
344         const webkit_glue::PasswordForm& password_form,
345         sync_api::WriteNode* node) {
346  sync_pb::PasswordSpecificsData password;
347  password.set_scheme(password_form.scheme);
348  password.set_signon_realm(password_form.signon_realm);
349  password.set_origin(password_form.origin.spec());
350  password.set_action(password_form.action.spec());
351  password.set_username_element(UTF16ToUTF8(password_form.username_element));
352  password.set_password_element(UTF16ToUTF8(password_form.password_element));
353  password.set_username_value(UTF16ToUTF8(password_form.username_value));
354  password.set_password_value(UTF16ToUTF8(password_form.password_value));
355  password.set_ssl_valid(password_form.ssl_valid);
356  password.set_preferred(password_form.preferred);
357  password.set_date_created(password_form.date_created.ToInternalValue());
358  password.set_blacklisted(password_form.blacklisted_by_user);
359
360  node->SetPasswordSpecifics(password);
361}
362
363// static
364std::string PasswordModelAssociator::MakeTag(
365                const webkit_glue::PasswordForm& password) {
366  return MakeTag(password.origin.spec(),
367                 UTF16ToUTF8(password.username_element),
368                 UTF16ToUTF8(password.username_value),
369                 UTF16ToUTF8(password.password_element),
370                 password.signon_realm);
371}
372
373// static
374std::string PasswordModelAssociator::MakeTag(
375                const sync_pb::PasswordSpecificsData& password) {
376  return MakeTag(password.origin(),
377                 password.username_element(),
378                 password.username_value(),
379                 password.password_element(),
380                 password.signon_realm());
381}
382
383// static
384std::string PasswordModelAssociator::MakeTag(
385    const std::string& origin_url,
386    const std::string& username_element,
387    const std::string& username_value,
388    const std::string& password_element,
389    const std::string& signon_realm) {
390  return EscapePath(origin_url) + "|" +
391         EscapePath(username_element) + "|" +
392         EscapePath(username_value) + "|" +
393         EscapePath(password_element) + "|" +
394         EscapePath(signon_realm);
395}
396
397}  // namespace browser_sync
398