// 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/sync/password_syncable_service.h"

#include <algorithm>
#include <iterator>
#include <memory>
#include <utility>

#include "base/auto_reset.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/common/password_form.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_store_sync.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/sync/model/sync_change_processor.h"
#include "components/sync/model/sync_error_factory.h"
#include "net/base/escape.h"

namespace password_manager {

// Converts the |password| into a SyncData object.
syncer::SyncData SyncDataFromPassword(const autofill::PasswordForm& password);

// Extracts the |PasswordForm| data from sync's protobuf format.
autofill::PasswordForm PasswordFromSpecifics(
    const sync_pb::PasswordSpecificsData& password);

// Returns the unique tag that will serve as the sync identifier for the
// |password| entry.
std::string MakePasswordSyncTag(const sync_pb::PasswordSpecificsData& password);
std::string MakePasswordSyncTag(const autofill::PasswordForm& password);

namespace {

// Returns true iff |password_specifics| and |password_specifics| are equal
// memberwise.
bool AreLocalAndSyncPasswordsEqual(
    const sync_pb::PasswordSpecificsData& password_specifics,
    const autofill::PasswordForm& password_form) {
  return (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.preferred == password_specifics.preferred() &&
          password_form.date_created.ToInternalValue() ==
              password_specifics.date_created() &&
          password_form.blacklisted_by_user ==
              password_specifics.blacklisted() &&
          password_form.type == password_specifics.type() &&
          password_form.times_used == password_specifics.times_used() &&
          base::UTF16ToUTF8(password_form.display_name) ==
              password_specifics.display_name() &&
          password_form.icon_url.spec() == password_specifics.avatar_url() &&
          url::Origin::Create(GURL(password_specifics.federation_url()))
                  .Serialize() == password_form.federation_origin.Serialize());
}

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;
}

// Creates a PasswordForm from |specifics| and |sync_time|, appends it to
// |entries|.
void AppendPasswordFromSpecifics(
    const sync_pb::PasswordSpecificsData& specifics,
    base::Time sync_time,
    std::vector<std::unique_ptr<autofill::PasswordForm>>* entries) {
  entries->push_back(std::make_unique<autofill::PasswordForm>(
      PasswordFromSpecifics(specifics)));
  entries->back()->date_synced = sync_time;
}

}  // namespace

struct PasswordSyncableService::SyncEntries {
  std::vector<std::unique_ptr<autofill::PasswordForm>>* EntriesForChangeType(
      syncer::SyncChange::SyncChangeType type) {
    switch (type) {
      case syncer::SyncChange::ACTION_ADD:
        return &new_entries;
      case syncer::SyncChange::ACTION_UPDATE:
        return &updated_entries;
      case syncer::SyncChange::ACTION_DELETE:
        return &deleted_entries;
      case syncer::SyncChange::ACTION_INVALID:
        return nullptr;
    }
    NOTREACHED();
    return nullptr;
  }

  // List that contains the entries that are known only to sync.
  std::vector<std::unique_ptr<autofill::PasswordForm>> new_entries;

  // List that contains the entries that are known to both sync and the local
  // database but have updates in sync. They need to be updated in the local
  // database.
  std::vector<std::unique_ptr<autofill::PasswordForm>> updated_entries;

  // The list of entries to be deleted from the local database.
  std::vector<std::unique_ptr<autofill::PasswordForm>> deleted_entries;
};

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

PasswordSyncableService::~PasswordSyncableService() = default;

syncer::SyncMergeResult PasswordSyncableService::MergeDataAndStartSyncing(
    syncer::ModelType type,
    const syncer::SyncDataList& initial_sync_data,
    std::unique_ptr<syncer::SyncChangeProcessor> sync_processor,
    std::unique_ptr<syncer::SyncErrorFactory> sync_error_factory) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(syncer::PASSWORDS, type);
  base::AutoReset<bool> processing_changes(&is_processing_sync_changes_, true);
  syncer::SyncMergeResult merge_result(type);

  // 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.
  std::vector<std::unique_ptr<autofill::PasswordForm>> password_entries;
  PasswordEntryMap new_local_entries;
  if (!ReadFromPasswordStore(&password_entries, &new_local_entries)) {
    if (!ShouldRecoverPasswordsDuringMerge()) {
      merge_result.set_error(sync_error_factory->CreateAndUploadError(
          FROM_HERE, "Failed to get passwords from store."));
      metrics_util::LogPasswordSyncState(metrics_util::NOT_SYNCING_FAILED_READ);
      return merge_result;
    }

    // On MacOS it may happen that some passwords cannot be decrypted due to
    // modification of encryption key in Keychain (https://crbug.com/730625).
    // Delete those logins from the store, they should be automatically updated
    // with Sync data.
    DatabaseCleanupResult cleanup_result =
        password_store_->DeleteUndecryptableLogins();

    if (cleanup_result == DatabaseCleanupResult::kEncryptionUnavailable) {
      merge_result.set_error(sync_error_factory->CreateAndUploadError(
          FROM_HERE, "Failed to get encryption key during database cleanup."));
      metrics_util::LogPasswordSyncState(
          metrics_util::NOT_SYNCING_FAILED_DECRYPTION);
      return merge_result;
    }

    if (cleanup_result != DatabaseCleanupResult::kSuccess) {
      merge_result.set_error(sync_error_factory->CreateAndUploadError(
          FROM_HERE, "Failed to cleanup database."));
      metrics_util::LogPasswordSyncState(
          metrics_util::NOT_SYNCING_FAILED_CLEANUP);
      return merge_result;
    }

    // Try to read all entries again. If deletion of passwords which couldn't
    // be deleted didn't help, return an error.
    password_entries.clear();
    new_local_entries.clear();
    if (!ReadFromPasswordStore(&password_entries, &new_local_entries)) {
      merge_result.set_error(sync_error_factory->CreateAndUploadError(
          FROM_HERE, "Failed to get passwords from store."));
      metrics_util::LogPasswordSyncState(metrics_util::NOT_SYNCING_FAILED_READ);
      return merge_result;
    }
  }

  if (password_entries.size() != new_local_entries.size()) {
    merge_result.set_error(sync_error_factory->CreateAndUploadError(
        FROM_HERE,
        "There are passwords with identical sync tags in the database."));
    metrics_util::LogPasswordSyncState(
        metrics_util::NOT_SYNCING_DUPLICATE_TAGS);
    return merge_result;
  }
  merge_result.set_num_items_before_association(new_local_entries.size());

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

  for (auto 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)));
  }

  WriteToPasswordStore(sync_entries);
  merge_result.set_error(
      sync_processor->ProcessSyncChanges(FROM_HERE, updated_db_entries));
  if (merge_result.error().IsSet()) {
    metrics_util::LogPasswordSyncState(metrics_util::NOT_SYNCING_SERVER_ERROR);
    return merge_result;
  }

  merge_result.set_num_items_after_association(
      merge_result.num_items_before_association() +
      sync_entries.new_entries.size());
  merge_result.set_num_items_added(sync_entries.new_entries.size());
  merge_result.set_num_items_modified(sync_entries.updated_entries.size());
  merge_result.set_num_items_deleted(sync_entries.deleted_entries.size());

  // Save |sync_processor_| only if the whole procedure succeeded. In case of
  // failure Sync shouldn't receive any updates from the PasswordStore.
  sync_error_factory_ = std::move(sync_error_factory);
  sync_processor_ = std::move(sync_processor);

  metrics_util::LogPasswordSyncState(metrics_util::SYNCING_OK);
  return merge_result;
}

void PasswordSyncableService::StopSyncing(syncer::ModelType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(syncer::PASSWORDS, type);

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

syncer::SyncDataList PasswordSyncableService::GetAllSyncData(
    syncer::ModelType type) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(syncer::PASSWORDS, type);
  std::vector<std::unique_ptr<autofill::PasswordForm>> password_entries;
  ReadFromPasswordStore(&password_entries, nullptr);

  syncer::SyncDataList sync_data;
  sync_data.reserve(password_entries.size());
  std::transform(password_entries.begin(), password_entries.end(),
                 std::back_inserter(sync_data),
                 [](const std::unique_ptr<autofill::PasswordForm>& form) {
                   return SyncDataFromPassword(*form);
                 });
  return sync_data;
}

syncer::SyncError PasswordSyncableService::ProcessSyncChanges(
    const base::Location& from_here,
    const syncer::SyncChangeList& change_list) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::AutoReset<bool> processing_changes(&is_processing_sync_changes_, true);
  SyncEntries sync_entries;
  base::Time time_now = base::Time::Now();

  for (auto it = change_list.begin(); it != change_list.end(); ++it) {
    const sync_pb::EntitySpecifics& specifics = it->sync_data().GetSpecifics();
    std::vector<std::unique_ptr<autofill::PasswordForm>>* entries =
        sync_entries.EntriesForChangeType(it->change_type());
    if (!entries) {
      return sync_error_factory_->CreateAndUploadError(
          FROM_HERE, "Failed to process sync changes for passwords datatype.");
    }
    AppendPasswordFromSpecifics(
        specifics.password().client_only_encrypted_data(), time_now, entries);
  }

  WriteToPasswordStore(sync_entries);
  return syncer::SyncError();
}

void PasswordSyncableService::ActOnPasswordStoreChanges(
    const PasswordStoreChangeList& local_changes) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  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 (auto it = local_changes.begin(); it != local_changes.end(); ++it) {
    syncer::SyncData data =
        (it->type() == PasswordStoreChange::REMOVE
             ? syncer::SyncData::CreateLocalDelete(
                   MakePasswordSyncTag(it->form()), syncer::PASSWORDS)
             : SyncDataFromPassword(it->form()));
    sync_changes.push_back(
        syncer::SyncChange(FROM_HERE, GetSyncChangeType(it->type()), data));
  }
  sync_processor_->ProcessSyncChanges(FROM_HERE, sync_changes);
}

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

bool PasswordSyncableService::ReadFromPasswordStore(
    std::vector<std::unique_ptr<autofill::PasswordForm>>* password_entries,
    PasswordEntryMap* passwords_entry_map) const {
  DCHECK(password_entries);
  std::vector<std::unique_ptr<autofill::PasswordForm>> autofillable_entries;
  std::vector<std::unique_ptr<autofill::PasswordForm>> blacklist_entries;
  if (!password_store_->FillAutofillableLogins(&autofillable_entries) ||
      !password_store_->FillBlacklistLogins(&blacklist_entries)) {
    // Password store often fails to load passwords. Track failures with UMA.
    // (http://crbug.com/249000)
    // TODO(wychen): enum uma should be strongly typed. crbug.com/661401
    UMA_HISTOGRAM_ENUMERATION("Sync.LocalDataFailedToLoad",
                              ModelTypeToHistogramInt(syncer::PASSWORDS),
                              static_cast<int>(syncer::MODEL_TYPE_COUNT));
    return false;
  }
  password_entries->resize(autofillable_entries.size() +
                           blacklist_entries.size());
  std::move(autofillable_entries.begin(), autofillable_entries.end(),
            password_entries->begin());
  std::move(blacklist_entries.begin(), blacklist_entries.end(),
            password_entries->begin() + autofillable_entries.size());

  if (!passwords_entry_map)
    return true;

  PasswordEntryMap& entry_map = *passwords_entry_map;
  for (const auto& form : *password_entries) {
    autofill::PasswordForm* password_form = form.get();
    entry_map[MakePasswordSyncTag(*password_form)] = password_form;
  }

  return true;
}

void PasswordSyncableService::WriteToPasswordStore(const SyncEntries& entries) {
  PasswordStoreChangeList changes;
  WriteEntriesToDatabase(&PasswordStoreSync::AddLoginSync, entries.new_entries,
                         &changes);
  WriteEntriesToDatabase(&PasswordStoreSync::UpdateLoginSync,
                         entries.updated_entries, &changes);
  WriteEntriesToDatabase(&PasswordStoreSync::RemoveLoginSync,
                         entries.deleted_entries, &changes);

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

// static
void PasswordSyncableService::CreateOrUpdateEntry(
    const syncer::SyncData& data,
    PasswordEntryMap* unmatched_data_from_password_db,
    SyncEntries* 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.
  auto existing_local_entry_iter = unmatched_data_from_password_db->find(tag);
  base::Time time_now = base::Time::Now();
  if (existing_local_entry_iter == unmatched_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.
    AppendPasswordFromSpecifics(password_specifics, time_now,
                                &sync_entries->new_entries);
  } else {
    // The entry is in password store. If the entries are not identical, then
    // the entries need to be merged.
    // If the passwords differ, take the one that was created more recently.
    const autofill::PasswordForm& password_form =
        *existing_local_entry_iter->second;
    if (!AreLocalAndSyncPasswordsEqual(password_specifics, password_form)) {
      if (base::Time::FromInternalValue(password_specifics.date_created()) <
          password_form.date_created) {
        updated_db_entries->push_back(
            syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
                               SyncDataFromPassword(password_form)));
      } else {
        AppendPasswordFromSpecifics(password_specifics, time_now,
                                    &sync_entries->updated_entries);
      }
    }
    // 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.
    unmatched_data_from_password_db->erase(existing_local_entry_iter);
  }
}

void PasswordSyncableService::WriteEntriesToDatabase(
    DatabaseOperation operation,
    const std::vector<std::unique_ptr<autofill::PasswordForm>>& entries,
    PasswordStoreChangeList* all_changes) {
  for (const std::unique_ptr<autofill::PasswordForm>& form : entries) {
    PasswordStoreChangeList new_changes = (password_store_->*operation)(*form);
    all_changes->insert(all_changes->end(), new_changes.begin(),
                        new_changes.end());
  }
}

bool PasswordSyncableService::ShouldRecoverPasswordsDuringMerge() const {
  return base::FeatureList::IsEnabled(
             features::kRecoverPasswordsForSyncUsers) &&
         !base::FeatureList::IsEnabled(features::kDeleteCorruptedPasswords);
}

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();
#define CopyField(field) password_specifics->set_##field(password_form.field)
#define CopyStringField(field) \
  password_specifics->set_##field(base::UTF16ToUTF8(password_form.field))
  CopyField(scheme);
  CopyField(signon_realm);
  password_specifics->set_origin(password_form.origin.spec());
  password_specifics->set_action(password_form.action.spec());
  CopyStringField(username_element);
  CopyStringField(password_element);
  CopyStringField(username_value);
  CopyStringField(password_value);
  CopyField(preferred);
  password_specifics->set_date_created(
      password_form.date_created.ToInternalValue());
  password_specifics->set_blacklisted(password_form.blacklisted_by_user);
  CopyField(type);
  CopyField(times_used);
  CopyStringField(display_name);
  password_specifics->set_avatar_url(password_form.icon_url.spec());
  password_specifics->set_federation_url(
      password_form.federation_origin.opaque()
          ? std::string()
          : password_form.federation_origin.Serialize());
#undef CopyStringField
#undef CopyField

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

autofill::PasswordForm 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.preferred = password.preferred();
  new_password.date_created =
      base::Time::FromInternalValue(password.date_created());
  new_password.blacklisted_by_user = password.blacklisted();
  new_password.type =
      static_cast<autofill::PasswordForm::Type>(password.type());
  new_password.times_used = password.times_used();
  new_password.display_name = base::UTF8ToUTF16(password.display_name());
  new_password.icon_url = GURL(password.avatar_url());
  new_password.federation_origin =
      url::Origin::Create(GURL(password.federation_url()));
  return new_password;
}

std::string MakePasswordSyncTag(
    const sync_pb::PasswordSpecificsData& password) {
  return MakePasswordSyncTag(PasswordFromSpecifics(password));
}

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

}  // namespace password_manager
