| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <string> | 5 #include <string> |
| 6 | 6 |
| 7 #include "base/format_macros.h" | |
| 8 #include "base/location.h" | 7 #include "base/location.h" |
| 9 #include "base/memory/scoped_ptr.h" | 8 #include "base/memory/scoped_ptr.h" |
| 10 #include "base/stringprintf.h" | 9 #include "base/stringprintf.h" |
| 11 #include "sync/engine/apply_updates_command.h" | 10 #include "sync/engine/apply_updates_command.h" |
| 12 #include "sync/engine/syncer.h" | 11 #include "sync/engine/syncer.h" |
| 13 #include "sync/internal_api/public/test/test_entry_factory.h" | 12 #include "sync/internal_api/public/test/test_entry_factory.h" |
| 14 #include "sync/protocol/bookmark_specifics.pb.h" | 13 #include "sync/protocol/bookmark_specifics.pb.h" |
| 15 #include "sync/protocol/password_specifics.pb.h" | 14 #include "sync/protocol/password_specifics.pb.h" |
| 16 #include "sync/sessions/sync_session.h" | |
| 17 #include "sync/syncable/mutable_entry.h" | 15 #include "sync/syncable/mutable_entry.h" |
| 18 #include "sync/syncable/nigori_util.h" | 16 #include "sync/syncable/nigori_util.h" |
| 19 #include "sync/syncable/read_transaction.h" | 17 #include "sync/syncable/read_transaction.h" |
| 20 #include "sync/syncable/syncable_id.h" | 18 #include "sync/syncable/syncable_id.h" |
| 21 #include "sync/syncable/syncable_util.h" | 19 #include "sync/syncable/syncable_util.h" |
| 22 #include "sync/syncable/write_transaction.h" | 20 #include "sync/syncable/write_transaction.h" |
| 23 #include "sync/test/engine/fake_model_worker.h" | 21 #include "sync/test/engine/fake_model_worker.h" |
| 24 #include "sync/test/engine/syncer_command_test.h" | 22 #include "sync/test/engine/syncer_command_test.h" |
| 25 #include "sync/test/engine/test_id_factory.h" | 23 #include "sync/test/engine/test_id_factory.h" |
| 26 #include "sync/test/fake_encryptor.h" | 24 #include "sync/test/fake_encryptor.h" |
| 27 #include "sync/test/fake_sync_encryption_handler.h" | 25 #include "sync/test/fake_sync_encryption_handler.h" |
| 28 #include "sync/util/cryptographer.h" | 26 #include "sync/util/cryptographer.h" |
| 29 #include "testing/gtest/include/gtest/gtest.h" | 27 #include "testing/gtest/include/gtest/gtest.h" |
| 30 | 28 |
| 31 namespace syncer { | 29 namespace syncer { |
| 32 | 30 |
| 33 using sessions::SyncSession; | |
| 34 using std::string; | 31 using std::string; |
| 35 using syncable::Id; | 32 using syncable::Id; |
| 36 using syncable::MutableEntry; | 33 using syncable::MutableEntry; |
| 37 using syncable::UNITTEST; | 34 using syncable::UNITTEST; |
| 38 using syncable::WriteTransaction; | 35 using syncable::WriteTransaction; |
| 39 | 36 |
| 40 namespace { | 37 namespace { |
| 41 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() { | 38 sync_pb::EntitySpecifics DefaultBookmarkSpecifics() { |
| 42 sync_pb::EntitySpecifics result; | 39 sync_pb::EntitySpecifics result; |
| 43 AddDefaultFieldValue(BOOKMARKS, &result); | 40 AddDefaultFieldValue(BOOKMARKS, &result); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 54 | 51 |
| 55 virtual void SetUp() { | 52 virtual void SetUp() { |
| 56 workers()->clear(); | 53 workers()->clear(); |
| 57 mutable_routing_info()->clear(); | 54 mutable_routing_info()->clear(); |
| 58 workers()->push_back( | 55 workers()->push_back( |
| 59 make_scoped_refptr(new FakeModelWorker(GROUP_UI))); | 56 make_scoped_refptr(new FakeModelWorker(GROUP_UI))); |
| 60 workers()->push_back( | 57 workers()->push_back( |
| 61 make_scoped_refptr(new FakeModelWorker(GROUP_PASSWORD))); | 58 make_scoped_refptr(new FakeModelWorker(GROUP_PASSWORD))); |
| 62 (*mutable_routing_info())[BOOKMARKS] = GROUP_UI; | 59 (*mutable_routing_info())[BOOKMARKS] = GROUP_UI; |
| 63 (*mutable_routing_info())[PASSWORDS] = GROUP_PASSWORD; | 60 (*mutable_routing_info())[PASSWORDS] = GROUP_PASSWORD; |
| 64 (*mutable_routing_info())[NIGORI] = GROUP_PASSIVE; | 61 (*mutable_routing_info())[NIGORI] = GROUP_CONTROL; |
| 65 SyncerCommandTest::SetUp(); | 62 SyncerCommandTest::SetUp(); |
| 66 entry_factory_.reset(new TestEntryFactory(directory())); | 63 entry_factory_.reset(new TestEntryFactory(directory())); |
| 67 ExpectNoGroupsToChange(apply_updates_command_); | 64 ExpectNoGroupsToChange(apply_updates_command_); |
| 68 | 65 |
| 69 syncable::ReadTransaction trans(FROM_HERE, directory()); | 66 syncable::ReadTransaction trans(FROM_HERE, directory()); |
| 70 directory()->GetCryptographer(&trans)->SetNigoriHandler( | 67 directory()->GetCryptographer(&trans)->SetNigoriHandler( |
| 71 &fake_encryption_handler_); | 68 &fake_encryption_handler_); |
| 72 fake_encryption_handler_.set_cryptographer( | 69 fake_encryption_handler_.set_cryptographer( |
| 73 directory()->GetCryptographer(&trans)); | 70 directory()->GetCryptographer(&trans)); |
| 74 } | 71 } |
| 75 | 72 |
| 76 protected: | 73 protected: |
| 77 DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest); | 74 DISALLOW_COPY_AND_ASSIGN(ApplyUpdatesCommandTest); |
| 78 | 75 |
| 79 ApplyUpdatesCommand apply_updates_command_; | 76 ApplyUpdatesCommand apply_updates_command_; |
| 80 FakeEncryptor encryptor_; | 77 FakeEncryptor encryptor_; |
| 81 TestIdFactory id_factory_; | |
| 82 scoped_ptr<TestEntryFactory> entry_factory_; | 78 scoped_ptr<TestEntryFactory> entry_factory_; |
| 83 FakeSyncEncryptionHandler fake_encryption_handler_; | 79 FakeSyncEncryptionHandler fake_encryption_handler_; |
| 84 }; | 80 }; |
| 85 | 81 |
| 86 TEST_F(ApplyUpdatesCommandTest, Simple) { | 82 TEST_F(ApplyUpdatesCommandTest, Simple) { |
| 87 string root_server_id = syncable::GetNullId().GetServerId(); | 83 string root_server_id = syncable::GetNullId().GetServerId(); |
| 88 entry_factory_->CreateUnappliedNewItemWithParent("parent", | 84 entry_factory_->CreateUnappliedNewItemWithParent("parent", |
| 89 DefaultBookmarkSpecifics(), | 85 DefaultBookmarkSpecifics(), |
| 90 root_server_id); | 86 root_server_id); |
| 91 entry_factory_->CreateUnappliedNewItemWithParent("child", | 87 entry_factory_->CreateUnappliedNewItemWithParent("child", |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 169 int64 handle = entry_factory_->CreateUnappliedAndUnsyncedItem( | 165 int64 handle = entry_factory_->CreateUnappliedAndUnsyncedItem( |
| 170 "orphaned_by_server", BOOKMARKS); | 166 "orphaned_by_server", BOOKMARKS); |
| 171 { | 167 { |
| 172 // Manually set the SERVER_PARENT_ID to bad value. | 168 // Manually set the SERVER_PARENT_ID to bad value. |
| 173 // A bad parent indicates a hierarchy conflict. | 169 // A bad parent indicates a hierarchy conflict. |
| 174 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | 170 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); |
| 175 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | 171 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); |
| 176 ASSERT_TRUE(entry.good()); | 172 ASSERT_TRUE(entry.good()); |
| 177 | 173 |
| 178 entry.Put(syncable::SERVER_PARENT_ID, | 174 entry.Put(syncable::SERVER_PARENT_ID, |
| 179 id_factory_.MakeServer("bogus_parent")); | 175 TestIdFactory::MakeServer("bogus_parent")); |
| 180 } | 176 } |
| 181 | 177 |
| 182 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | 178 ExpectGroupToChange(apply_updates_command_, GROUP_UI); |
| 183 apply_updates_command_.ExecuteImpl(session()); | 179 apply_updates_command_.ExecuteImpl(session()); |
| 184 | 180 |
| 185 sessions::StatusController* status = session()->mutable_status_controller(); | 181 sessions::StatusController* status = session()->mutable_status_controller(); |
| 186 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | 182 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); |
| 187 | 183 |
| 188 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()); | 184 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()); |
| 189 | 185 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 206 // Create it as a child of root node. | 202 // Create it as a child of root node. |
| 207 int64 handle = entry_factory_->CreateSyncedItem("X", BOOKMARKS, true); | 203 int64 handle = entry_factory_->CreateSyncedItem("X", BOOKMARKS, true); |
| 208 | 204 |
| 209 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | 205 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); |
| 210 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | 206 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); |
| 211 ASSERT_TRUE(entry.good()); | 207 ASSERT_TRUE(entry.good()); |
| 212 | 208 |
| 213 // Re-parent from root to "Y" | 209 // Re-parent from root to "Y" |
| 214 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision()); | 210 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision()); |
| 215 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | 211 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); |
| 216 entry.Put(syncable::SERVER_PARENT_ID, id_factory_.MakeServer("Y")); | 212 entry.Put(syncable::SERVER_PARENT_ID, TestIdFactory::MakeServer("Y")); |
| 217 } | 213 } |
| 218 | 214 |
| 219 // Item 'Y' is child of 'X'. | 215 // Item 'Y' is child of 'X'. |
| 220 entry_factory_->CreateUnsyncedItem( | 216 entry_factory_->CreateUnsyncedItem( |
| 221 id_factory_.MakeServer("Y"), id_factory_.MakeServer("X"), "Y", true, | 217 TestIdFactory::MakeServer("Y"), TestIdFactory::MakeServer("X"), "Y", true, |
| 222 BOOKMARKS, NULL); | 218 BOOKMARKS, NULL); |
| 223 | 219 |
| 224 // If the server's update were applied, we would have X be a child of Y, and Y | 220 // If the server's update were applied, we would have X be a child of Y, and Y |
| 225 // as a child of X. That's a directory loop. The UpdateApplicator should | 221 // as a child of X. That's a directory loop. The UpdateApplicator should |
| 226 // prevent the update from being applied and note that this is a hierarchy | 222 // prevent the update from being applied and note that this is a hierarchy |
| 227 // conflict. | 223 // conflict. |
| 228 | 224 |
| 229 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | 225 ExpectGroupToChange(apply_updates_command_, GROUP_UI); |
| 230 apply_updates_command_.ExecuteImpl(session()); | 226 apply_updates_command_.ExecuteImpl(session()); |
| 231 | 227 |
| 232 sessions::StatusController* status = session()->mutable_status_controller(); | 228 sessions::StatusController* status = session()->mutable_status_controller(); |
| 233 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | 229 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); |
| 234 | 230 |
| 235 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()); | 231 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()); |
| 236 | 232 |
| 237 // This should count as a hierarchy conflict. | 233 // This should count as a hierarchy conflict. |
| 238 ASSERT_TRUE(status->conflict_progress()); | 234 ASSERT_TRUE(status->conflict_progress()); |
| 239 EXPECT_EQ(1, status->conflict_progress()->HierarchyConflictingItemsSize()); | 235 EXPECT_EQ(1, status->conflict_progress()->HierarchyConflictingItemsSize()); |
| 240 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()); | 236 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()); |
| 241 } | 237 } |
| 242 | 238 |
| 243 // Runs the ApplyUpdatesCommand on a directory where the server sent us an | 239 // Runs the ApplyUpdatesCommand on a directory where the server sent us an |
| 244 // update to add a child to a locally deleted (and unsynced) parent. We expect | 240 // update to add a child to a locally deleted (and unsynced) parent. We expect |
| 245 // the command to not apply the update and to indicate the update is in a | 241 // the command to not apply the update and to indicate the update is in a |
| 246 // CONFLICT_HIERARCHY state. | 242 // CONFLICT_HIERARCHY state. |
| 247 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictDeletedParent) { | 243 TEST_F(ApplyUpdatesCommandTest, HierarchyConflictDeletedParent) { |
| 248 // Create a locally deleted parent item. | 244 // Create a locally deleted parent item. |
| 249 int64 parent_handle; | 245 int64 parent_handle; |
| 250 entry_factory_->CreateUnsyncedItem( | 246 entry_factory_->CreateUnsyncedItem( |
| 251 Id::CreateFromServerId("parent"), id_factory_.root(), | 247 Id::CreateFromServerId("parent"), TestIdFactory::root(), |
| 252 "parent", true, BOOKMARKS, &parent_handle); | 248 "parent", true, BOOKMARKS, &parent_handle); |
| 253 { | 249 { |
| 254 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | 250 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); |
| 255 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, parent_handle); | 251 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, parent_handle); |
| 256 entry.Put(syncable::IS_DEL, true); | 252 entry.Put(syncable::IS_DEL, true); |
| 257 } | 253 } |
| 258 | 254 |
| 259 // Create an incoming child from the server. | 255 // Create an incoming child from the server. |
| 260 entry_factory_->CreateUnappliedNewItemWithParent( | 256 entry_factory_->CreateUnappliedNewItemWithParent( |
| 261 "child", DefaultBookmarkSpecifics(), "parent"); | 257 "child", DefaultBookmarkSpecifics(), "parent"); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 287 int64 handle = | 283 int64 handle = |
| 288 entry_factory_->CreateSyncedItem("parent", BOOKMARKS, true); | 284 entry_factory_->CreateSyncedItem("parent", BOOKMARKS, true); |
| 289 | 285 |
| 290 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | 286 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); |
| 291 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); | 287 MutableEntry entry(&trans, syncable::GET_BY_HANDLE, handle); |
| 292 ASSERT_TRUE(entry.good()); | 288 ASSERT_TRUE(entry.good()); |
| 293 | 289 |
| 294 // Delete it on the server. | 290 // Delete it on the server. |
| 295 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision()); | 291 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision()); |
| 296 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | 292 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); |
| 297 entry.Put(syncable::SERVER_PARENT_ID, id_factory_.root()); | 293 entry.Put(syncable::SERVER_PARENT_ID, TestIdFactory::root()); |
| 298 entry.Put(syncable::SERVER_IS_DEL, true); | 294 entry.Put(syncable::SERVER_IS_DEL, true); |
| 299 } | 295 } |
| 300 | 296 |
| 301 // Create a local child of the server-deleted directory. | 297 // Create a local child of the server-deleted directory. |
| 302 entry_factory_->CreateUnsyncedItem( | 298 entry_factory_->CreateUnsyncedItem( |
| 303 id_factory_.MakeServer("child"), id_factory_.MakeServer("parent"), | 299 TestIdFactory::MakeServer("child"), TestIdFactory::MakeServer("parent"), |
| 304 "child", false, BOOKMARKS, NULL); | 300 "child", false, BOOKMARKS, NULL); |
| 305 | 301 |
| 306 // The server's request to delete the directory must be ignored, otherwise our | 302 // The server's request to delete the directory must be ignored, otherwise our |
| 307 // unsynced new child would be orphaned. This is a hierarchy conflict. | 303 // unsynced new child would be orphaned. This is a hierarchy conflict. |
| 308 | 304 |
| 309 ExpectGroupToChange(apply_updates_command_, GROUP_UI); | 305 ExpectGroupToChange(apply_updates_command_, GROUP_UI); |
| 310 apply_updates_command_.ExecuteImpl(session()); | 306 apply_updates_command_.ExecuteImpl(session()); |
| 311 | 307 |
| 312 sessions::StatusController* status = session()->mutable_status_controller(); | 308 sessions::StatusController* status = session()->mutable_status_controller(); |
| 313 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); | 309 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_UI); |
| (...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 515 << "The updates that can't be decrypted should not be in regular " | 511 << "The updates that can't be decrypted should not be in regular " |
| 516 << "conflict"; | 512 << "conflict"; |
| 517 EXPECT_EQ(1, status->conflict_progress()->EncryptionConflictingItemsSize()) | 513 EXPECT_EQ(1, status->conflict_progress()->EncryptionConflictingItemsSize()) |
| 518 << "The updates that can't be decrypted should be in encryption " | 514 << "The updates that can't be decrypted should be in encryption " |
| 519 << "conflict"; | 515 << "conflict"; |
| 520 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | 516 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) |
| 521 << "The undecryptable password update shouldn't be applied"; | 517 << "The undecryptable password update shouldn't be applied"; |
| 522 } | 518 } |
| 523 } | 519 } |
| 524 | 520 |
| 525 TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) { | |
| 526 // Storing the cryptographer separately is bad, but for this test we | |
| 527 // know it's safe. | |
| 528 Cryptographer* cryptographer; | |
| 529 ModelTypeSet encrypted_types; | |
| 530 encrypted_types.Put(PASSWORDS); | |
| 531 encrypted_types.Put(NIGORI); | |
| 532 { | |
| 533 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 534 cryptographer = directory()->GetCryptographer(&trans); | |
| 535 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
| 536 } | |
| 537 | |
| 538 // Nigori node updates should update the Cryptographer. | |
| 539 Cryptographer other_cryptographer(&encryptor_); | |
| 540 KeyParams params = {"localhost", "dummy", "foobar"}; | |
| 541 other_cryptographer.AddKey(params); | |
| 542 | |
| 543 sync_pb::EntitySpecifics specifics; | |
| 544 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
| 545 other_cryptographer.GetKeys(nigori->mutable_encrypted()); | |
| 546 nigori->set_encrypt_everything(true); | |
| 547 entry_factory_->CreateUnappliedNewItem( | |
| 548 ModelTypeToRootTag(NIGORI), specifics, true); | |
| 549 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 550 | |
| 551 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
| 552 apply_updates_command_.ExecuteImpl(session()); | |
| 553 | |
| 554 sessions::StatusController* status = session()->mutable_status_controller(); | |
| 555 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
| 556 ASSERT_TRUE(status->update_progress()); | |
| 557 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
| 558 << "All updates should have been attempted"; | |
| 559 ASSERT_TRUE(status->conflict_progress()); | |
| 560 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
| 561 << "The nigori update shouldn't be in conflict"; | |
| 562 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
| 563 << "The nigori update should be applied"; | |
| 564 | |
| 565 EXPECT_FALSE(cryptographer->is_ready()); | |
| 566 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
| 567 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(ModelTypeSet::All())); | |
| 568 } | |
| 569 | |
| 570 TEST_F(ApplyUpdatesCommandTest, NigoriUpdateForDisabledTypes) { | |
| 571 // Storing the cryptographer separately is bad, but for this test we | |
| 572 // know it's safe. | |
| 573 Cryptographer* cryptographer; | |
| 574 ModelTypeSet encrypted_types; | |
| 575 encrypted_types.Put(PASSWORDS); | |
| 576 encrypted_types.Put(NIGORI); | |
| 577 { | |
| 578 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 579 cryptographer = directory()->GetCryptographer(&trans); | |
| 580 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
| 581 } | |
| 582 | |
| 583 // Nigori node updates should update the Cryptographer. | |
| 584 Cryptographer other_cryptographer(&encryptor_); | |
| 585 KeyParams params = {"localhost", "dummy", "foobar"}; | |
| 586 other_cryptographer.AddKey(params); | |
| 587 | |
| 588 sync_pb::EntitySpecifics specifics; | |
| 589 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
| 590 other_cryptographer.GetKeys(nigori->mutable_encrypted()); | |
| 591 nigori->set_encrypt_everything(true); | |
| 592 entry_factory_->CreateUnappliedNewItem( | |
| 593 ModelTypeToRootTag(NIGORI), specifics, true); | |
| 594 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 595 | |
| 596 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
| 597 apply_updates_command_.ExecuteImpl(session()); | |
| 598 | |
| 599 sessions::StatusController* status = session()->mutable_status_controller(); | |
| 600 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
| 601 ASSERT_TRUE(status->update_progress()); | |
| 602 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
| 603 << "All updates should have been attempted"; | |
| 604 ASSERT_TRUE(status->conflict_progress()); | |
| 605 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
| 606 << "The nigori update shouldn't be in conflict"; | |
| 607 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
| 608 << "The nigori update should be applied"; | |
| 609 | |
| 610 EXPECT_FALSE(cryptographer->is_ready()); | |
| 611 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
| 612 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(ModelTypeSet::All())); | |
| 613 } | |
| 614 | |
| 615 // Create some local unsynced and unencrypted data. Apply a nigori update that | |
| 616 // turns on encryption for the unsynced data. Ensure we properly encrypt the | |
| 617 // data as part of the nigori update. Apply another nigori update with no | |
| 618 // changes. Ensure we ignore already-encrypted unsynced data and that nothing | |
| 619 // breaks. | |
| 620 TEST_F(ApplyUpdatesCommandTest, EncryptUnsyncedChanges) { | |
| 621 // Storing the cryptographer separately is bad, but for this test we | |
| 622 // know it's safe. | |
| 623 Cryptographer* cryptographer; | |
| 624 ModelTypeSet encrypted_types; | |
| 625 encrypted_types.Put(PASSWORDS); | |
| 626 encrypted_types.Put(NIGORI); | |
| 627 { | |
| 628 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 629 cryptographer = directory()->GetCryptographer(&trans); | |
| 630 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
| 631 | |
| 632 // With default encrypted_types, this should be true. | |
| 633 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 634 | |
| 635 Syncer::UnsyncedMetaHandles handles; | |
| 636 GetUnsyncedEntries(&trans, &handles); | |
| 637 EXPECT_TRUE(handles.empty()); | |
| 638 } | |
| 639 | |
| 640 // Create unsynced bookmarks without encryption. | |
| 641 // First item is a folder | |
| 642 Id folder_id = id_factory_.NewLocalId(); | |
| 643 entry_factory_->CreateUnsyncedItem(folder_id, id_factory_.root(), "folder", | |
| 644 true, BOOKMARKS, NULL); | |
| 645 // Next five items are children of the folder | |
| 646 size_t i; | |
| 647 size_t batch_s = 5; | |
| 648 for (i = 0; i < batch_s; ++i) { | |
| 649 entry_factory_->CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id, | |
| 650 base::StringPrintf("Item %"PRIuS"", i), | |
| 651 false, BOOKMARKS, NULL); | |
| 652 } | |
| 653 // Next five items are children of the root. | |
| 654 for (; i < 2*batch_s; ++i) { | |
| 655 entry_factory_->CreateUnsyncedItem( | |
| 656 id_factory_.NewLocalId(), id_factory_.root(), | |
| 657 base::StringPrintf("Item %"PRIuS"", i), false, | |
| 658 BOOKMARKS, NULL); | |
| 659 } | |
| 660 | |
| 661 KeyParams params = {"localhost", "dummy", "foobar"}; | |
| 662 cryptographer->AddKey(params); | |
| 663 sync_pb::EntitySpecifics specifics; | |
| 664 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
| 665 cryptographer->GetKeys(nigori->mutable_encrypted()); | |
| 666 nigori->set_encrypt_everything(true); | |
| 667 encrypted_types.Put(BOOKMARKS); | |
| 668 entry_factory_->CreateUnappliedNewItem( | |
| 669 ModelTypeToRootTag(NIGORI), specifics, true); | |
| 670 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 671 EXPECT_TRUE(cryptographer->is_ready()); | |
| 672 | |
| 673 { | |
| 674 // Ensure we have unsynced nodes that aren't properly encrypted. | |
| 675 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 676 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 677 | |
| 678 Syncer::UnsyncedMetaHandles handles; | |
| 679 GetUnsyncedEntries(&trans, &handles); | |
| 680 EXPECT_EQ(2*batch_s+1, handles.size()); | |
| 681 } | |
| 682 | |
| 683 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
| 684 apply_updates_command_.ExecuteImpl(session()); | |
| 685 | |
| 686 { | |
| 687 sessions::StatusController* status = session()->mutable_status_controller(); | |
| 688 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
| 689 ASSERT_TRUE(status->update_progress()); | |
| 690 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
| 691 << "All updates should have been attempted"; | |
| 692 ASSERT_TRUE(status->conflict_progress()); | |
| 693 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
| 694 << "No updates should be in conflict"; | |
| 695 EXPECT_EQ(0, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
| 696 << "No updates should be in conflict"; | |
| 697 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
| 698 << "The nigori update should be applied"; | |
| 699 } | |
| 700 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 701 EXPECT_TRUE(cryptographer->is_ready()); | |
| 702 { | |
| 703 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 704 | |
| 705 // If ProcessUnsyncedChangesForEncryption worked, all our unsynced changes | |
| 706 // should be encrypted now. | |
| 707 EXPECT_TRUE(ModelTypeSet::All().Equals( | |
| 708 cryptographer->GetEncryptedTypes())); | |
| 709 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 710 | |
| 711 Syncer::UnsyncedMetaHandles handles; | |
| 712 GetUnsyncedEntries(&trans, &handles); | |
| 713 EXPECT_EQ(2*batch_s+1, handles.size()); | |
| 714 } | |
| 715 | |
| 716 // Simulate another nigori update that doesn't change anything. | |
| 717 { | |
| 718 WriteTransaction trans(FROM_HERE, UNITTEST, directory()); | |
| 719 MutableEntry entry(&trans, syncable::GET_BY_SERVER_TAG, | |
| 720 ModelTypeToRootTag(NIGORI)); | |
| 721 ASSERT_TRUE(entry.good()); | |
| 722 entry.Put(syncable::SERVER_VERSION, entry_factory_->GetNextRevision()); | |
| 723 entry.Put(syncable::IS_UNAPPLIED_UPDATE, true); | |
| 724 } | |
| 725 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
| 726 apply_updates_command_.ExecuteImpl(session()); | |
| 727 { | |
| 728 sessions::StatusController* status = session()->mutable_status_controller(); | |
| 729 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
| 730 ASSERT_TRUE(status->update_progress()); | |
| 731 EXPECT_EQ(2, status->update_progress()->AppliedUpdatesSize()) | |
| 732 << "All updates should have been attempted"; | |
| 733 ASSERT_TRUE(status->conflict_progress()); | |
| 734 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
| 735 << "No updates should be in conflict"; | |
| 736 EXPECT_EQ(0, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
| 737 << "No updates should be in conflict"; | |
| 738 EXPECT_EQ(2, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
| 739 << "The nigori update should be applied"; | |
| 740 } | |
| 741 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 742 EXPECT_TRUE(cryptographer->is_ready()); | |
| 743 { | |
| 744 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 745 | |
| 746 // All our changes should still be encrypted. | |
| 747 EXPECT_TRUE(ModelTypeSet::All().Equals( | |
| 748 cryptographer->GetEncryptedTypes())); | |
| 749 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 750 | |
| 751 Syncer::UnsyncedMetaHandles handles; | |
| 752 GetUnsyncedEntries(&trans, &handles); | |
| 753 EXPECT_EQ(2*batch_s+1, handles.size()); | |
| 754 } | |
| 755 } | |
| 756 | |
| 757 TEST_F(ApplyUpdatesCommandTest, CannotEncryptUnsyncedChanges) { | |
| 758 // Storing the cryptographer separately is bad, but for this test we | |
| 759 // know it's safe. | |
| 760 Cryptographer* cryptographer; | |
| 761 ModelTypeSet encrypted_types; | |
| 762 encrypted_types.Put(PASSWORDS); | |
| 763 encrypted_types.Put(NIGORI); | |
| 764 { | |
| 765 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 766 cryptographer = directory()->GetCryptographer(&trans); | |
| 767 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals(encrypted_types)); | |
| 768 | |
| 769 // With default encrypted_types, this should be true. | |
| 770 EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 771 | |
| 772 Syncer::UnsyncedMetaHandles handles; | |
| 773 GetUnsyncedEntries(&trans, &handles); | |
| 774 EXPECT_TRUE(handles.empty()); | |
| 775 } | |
| 776 | |
| 777 // Create unsynced bookmarks without encryption. | |
| 778 // First item is a folder | |
| 779 Id folder_id = id_factory_.NewLocalId(); | |
| 780 entry_factory_->CreateUnsyncedItem( | |
| 781 folder_id, id_factory_.root(), "folder", true, | |
| 782 BOOKMARKS, NULL); | |
| 783 // Next five items are children of the folder | |
| 784 size_t i; | |
| 785 size_t batch_s = 5; | |
| 786 for (i = 0; i < batch_s; ++i) { | |
| 787 entry_factory_->CreateUnsyncedItem(id_factory_.NewLocalId(), folder_id, | |
| 788 base::StringPrintf("Item %"PRIuS"", i), | |
| 789 false, BOOKMARKS, NULL); | |
| 790 } | |
| 791 // Next five items are children of the root. | |
| 792 for (; i < 2*batch_s; ++i) { | |
| 793 entry_factory_->CreateUnsyncedItem( | |
| 794 id_factory_.NewLocalId(), id_factory_.root(), | |
| 795 base::StringPrintf("Item %"PRIuS"", i), false, | |
| 796 BOOKMARKS, NULL); | |
| 797 } | |
| 798 | |
| 799 // We encrypt with new keys, triggering the local cryptographer to be unready | |
| 800 // and unable to decrypt data (once updated). | |
| 801 Cryptographer other_cryptographer(&encryptor_); | |
| 802 KeyParams params = {"localhost", "dummy", "foobar"}; | |
| 803 other_cryptographer.AddKey(params); | |
| 804 sync_pb::EntitySpecifics specifics; | |
| 805 sync_pb::NigoriSpecifics* nigori = specifics.mutable_nigori(); | |
| 806 other_cryptographer.GetKeys(nigori->mutable_encrypted()); | |
| 807 nigori->set_encrypt_everything(true); | |
| 808 encrypted_types.Put(BOOKMARKS); | |
| 809 entry_factory_->CreateUnappliedNewItem( | |
| 810 ModelTypeToRootTag(NIGORI), specifics, true); | |
| 811 EXPECT_FALSE(cryptographer->has_pending_keys()); | |
| 812 | |
| 813 { | |
| 814 // Ensure we have unsynced nodes that aren't properly encrypted. | |
| 815 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 816 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 817 Syncer::UnsyncedMetaHandles handles; | |
| 818 GetUnsyncedEntries(&trans, &handles); | |
| 819 EXPECT_EQ(2*batch_s+1, handles.size()); | |
| 820 } | |
| 821 | |
| 822 ExpectGroupToChange(apply_updates_command_, GROUP_PASSIVE); | |
| 823 apply_updates_command_.ExecuteImpl(session()); | |
| 824 | |
| 825 sessions::StatusController* status = session()->mutable_status_controller(); | |
| 826 sessions::ScopedModelSafeGroupRestriction r(status, GROUP_PASSIVE); | |
| 827 ASSERT_TRUE(status->update_progress()); | |
| 828 EXPECT_EQ(1, status->update_progress()->AppliedUpdatesSize()) | |
| 829 << "All updates should have been attempted"; | |
| 830 ASSERT_TRUE(status->conflict_progress()); | |
| 831 EXPECT_EQ(0, status->conflict_progress()->SimpleConflictingItemsSize()) | |
| 832 << "The unsynced changes don't trigger a blocking conflict with the " | |
| 833 << "nigori update."; | |
| 834 EXPECT_EQ(0, status->conflict_progress()->EncryptionConflictingItemsSize()) | |
| 835 << "The unsynced changes don't trigger an encryption conflict with the " | |
| 836 << "nigori update."; | |
| 837 EXPECT_EQ(1, status->update_progress()->SuccessfullyAppliedUpdateCount()) | |
| 838 << "The nigori update should be applied"; | |
| 839 EXPECT_FALSE(cryptographer->is_ready()); | |
| 840 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
| 841 { | |
| 842 syncable::ReadTransaction trans(FROM_HERE, directory()); | |
| 843 | |
| 844 // Since we have pending keys, we would have failed to encrypt, but the | |
| 845 // cryptographer should be updated. | |
| 846 EXPECT_FALSE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); | |
| 847 EXPECT_TRUE(cryptographer->GetEncryptedTypes().Equals( | |
| 848 ModelTypeSet().All())); | |
| 849 EXPECT_FALSE(cryptographer->is_ready()); | |
| 850 EXPECT_TRUE(cryptographer->has_pending_keys()); | |
| 851 | |
| 852 Syncer::UnsyncedMetaHandles handles; | |
| 853 GetUnsyncedEntries(&trans, &handles); | |
| 854 EXPECT_EQ(2*batch_s+1, handles.size()); | |
| 855 } | |
| 856 } | |
| 857 | |
| 858 } // namespace syncer | 521 } // namespace syncer |
| OLD | NEW |