Chromium Code Reviews| Index: sync/engine/model_type_sync_worker_impl.cc |
| diff --git a/sync/engine/model_type_sync_worker_impl.cc b/sync/engine/model_type_sync_worker_impl.cc |
| index a5402c20ae42b875482b75f16ab0e98258dd0c97..b1622540156df0913b5d9b276d8e787656d12458 100644 |
| --- a/sync/engine/model_type_sync_worker_impl.cc |
| +++ b/sync/engine/model_type_sync_worker_impl.cc |
| @@ -13,6 +13,7 @@ |
| #include "sync/engine/model_type_sync_proxy.h" |
| #include "sync/engine/non_blocking_type_commit_contribution.h" |
| #include "sync/syncable/syncable_util.h" |
| +#include "sync/util/cryptographer.h" |
| #include "sync/util/time.h" |
| namespace syncer { |
| @@ -20,11 +21,14 @@ namespace syncer { |
| ModelTypeSyncWorkerImpl::ModelTypeSyncWorkerImpl( |
| ModelType type, |
| const DataTypeState& initial_state, |
| + const UpdateResponseDataList& saved_inapplicable_updates, |
| + CryptographerProvider* cryptographer_provider, |
| NudgeHandler* nudge_handler, |
| scoped_ptr<ModelTypeSyncProxy> type_sync_proxy) |
| : type_(type), |
| data_type_state_(initial_state), |
| type_sync_proxy_(type_sync_proxy.Pass()), |
| + cryptographer_provider_(cryptographer_provider), |
| nudge_handler_(nudge_handler), |
| entities_deleter_(&entities_), |
| weak_ptr_factory_(this) { |
| @@ -32,6 +36,18 @@ ModelTypeSyncWorkerImpl::ModelTypeSyncWorkerImpl( |
| if (!data_type_state_.initial_sync_done) { |
| nudge_handler_->NudgeForInitialDownload(type_); |
| } |
| + |
| + for (UpdateResponseDataList::const_iterator it = |
| + saved_inapplicable_updates.begin(); |
| + it != saved_inapplicable_updates.end(); |
| + ++it) { |
| + EntityTracker* entity_tracker = EntityTracker::FromServerUpdate( |
| + it->id, it->client_tag_hash, it->response_version); |
| + entity_tracker->ReceiveInapplicableUpdate(*it); |
| + entities_.insert(std::make_pair(it->client_tag_hash, entity_tracker)); |
| + } |
| + |
| + TryDecryptPendingUpdates(); |
| } |
| ModelTypeSyncWorkerImpl::~ModelTypeSyncWorkerImpl() { |
| @@ -42,6 +58,33 @@ ModelType ModelTypeSyncWorkerImpl::GetModelType() const { |
| return type_; |
| } |
| +bool ModelTypeSyncWorkerImpl::IsEncryptionRequired() const { |
| + return !data_type_state_.encryption_key_name.empty(); |
| +} |
| + |
| +void ModelTypeSyncWorkerImpl::SetEncryptionKeyName(const std::string& name) { |
| + if (data_type_state_.encryption_key_name == name) |
| + return; |
| + |
| + data_type_state_.encryption_key_name = name; |
| + |
| + // Pretend to send an update. This will cause the TypeSyncProxy to notice |
| + // the new encryption key and take appropriate action. |
| + type_sync_proxy_->OnUpdateReceived( |
| + data_type_state_, UpdateResponseDataList(), UpdateResponseDataList()); |
| +} |
| + |
| +void ModelTypeSyncWorkerImpl::OnCryptographerStateChanged() { |
| + TryDecryptPendingUpdates(); |
| + |
| + ScopedCryptographerRef scoped_cryptographer_ref; |
| + cryptographer_provider_->InitScopedCryptographerRef( |
| + &scoped_cryptographer_ref); |
| + Cryptographer* cryptographer = scoped_cryptographer_ref.Get(); |
| + if (CanCommitItems(cryptographer)) |
| + nudge_handler_->NudgeForCommit(type_); |
| +} |
| + |
| // UpdateHandler implementation. |
| void ModelTypeSyncWorkerImpl::GetDownloadProgress( |
| sync_pb::DataTypeProgressMarker* progress_marker) const { |
| @@ -66,7 +109,14 @@ SyncerError ModelTypeSyncWorkerImpl::ProcessGetUpdatesResponse( |
| data_type_state_.type_context = mutated_context; |
| data_type_state_.progress_marker = progress_marker; |
| + ScopedCryptographerRef scoped_cryptographer_ref; |
| + cryptographer_provider_->InitScopedCryptographerRef( |
| + &scoped_cryptographer_ref); |
| + Cryptographer* cryptographer = scoped_cryptographer_ref.Get(); |
| + DCHECK(cryptographer); |
| + |
| UpdateResponseDataList response_datas; |
| + UpdateResponseDataList inapplicable_updates; |
| for (SyncEntityList::const_iterator update_it = applicable_updates.begin(); |
| update_it != applicable_updates.end(); |
| @@ -86,16 +136,17 @@ SyncerError ModelTypeSyncWorkerImpl::ProcessGetUpdatesResponse( |
| const std::string& client_tag_hash = |
| update_entity->client_defined_unique_tag(); |
| DCHECK(!client_tag_hash.empty()); |
| + |
| + EntityTracker* entity_tracker = NULL; |
| EntityMap::const_iterator map_it = entities_.find(client_tag_hash); |
| if (map_it == entities_.end()) { |
| - EntityTracker* entity = |
| + entity_tracker = |
| EntityTracker::FromServerUpdate(update_entity->id_string(), |
| client_tag_hash, |
| update_entity->version()); |
| - entities_.insert(std::make_pair(client_tag_hash, entity)); |
| + entities_.insert(std::make_pair(client_tag_hash, entity_tracker)); |
| } else { |
| - EntityTracker* entity = map_it->second; |
| - entity->ReceiveUpdate(update_entity->version()); |
| + entity_tracker = map_it->second; |
| } |
| // Prepare the message for the model thread. |
| @@ -107,14 +158,39 @@ SyncerError ModelTypeSyncWorkerImpl::ProcessGetUpdatesResponse( |
| response_data.mtime = ProtoTimeToTime(update_entity->mtime()); |
| response_data.non_unique_name = update_entity->name(); |
| response_data.deleted = update_entity->deleted(); |
| - response_data.specifics = update_entity->specifics(); |
| - response_datas.push_back(response_data); |
| + const sync_pb::EntitySpecifics& specifics = update_entity->specifics(); |
| + |
| + if (!specifics.has_encrypted()) { |
| + // No encryption. |
| + entity_tracker->ReceiveUpdate(update_entity->version()); |
| + response_data.specifics = specifics; |
| + response_datas.push_back(response_data); |
| + } else if (specifics.has_encrypted() && |
| + cryptographer->CanDecrypt(specifics.encrypted())) { |
| + // Encrypted, but we know the key. |
| + if (DecryptSpecifics( |
| + cryptographer, specifics, &response_data.specifics)) { |
| + entity_tracker->ReceiveUpdate(update_entity->version()); |
| + response_data.encryption_key_name = specifics.encrypted().key_name(); |
| + response_datas.push_back(response_data); |
| + } |
| + } else if (specifics.has_encrypted() && |
| + !cryptographer->CanDecrypt(specifics.encrypted())) { |
| + // Can't decrypt right now. Ask the entity tracker to handle it. |
| + response_data.specifics = specifics; |
| + if (entity_tracker->ReceiveInapplicableUpdate(response_data)) { |
| + // Send to the model thread for safe-keeping across restarts if the |
| + // tracker decides the update is worth keeping. |
| + inapplicable_updates.push_back(response_data); |
| + } |
| + } |
| } |
| } |
| // Forward these updates to the model thread so it can do the rest. |
| - type_sync_proxy_->OnUpdateReceived(data_type_state_, response_datas); |
| + type_sync_proxy_->OnUpdateReceived( |
| + data_type_state_, response_datas, inapplicable_updates); |
| return SYNCER_OK; |
| } |
| @@ -128,8 +204,8 @@ void ModelTypeSyncWorkerImpl::ApplyUpdates(sessions::StatusController* status) { |
| if (!data_type_state_.initial_sync_done) { |
| data_type_state_.initial_sync_done = true; |
| - UpdateResponseDataList empty_update_list; |
| - type_sync_proxy_->OnUpdateReceived(data_type_state_, empty_update_list); |
| + type_sync_proxy_->OnUpdateReceived( |
| + data_type_state_, UpdateResponseDataList(), UpdateResponseDataList()); |
| } |
| } |
| @@ -144,7 +220,7 @@ void ModelTypeSyncWorkerImpl::EnqueueForCommit( |
| const CommitRequestDataList& list) { |
| DCHECK(CalledOnValidThread()); |
| - DCHECK(CanCommitItems()) |
| + DCHECK(IsTypeInitialized()) |
| << "Asked to commit items before type was initialized. " |
| << "ModelType is: " << ModelTypeToString(type_); |
| @@ -153,6 +229,13 @@ void ModelTypeSyncWorkerImpl::EnqueueForCommit( |
| ++it) { |
| StorePendingCommit(*it); |
| } |
| + |
| + ScopedCryptographerRef scoped_cryptographer_ref; |
| + cryptographer_provider_->InitScopedCryptographerRef( |
| + &scoped_cryptographer_ref); |
| + Cryptographer* cryptographer = scoped_cryptographer_ref.Get(); |
| + if (CanCommitItems(cryptographer)) |
| + nudge_handler_->NudgeForCommit(type_); |
| } |
| // CommitContributor implementation. |
| @@ -164,7 +247,12 @@ scoped_ptr<CommitContribution> ModelTypeSyncWorkerImpl::GetContribution( |
| std::vector<int64> sequence_numbers; |
| google::protobuf::RepeatedPtrField<sync_pb::SyncEntity> commit_entities; |
| - if (!CanCommitItems()) |
| + ScopedCryptographerRef scoped_cryptographer_ref; |
| + cryptographer_provider_->InitScopedCryptographerRef( |
| + &scoped_cryptographer_ref); |
| + Cryptographer* cryptographer = scoped_cryptographer_ref.Get(); |
| + |
| + if (!CanCommitItems(cryptographer)) |
| return scoped_ptr<CommitContribution>(); |
| // TODO(rlarocque): Avoid iterating here. |
| @@ -177,7 +265,7 @@ scoped_ptr<CommitContribution> ModelTypeSyncWorkerImpl::GetContribution( |
| int64 sequence_number = -1; |
| entity->PrepareCommitProto(commit_entity, &sequence_number); |
| - HelpInitializeCommitEntity(commit_entity); |
| + HelpInitializeCommitEntity(cryptographer, commit_entity); |
| sequence_numbers.push_back(sequence_number); |
| space_remaining--; |
| @@ -222,9 +310,6 @@ void ModelTypeSyncWorkerImpl::StorePendingCommit( |
| request.deleted, |
| request.specifics); |
| } |
| - |
| - if (CanCommitItems()) |
| - nudge_handler_->NudgeForCommit(type_); |
| } |
| void ModelTypeSyncWorkerImpl::OnCommitResponse( |
| @@ -260,14 +345,30 @@ base::WeakPtr<ModelTypeSyncWorkerImpl> ModelTypeSyncWorkerImpl::AsWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| -bool ModelTypeSyncWorkerImpl::CanCommitItems() const { |
| - // We can't commit anything until we know the type's parent node. |
| - // We'll get it in the first update response. |
| +bool ModelTypeSyncWorkerImpl::IsTypeInitialized() const { |
| return !data_type_state_.type_root_id.empty() && |
| data_type_state_.initial_sync_done; |
| } |
| +bool ModelTypeSyncWorkerImpl::CanCommitItems( |
| + Cryptographer* cryptographer) const { |
| + // We can't commit anything until we know the type's parent node. |
| + // We'll get it in the first update response. |
| + if (!IsTypeInitialized()) |
| + return false; |
| + |
| + // Don't commit if we should be encrypting but don't have the required keys. |
| + if (IsEncryptionRequired() && (!cryptographer || !cryptographer->is_ready() || |
| + cryptographer->GetDefaultKeyName() != |
| + data_type_state_.encryption_key_name)) { |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| void ModelTypeSyncWorkerImpl::HelpInitializeCommitEntity( |
| + Cryptographer* cryptographer, |
| sync_pb::SyncEntity* sync_entity) { |
| // Initial commits need our help to generate a client ID. |
| if (!sync_entity->has_id_string()) { |
| @@ -277,14 +378,81 @@ void ModelTypeSyncWorkerImpl::HelpInitializeCommitEntity( |
| base::StringPrintf("%s-%" PRId64, ModelTypeToString(type_), id)); |
| } |
| + // Encrypt the specifics and hide the title if necessary. |
| + if (IsEncryptionRequired() && cryptographer) { |
|
Nicolas Zea
2014/07/30 00:34:16
Seems like if IsEncryptionRequired() is true, we s
rlarocque
2014/07/30 21:56:01
Good point. I don't know why it would be NULL.
D
|
| + sync_pb::EntitySpecifics encrypted_specifics; |
| + cryptographer->Encrypt(sync_entity->specifics(), |
| + encrypted_specifics.mutable_encrypted()); |
| + sync_entity->mutable_specifics()->CopyFrom(encrypted_specifics); |
| + sync_entity->set_name("encrypted"); |
| + } |
| + |
| // Always include enough specifics to identify the type. Do this even in |
| // deletion requests, where the specifics are otherwise invalid. |
| - if (!sync_entity->has_specifics()) { |
| - AddDefaultFieldValue(type_, sync_entity->mutable_specifics()); |
| - } |
| + AddDefaultFieldValue(type_, sync_entity->mutable_specifics()); |
| // We're always responsible for the parent ID. |
| sync_entity->set_parent_id_string(data_type_state_.type_root_id); |
| } |
| +void ModelTypeSyncWorkerImpl::TryDecryptPendingUpdates() { |
| + UpdateResponseDataList response_datas; |
| + |
| + ScopedCryptographerRef scoped_cryptographer_ref; |
| + cryptographer_provider_->InitScopedCryptographerRef( |
| + &scoped_cryptographer_ref); |
| + Cryptographer* cryptographer = scoped_cryptographer_ref.Get(); |
| + DCHECK(cryptographer); |
| + |
| + for (EntityMap::const_iterator it = entities_.begin(); it != entities_.end(); |
| + ++it) { |
| + if (it->second->HasInapplicableUpdate()) { |
| + const UpdateResponseData& saved_inapplicable = |
| + it->second->GetInapplicableUpdate(); |
| + |
| + // We assume all inapplicable updates are encrypted items for which we |
| + // don't have the key. |
| + DCHECK(saved_inapplicable.specifics.has_encrypted()); |
| + |
| + if (cryptographer->CanDecrypt(saved_inapplicable.specifics.encrypted())) { |
| + UpdateResponseData decrypted_response = saved_inapplicable; |
| + if (DecryptSpecifics(cryptographer, |
| + saved_inapplicable.specifics, |
| + &decrypted_response.specifics)) { |
| + decrypted_response.encryption_key_name = |
| + saved_inapplicable.specifics.encrypted().key_name(); |
| + response_datas.push_back(decrypted_response); |
| + |
| + it->second->ClearInapplicableUpdate(); |
| + } |
| + } |
| + } |
| + } |
| + |
| + if (!response_datas.empty()) { |
| + type_sync_proxy_->OnUpdateReceived( |
| + data_type_state_, response_datas, UpdateResponseDataList()); |
| + } |
| +} |
| + |
| +bool ModelTypeSyncWorkerImpl::DecryptSpecifics( |
| + Cryptographer* cryptographer, |
| + const sync_pb::EntitySpecifics& in, |
| + sync_pb::EntitySpecifics* out) { |
| + DCHECK(in.has_encrypted()); |
| + DCHECK(cryptographer->CanDecrypt(in.encrypted())); |
| + |
| + std::string plaintext; |
| + plaintext = cryptographer->DecryptToString(in.encrypted()); |
| + if (plaintext.empty()) { |
| + LOG(ERROR) << "Failed to decrypt a decryptable entity"; |
| + return false; |
| + } |
| + if (!out->ParseFromString(plaintext)) { |
| + LOG(ERROR) << "Failed to parse decrypted entity"; |
| + return false; |
| + } |
| + return true; |
| +} |
| + |
| } // namespace syncer |