Commit 11941c5b authored by Jan Krcal's avatar Jan Krcal Committed by Commit Bot

[ModelTypeWorker] Store SyncEntities if encryption pending

This CL changes the way we process incoming SyncEntities when decryption
is pending. After the change:
 - PopulateUpdateResponseData() early returns and does not populate
   anything if decryption is pending.
 - ProcessGetUpdatesResponse() then stores the original SyncEntity
   instead of the previous (partially) populated UpdateResponseData.
 - DecryptStoredEntities() gets much simpler because it can now reuse
   PopulateUpdateResponseData() and does not have to duplicate the
   decryption logic.

As a result this CL reconciles all bookmarks adaptations into a single
place in PopulateUpdateResponseData().

This CL does not introduce any behavioral change.

Bug: 1007199
Change-Id: I5ebbfcbaf5dd6bd677aba1697e7ee8d8cd08d75f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1827427
Commit-Queue: Jan Krcal <jkrcal@chromium.org>
Reviewed-by: default avatarMikel Astiz <mastiz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#723431}
parent 8f8ca702
...@@ -142,25 +142,4 @@ void AdaptGuidForBookmark(const sync_pb::SyncEntity& update_entity, ...@@ -142,25 +142,4 @@ void AdaptGuidForBookmark(const sync_pb::SyncEntity& update_entity,
} }
} }
void AdaptGuidForBookmarkEntityData(EntityData* entity_data) {
DCHECK(entity_data);
DCHECK(entity_data->specifics.has_bookmark());
DCHECK(!entity_data->specifics.has_encrypted());
if (entity_data->is_deleted() ||
!entity_data->server_defined_unique_tag.empty()) {
return;
}
if (entity_data->specifics.bookmark().has_guid()) {
LogGuidSource(BookmarkGuidSource::kSpecifics);
} else if (base::IsValidGUID(entity_data->originator_client_item_id)) {
entity_data->specifics.mutable_bookmark()->set_guid(
entity_data->originator_client_item_id);
LogGuidSource(BookmarkGuidSource::kValidOCII);
} else {
LogGuidSource(BookmarkGuidSource::kLeftEmpty);
}
}
} // namespace syncer } // namespace syncer
...@@ -37,11 +37,6 @@ void AdaptTitleForBookmark(const sync_pb::SyncEntity& update_entity, ...@@ -37,11 +37,6 @@ void AdaptTitleForBookmark(const sync_pb::SyncEntity& update_entity,
void AdaptGuidForBookmark(const sync_pb::SyncEntity& update_entity, void AdaptGuidForBookmark(const sync_pb::SyncEntity& update_entity,
sync_pb::EntitySpecifics* specifics); sync_pb::EntitySpecifics* specifics);
// Analogous to AdaptGuidForBookmark() but deals with EntityData structs.
// |entity_data| must not be null.
// TODO(crbug.com/1007199): Reconcile with AdaptGuidForBookmark().
void AdaptGuidForBookmarkEntityData(EntityData* entity_data);
} // namespace syncer } // namespace syncer
#endif // COMPONENTS_SYNC_ENGINE_IMPL_BOOKMARK_UPDATE_PREPROCESSING_H_ #endif // COMPONENTS_SYNC_ENGINE_IMPL_BOOKMARK_UPDATE_PREPROCESSING_H_
...@@ -29,22 +29,6 @@ ...@@ -29,22 +29,6 @@
namespace syncer { namespace syncer {
namespace {
void AdaptUpdateForCompatibilityAfterDecryption(
ModelType model_type,
const sync_pb::SyncEntity& update_entity,
sync_pb::EntitySpecifics* specifics,
bool specifics_were_encrypted) {
DCHECK(specifics);
if (model_type == BOOKMARKS) {
AdaptTitleForBookmark(update_entity, specifics, specifics_were_encrypted);
AdaptGuidForBookmark(update_entity, specifics);
}
}
} // namespace
ModelTypeWorker::ModelTypeWorker( ModelTypeWorker::ModelTypeWorker(
ModelType type, ModelType type,
const sync_pb::ModelTypeState& initial_state, const sync_pb::ModelTypeState& initial_state,
...@@ -185,8 +169,9 @@ SyncerError ModelTypeWorker::ProcessGetUpdatesResponse( ...@@ -185,8 +169,9 @@ SyncerError ModelTypeWorker::ProcessGetUpdatesResponse(
pending_updates_.push_back(std::move(response_data)); pending_updates_.push_back(std::move(response_data));
break; break;
case DECRYPTION_PENDING: case DECRYPTION_PENDING:
// Cannot decrypt now, copy the sync entity for later decryption.
entries_pending_decryption_[update_entity->id_string()] = entries_pending_decryption_[update_entity->id_string()] =
std::move(response_data); *update_entity;
break; break;
case FAILED_TO_DECRYPT: case FAILED_TO_DECRYPT:
// Failed to decrypt the entity. Likely it is corrupt. Move on. // Failed to decrypt the entity. Likely it is corrupt. Move on.
...@@ -206,39 +191,17 @@ ModelTypeWorker::DecryptionStatus ModelTypeWorker::PopulateUpdateResponseData( ...@@ -206,39 +191,17 @@ ModelTypeWorker::DecryptionStatus ModelTypeWorker::PopulateUpdateResponseData(
ModelType model_type, ModelType model_type,
const sync_pb::SyncEntity& update_entity, const sync_pb::SyncEntity& update_entity,
UpdateResponseData* response_data) { UpdateResponseData* response_data) {
response_data->response_version = update_entity.version();
auto data = std::make_unique<syncer::EntityData>(); auto data = std::make_unique<syncer::EntityData>();
// Prepare the message for the model thread.
data->id = update_entity.id_string();
data->client_tag_hash =
ClientTagHash::FromHashed(update_entity.client_defined_unique_tag());
data->creation_time = ProtoTimeToTime(update_entity.ctime());
data->modification_time = ProtoTimeToTime(update_entity.mtime());
data->name = update_entity.name();
data->is_folder = update_entity.folder();
data->parent_id = update_entity.parent_id_string();
data->server_defined_unique_tag = update_entity.server_defined_unique_tag();
// Populate |originator_cache_guid| and |originator_client_item_id|. This is
// currently relevant only for bookmarks.
data->originator_cache_guid = update_entity.originator_cache_guid();
data->originator_client_item_id = update_entity.originator_client_item_id();
// Adapt the update for compatibility (all except specifics that may need
// encryption).
if (model_type == BOOKMARKS) {
AdaptUniquePositionForBookmark(update_entity, data.get());
}
// TODO(crbug.com/881289): Generate client tag hash for wallet data.
// Deleted entities must use the default instance of EntitySpecifics in // Deleted entities must use the default instance of EntitySpecifics in
// order for EntityData to correctly reflect that they are deleted. // order for EntityData to correctly reflect that they are deleted.
const sync_pb::EntitySpecifics& specifics = const sync_pb::EntitySpecifics& specifics =
update_entity.deleted() ? sync_pb::EntitySpecifics::default_instance() update_entity.deleted() ? sync_pb::EntitySpecifics::default_instance()
: update_entity.specifics(); : update_entity.specifics();
bool specifics_were_encrypted = false;
// Passwords use their own legacy encryption scheme.
if (specifics.password().has_encrypted()) { if (specifics.password().has_encrypted()) {
// Passwords use their own legacy encryption scheme.
DCHECK(cryptographer); DCHECK(cryptographer);
// TODO(crbug.com/516866): If we switch away from the password legacy // TODO(crbug.com/516866): If we switch away from the password legacy
// encryption, this method and DecryptStoredEntities() )should be already // encryption, this method and DecryptStoredEntities() )should be already
...@@ -247,51 +210,62 @@ ModelTypeWorker::DecryptionStatus ModelTypeWorker::PopulateUpdateResponseData( ...@@ -247,51 +210,62 @@ ModelTypeWorker::DecryptionStatus ModelTypeWorker::PopulateUpdateResponseData(
// Make sure the worker defers password entities if the encryption key // Make sure the worker defers password entities if the encryption key
// hasn't been received yet. // hasn't been received yet.
if (!cryptographer->CanDecrypt(specifics.password().encrypted())) { if (!cryptographer->CanDecrypt(specifics.password().encrypted())) {
data->specifics = specifics;
response_data->entity = std::move(data);
return DECRYPTION_PENDING; return DECRYPTION_PENDING;
} }
response_data->encryption_key_name =
specifics.password().encrypted().key_name();
if (!DecryptPasswordSpecifics(*cryptographer, specifics, if (!DecryptPasswordSpecifics(*cryptographer, specifics,
&data->specifics)) { &data->specifics)) {
return FAILED_TO_DECRYPT; return FAILED_TO_DECRYPT;
} }
AdaptUpdateForCompatibilityAfterDecryption( response_data->encryption_key_name =
model_type, update_entity, &data->specifics, specifics.password().encrypted().key_name();
/*specifics_were_encrypted=*/true); specifics_were_encrypted = true;
response_data->entity = std::move(data); } else if (specifics.has_encrypted()) {
return SUCCESS; // Check if specifics are encrypted and try to decrypt if so.
} // Deleted entities should not be encrypted.
DCHECK(!update_entity.deleted());
// Check if specifics are encrypted and try to decrypt if so. if (!cryptographer || !cryptographer->CanDecrypt(specifics.encrypted())) {
if (!specifics.has_encrypted()) { // Can't decrypt right now.
// No encryption. return DECRYPTION_PENDING;
data->specifics = specifics; }
AdaptUpdateForCompatibilityAfterDecryption(
model_type, update_entity, &data->specifics,
/*specifics_were_encrypted=*/false);
response_data->entity = std::move(data);
return SUCCESS;
}
// Deleted entities should not be encrypted.
DCHECK(!update_entity.deleted());
if (cryptographer && cryptographer->CanDecrypt(specifics.encrypted())) {
// Encrypted and we know the key. // Encrypted and we know the key.
if (!DecryptSpecifics(*cryptographer, specifics, &data->specifics)) { if (!DecryptSpecifics(*cryptographer, specifics, &data->specifics)) {
return FAILED_TO_DECRYPT; return FAILED_TO_DECRYPT;
} }
AdaptUpdateForCompatibilityAfterDecryption(
model_type, update_entity, &data->specifics,
/*specifics_were_encrypted=*/true);
response_data->entity = std::move(data);
response_data->encryption_key_name = specifics.encrypted().key_name(); response_data->encryption_key_name = specifics.encrypted().key_name();
return SUCCESS; specifics_were_encrypted = true;
} else {
// No encryption.
data->specifics = specifics;
}
response_data->response_version = update_entity.version();
// Prepare the message for the model thread.
data->id = update_entity.id_string();
data->client_tag_hash =
ClientTagHash::FromHashed(update_entity.client_defined_unique_tag());
data->creation_time = ProtoTimeToTime(update_entity.ctime());
data->modification_time = ProtoTimeToTime(update_entity.mtime());
data->name = update_entity.name();
data->is_folder = update_entity.folder();
data->parent_id = update_entity.parent_id_string();
data->server_defined_unique_tag = update_entity.server_defined_unique_tag();
// Populate |originator_cache_guid| and |originator_client_item_id|. This is
// currently relevant only for bookmarks.
data->originator_cache_guid = update_entity.originator_cache_guid();
data->originator_client_item_id = update_entity.originator_client_item_id();
// Adapt the update for compatibility.
if (model_type == BOOKMARKS) {
AdaptUniquePositionForBookmark(update_entity, data.get());
AdaptTitleForBookmark(update_entity, &data->specifics,
specifics_were_encrypted);
AdaptGuidForBookmark(update_entity, &data->specifics);
} }
// Can't decrypt right now. // TODO(crbug.com/881289): Generate client tag hash for wallet data.
data->specifics = specifics;
response_data->entity = std::move(data); response_data->entity = std::move(data);
return DECRYPTION_PENDING; return SUCCESS;
} }
void ModelTypeWorker::ApplyUpdates(StatusController* status) { void ModelTypeWorker::ApplyUpdates(StatusController* status) {
...@@ -475,57 +449,26 @@ bool ModelTypeWorker::UpdateEncryptionKeyName() { ...@@ -475,57 +449,26 @@ bool ModelTypeWorker::UpdateEncryptionKeyName() {
void ModelTypeWorker::DecryptStoredEntities() { void ModelTypeWorker::DecryptStoredEntities() {
for (auto it = entries_pending_decryption_.begin(); for (auto it = entries_pending_decryption_.begin();
it != entries_pending_decryption_.end();) { it != entries_pending_decryption_.end();) {
const UpdateResponseData& encrypted_update = *it->second; const sync_pb::SyncEntity& encrypted_update = it->second;
const EntityData& data = *encrypted_update.entity;
DCHECK(!data.is_deleted());
sync_pb::EntitySpecifics specifics;
std::string encryption_key_name;
if (data.specifics.password().has_encrypted()) { auto response_data = std::make_unique<UpdateResponseData>();
encryption_key_name = data.specifics.password().encrypted().key_name(); switch (PopulateUpdateResponseData(cryptographer_.get(), type_,
if (!cryptographer_->CanDecrypt(data.specifics.password().encrypted())) { encrypted_update, response_data.get())) {
++it; case SUCCESS:
continue; pending_updates_.push_back(std::move(response_data));
}
if (!DecryptPasswordSpecifics(*cryptographer_, data.specifics,
&specifics)) {
// Decryption error should be permanent (e.g. corrupt data), since
// CanDecrypt() above claims decryption keys are up-to-date. Let's
// ignore this update to avoid blocking other updates.
it = entries_pending_decryption_.erase(it); it = entries_pending_decryption_.erase(it);
continue; break;
} case DECRYPTION_PENDING:
} else { // Still cannot decrypt, move on and keep this one for later.
DCHECK(data.specifics.has_encrypted());
encryption_key_name = data.specifics.encrypted().key_name();
if (!cryptographer_->CanDecrypt(data.specifics.encrypted())) {
++it; ++it;
continue; break;
} case FAILED_TO_DECRYPT:
if (!DecryptSpecifics(*cryptographer_, data.specifics, &specifics)) {
// Decryption error should be permanent (e.g. corrupt data), since // Decryption error should be permanent (e.g. corrupt data), since
// CanDecrypt() above claims decryption keys are up-to-date. Let's // decryption keys are up-to-date. Let's ignore this update to avoid
// ignore this update to avoid blocking other updates. // blocking other updates.
it = entries_pending_decryption_.erase(it); it = entries_pending_decryption_.erase(it);
continue; break;
}
}
auto decrypted_update = std::make_unique<UpdateResponseData>();
decrypted_update->response_version = encrypted_update.response_version;
decrypted_update->encryption_key_name = encryption_key_name;
decrypted_update->entity = std::move(it->second->entity);
decrypted_update->entity->specifics = std::move(specifics);
if (decrypted_update->entity->specifics.has_bookmark()) {
AdaptGuidForBookmarkEntityData(decrypted_update->entity.get());
} }
pending_updates_.push_back(std::move(decrypted_update));
it = entries_pending_decryption_.erase(it);
} }
} }
......
...@@ -231,10 +231,10 @@ class ModelTypeWorker : public UpdateHandler, ...@@ -231,10 +231,10 @@ class ModelTypeWorker : public UpdateHandler,
// Interface used to access and send nudges to the sync scheduler. Not owned. // Interface used to access and send nudges to the sync scheduler. Not owned.
NudgeHandler* nudge_handler_; NudgeHandler* nudge_handler_;
// A map of update responses, keyed by server_id. // A map of sync entities, keyed by server_id. Holds updates encrypted with
// Holds updates encrypted with pending keys. // pending keys. Entries are stored in a map for de-duplication (applying only
std::map<std::string, std::unique_ptr<UpdateResponseData>> // the latest).
entries_pending_decryption_; std::map<std::string, sync_pb::SyncEntity> entries_pending_decryption_;
// Accumulates all the updates from a single GetUpdates cycle in memory so // Accumulates all the updates from a single GetUpdates cycle in memory so
// they can all be sent to the processor at once. // they can all be sent to the processor at once.
......
...@@ -127,6 +127,7 @@ INSTANTIATE(EntityMetadata) ...@@ -127,6 +127,7 @@ INSTANTIATE(EntityMetadata)
INSTANTIATE(EntitySpecifics) INSTANTIATE(EntitySpecifics)
INSTANTIATE(ModelTypeState) INSTANTIATE(ModelTypeState)
INSTANTIATE(PersistedEntityData) INSTANTIATE(PersistedEntityData)
INSTANTIATE(SyncEntity)
INSTANTIATE(UniquePosition) INSTANTIATE(UniquePosition)
} // namespace sync_pb } // namespace sync_pb
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment