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