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_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    base::AutoLock lock(abort_association_pending_lock_);
45    abort_association_pending_ = false;
46  }
47
48  // We must not be holding a transaction when we interact with the password
49  // store, as it can post tasks to the UI thread which can itself be blocked
50  // on our transaction, resulting in deadlock. (http://crbug.com/70658)
51  std::vector<webkit_glue::PasswordForm*> passwords;
52  if (!password_store_->FillAutofillableLogins(&passwords) ||
53      !password_store_->FillBlacklistLogins(&passwords)) {
54    STLDeleteElements(&passwords);
55    LOG(ERROR) << "Could not get the password entries.";
56    return false;
57  }
58
59  std::set<std::string> current_passwords;
60  PasswordVector new_passwords;
61  PasswordVector updated_passwords;
62  {
63    sync_api::WriteTransaction trans(sync_service_->GetUserShare());
64    sync_api::ReadNode password_root(&trans);
65    if (!password_root.InitByTagLookup(kPasswordTag)) {
66      LOG(ERROR) << "Server did not create the top-level password node. We "
67                 << "might be running against an out-of-date server.";
68      return false;
69    }
70
71    for (std::vector<webkit_glue::PasswordForm*>::iterator ix =
72             passwords.begin();
73         ix != passwords.end(); ++ix) {
74      if (IsAbortPending())
75        return false;
76      std::string tag = MakeTag(**ix);
77
78      sync_api::ReadNode node(&trans);
79      if (node.InitByClientTagLookup(syncable::PASSWORDS, tag)) {
80        const sync_pb::PasswordSpecificsData& password =
81            node.GetPasswordSpecifics();
82        DCHECK_EQ(tag, MakeTag(password));
83
84        webkit_glue::PasswordForm new_password;
85
86        if (MergePasswords(password, **ix, &new_password)) {
87          sync_api::WriteNode write_node(&trans);
88          if (!write_node.InitByClientTagLookup(syncable::PASSWORDS, tag)) {
89            STLDeleteElements(&passwords);
90            LOG(ERROR) << "Failed to edit password sync node.";
91            return false;
92          }
93          WriteToSyncNode(new_password, &write_node);
94          updated_passwords.push_back(new_password);
95        }
96
97        Associate(&tag, node.GetId());
98      } else {
99        sync_api::WriteNode node(&trans);
100        if (!node.InitUniqueByCreation(syncable::PASSWORDS,
101                                       password_root, tag)) {
102          STLDeleteElements(&passwords);
103          LOG(ERROR) << "Failed to create password sync node.";
104          return false;
105        }
106
107        WriteToSyncNode(**ix, &node);
108
109        Associate(&tag, node.GetId());
110      }
111
112      current_passwords.insert(tag);
113    }
114
115    STLDeleteElements(&passwords);
116
117    int64 sync_child_id = password_root.GetFirstChildId();
118    while (sync_child_id != sync_api::kInvalidId) {
119      sync_api::ReadNode sync_child_node(&trans);
120      if (!sync_child_node.InitByIdLookup(sync_child_id)) {
121        LOG(ERROR) << "Failed to fetch child node.";
122        return false;
123      }
124      const sync_pb::PasswordSpecificsData& password =
125          sync_child_node.GetPasswordSpecifics();
126      std::string tag = MakeTag(password);
127
128      // The password only exists on the server.  Add it to the local
129      // model.
130      if (current_passwords.find(tag) == current_passwords.end()) {
131        webkit_glue::PasswordForm new_password;
132
133        CopyPassword(password, &new_password);
134        Associate(&tag, sync_child_node.GetId());
135        new_passwords.push_back(new_password);
136      }
137
138      sync_child_id = sync_child_node.GetSuccessorId();
139    }
140  }
141
142  // We must not be holding a transaction when we interact with the password
143  // store, as it can post tasks to the UI thread which can itself be blocked
144  // on our transaction, resulting in deadlock. (http://crbug.com/70658)
145  if (!WriteToPasswordStore(&new_passwords, &updated_passwords, NULL)) {
146    LOG(ERROR) << "Failed to write passwords.";
147    return false;
148  }
149
150  return true;
151}
152
153bool PasswordModelAssociator::DeleteAllNodes(
154    sync_api::WriteTransaction* trans) {
155  DCHECK(expected_loop_ == MessageLoop::current());
156  for (PasswordToSyncIdMap::iterator node_id = id_map_.begin();
157       node_id != id_map_.end(); ++node_id) {
158    sync_api::WriteNode sync_node(trans);
159    if (!sync_node.InitByIdLookup(node_id->second)) {
160      LOG(ERROR) << "Typed url node lookup failed.";
161      return false;
162    }
163    sync_node.Remove();
164  }
165
166  id_map_.clear();
167  id_map_inverse_.clear();
168  return true;
169}
170
171bool PasswordModelAssociator::DisassociateModels() {
172  id_map_.clear();
173  id_map_inverse_.clear();
174  return true;
175}
176
177bool PasswordModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
178  DCHECK(has_nodes);
179  *has_nodes = false;
180  int64 password_sync_id;
181  if (!GetSyncIdForTaggedNode(kPasswordTag, &password_sync_id)) {
182    LOG(ERROR) << "Server did not create the top-level password node. We "
183               << "might be running against an out-of-date server.";
184    return false;
185  }
186  sync_api::ReadTransaction trans(sync_service_->GetUserShare());
187
188  sync_api::ReadNode password_node(&trans);
189  if (!password_node.InitByIdLookup(password_sync_id)) {
190    LOG(ERROR) << "Server did not create the top-level password node. We "
191               << "might be running against an out-of-date server.";
192    return false;
193  }
194
195  // The sync model has user created nodes if the password folder has any
196  // children.
197  *has_nodes = sync_api::kInvalidId != password_node.GetFirstChildId();
198  return true;
199}
200
201void PasswordModelAssociator::AbortAssociation() {
202  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
203  base::AutoLock lock(abort_association_pending_lock_);
204  abort_association_pending_ = true;
205}
206
207bool PasswordModelAssociator::CryptoReadyIfNecessary() {
208  // We only access the cryptographer while holding a transaction.
209  sync_api::ReadTransaction trans(sync_service_->GetUserShare());
210  // We always encrypt passwords, so no need to check if encryption is enabled.
211  return sync_service_->IsCryptographerReady(&trans);
212}
213
214const std::string* PasswordModelAssociator::GetChromeNodeFromSyncId(
215    int64 sync_id) {
216  return NULL;
217}
218
219bool PasswordModelAssociator::InitSyncNodeFromChromeId(
220    const std::string& node_id,
221    sync_api::BaseNode* sync_node) {
222  return false;
223}
224
225bool PasswordModelAssociator::IsAbortPending() {
226  base::AutoLock lock(abort_association_pending_lock_);
227  return abort_association_pending_;
228}
229
230int64 PasswordModelAssociator::GetSyncIdFromChromeId(
231    const std::string& password) {
232  PasswordToSyncIdMap::const_iterator iter = id_map_.find(password);
233  return iter == id_map_.end() ? sync_api::kInvalidId : iter->second;
234}
235
236void PasswordModelAssociator::Associate(
237    const std::string* password, int64 sync_id) {
238  DCHECK(expected_loop_ == MessageLoop::current());
239  DCHECK_NE(sync_api::kInvalidId, sync_id);
240  DCHECK(id_map_.find(*password) == id_map_.end());
241  DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end());
242  id_map_[*password] = sync_id;
243  id_map_inverse_[sync_id] = *password;
244}
245
246void PasswordModelAssociator::Disassociate(int64 sync_id) {
247  DCHECK(expected_loop_ == MessageLoop::current());
248  SyncIdToPasswordMap::iterator iter = id_map_inverse_.find(sync_id);
249  if (iter == id_map_inverse_.end())
250    return;
251  CHECK(id_map_.erase(iter->second));
252  id_map_inverse_.erase(iter);
253}
254
255bool PasswordModelAssociator::GetSyncIdForTaggedNode(const std::string& tag,
256                                                     int64* sync_id) {
257  sync_api::ReadTransaction trans(sync_service_->GetUserShare());
258  sync_api::ReadNode sync_node(&trans);
259  if (!sync_node.InitByTagLookup(tag.c_str()))
260    return false;
261  *sync_id = sync_node.GetId();
262  return true;
263}
264
265bool PasswordModelAssociator::WriteToPasswordStore(
266         const PasswordVector* new_passwords,
267         const PasswordVector* updated_passwords,
268         const PasswordVector* deleted_passwords) {
269  if (new_passwords) {
270    for (PasswordVector::const_iterator password = new_passwords->begin();
271         password != new_passwords->end(); ++password) {
272      password_store_->AddLoginImpl(*password);
273    }
274  }
275
276  if (updated_passwords) {
277    for (PasswordVector::const_iterator password = updated_passwords->begin();
278         password != updated_passwords->end(); ++password) {
279      password_store_->UpdateLoginImpl(*password);
280    }
281  }
282
283  if (deleted_passwords) {
284    for (PasswordVector::const_iterator password = deleted_passwords->begin();
285         password != deleted_passwords->end(); ++password) {
286      password_store_->RemoveLoginImpl(*password);
287    }
288  }
289
290  if (new_passwords || updated_passwords || deleted_passwords) {
291    // We have to notify password store observers of the change by hand since
292    // we use internal password store interfaces to make changes synchronously.
293    password_store_->PostNotifyLoginsChanged();
294  }
295  return true;
296}
297
298// static
299void PasswordModelAssociator::CopyPassword(
300        const sync_pb::PasswordSpecificsData& password,
301        webkit_glue::PasswordForm* new_password) {
302  new_password->scheme =
303      static_cast<webkit_glue::PasswordForm::Scheme>(password.scheme());
304  new_password->signon_realm = password.signon_realm();
305  new_password->origin = GURL(password.origin());
306  new_password->action = GURL(password.action());
307  new_password->username_element =
308      UTF8ToUTF16(password.username_element());
309  new_password->password_element =
310      UTF8ToUTF16(password.password_element());
311  new_password->username_value =
312      UTF8ToUTF16(password.username_value());
313  new_password->password_value =
314      UTF8ToUTF16(password.password_value());
315  new_password->ssl_valid = password.ssl_valid();
316  new_password->preferred = password.preferred();
317  new_password->date_created =
318      base::Time::FromInternalValue(password.date_created());
319  new_password->blacklisted_by_user =
320      password.blacklisted();
321}
322
323// static
324bool PasswordModelAssociator::MergePasswords(
325        const sync_pb::PasswordSpecificsData& password,
326        const webkit_glue::PasswordForm& password_form,
327        webkit_glue::PasswordForm* new_password) {
328  DCHECK(new_password);
329
330  if (password.scheme() == password_form.scheme &&
331      password_form.signon_realm == password.signon_realm() &&
332      password_form.origin.spec() == password.origin() &&
333      password_form.action.spec() == password.action() &&
334      UTF16ToUTF8(password_form.username_element) ==
335          password.username_element() &&
336      UTF16ToUTF8(password_form.password_element) ==
337          password.password_element() &&
338      UTF16ToUTF8(password_form.username_value) ==
339          password.username_value() &&
340      UTF16ToUTF8(password_form.password_value) ==
341          password.password_value() &&
342      password.ssl_valid() == password_form.ssl_valid &&
343      password.preferred() == password_form.preferred &&
344      password.date_created() == password_form.date_created.ToInternalValue() &&
345      password.blacklisted() == password_form.blacklisted_by_user) {
346    return false;
347  }
348
349  // If the passwords differ, we take the one that was created more recently.
350  if (base::Time::FromInternalValue(password.date_created()) <=
351      password_form.date_created) {
352    *new_password = password_form;
353  } else {
354    CopyPassword(password, new_password);
355  }
356
357  return true;
358}
359
360// static
361void PasswordModelAssociator::WriteToSyncNode(
362         const webkit_glue::PasswordForm& password_form,
363         sync_api::WriteNode* node) {
364  sync_pb::PasswordSpecificsData password;
365  password.set_scheme(password_form.scheme);
366  password.set_signon_realm(password_form.signon_realm);
367  password.set_origin(password_form.origin.spec());
368  password.set_action(password_form.action.spec());
369  password.set_username_element(UTF16ToUTF8(password_form.username_element));
370  password.set_password_element(UTF16ToUTF8(password_form.password_element));
371  password.set_username_value(UTF16ToUTF8(password_form.username_value));
372  password.set_password_value(UTF16ToUTF8(password_form.password_value));
373  password.set_ssl_valid(password_form.ssl_valid);
374  password.set_preferred(password_form.preferred);
375  password.set_date_created(password_form.date_created.ToInternalValue());
376  password.set_blacklisted(password_form.blacklisted_by_user);
377
378  node->SetPasswordSpecifics(password);
379}
380
381// static
382std::string PasswordModelAssociator::MakeTag(
383                const webkit_glue::PasswordForm& password) {
384  return MakeTag(password.origin.spec(),
385                 UTF16ToUTF8(password.username_element),
386                 UTF16ToUTF8(password.username_value),
387                 UTF16ToUTF8(password.password_element),
388                 password.signon_realm);
389}
390
391// static
392std::string PasswordModelAssociator::MakeTag(
393                const sync_pb::PasswordSpecificsData& password) {
394  return MakeTag(password.origin(),
395                 password.username_element(),
396                 password.username_value(),
397                 password.password_element(),
398                 password.signon_realm());
399}
400
401// static
402std::string PasswordModelAssociator::MakeTag(
403    const std::string& origin_url,
404    const std::string& username_element,
405    const std::string& username_value,
406    const std::string& password_element,
407    const std::string& signon_realm) {
408  return EscapePath(origin_url) + "|" +
409         EscapePath(username_element) + "|" +
410         EscapePath(username_value) + "|" +
411         EscapePath(password_element) + "|" +
412         EscapePath(signon_realm);
413}
414
415}  // namespace browser_sync
416