| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/consent_auditor/consent_sync_bridge_impl.h" |
| |
| #include <map> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/numerics/byte_conversions.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "components/sync/model/entity_change.h" |
| #include "components/sync/model/metadata_batch.h" |
| #include "components/sync/model/mutable_data_batch.h" |
| #include "components/sync/protocol/user_consent_specifics.pb.h" |
| |
| namespace consent_auditor { |
| |
| using sync_pb::UserConsentSpecifics; |
| using syncer::DataTypeLocalChangeProcessor; |
| using syncer::DataTypeStore; |
| using syncer::DataTypeSyncBridge; |
| using syncer::EntityChange; |
| using syncer::EntityChangeList; |
| using syncer::EntityData; |
| using syncer::MetadataBatch; |
| using syncer::MetadataChangeList; |
| using syncer::ModelError; |
| using syncer::MutableDataBatch; |
| using syncer::OnceDataTypeStoreFactory; |
| |
| namespace { |
| |
| std::string GetStorageKeyFromSpecifics(const UserConsentSpecifics& specifics) { |
| // Force Big Endian, this means newly created keys are last in sort order, |
| // which allows leveldb to append new writes, which it is best at. |
| // TODO(skym): Until we force |event_time_usec| to never conflict, this has |
| // the potential for errors. |
| std::string key(8u, char{0}); |
| base::as_writable_byte_span(key).copy_from(base::U64ToBigEndian( |
| base::checked_cast<uint64_t>(specifics.client_consent_time_usec()))); |
| return key; |
| } |
| |
| std::unique_ptr<EntityData> MoveToEntityData( |
| std::unique_ptr<UserConsentSpecifics> specifics) { |
| auto entity_data = std::make_unique<EntityData>(); |
| entity_data->name = |
| base::NumberToString(specifics->client_consent_time_usec()); |
| entity_data->specifics.set_allocated_user_consent(specifics.release()); |
| return entity_data; |
| } |
| |
| } // namespace |
| |
| ConsentSyncBridgeImpl::ConsentSyncBridgeImpl( |
| OnceDataTypeStoreFactory store_factory, |
| std::unique_ptr<DataTypeLocalChangeProcessor> change_processor) |
| : DataTypeSyncBridge(std::move(change_processor)) { |
| StoreWithCache::CreateAndLoad( |
| std::move(store_factory), syncer::USER_CONSENTS, |
| base::BindOnce(&ConsentSyncBridgeImpl::OnStoreLoaded, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| ConsentSyncBridgeImpl::~ConsentSyncBridgeImpl() { |
| if (!deferred_consents_while_initializing_.empty()) { |
| LOG(ERROR) << "Non-empty event queue at shutdown!"; |
| } |
| // TODO(crbug.com/362428820): Remove logging once investigation is complete. |
| if (store_) { |
| VLOG(1) << "UserConsents during destruction: " |
| << store_->in_memory_data().size(); |
| } |
| } |
| |
| std::unique_ptr<MetadataChangeList> |
| ConsentSyncBridgeImpl::CreateMetadataChangeList() { |
| return DataTypeStore::WriteBatch::CreateMetadataChangeList(); |
| } |
| |
| std::optional<ModelError> ConsentSyncBridgeImpl::MergeFullSyncData( |
| std::unique_ptr<MetadataChangeList> metadata_change_list, |
| EntityChangeList entity_data) { |
| DCHECK(entity_data.empty()); |
| DCHECK(change_processor()->IsTrackingMetadata()); |
| DCHECK(!change_processor()->TrackedGaiaId().empty()); |
| ResubmitAllData(); |
| return ApplyIncrementalSyncChanges(std::move(metadata_change_list), |
| std::move(entity_data)); |
| } |
| |
| std::optional<ModelError> ConsentSyncBridgeImpl::ApplyIncrementalSyncChanges( |
| std::unique_ptr<MetadataChangeList> metadata_change_list, |
| EntityChangeList entity_changes) { |
| CHECK(store_); |
| |
| std::unique_ptr<StoreWithCache::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| for (const std::unique_ptr<EntityChange>& change : entity_changes) { |
| DCHECK_EQ(EntityChange::ACTION_DELETE, change->type()); |
| batch->DeleteData(change->storage_key()); |
| } |
| |
| batch->TakeMetadataChangesFrom(std::move(metadata_change_list)); |
| store_->CommitWriteBatch(std::move(batch), |
| base::BindOnce(&ConsentSyncBridgeImpl::OnStoreCommit, |
| weak_ptr_factory_.GetWeakPtr())); |
| return {}; |
| } |
| |
| std::unique_ptr<syncer::DataBatch> ConsentSyncBridgeImpl::GetDataForCommit( |
| StorageKeyList storage_keys) { |
| CHECK(store_); |
| |
| auto batch = std::make_unique<MutableDataBatch>(); |
| const std::map<std::string, UserConsentSpecifics>& in_memory_data = |
| store_->in_memory_data(); |
| for (const std::string& storage_key : storage_keys) { |
| auto it = in_memory_data.find(storage_key); |
| if (it != in_memory_data.end()) { |
| auto specifics = std::make_unique<UserConsentSpecifics>(it->second); |
| batch->Put(it->first, MoveToEntityData(std::move(specifics))); |
| } |
| } |
| return batch; |
| } |
| |
| std::unique_ptr<syncer::DataBatch> |
| ConsentSyncBridgeImpl::GetAllDataForDebugging() { |
| CHECK(store_); |
| |
| auto batch = std::make_unique<MutableDataBatch>(); |
| for (const auto& [storage_key, specifics] : store_->in_memory_data()) { |
| auto specifics_copy = std::make_unique<UserConsentSpecifics>(specifics); |
| batch->Put(storage_key, MoveToEntityData(std::move(specifics_copy))); |
| } |
| return batch; |
| } |
| |
| std::string ConsentSyncBridgeImpl::GetClientTag( |
| const EntityData& entity_data) const { |
| return GetStorageKey(entity_data); |
| } |
| |
| std::string ConsentSyncBridgeImpl::GetStorageKey( |
| const EntityData& entity_data) const { |
| return GetStorageKeyFromSpecifics(entity_data.specifics.user_consent()); |
| } |
| |
| bool ConsentSyncBridgeImpl::IsEntityDataValid( |
| const EntityData& entity_data) const { |
| // USER_CONSENT is a commit only data type so this method is not called. |
| NOTREACHED(); |
| } |
| |
| void ConsentSyncBridgeImpl::ApplyDisableSyncChanges( |
| std::unique_ptr<MetadataChangeList> delete_metadata_change_list) { |
| // Sync can only be stopped after initialization. |
| DCHECK(deferred_consents_while_initializing_.empty()); |
| CHECK(store_); |
| |
| // Preserve all consents in the store, but delete their metadata, because it |
| // may become invalid when sync is reenabled. It is important to report all |
| // user consents, thus, they are persisted for some time even after signout. |
| // The bridge will try to resubmit these consents once sync is enabled again. |
| // This may lead to same consent being submitted multiple times, but this is |
| // allowed. |
| std::unique_ptr<StoreWithCache::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| batch->TakeMetadataChangesFrom(std::move(delete_metadata_change_list)); |
| |
| store_->CommitWriteBatch(std::move(batch), |
| base::BindOnce(&ConsentSyncBridgeImpl::OnStoreCommit, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ConsentSyncBridgeImpl::ResubmitAllData() { |
| DCHECK(!change_processor()->TrackedGaiaId().empty()); |
| DCHECK(change_processor()->IsTrackingMetadata()); |
| CHECK(store_); |
| |
| std::unique_ptr<StoreWithCache::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| |
| for (const auto& [storage_key, specifics] : store_->in_memory_data()) { |
| if (specifics.obfuscated_gaia_id() == |
| change_processor()->TrackedGaiaId().ToString()) { |
| auto specifics_copy = std::make_unique<UserConsentSpecifics>(specifics); |
| change_processor()->Put(storage_key, |
| MoveToEntityData(std::move(specifics_copy)), |
| batch->GetMetadataChangeList()); |
| } |
| } |
| |
| store_->CommitWriteBatch(std::move(batch), |
| base::BindOnce(&ConsentSyncBridgeImpl::OnStoreCommit, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ConsentSyncBridgeImpl::RecordConsent( |
| std::unique_ptr<UserConsentSpecifics> specifics) { |
| // TODO(vitaliii): Sanity-check specifics->obfuscated_gaia_id() against |
| // change_processor()->TrackedAccountId(), maybe DCHECK. |
| DCHECK(!specifics->obfuscated_gaia_id().empty()); |
| if (store_) { |
| RecordConsentImpl(std::move(specifics)); |
| return; |
| } |
| deferred_consents_while_initializing_.push_back(std::move(specifics)); |
| } |
| |
| // static |
| std::string ConsentSyncBridgeImpl::GetStorageKeyFromSpecificsForTest( |
| const UserConsentSpecifics& specifics) { |
| return GetStorageKeyFromSpecifics(specifics); |
| } |
| |
| std::unique_ptr<DataTypeStore> ConsentSyncBridgeImpl::StealStoreForTest() { |
| return StoreWithCache::ExtractUnderlyingStoreForTest(std::move(store_)); |
| } |
| |
| void ConsentSyncBridgeImpl::RecordConsentImpl( |
| std::unique_ptr<UserConsentSpecifics> specifics) { |
| CHECK(store_); |
| |
| std::string storage_key = GetStorageKeyFromSpecifics(*specifics); |
| std::unique_ptr<StoreWithCache::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| batch->WriteData(storage_key, *specifics); |
| |
| if (specifics->obfuscated_gaia_id() == |
| change_processor()->TrackedGaiaId().ToString()) { |
| change_processor()->Put(storage_key, MoveToEntityData(std::move(specifics)), |
| batch->GetMetadataChangeList()); |
| } |
| |
| store_->CommitWriteBatch(std::move(batch), |
| base::BindOnce(&ConsentSyncBridgeImpl::OnStoreCommit, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| base::WeakPtr<syncer::DataTypeControllerDelegate> |
| ConsentSyncBridgeImpl::GetControllerDelegate() { |
| return change_processor()->GetControllerDelegate(); |
| } |
| |
| void ConsentSyncBridgeImpl::ProcessQueuedEvents() { |
| for (std::unique_ptr<UserConsentSpecifics>& event : |
| deferred_consents_while_initializing_) { |
| RecordConsentImpl(std::move(event)); |
| } |
| deferred_consents_while_initializing_.clear(); |
| } |
| |
| void ConsentSyncBridgeImpl::OnStoreLoaded( |
| const std::optional<ModelError>& error, |
| std::unique_ptr<StoreWithCache> store, |
| std::unique_ptr<MetadataBatch> metadata_batch) { |
| TRACE_EVENT0("ui", "ConsentSyncBridgeImpl::OnStoreLoaded"); |
| if (error) { |
| change_processor()->ReportError(*error); |
| return; |
| } |
| |
| store_ = std::move(store); |
| |
| change_processor()->ModelReadyToSync(std::move(metadata_batch)); |
| if (!change_processor()->TrackedGaiaId().empty()) { |
| // Resubmit all data in case the client crashed immediately after |
| // MergeFullSyncData(), where submissions are supposed to happen and |
| // metadata populated. |
| ResubmitAllData(); |
| } |
| ProcessQueuedEvents(); |
| } |
| |
| void ConsentSyncBridgeImpl::OnStoreCommit( |
| const std::optional<ModelError>& error) { |
| if (error) { |
| change_processor()->ReportError(*error); |
| } |
| } |
| |
| } // namespace consent_auditor |