root/components/password_manager/core/browser/password_syncable_service.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. MergeLocalAndSyncPasswords
  2. MakePasswordSyncTag
  3. MakePasswordSyncTag
  4. GetSyncChangeType
  5. AppendChanges
  6. is_processing_sync_changes_
  7. MergeDataAndStartSyncing
  8. StopSyncing
  9. GetAllSyncData
  10. ProcessSyncChanges
  11. ActOnPasswordStoreChanges
  12. InjectStartSyncFlare
  13. ReadFromPasswordStore
  14. WriteToPasswordStore
  15. NotifyPasswordStoreOfLoginChanges
  16. CreateOrUpdateEntry
  17. SyncDataFromPassword
  18. PasswordFromSpecifics
  19. MakePasswordSyncTag

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/password_manager/core/browser/password_syncable_service.h"

#include "base/auto_reset.h"
#include "base/location.h"
#include "base/memory/scoped_vector.h"
#include "base/metrics/histogram.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/password_store.h"
#include "net/base/escape.h"
#include "sync/api/sync_error_factory.h"

namespace {

// Describes the result of merging sync and local passwords.
enum MergeResult {
  IDENTICAL,
  SYNC,
  LOCAL,
};

// Merges the local and sync passwords and outputs the entry into
// |new_password_form|. Returns MergeResult value describing the content of
// |new_password_form|.
MergeResult MergeLocalAndSyncPasswords(
    const sync_pb::PasswordSpecificsData& password_specifics,
    const autofill::PasswordForm& password_form,
    autofill::PasswordForm* new_password_form) {
  DCHECK(new_password_form);
  if (password_form.scheme == password_specifics.scheme() &&
      password_form.signon_realm == password_specifics.signon_realm() &&
      password_form.origin.spec() == password_specifics.origin() &&
      password_form.action.spec() == password_specifics.action() &&
      base::UTF16ToUTF8(password_form.username_element) ==
          password_specifics.username_element() &&
      base::UTF16ToUTF8(password_form.password_element) ==
          password_specifics.password_element() &&
      base::UTF16ToUTF8(password_form.username_value) ==
          password_specifics.username_value() &&
      base::UTF16ToUTF8(password_form.password_value) ==
          password_specifics.password_value() &&
      password_form.ssl_valid == password_specifics.ssl_valid() &&
      password_form.preferred == password_specifics.preferred() &&
      password_form.date_created.ToInternalValue() ==
          password_specifics.date_created() &&
      password_form.blacklisted_by_user == password_specifics.blacklisted()) {
    return IDENTICAL;
  }

  // If the passwords differ, take the one that was created more recently.
  if (base::Time::FromInternalValue(password_specifics.date_created()) <=
          password_form.date_created) {
    *new_password_form = password_form;
    return LOCAL;
  }

  PasswordFromSpecifics(password_specifics, new_password_form);
  return SYNC;
}

std::string MakePasswordSyncTag(const std::string& origin_url,
                                const std::string& username_element,
                                const std::string& username_value,
                                const std::string& password_element,
                                const std::string& signon_realm) {
  return net::EscapePath(origin_url) + "|" +
         net::EscapePath(username_element) + "|" +
         net::EscapePath(username_value) + "|" +
         net::EscapePath(password_element) + "|" +
         net::EscapePath(signon_realm);
}

std::string MakePasswordSyncTag(const autofill::PasswordForm& password) {
  return MakePasswordSyncTag(password.origin.spec(),
                             base::UTF16ToUTF8(password.username_element),
                             base::UTF16ToUTF8(password.username_value),
                             base::UTF16ToUTF8(password.password_element),
                             password.signon_realm);
}

syncer::SyncChange::SyncChangeType GetSyncChangeType(
    PasswordStoreChange::Type type) {
  switch (type) {
    case PasswordStoreChange::ADD:
      return syncer::SyncChange::ACTION_ADD;
    case PasswordStoreChange::UPDATE:
      return syncer::SyncChange::ACTION_UPDATE;
    case PasswordStoreChange::REMOVE:
      return syncer::SyncChange::ACTION_DELETE;
  }
  NOTREACHED();
  return syncer::SyncChange::ACTION_INVALID;
}

void AppendChanges(const PasswordStoreChangeList& new_changes,
                   PasswordStoreChangeList* all_changes) {
  all_changes->insert(all_changes->end(),
                      new_changes.begin(),
                      new_changes.end());
}

}  // namespace

PasswordSyncableService::PasswordSyncableService(PasswordStore* password_store)
    : password_store_(password_store),
      is_processing_sync_changes_(false) {
}

PasswordSyncableService::~PasswordSyncableService() {}

syncer::SyncMergeResult PasswordSyncableService::MergeDataAndStartSyncing(
    syncer::ModelType type,
    const syncer::SyncDataList& initial_sync_data,
    scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
    scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(syncer::PASSWORDS, type);
  base::AutoReset<bool> processing_changes(&is_processing_sync_changes_, true);
  syncer::SyncMergeResult merge_result(type);
  sync_error_factory_ = sync_error_factory.Pass();
  sync_processor_ = sync_processor.Pass();

  // We add all the db entries as |new_local_entries| initially. During model
  // association entries that match a sync entry will be removed and this list
  // will only contain entries that are not in sync.
  ScopedVector<autofill::PasswordForm> password_entries;
  PasswordEntryMap new_local_entries;
  if (!ReadFromPasswordStore(&password_entries, &new_local_entries)) {
    DCHECK(sync_error_factory_);
    merge_result.set_error(sync_error_factory_->CreateAndUploadError(
        FROM_HERE,
        "Failed to get passwords from store."));
    return merge_result;
  }

  merge_result.set_num_items_before_association(new_local_entries.size());

  // List that contains the entries that are known only to sync.
  ScopedVector<autofill::PasswordForm> new_sync_entries;

  // List that contains the entries that are known to both sync and db but
  // have updates in sync. They need to be updated in the passwords db.
  ScopedVector<autofill::PasswordForm> updated_sync_entries;

  // Changes from password db that need to be propagated to sync.
  syncer::SyncChangeList updated_db_entries;
  for (syncer::SyncDataList::const_iterator sync_iter =
           initial_sync_data.begin();
       sync_iter != initial_sync_data.end(); ++sync_iter) {
    CreateOrUpdateEntry(*sync_iter,
                        &new_local_entries,
                        &new_sync_entries,
                        &updated_sync_entries,
                        &updated_db_entries);
  }

  WriteToPasswordStore(new_sync_entries.get(),
                       updated_sync_entries.get(),
                       PasswordForms());

  merge_result.set_num_items_after_association(
      merge_result.num_items_before_association() + new_sync_entries.size());

  merge_result.set_num_items_added(new_sync_entries.size());

  merge_result.set_num_items_modified(updated_sync_entries.size());

  for (PasswordEntryMap::iterator it = new_local_entries.begin();
       it != new_local_entries.end();
       ++it) {
    updated_db_entries.push_back(
        syncer::SyncChange(FROM_HERE,
                           syncer::SyncChange::ACTION_ADD,
                           SyncDataFromPassword(*it->second)));
  }

  merge_result.set_error(
      sync_processor_->ProcessSyncChanges(FROM_HERE, updated_db_entries));
  return merge_result;
}

void PasswordSyncableService::StopSyncing(syncer::ModelType type) {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(syncer::PASSWORDS, type);

  sync_processor_.reset();
  sync_error_factory_.reset();
}

syncer::SyncDataList PasswordSyncableService::GetAllSyncData(
    syncer::ModelType type) const {
  DCHECK(CalledOnValidThread());
  DCHECK_EQ(syncer::PASSWORDS, type);
  ScopedVector<autofill::PasswordForm> password_entries;
  ReadFromPasswordStore(&password_entries, NULL);

  syncer::SyncDataList sync_data;
  for (PasswordForms::iterator it = password_entries.begin();
       it != password_entries.end(); ++it) {
    sync_data.push_back(SyncDataFromPassword(**it));
  }
  return sync_data;
}

syncer::SyncError PasswordSyncableService::ProcessSyncChanges(
    const tracked_objects::Location& from_here,
    const syncer::SyncChangeList& change_list) {
  DCHECK(CalledOnValidThread());
  base::AutoReset<bool> processing_changes(&is_processing_sync_changes_, true);
  // The |db_entries_map| and associated vectors are filled only in case of
  // update change.
  PasswordEntryMap db_entries_map;
  ScopedVector<autofill::PasswordForm> password_db_entries;
  ScopedVector<autofill::PasswordForm> new_sync_entries;
  ScopedVector<autofill::PasswordForm> updated_sync_entries;
  ScopedVector<autofill::PasswordForm> deleted_entries;
  bool has_read_passwords = false;

  for (syncer::SyncChangeList::const_iterator it = change_list.begin();
       it != change_list.end();
       ++it) {
    switch (it->change_type()) {
      case syncer::SyncChange::ACTION_ADD: {
        const sync_pb::EntitySpecifics& specifics =
            it->sync_data().GetSpecifics();
        new_sync_entries.push_back(new autofill::PasswordForm);
        PasswordFromSpecifics(specifics.password().client_only_encrypted_data(),
                              new_sync_entries.back());
        break;
      }
      case syncer::SyncChange::ACTION_UPDATE: {
        if (!has_read_passwords) {
          if (ReadFromPasswordStore(&password_db_entries,
                                    &db_entries_map)) {
            has_read_passwords = true;
          } else {
            return sync_error_factory_->CreateAndUploadError(
                FROM_HERE,
                "Failed to get passwords from store.");
          }
        }
        syncer::SyncChangeList sync_changes;

        CreateOrUpdateEntry(it->sync_data(),
                            &db_entries_map,
                            &new_sync_entries,
                            &updated_sync_entries,
                            &sync_changes);
        DCHECK(sync_changes.empty());
        break;
      }

      case syncer::SyncChange::ACTION_DELETE: {
        const sync_pb::EntitySpecifics& specifics =
            it->sync_data().GetSpecifics();
        const sync_pb::PasswordSpecificsData& password_specifics(
            specifics.password().client_only_encrypted_data());
        deleted_entries.push_back(new autofill::PasswordForm);
        PasswordFromSpecifics(password_specifics, deleted_entries.back());
        break;
      }
      case syncer::SyncChange::ACTION_INVALID:
        return sync_error_factory_->CreateAndUploadError(
            FROM_HERE,
            "Failed to process sync changes for passwords datatype.");
    }
  }
  WriteToPasswordStore(new_sync_entries.get(),
                       updated_sync_entries.get(),
                       deleted_entries.get());
  return syncer::SyncError();
}

void PasswordSyncableService::ActOnPasswordStoreChanges(
    const PasswordStoreChangeList& local_changes) {
  DCHECK(CalledOnValidThread());

  if (!sync_processor_) {
    if (!flare_.is_null()) {
      flare_.Run(syncer::PASSWORDS);
      flare_.Reset();
    }
    return;
  }

  // ActOnPasswordStoreChanges() can be called from ProcessSyncChanges(). Do
  // nothing in this case.
  if (is_processing_sync_changes_)
    return;
  syncer::SyncChangeList sync_changes;
  for (PasswordStoreChangeList::const_iterator it = local_changes.begin();
       it != local_changes.end();
       ++it) {
    sync_changes.push_back(
        syncer::SyncChange(FROM_HERE,
                           GetSyncChangeType(it->type()),
                           SyncDataFromPassword(it->form())));
  }
  sync_processor_->ProcessSyncChanges(FROM_HERE, sync_changes);
}

void PasswordSyncableService::InjectStartSyncFlare(
    const syncer::SyncableService::StartSyncFlare& flare) {
  DCHECK(CalledOnValidThread());
  flare_ = flare;
}

bool PasswordSyncableService::ReadFromPasswordStore(
    ScopedVector<autofill::PasswordForm>* password_entries,
    PasswordEntryMap* passwords_entry_map) const {
  DCHECK(password_entries);
  if (!password_store_->FillAutofillableLogins(&password_entries->get()) ||
      !password_store_->FillBlacklistLogins(&password_entries->get())) {
    // Password store often fails to load passwords. Track failures with UMA.
    // (http://crbug.com/249000)
    UMA_HISTOGRAM_ENUMERATION("Sync.LocalDataFailedToLoad",
                              ModelTypeToHistogramInt(syncer::PASSWORDS),
                              syncer::MODEL_TYPE_COUNT);
    return false;
  }

  if (!passwords_entry_map)
    return true;

  for (PasswordForms::iterator it = password_entries->begin();
       it != password_entries->end(); ++it) {
     autofill::PasswordForm* password_form = *it;
     passwords_entry_map->insert(
         std::make_pair(MakePasswordSyncTag(*password_form), password_form));
  }

  return true;
}

void PasswordSyncableService::WriteToPasswordStore(
    const PasswordForms& new_entries,
    const PasswordForms& updated_entries,
    const PasswordForms& deleted_entries) {
  PasswordStoreChangeList changes;
  for (std::vector<autofill::PasswordForm*>::const_iterator it =
           new_entries.begin();
       it != new_entries.end();
       ++it) {
    AppendChanges(password_store_->AddLoginImpl(**it), &changes);
  }

  for (std::vector<autofill::PasswordForm*>::const_iterator it =
           updated_entries.begin();
       it != updated_entries.end();
       ++it) {
    AppendChanges(password_store_->UpdateLoginImpl(**it), &changes);
  }

  for (std::vector<autofill::PasswordForm*>::const_iterator it =
           deleted_entries.begin();
       it != deleted_entries.end();
       ++it) {
    AppendChanges(password_store_->RemoveLoginImpl(**it), &changes);
  }

  // We have to notify password store observers of the change by hand since
  // we use internal password store interfaces to make changes synchronously.
  NotifyPasswordStoreOfLoginChanges(changes);
}

void PasswordSyncableService::NotifyPasswordStoreOfLoginChanges(
    const PasswordStoreChangeList& changes) {
  password_store_->NotifyLoginsChanged(changes);
}

void PasswordSyncableService::CreateOrUpdateEntry(
    const syncer::SyncData& data,
    PasswordEntryMap* umatched_data_from_password_db,
    ScopedVector<autofill::PasswordForm>* new_sync_entries,
    ScopedVector<autofill::PasswordForm>* updated_sync_entries,
    syncer::SyncChangeList* updated_db_entries) {
  const sync_pb::EntitySpecifics& specifics = data.GetSpecifics();
  const sync_pb::PasswordSpecificsData& password_specifics(
      specifics.password().client_only_encrypted_data());
  std::string tag = MakePasswordSyncTag(password_specifics);

  // Check whether the data from sync is already in the password store.
  PasswordEntryMap::iterator existing_local_entry_iter =
      umatched_data_from_password_db->find(tag);
  if (existing_local_entry_iter == umatched_data_from_password_db->end()) {
    // The sync data is not in the password store, so we need to create it in
    // the password store. Add the entry to the new_entries list.
    scoped_ptr<autofill::PasswordForm> new_password(new autofill::PasswordForm);
    PasswordFromSpecifics(password_specifics, new_password.get());
    new_sync_entries->push_back(new_password.release());
  } else {
    // The entry is in password store. If the entries are not identical, then
    // the entries need to be merged.
    scoped_ptr<autofill::PasswordForm> new_password(new autofill::PasswordForm);
    switch (MergeLocalAndSyncPasswords(password_specifics,
                                       *existing_local_entry_iter->second,
                                       new_password.get())) {
      case IDENTICAL:
        break;
      case SYNC:
        updated_sync_entries->push_back(new_password.release());
        break;
      case LOCAL:
        updated_db_entries->push_back(
            syncer::SyncChange(FROM_HERE,
                               syncer::SyncChange::ACTION_UPDATE,
                               SyncDataFromPassword(*new_password)));
        break;
    }
    // Remove the entry from the entry map to indicate a match has been found.
    // Entries that remain in the map at the end of associating all sync entries
    // will be treated as additions that need to be propagated to sync.
    umatched_data_from_password_db->erase(existing_local_entry_iter);
  }
}

syncer::SyncData SyncDataFromPassword(
    const autofill::PasswordForm& password_form) {
  sync_pb::EntitySpecifics password_data;
  sync_pb::PasswordSpecificsData* password_specifics =
      password_data.mutable_password()->mutable_client_only_encrypted_data();
  password_specifics->set_scheme(password_form.scheme);
  password_specifics->set_signon_realm(password_form.signon_realm);
  password_specifics->set_origin(password_form.origin.spec());
  password_specifics->set_action(password_form.action.spec());
  password_specifics->set_username_element(
      base::UTF16ToUTF8(password_form.username_element));
  password_specifics->set_password_element(
      base::UTF16ToUTF8(password_form.password_element));
  password_specifics->set_username_value(
      base::UTF16ToUTF8(password_form.username_value));
  password_specifics->set_password_value(
      base::UTF16ToUTF8(password_form.password_value));
  password_specifics->set_ssl_valid(password_form.ssl_valid);
  password_specifics->set_preferred(password_form.preferred);
  password_specifics->set_date_created(
      password_form.date_created.ToInternalValue());
  password_specifics->set_blacklisted(password_form.blacklisted_by_user);

  std::string tag = MakePasswordSyncTag(*password_specifics);
  return syncer::SyncData::CreateLocalData(tag, tag, password_data);
}

void PasswordFromSpecifics(const sync_pb::PasswordSpecificsData& password,
                           autofill::PasswordForm* new_password) {
  new_password->scheme =
      static_cast<autofill::PasswordForm::Scheme>(password.scheme());
  new_password->signon_realm = password.signon_realm();
  new_password->origin = GURL(password.origin());
  new_password->action = GURL(password.action());
  new_password->username_element =
      base::UTF8ToUTF16(password.username_element());
  new_password->password_element =
      base::UTF8ToUTF16(password.password_element());
  new_password->username_value = base::UTF8ToUTF16(password.username_value());
  new_password->password_value = base::UTF8ToUTF16(password.password_value());
  new_password->ssl_valid = password.ssl_valid();
  new_password->preferred = password.preferred();
  new_password->date_created =
      base::Time::FromInternalValue(password.date_created());
  new_password->blacklisted_by_user = password.blacklisted();
}

std::string MakePasswordSyncTag(
    const sync_pb::PasswordSpecificsData& password) {
  return MakePasswordSyncTag(password.origin(),
                             password.username_element(),
                             password.username_value(),
                             password.password_element(),
                             password.signon_realm());
}

/* [<][>][^][v][top][bottom][index][help] */