blob: fac0d5944b35086f39b1ebf78486cfc1e797efd3 [file] [log] [blame]
// Copyright 2018 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/autofill/core/browser/webdata/autofill_wallet_metadata_sync_bridge.h"
#include <unordered_map>
#include <utility>
#include "base/base64.h"
#include "base/logging.h"
#include "base/optional.h"
#include "base/pickle.h"
#include "components/autofill/core/browser/autofill_metadata.h"
#include "components/autofill/core/browser/autofill_profile.h"
#include "components/autofill/core/browser/credit_card.h"
#include "components/autofill/core/browser/webdata/autofill_sync_bridge_util.h"
#include "components/autofill/core/browser/webdata/autofill_table.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_backend.h"
#include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
#include "components/sync/model/entity_data.h"
#include "components/sync/model/mutable_data_batch.h"
#include "components/sync/model_impl/client_tag_based_model_type_processor.h"
#include "components/sync/model_impl/sync_metadata_store_change_list.h"
namespace autofill {
namespace {
using sync_pb::WalletMetadataSpecifics;
using syncer::EntityData;
using syncer::MetadataChangeList;
// Address to this variable used as the user data key.
static int kAutofillWalletMetadataSyncBridgeUserDataKey = 0;
std::string GetClientTagForSpecificsId(WalletMetadataSpecifics::Type type,
const std::string& specifics_id) {
switch (type) {
case WalletMetadataSpecifics::ADDRESS:
return "address-" + specifics_id;
case WalletMetadataSpecifics::CARD:
return "card-" + specifics_id;
case WalletMetadataSpecifics::UNKNOWN:
NOTREACHED();
return "";
}
}
// Returns the wallet metadata specifics id for the specified |metadata_id|.
std::string GetSpecificsIdForMetadataId(const std::string& metadata_id) {
return GetBase64EncodedId(metadata_id);
}
// Returns the wallet metadata specifics storage key for the specified |type|
// and |metadata_id|.
std::string GetStorageKeyForWalletMetadataTypeAndId(
WalletMetadataSpecifics::Type type,
const std::string& metadata_id) {
return GetStorageKeyForWalletMetadataTypeAndSpecificsId(
type, GetSpecificsIdForMetadataId(metadata_id));
}
struct TypeAndMetadataId {
WalletMetadataSpecifics::Type type;
std::string metadata_id;
};
TypeAndMetadataId ParseWalletMetadataStorageKey(
const std::string& storage_key) {
TypeAndMetadataId parsed;
base::Pickle pickle(storage_key.data(), storage_key.size());
base::PickleIterator iterator(pickle);
int type_int;
if (!iterator.ReadInt(&type_int) ||
!iterator.ReadString(&parsed.metadata_id)) {
NOTREACHED() << "Unsupported storage_key provided " << storage_key;
}
parsed.type = static_cast<WalletMetadataSpecifics::Type>(type_int);
return parsed;
}
// Returns EntityData for wallet_metadata for |local_metadata| and |type|.
std::unique_ptr<EntityData> CreateEntityDataFromAutofillMetadata(
WalletMetadataSpecifics::Type type,
const AutofillMetadata& local_metadata) {
auto entity_data = std::make_unique<EntityData>();
std::string specifics_id = GetSpecificsIdForMetadataId(local_metadata.id);
entity_data->non_unique_name = GetClientTagForSpecificsId(type, specifics_id);
WalletMetadataSpecifics* remote_metadata =
entity_data->specifics.mutable_wallet_metadata();
remote_metadata->set_type(type);
remote_metadata->set_id(specifics_id);
remote_metadata->set_use_count(local_metadata.use_count);
remote_metadata->set_use_date(
local_metadata.use_date.ToDeltaSinceWindowsEpoch().InMicroseconds());
switch (type) {
case WalletMetadataSpecifics::ADDRESS: {
remote_metadata->set_address_has_converted(local_metadata.has_converted);
break;
}
case WalletMetadataSpecifics::CARD: {
// The strings must be in valid UTF-8 to sync.
remote_metadata->set_card_billing_address_id(
GetBase64EncodedId(local_metadata.billing_address_id));
break;
}
case WalletMetadataSpecifics::UNKNOWN: {
NOTREACHED();
break;
}
}
return entity_data;
}
// Metadata is worth updating if its value is "newer" then before; here "newer"
// is the ordering of legal state transitions that metadata can take that is
// defined below.
bool IsMetadataWorthUpdating(AutofillMetadata existing_entry,
AutofillMetadata new_entry) {
if (existing_entry.use_count < new_entry.use_count &&
existing_entry.use_date < new_entry.use_date) {
return true;
}
// For the following type-specific fields, we don't have to distinguish the
// type of metadata as both entries must be of the same type and therefore
// irrelevant values are default, thus equal.
// It is only legal to move from non-converted to converted. Do not accept
// the other transition.
if (!existing_entry.has_converted && new_entry.has_converted) {
return true;
}
if (existing_entry.billing_address_id != new_entry.billing_address_id) {
return true;
}
return false;
}
bool AddServerMetadata(AutofillTable* table,
WalletMetadataSpecifics::Type type,
const AutofillMetadata& metadata) {
switch (type) {
case WalletMetadataSpecifics::ADDRESS:
return table->AddServerAddressMetadata(metadata);
case WalletMetadataSpecifics::CARD:
return table->AddServerCardMetadata(metadata);
case WalletMetadataSpecifics::UNKNOWN:
NOTREACHED();
return false;
}
}
bool RemoveServerMetadata(AutofillTable* table,
WalletMetadataSpecifics::Type type,
const std::string& id) {
switch (type) {
case WalletMetadataSpecifics::ADDRESS:
return table->RemoveServerAddressMetadata(id);
case WalletMetadataSpecifics::CARD:
return table->RemoveServerCardMetadata(id);
case WalletMetadataSpecifics::UNKNOWN:
NOTREACHED();
return false;
}
}
bool UpdateServerMetadata(AutofillTable* table,
WalletMetadataSpecifics::Type type,
const AutofillMetadata& metadata) {
// TODO: Create UpdateServerAddressMetadata() that takes metadata as arg.
switch (type) {
case WalletMetadataSpecifics::ADDRESS:
return table->UpdateServerAddressMetadata(metadata);
case WalletMetadataSpecifics::CARD:
return table->UpdateServerCardMetadata(metadata);
case WalletMetadataSpecifics::UNKNOWN:
NOTREACHED();
return false;
}
}
} // namespace
// static
void AutofillWalletMetadataSyncBridge::CreateForWebDataServiceAndBackend(
const std::string& app_locale,
AutofillWebDataBackend* web_data_backend,
AutofillWebDataService* web_data_service) {
web_data_service->GetDBUserData()->SetUserData(
&kAutofillWalletMetadataSyncBridgeUserDataKey,
std::make_unique<AutofillWalletMetadataSyncBridge>(
std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
syncer::AUTOFILL_WALLET_METADATA,
/*dump_stack=*/base::RepeatingClosure()),
web_data_backend));
}
// static
AutofillWalletMetadataSyncBridge*
AutofillWalletMetadataSyncBridge::FromWebDataService(
AutofillWebDataService* web_data_service) {
return static_cast<AutofillWalletMetadataSyncBridge*>(
web_data_service->GetDBUserData()->GetUserData(
&kAutofillWalletMetadataSyncBridgeUserDataKey));
}
AutofillWalletMetadataSyncBridge::AutofillWalletMetadataSyncBridge(
std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor,
AutofillWebDataBackend* web_data_backend)
: ModelTypeSyncBridge(std::move(change_processor)),
web_data_backend_(web_data_backend),
scoped_observer_(this),
track_wallet_data_(false),
weak_ptr_factory_(this) {
DCHECK(web_data_backend_);
scoped_observer_.Add(web_data_backend_);
LoadDataCacheAndMetadata();
}
AutofillWalletMetadataSyncBridge::~AutofillWalletMetadataSyncBridge() {}
void AutofillWalletMetadataSyncBridge::OnWalletDataTrackingStateChanged(
bool is_tracking) {
track_wallet_data_ = is_tracking;
}
std::unique_ptr<syncer::MetadataChangeList>
AutofillWalletMetadataSyncBridge::CreateMetadataChangeList() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return std::make_unique<syncer::SyncMetadataStoreChangeList>(
GetAutofillTable(), syncer::AUTOFILL_WALLET_METADATA);
}
base::Optional<syncer::ModelError>
AutofillWalletMetadataSyncBridge::MergeSyncData(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_data) {
NOTIMPLEMENTED();
return base::nullopt;
}
base::Optional<syncer::ModelError>
AutofillWalletMetadataSyncBridge::ApplySyncChanges(
std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
syncer::EntityChangeList entity_data) {
NOTIMPLEMENTED();
return base::nullopt;
}
void AutofillWalletMetadataSyncBridge::GetData(StorageKeyList storage_keys,
DataCallback callback) {
// Build a set out of the list to allow quick lookup.
std::unordered_set<std::string> storage_keys_set(storage_keys.begin(),
storage_keys.end());
GetDataImpl(std::move(storage_keys_set), std::move(callback));
}
void AutofillWalletMetadataSyncBridge::GetAllDataForDebugging(
DataCallback callback) {
// Get all data by not providing any |storage_keys| filter.
GetDataImpl(/*storage_keys=*/base::nullopt, std::move(callback));
}
std::string AutofillWalletMetadataSyncBridge::GetClientTag(
const syncer::EntityData& entity_data) {
const WalletMetadataSpecifics& remote_metadata =
entity_data.specifics.wallet_metadata();
return GetClientTagForSpecificsId(remote_metadata.type(),
remote_metadata.id());
}
std::string AutofillWalletMetadataSyncBridge::GetStorageKey(
const syncer::EntityData& entity_data) {
return GetStorageKeyForWalletMetadataTypeAndSpecificsId(
entity_data.specifics.wallet_metadata().type(),
entity_data.specifics.wallet_metadata().id());
}
void AutofillWalletMetadataSyncBridge::AutofillProfileChanged(
const AutofillProfileChange& change) {
// Skip local profiles (if possible, i.e. if it is not a deletion where
// data_model() is not set).
if (change.data_model() &&
change.data_model()->record_type() != AutofillProfile::SERVER_PROFILE) {
return;
}
LocalMetadataChanged(WalletMetadataSpecifics::ADDRESS, change);
}
void AutofillWalletMetadataSyncBridge::CreditCardChanged(
const CreditCardChange& change) {
LocalMetadataChanged(WalletMetadataSpecifics::CARD, change);
}
AutofillTable* AutofillWalletMetadataSyncBridge::GetAutofillTable() {
return AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase());
}
void AutofillWalletMetadataSyncBridge::LoadDataCacheAndMetadata() {
if (!web_data_backend_ || !web_data_backend_->GetDatabase() ||
!GetAutofillTable()) {
change_processor()->ReportError(
{FROM_HERE, "Failed to load AutofillWebDatabase."});
return;
}
// Load the data cache (both addresses and cards into the same cache, the keys
// in the cache never overlap).
std::map<std::string, AutofillMetadata> addresses_metadata;
std::map<std::string, AutofillMetadata> cards_metadata;
if (!GetAutofillTable()->GetServerAddressesMetadata(&addresses_metadata) ||
!GetAutofillTable()->GetServerCardsMetadata(&cards_metadata)) {
change_processor()->ReportError(
{FROM_HERE, "Failed reading autofill data from WebDatabase."});
return;
}
for (const auto& it : addresses_metadata) {
cache_[GetStorageKeyForWalletMetadataTypeAndId(
WalletMetadataSpecifics::ADDRESS, it.first)] = it.second;
}
for (const auto& it : cards_metadata) {
cache_[GetStorageKeyForWalletMetadataTypeAndId(
WalletMetadataSpecifics::CARD, it.first)] = it.second;
}
// Load the metadata and send to the processor.
auto batch = std::make_unique<syncer::MetadataBatch>();
if (!GetAutofillTable()->GetAllSyncMetadata(syncer::AUTOFILL_WALLET_METADATA,
batch.get())) {
change_processor()->ReportError(
{FROM_HERE, "Failed reading autofill metadata from WebDatabase."});
return;
}
change_processor()->ModelReadyToSync(std::move(batch));
}
void AutofillWalletMetadataSyncBridge::GetDataImpl(
base::Optional<std::unordered_set<std::string>> storage_keys_set,
DataCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto batch = std::make_unique<syncer::MutableDataBatch>();
for (const auto& pair : cache_) {
const std::string& storage_key = pair.first;
const AutofillMetadata& metadata = pair.second;
TypeAndMetadataId parsed_storage_key =
ParseWalletMetadataStorageKey(storage_key);
if (!storage_keys_set ||
base::ContainsKey(*storage_keys_set, storage_key)) {
batch->Put(storage_key, CreateEntityDataFromAutofillMetadata(
parsed_storage_key.type, metadata));
}
}
std::move(callback).Run(std::move(batch));
}
template <class DataType>
void AutofillWalletMetadataSyncBridge::LocalMetadataChanged(
WalletMetadataSpecifics::Type type,
AutofillDataModelChange<DataType> change) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const std::string& metadata_id = change.key();
std::string storage_key =
GetStorageKeyForWalletMetadataTypeAndId(type, metadata_id);
std::unique_ptr<MetadataChangeList> metadata_change_list =
CreateMetadataChangeList();
switch (change.type()) {
case AutofillProfileChange::EXPIRE:
NOTREACHED() << "EXPIRE change is not allowed for wallet entities";
return;
case AutofillProfileChange::REMOVE:
if (RemoveServerMetadata(GetAutofillTable(), type, metadata_id)) {
cache_.erase(storage_key);
// Send up deletion only if we had this entry in the DB. It is not there
// if (i) it was previously deleted by a remote deletion or (ii) this is
// notification for a LOCAL_PROFILE (which have non-overlapping
// storage_keys).
change_processor()->Delete(storage_key, metadata_change_list.get());
}
return;
case AutofillProfileChange::ADD:
case AutofillProfileChange::UPDATE:
DCHECK(change.data_model());
AutofillMetadata new_entry = change.data_model()->GetMetadata();
auto it = cache_.find(storage_key);
base::Optional<AutofillMetadata> existing_entry = base::nullopt;
if (it != cache_.end()) {
existing_entry = it->second;
}
if (existing_entry &&
!IsMetadataWorthUpdating(*existing_entry, new_entry)) {
// Skip changes that are outdated, etc. (changes that would result in
// inferior metadata compared to what we have now).
return;
}
cache_[storage_key] = new_entry;
if (existing_entry) {
UpdateServerMetadata(GetAutofillTable(), type, new_entry);
} else {
AddServerMetadata(GetAutofillTable(), type, new_entry);
}
change_processor()->Put(
storage_key, CreateEntityDataFromAutofillMetadata(type, new_entry),
metadata_change_list.get());
return;
}
}
} // namespace autofill