commit_contribution_impl_unittest.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/sync/engine/commit_contribution_impl.h"
  5. #include <memory>
  6. #include <string>
  7. #include <utility>
  8. #include "base/base64.h"
  9. #include "base/callback_helpers.h"
  10. #include "base/hash/sha1.h"
  11. #include "base/test/mock_callback.h"
  12. #include "base/test/scoped_feature_list.h"
  13. #include "components/sync/base/client_tag_hash.h"
  14. #include "components/sync/base/features.h"
  15. #include "components/sync/base/model_type.h"
  16. #include "components/sync/base/unique_position.h"
  17. #include "components/sync/engine/nigori/cryptographer.h"
  18. #include "components/sync/protocol/data_type_progress_marker.pb.h"
  19. #include "components/sync/protocol/entity_specifics.pb.h"
  20. #include "components/sync/protocol/password_specifics.pb.h"
  21. #include "components/sync/protocol/sharing_message_specifics.pb.h"
  22. #include "components/sync/protocol/sync.pb.h"
  23. #include "components/sync/protocol/sync_entity.pb.h"
  24. #include "components/sync/test/fake_cryptographer.h"
  25. #include "testing/gtest/include/gtest/gtest.h"
  26. namespace syncer {
  27. namespace {
  28. using sync_pb::CommitResponse;
  29. using sync_pb::EntitySpecifics;
  30. using sync_pb::SharingMessageCommitError;
  31. using sync_pb::SyncEntity;
  32. const ClientTagHash kTag = ClientTagHash::FromHashed("tag");
  33. const char kValue[] = "value";
  34. const char kURL[] = "url";
  35. const char kTitle[] = "title";
  36. EntitySpecifics GeneratePreferenceSpecifics(const ClientTagHash& tag,
  37. const std::string& value) {
  38. EntitySpecifics specifics;
  39. specifics.mutable_preference()->set_name(tag.value());
  40. specifics.mutable_preference()->set_value(value);
  41. return specifics;
  42. }
  43. EntitySpecifics GenerateBookmarkSpecifics(const std::string& url,
  44. const std::string& title) {
  45. EntitySpecifics specifics;
  46. specifics.mutable_bookmark()->set_legacy_canonicalized_title(title);
  47. if (url.empty()) {
  48. specifics.mutable_bookmark()->set_type(sync_pb::BookmarkSpecifics::FOLDER);
  49. } else {
  50. specifics.mutable_bookmark()->set_type(sync_pb::BookmarkSpecifics::URL);
  51. specifics.mutable_bookmark()->set_url(url);
  52. }
  53. *specifics.mutable_bookmark()->mutable_unique_position() =
  54. syncer::UniquePosition::FromInt64(10,
  55. syncer::UniquePosition::RandomSuffix())
  56. .ToProto();
  57. return specifics;
  58. }
  59. TEST(CommitContributionImplTest, PopulateCommitProtoDefault) {
  60. const int64_t kBaseVersion = 7;
  61. base::Time creation_time = base::Time::UnixEpoch() + base::Days(1);
  62. base::Time modification_time = creation_time + base::Seconds(1);
  63. auto data = std::make_unique<syncer::EntityData>();
  64. data->client_tag_hash = kTag;
  65. data->specifics = GeneratePreferenceSpecifics(kTag, kValue);
  66. // These fields are not really used for much, but we set them anyway
  67. // to make this item look more realistic.
  68. data->creation_time = creation_time;
  69. data->modification_time = modification_time;
  70. data->name = "Name:";
  71. CommitRequestData request_data;
  72. request_data.sequence_number = 2;
  73. request_data.base_version = kBaseVersion;
  74. base::Base64Encode(base::SHA1HashString(data->specifics.SerializeAsString()),
  75. &request_data.specifics_hash);
  76. request_data.entity = std::move(data);
  77. SyncEntity entity;
  78. CommitContributionImpl::PopulateCommitProto(PREFERENCES, request_data,
  79. &entity);
  80. // Exhaustively verify the populated SyncEntity.
  81. EXPECT_TRUE(entity.id_string().empty());
  82. EXPECT_EQ(7, entity.version());
  83. EXPECT_EQ(modification_time.ToJsTime(), entity.mtime());
  84. EXPECT_EQ(creation_time.ToJsTime(), entity.ctime());
  85. EXPECT_FALSE(entity.name().empty());
  86. EXPECT_FALSE(entity.client_defined_unique_tag().empty());
  87. EXPECT_EQ(kTag.value(), entity.specifics().preference().name());
  88. EXPECT_FALSE(entity.deleted());
  89. EXPECT_EQ(kValue, entity.specifics().preference().value());
  90. EXPECT_TRUE(entity.parent_id_string().empty());
  91. EXPECT_FALSE(entity.unique_position().has_custom_compressed_v1());
  92. }
  93. TEST(CommitContributionImplTest, PopulateCommitProtoBookmark) {
  94. const int64_t kBaseVersion = 7;
  95. base::Time creation_time = base::Time::UnixEpoch() + base::Days(1);
  96. base::Time modification_time = creation_time + base::Seconds(1);
  97. auto data = std::make_unique<syncer::EntityData>();
  98. data->id = "bookmark";
  99. data->specifics = GenerateBookmarkSpecifics(kURL, kTitle);
  100. // These fields are not really used for much, but we set them anyway
  101. // to make this item look more realistic.
  102. data->creation_time = creation_time;
  103. data->modification_time = modification_time;
  104. data->name = "Name:";
  105. data->legacy_parent_id = "ParentOf:";
  106. CommitRequestData request_data;
  107. request_data.sequence_number = 2;
  108. request_data.base_version = kBaseVersion;
  109. base::Base64Encode(base::SHA1HashString(data->specifics.SerializeAsString()),
  110. &request_data.specifics_hash);
  111. request_data.entity = std::move(data);
  112. SyncEntity entity;
  113. CommitContributionImpl::PopulateCommitProto(BOOKMARKS, request_data, &entity);
  114. // Exhaustively verify the populated SyncEntity.
  115. EXPECT_FALSE(entity.id_string().empty());
  116. EXPECT_EQ(7, entity.version());
  117. EXPECT_EQ(modification_time.ToJsTime(), entity.mtime());
  118. EXPECT_EQ(creation_time.ToJsTime(), entity.ctime());
  119. EXPECT_FALSE(entity.name().empty());
  120. EXPECT_TRUE(entity.client_defined_unique_tag().empty());
  121. EXPECT_EQ(kURL, entity.specifics().bookmark().url());
  122. EXPECT_FALSE(entity.deleted());
  123. EXPECT_EQ(kTitle, entity.specifics().bookmark().legacy_canonicalized_title());
  124. EXPECT_FALSE(entity.folder());
  125. EXPECT_FALSE(entity.parent_id_string().empty());
  126. EXPECT_TRUE(entity.unique_position().has_custom_compressed_v1());
  127. }
  128. TEST(CommitContributionImplTest, PopulateCommitProtoBookmarkFolder) {
  129. const int64_t kBaseVersion = 7;
  130. base::Time creation_time = base::Time::UnixEpoch() + base::Days(1);
  131. base::Time modification_time = creation_time + base::Seconds(1);
  132. auto data = std::make_unique<syncer::EntityData>();
  133. data->id = "bookmark";
  134. data->specifics = GenerateBookmarkSpecifics(/*url=*/"", kTitle);
  135. // These fields are not really used for much, but we set them anyway
  136. // to make this item look more realistic.
  137. data->creation_time = creation_time;
  138. data->modification_time = modification_time;
  139. data->name = "Name:";
  140. data->legacy_parent_id = "ParentOf:";
  141. CommitRequestData request_data;
  142. request_data.sequence_number = 2;
  143. request_data.base_version = kBaseVersion;
  144. base::Base64Encode(base::SHA1HashString(data->specifics.SerializeAsString()),
  145. &request_data.specifics_hash);
  146. request_data.entity = std::move(data);
  147. SyncEntity entity;
  148. CommitContributionImpl::PopulateCommitProto(BOOKMARKS, request_data, &entity);
  149. // Exhaustively verify the populated SyncEntity.
  150. EXPECT_FALSE(entity.id_string().empty());
  151. EXPECT_EQ(7, entity.version());
  152. EXPECT_EQ(modification_time.ToJsTime(), entity.mtime());
  153. EXPECT_EQ(creation_time.ToJsTime(), entity.ctime());
  154. EXPECT_FALSE(entity.name().empty());
  155. EXPECT_TRUE(entity.client_defined_unique_tag().empty());
  156. EXPECT_FALSE(entity.specifics().bookmark().has_url());
  157. EXPECT_FALSE(entity.deleted());
  158. EXPECT_EQ(kTitle, entity.specifics().bookmark().legacy_canonicalized_title());
  159. EXPECT_TRUE(entity.folder());
  160. EXPECT_FALSE(entity.parent_id_string().empty());
  161. EXPECT_TRUE(entity.unique_position().has_custom_compressed_v1());
  162. }
  163. // Verifies how PASSWORDS protos are committed on the wire, making sure the data
  164. // is properly encrypted except for password metadata.
  165. TEST(CommitContributionImplTest,
  166. PopulateCommitProtoPasswordWithoutCustomPassphrase) {
  167. const std::string kMetadataUrl = "http://foo.com";
  168. const std::string kSignonRealm = "signon_realm";
  169. const int64_t kBaseVersion = 7;
  170. auto data = std::make_unique<syncer::EntityData>();
  171. data->client_tag_hash = kTag;
  172. sync_pb::PasswordSpecificsData* password_data =
  173. data->specifics.mutable_password()->mutable_client_only_encrypted_data();
  174. password_data->set_signon_realm(kSignonRealm);
  175. data->specifics.mutable_password()->mutable_unencrypted_metadata()->set_url(
  176. kMetadataUrl);
  177. auto request_data = std::make_unique<CommitRequestData>();
  178. request_data->sequence_number = 2;
  179. request_data->base_version = kBaseVersion;
  180. base::Base64Encode(base::SHA1HashString(data->specifics.SerializeAsString()),
  181. &request_data->specifics_hash);
  182. request_data->entity = std::move(data);
  183. std::unique_ptr<FakeCryptographer> cryptographer =
  184. FakeCryptographer::FromSingleDefaultKey("dummy");
  185. CommitRequestDataList requests_data;
  186. requests_data.push_back(std::move(request_data));
  187. CommitContributionImpl contribution(
  188. PASSWORDS, sync_pb::DataTypeContext(), std::move(requests_data),
  189. /*on_commit_response_callback=*/base::NullCallback(),
  190. /*on_full_commit_failure_callback=*/base::NullCallback(),
  191. cryptographer.get(), PassphraseType::kImplicitPassphrase,
  192. /*only_commit_specifics=*/false);
  193. sync_pb::ClientToServerMessage msg;
  194. contribution.AddToCommitMessage(&msg);
  195. ASSERT_EQ(1, msg.commit().entries().size());
  196. SyncEntity entity = msg.commit().entries(0);
  197. // Exhaustively verify the populated SyncEntity.
  198. EXPECT_TRUE(entity.id_string().empty());
  199. EXPECT_EQ(7, entity.version());
  200. EXPECT_EQ("encrypted", entity.name());
  201. EXPECT_EQ(kTag.value(), entity.client_defined_unique_tag());
  202. EXPECT_FALSE(entity.deleted());
  203. EXPECT_FALSE(entity.specifics().has_encrypted());
  204. EXPECT_TRUE(entity.specifics().has_password());
  205. EXPECT_EQ(kSignonRealm,
  206. entity.specifics().password().unencrypted_metadata().url());
  207. EXPECT_TRUE(
  208. entity.specifics().password().unencrypted_metadata().has_blacklisted());
  209. EXPECT_FALSE(
  210. entity.specifics().password().unencrypted_metadata().blacklisted());
  211. EXPECT_FALSE(entity.specifics().password().encrypted().blob().empty());
  212. EXPECT_TRUE(entity.parent_id_string().empty());
  213. EXPECT_FALSE(entity.unique_position().has_custom_compressed_v1());
  214. }
  215. // Same as above but uses CUSTOM_PASSPHRASE. In this case, field
  216. // |unencrypted_metadata| should be cleared.
  217. TEST(CommitContributionImplTest,
  218. PopulateCommitProtoPasswordWithCustomPassphrase) {
  219. const std::string kMetadataUrl = "http://foo.com";
  220. const std::string kSignonRealm = "signon_realm";
  221. const int64_t kBaseVersion = 7;
  222. auto data = std::make_unique<syncer::EntityData>();
  223. data->client_tag_hash = kTag;
  224. sync_pb::PasswordSpecificsData* password_data =
  225. data->specifics.mutable_password()->mutable_client_only_encrypted_data();
  226. password_data->set_signon_realm(kSignonRealm);
  227. data->specifics.mutable_password()->mutable_unencrypted_metadata()->set_url(
  228. kMetadataUrl);
  229. auto request_data = std::make_unique<CommitRequestData>();
  230. request_data->sequence_number = 2;
  231. request_data->base_version = kBaseVersion;
  232. base::Base64Encode(base::SHA1HashString(data->specifics.SerializeAsString()),
  233. &request_data->specifics_hash);
  234. request_data->entity = std::move(data);
  235. std::unique_ptr<FakeCryptographer> cryptographer =
  236. FakeCryptographer::FromSingleDefaultKey("dummy");
  237. CommitRequestDataList requests_data;
  238. requests_data.push_back(std::move(request_data));
  239. CommitContributionImpl contribution(
  240. PASSWORDS, sync_pb::DataTypeContext(), std::move(requests_data),
  241. /*on_commit_response_callback=*/base::NullCallback(),
  242. /*on_full_commit_failure_callback=*/base::NullCallback(),
  243. cryptographer.get(), PassphraseType::kCustomPassphrase,
  244. /*only_commit_specifics=*/false);
  245. sync_pb::ClientToServerMessage msg;
  246. contribution.AddToCommitMessage(&msg);
  247. ASSERT_EQ(1, msg.commit().entries().size());
  248. SyncEntity entity = msg.commit().entries(0);
  249. // Exhaustively verify the populated SyncEntity.
  250. EXPECT_TRUE(entity.id_string().empty());
  251. EXPECT_EQ(7, entity.version());
  252. EXPECT_EQ("encrypted", entity.name());
  253. EXPECT_EQ(kTag.value(), entity.client_defined_unique_tag());
  254. EXPECT_FALSE(entity.deleted());
  255. EXPECT_FALSE(entity.specifics().has_encrypted());
  256. EXPECT_TRUE(entity.specifics().has_password());
  257. EXPECT_FALSE(entity.specifics().password().encrypted().blob().empty());
  258. EXPECT_FALSE(entity.specifics().password().has_unencrypted_metadata());
  259. EXPECT_TRUE(entity.parent_id_string().empty());
  260. EXPECT_FALSE(entity.unique_position().has_custom_compressed_v1());
  261. }
  262. TEST(CommitContributionImplTest, ShouldPropagateFailedItemsOnCommitResponse) {
  263. auto data = std::make_unique<syncer::EntityData>();
  264. data->client_tag_hash = ClientTagHash::FromHashed("hash");
  265. auto request_data = std::make_unique<CommitRequestData>();
  266. request_data->entity = std::move(data);
  267. CommitRequestDataList requests_data;
  268. requests_data.push_back(std::move(request_data));
  269. FakeCryptographer cryptographer;
  270. FailedCommitResponseDataList actual_error_response_list;
  271. auto on_commit_response_callback = base::BindOnce(
  272. [](FailedCommitResponseDataList* actual_error_response_list,
  273. const CommitResponseDataList& committed_response_list,
  274. const FailedCommitResponseDataList& error_response_list) {
  275. // We put expectations outside of the callback, so that they fail if
  276. // callback is not ran.
  277. *actual_error_response_list = error_response_list;
  278. },
  279. &actual_error_response_list);
  280. CommitContributionImpl contribution(
  281. PASSWORDS, sync_pb::DataTypeContext(), std::move(requests_data),
  282. std::move(on_commit_response_callback),
  283. /*on_full_commit_failure_callback=*/base::NullCallback(), &cryptographer,
  284. PassphraseType::kCustomPassphrase,
  285. /*only_commit_specifics=*/false);
  286. sync_pb::ClientToServerMessage msg;
  287. contribution.AddToCommitMessage(&msg);
  288. sync_pb::ClientToServerResponse response;
  289. sync_pb::CommitResponse* commit_response = response.mutable_commit();
  290. {
  291. sync_pb::CommitResponse_EntryResponse* entry =
  292. commit_response->add_entryresponse();
  293. entry->set_response_type(CommitResponse::TRANSIENT_ERROR);
  294. SharingMessageCommitError* sharing_message_error =
  295. entry->mutable_datatype_specific_error()
  296. ->mutable_sharing_message_error();
  297. sharing_message_error->set_error_code(
  298. SharingMessageCommitError::INVALID_ARGUMENT);
  299. }
  300. StatusController status;
  301. contribution.ProcessCommitResponse(response, &status);
  302. ASSERT_EQ(1u, actual_error_response_list.size());
  303. FailedCommitResponseData failed_item = actual_error_response_list[0];
  304. EXPECT_EQ(ClientTagHash::FromHashed("hash"), failed_item.client_tag_hash);
  305. EXPECT_EQ(CommitResponse::TRANSIENT_ERROR, failed_item.response_type);
  306. EXPECT_EQ(
  307. SharingMessageCommitError::INVALID_ARGUMENT,
  308. failed_item.datatype_specific_error.sharing_message_error().error_code());
  309. }
  310. TEST(CommitContributionImplTest, ShouldPropagateFullCommitFailure) {
  311. base::MockOnceCallback<void(SyncCommitError commit_error)>
  312. on_commit_failure_callback;
  313. EXPECT_CALL(on_commit_failure_callback, Run(SyncCommitError::kNetworkError));
  314. CommitContributionImpl contribution(
  315. BOOKMARKS, sync_pb::DataTypeContext(), CommitRequestDataList(),
  316. /*on_commit_response_callback=*/base::NullCallback(),
  317. on_commit_failure_callback.Get(), /*cryptographer=*/nullptr,
  318. PassphraseType::kKeystorePassphrase,
  319. /*only_commit_specifics=*/false);
  320. contribution.ProcessCommitFailure(SyncCommitError::kNetworkError);
  321. }
  322. TEST(CommitContributionImplTest, ShouldPopulatePasswordNotesBackup) {
  323. base::test::ScopedFeatureList feature_list;
  324. feature_list.InitAndEnableFeature(syncer::kReadWritePasswordNotesBackupField);
  325. const std::string kNoteValue = "Note Value";
  326. auto data = std::make_unique<syncer::EntityData>();
  327. data->client_tag_hash = kTag;
  328. sync_pb::PasswordSpecificsData* password_data =
  329. data->specifics.mutable_password()->mutable_client_only_encrypted_data();
  330. sync_pb::PasswordSpecificsData_Notes_Note* note =
  331. password_data->mutable_notes()->add_note();
  332. note->set_value(kNoteValue);
  333. auto request_data = std::make_unique<CommitRequestData>();
  334. base::Base64Encode(base::SHA1HashString(data->specifics.SerializeAsString()),
  335. &request_data->specifics_hash);
  336. request_data->entity = std::move(data);
  337. CommitRequestDataList requests_data;
  338. requests_data.push_back(std::move(request_data));
  339. std::unique_ptr<FakeCryptographer> cryptographer =
  340. FakeCryptographer::FromSingleDefaultKey("dummy");
  341. CommitContributionImpl contribution(
  342. PASSWORDS, sync_pb::DataTypeContext(), std::move(requests_data),
  343. /*on_commit_response_callback=*/base::NullCallback(),
  344. /*on_full_commit_failure_callback=*/base::NullCallback(),
  345. cryptographer.get(), PassphraseType::kImplicitPassphrase,
  346. /*only_commit_specifics=*/false);
  347. sync_pb::ClientToServerMessage msg;
  348. contribution.AddToCommitMessage(&msg);
  349. ASSERT_EQ(1, msg.commit().entries().size());
  350. SyncEntity entity = msg.commit().entries(0);
  351. ASSERT_TRUE(entity.specifics().has_password());
  352. // Verify the contents of the encrypted notes backup blob.
  353. sync_pb::PasswordSpecificsData_Notes decrypted_notes;
  354. cryptographer->Decrypt(entity.specifics().password().encrypted_notes_backup(),
  355. &decrypted_notes);
  356. ASSERT_EQ(1, decrypted_notes.note_size());
  357. EXPECT_EQ(kNoteValue, decrypted_notes.note(0).value());
  358. }
  359. TEST(CommitContributionImplTest,
  360. ShouldPopulatePasswordNotesBackupWhenNoLocalNotes) {
  361. base::test::ScopedFeatureList feature_list;
  362. feature_list.InitAndEnableFeature(syncer::kReadWritePasswordNotesBackupField);
  363. auto data = std::make_unique<syncer::EntityData>();
  364. data->client_tag_hash = kTag;
  365. sync_pb::PasswordSpecificsData* password_data =
  366. data->specifics.mutable_password()->mutable_client_only_encrypted_data();
  367. password_data->set_signon_realm("signon_realm");
  368. auto request_data = std::make_unique<CommitRequestData>();
  369. base::Base64Encode(base::SHA1HashString(data->specifics.SerializeAsString()),
  370. &request_data->specifics_hash);
  371. request_data->entity = std::move(data);
  372. CommitRequestDataList requests_data;
  373. requests_data.push_back(std::move(request_data));
  374. std::unique_ptr<FakeCryptographer> cryptographer =
  375. FakeCryptographer::FromSingleDefaultKey("dummy");
  376. CommitContributionImpl contribution(
  377. PASSWORDS, sync_pb::DataTypeContext(), std::move(requests_data),
  378. /*on_commit_response_callback=*/base::NullCallback(),
  379. /*on_full_commit_failure_callback=*/base::NullCallback(),
  380. cryptographer.get(), PassphraseType::kImplicitPassphrase,
  381. /*only_commit_specifics=*/false);
  382. sync_pb::ClientToServerMessage msg;
  383. contribution.AddToCommitMessage(&msg);
  384. ASSERT_EQ(1, msg.commit().entries().size());
  385. SyncEntity entity = msg.commit().entries(0);
  386. ASSERT_TRUE(entity.specifics().has_password());
  387. EXPECT_FALSE(
  388. entity.specifics().password().encrypted_notes_backup().blob().empty());
  389. }
  390. } // namespace
  391. } // namespace syncer