// Copyright 2012 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 "sync/engine/apply_control_data_updates.h" #include "base/metrics/histogram.h" #include "sync/engine/conflict_resolver.h" #include "sync/engine/conflict_util.h" #include "sync/engine/syncer_util.h" #include "sync/syncable/directory.h" #include "sync/syncable/mutable_entry.h" #include "sync/syncable/nigori_handler.h" #include "sync/syncable/nigori_util.h" #include "sync/syncable/syncable_write_transaction.h" #include "sync/util/cryptographer.h" namespace syncer { using syncable::GET_TYPE_ROOT; using syncable::IS_UNAPPLIED_UPDATE; using syncable::IS_UNSYNCED; using syncable::SERVER_SPECIFICS; using syncable::SPECIFICS; using syncable::SYNCER; void ApplyControlDataUpdates(syncable::Directory* dir) { syncable::WriteTransaction trans(FROM_HERE, SYNCER, dir); std::vector<int64> handles; dir->GetUnappliedUpdateMetaHandles( &trans, ToFullModelTypeSet(ControlTypes()), &handles); // First, go through and manually apply any new top level datatype nodes (so // that we don't have to worry about hitting a CONFLICT_HIERARCHY with an // entry because we haven't applied its parent yet). // TODO(sync): if at some point we support control datatypes with actual // hierarchies we'll need to revisit this logic. ModelTypeSet control_types = ControlTypes(); for (ModelTypeSet::Iterator iter = control_types.First(); iter.Good(); iter.Inc()) { syncable::MutableEntry entry(&trans, syncable::GET_TYPE_ROOT, iter.Get()); if (!entry.good()) continue; if (!entry.GetIsUnappliedUpdate()) continue; ModelType type = entry.GetServerModelType(); if (type == NIGORI) { // Nigori node applications never fail. ApplyNigoriUpdate(&trans, &entry, dir->GetCryptographer(&trans)); } else { ApplyControlUpdate(&trans, &entry, dir->GetCryptographer(&trans)); } } // Go through the rest of the unapplied control updates, skipping over any // top level folders. for (std::vector<int64>::const_iterator iter = handles.begin(); iter != handles.end(); ++iter) { syncable::MutableEntry entry(&trans, syncable::GET_BY_HANDLE, *iter); CHECK(entry.good()); ModelType type = entry.GetServerModelType(); CHECK(ControlTypes().Has(type)); if (!entry.GetUniqueServerTag().empty()) { // We should have already applied all top level control nodes. DCHECK(!entry.GetIsUnappliedUpdate()); continue; } ApplyControlUpdate(&trans, &entry, dir->GetCryptographer(&trans)); } } // Update the nigori handler with the server's nigori node. // // If we have a locally modified nigori node, we merge them manually. This // handles the case where two clients both set a different passphrase. The // second client to attempt to commit will go into a state of having pending // keys, unioned the set of encrypted types, and eventually re-encrypt // everything with the passphrase of the first client and commit the set of // merged encryption keys. Until the second client provides the pending // passphrase, the cryptographer will preserve the encryption keys based on the // local passphrase, while the nigori node will preserve the server encryption // keys. void ApplyNigoriUpdate(syncable::WriteTransaction* const trans, syncable::MutableEntry* const entry, Cryptographer* cryptographer) { DCHECK(entry->GetIsUnappliedUpdate()); // We apply the nigori update regardless of whether there's a conflict or // not in order to preserve any new encrypted types or encryption keys. // TODO(zea): consider having this return a bool reflecting whether it was a // valid update or not, and in the case of invalid updates not overwrite the // local data. const sync_pb::NigoriSpecifics& nigori = entry->GetServerSpecifics().nigori(); trans->directory()->GetNigoriHandler()->ApplyNigoriUpdate(nigori, trans); // Make sure any unsynced changes are properly encrypted as necessary. // We only perform this if the cryptographer is ready. If not, these are // re-encrypted at SetDecryptionPassphrase time (via ReEncryptEverything). // This logic covers the case where the nigori update marked new datatypes // for encryption, but didn't change the passphrase. if (cryptographer->is_ready()) { // Note that we don't bother to encrypt any data for which IS_UNSYNCED // == false here. The machine that turned on encryption should know about // and re-encrypt all synced data. It's possible it could get interrupted // during this process, but we currently reencrypt everything at startup // as well, so as soon as a client is restarted with this datatype marked // for encryption, all the data should be updated as necessary. // If this fails, something is wrong with the cryptographer, but there's // nothing we can do about it here. DVLOG(1) << "Received new nigori, encrypting unsynced changes."; syncable::ProcessUnsyncedChangesForEncryption(trans); } if (!entry->GetIsUnsynced()) { // Update only. UpdateLocalDataFromServerData(trans, entry); } else { // Conflict. const sync_pb::EntitySpecifics& server_specifics = entry->GetServerSpecifics(); const sync_pb::NigoriSpecifics& server_nigori = server_specifics.nigori(); const sync_pb::EntitySpecifics& local_specifics = entry->GetSpecifics(); const sync_pb::NigoriSpecifics& local_nigori = local_specifics.nigori(); // We initialize the new nigori with the server state, and will override // it as necessary below. sync_pb::EntitySpecifics new_specifics = entry->GetServerSpecifics(); sync_pb::NigoriSpecifics* new_nigori = new_specifics.mutable_nigori(); // If the cryptographer is not ready, another client set a new encryption // passphrase. If we had migrated locally, we will re-migrate when the // pending keys are provided. If we had set a new custom passphrase locally // the user will have another chance to set a custom passphrase later // (assuming they hadn't set a custom passphrase on the other client). // Therefore, we only attempt to merge the nigori nodes if the cryptographer // is ready. // Note: we only update the encryption keybag if we're sure that we aren't // invalidating the keystore_decryptor_token (i.e. we're either // not migrated or we copying over all local state). if (cryptographer->is_ready()) { if (local_nigori.has_passphrase_type() && server_nigori.has_passphrase_type()) { // They're both migrated, preserve the local nigori if the passphrase // type is more conservative. if (server_nigori.passphrase_type() == sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE && local_nigori.passphrase_type() != sync_pb::NigoriSpecifics::KEYSTORE_PASSPHRASE) { DCHECK(local_nigori.passphrase_type() == sync_pb::NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE || local_nigori.passphrase_type() == sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE); new_nigori->CopyFrom(local_nigori); cryptographer->GetKeys(new_nigori->mutable_encryption_keybag()); } } else if (!local_nigori.has_passphrase_type() && !server_nigori.has_passphrase_type()) { // Set the explicit passphrase based on the local state. If the server // had set an explict passphrase, we should have pending keys, so // should not reach this code. // Because neither side is migrated, we don't have to worry about the // keystore decryptor token. new_nigori->set_keybag_is_frozen(local_nigori.keybag_is_frozen()); cryptographer->GetKeys(new_nigori->mutable_encryption_keybag()); } else if (local_nigori.has_passphrase_type()) { // Local is migrated but server is not. Copy over the local migrated // data. new_nigori->CopyFrom(local_nigori); cryptographer->GetKeys(new_nigori->mutable_encryption_keybag()); } // else leave the new nigori with the server state. } // Always update to the safest set of encrypted types. trans->directory()->GetNigoriHandler()->UpdateNigoriFromEncryptedTypes( new_nigori, trans); entry->PutSpecifics(new_specifics); DVLOG(1) << "Resolving simple conflict, merging nigori nodes: " << entry; conflict_util::OverwriteServerChanges(entry); UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", ConflictResolver::NIGORI_MERGE, ConflictResolver::CONFLICT_RESOLUTION_SIZE); } } void ApplyControlUpdate(syncable::WriteTransaction* const trans, syncable::MutableEntry* const entry, Cryptographer* cryptographer) { DCHECK_NE(entry->GetServerModelType(), NIGORI); DCHECK(entry->GetIsUnappliedUpdate()); if (entry->GetIsUnsynced()) { // We just let the server win all conflicts with control types. DVLOG(1) << "Ignoring local changes for control update."; conflict_util::IgnoreLocalChanges(entry); UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", ConflictResolver::OVERWRITE_LOCAL, ConflictResolver::CONFLICT_RESOLUTION_SIZE); } UpdateAttemptResponse response = AttemptToUpdateEntry( trans, entry, cryptographer); DCHECK_EQ(SUCCESS, response); } } // namespace syncer