blob: da378b142ea0cb549b5d4ecdbca778f23b7a7108 [file] [log] [blame]
// Copyright 2018 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 "components/sync/engine_impl/non_blocking_type_commit_contribution.h"
#include <string>
#include "base/base64.h"
#include "base/sha1.h"
#include "components/sync/base/cryptographer.h"
#include "components/sync/base/fake_encryptor.h"
#include "components/sync/base/hash_util.h"
#include "components/sync/base/model_type.h"
#include "components/sync/base/unique_position.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
using sync_pb::EntitySpecifics;
using sync_pb::SyncEntity;
const char kTag[] = "tag";
const char kValue[] = "value";
const char kURL[] = "url";
const char kTitle[] = "title";
EntitySpecifics GeneratePreferenceSpecifics(const std::string& tag,
const std::string& value) {
EntitySpecifics specifics;
specifics.mutable_preference()->set_name(tag);
specifics.mutable_preference()->set_value(value);
return specifics;
}
EntitySpecifics GenerateBookmarkSpecifics(const std::string& url,
const std::string& title) {
EntitySpecifics specifics;
specifics.mutable_bookmark()->set_url(url);
specifics.mutable_bookmark()->set_title(title);
return specifics;
}
TEST(NonBlockingTypeCommitContributionTest, PopulateCommitProtoDefault) {
const int64_t kBaseVersion = 7;
base::Time creation_time =
base::Time::UnixEpoch() + base::TimeDelta::FromDays(1);
base::Time modification_time =
creation_time + base::TimeDelta::FromSeconds(1);
EntityData data;
data.client_tag_hash = kTag;
data.specifics = GeneratePreferenceSpecifics(kTag, kValue);
// These fields are not really used for much, but we set them anyway
// to make this item look more realistic.
data.creation_time = creation_time;
data.modification_time = modification_time;
data.non_unique_name = "Name:";
CommitRequestData request_data;
request_data.entity = data.PassToPtr();
request_data.sequence_number = 2;
request_data.base_version = kBaseVersion;
base::Base64Encode(base::SHA1HashString(data.specifics.SerializeAsString()),
&request_data.specifics_hash);
SyncEntity entity;
NonBlockingTypeCommitContribution::PopulateCommitProto(request_data, &entity);
// Exhaustively verify the populated SyncEntity.
EXPECT_TRUE(entity.id_string().empty());
EXPECT_EQ(7, entity.version());
EXPECT_EQ(modification_time.ToJsTime(), entity.mtime());
EXPECT_EQ(creation_time.ToJsTime(), entity.ctime());
EXPECT_FALSE(entity.name().empty());
EXPECT_FALSE(entity.client_defined_unique_tag().empty());
EXPECT_EQ(kTag, entity.specifics().preference().name());
EXPECT_FALSE(entity.deleted());
EXPECT_EQ(kValue, entity.specifics().preference().value());
EXPECT_TRUE(entity.parent_id_string().empty());
EXPECT_FALSE(entity.unique_position().has_custom_compressed_v1());
EXPECT_EQ(0, entity.position_in_parent());
}
TEST(NonBlockingTypeCommitContributionTest, PopulateCommitProtoBookmark) {
const int64_t kBaseVersion = 7;
base::Time creation_time =
base::Time::UnixEpoch() + base::TimeDelta::FromDays(1);
base::Time modification_time =
creation_time + base::TimeDelta::FromSeconds(1);
EntityData data;
data.id = "bookmark";
data.specifics = GenerateBookmarkSpecifics(kURL, kTitle);
// These fields are not really used for much, but we set them anyway
// to make this item look more realistic.
data.creation_time = creation_time;
data.modification_time = modification_time;
data.non_unique_name = "Name:";
data.parent_id = "ParentOf:";
data.is_folder = true;
syncer::UniquePosition uniquePosition = syncer::UniquePosition::FromInt64(
10, syncer::UniquePosition::RandomSuffix());
data.unique_position = uniquePosition.ToProto();
CommitRequestData request_data;
request_data.entity = data.PassToPtr();
request_data.sequence_number = 2;
request_data.base_version = kBaseVersion;
base::Base64Encode(base::SHA1HashString(data.specifics.SerializeAsString()),
&request_data.specifics_hash);
SyncEntity entity;
NonBlockingTypeCommitContribution::PopulateCommitProto(request_data, &entity);
// Exhaustively verify the populated SyncEntity.
EXPECT_FALSE(entity.id_string().empty());
EXPECT_EQ(7, entity.version());
EXPECT_EQ(modification_time.ToJsTime(), entity.mtime());
EXPECT_EQ(creation_time.ToJsTime(), entity.ctime());
EXPECT_FALSE(entity.name().empty());
EXPECT_TRUE(entity.client_defined_unique_tag().empty());
EXPECT_EQ(kURL, entity.specifics().bookmark().url());
EXPECT_FALSE(entity.deleted());
EXPECT_EQ(kTitle, entity.specifics().bookmark().title());
EXPECT_TRUE(entity.folder());
EXPECT_FALSE(entity.parent_id_string().empty());
EXPECT_TRUE(entity.unique_position().has_custom_compressed_v1());
EXPECT_NE(0, entity.position_in_parent());
}
// Verifies how PASSWORDS protos are committed on the wire, making sure the data
// is properly encrypted except for password metadata.
TEST(NonBlockingTypeCommitContributionTest,
PopulateCommitProtoPasswordWithoutCustomPassphrase) {
const std::string kMetadataUrl = "http://foo.com";
const std::string kSignonRealm = "signon_realm";
const int64_t kBaseVersion = 7;
EntityData data;
data.client_tag_hash = kTag;
sync_pb::PasswordSpecificsData* password_data =
data.specifics.mutable_password()->mutable_client_only_encrypted_data();
password_data->set_signon_realm(kSignonRealm);
data.specifics.mutable_password()->mutable_unencrypted_metadata()->set_url(
kMetadataUrl);
CommitRequestData request_data;
request_data.entity = data.PassToPtr();
request_data.sequence_number = 2;
request_data.base_version = kBaseVersion;
base::Base64Encode(base::SHA1HashString(data.specifics.SerializeAsString()),
&request_data.specifics_hash);
base::ObserverList<TypeDebugInfoObserver>::Unchecked observers;
DataTypeDebugInfoEmitter debug_info_emitter(PASSWORDS, &observers);
FakeEncryptor fake_encryptor;
Cryptographer cryptographer(&fake_encryptor);
cryptographer.AddKey({KeyDerivationParams::CreateForPbkdf2(), "dummy"});
NonBlockingTypeCommitContribution contribution(
PASSWORDS, sync_pb::DataTypeContext(), {request_data},
/*worker=*/nullptr, &cryptographer, PassphraseType::IMPLICIT_PASSPHRASE,
&debug_info_emitter,
/*only_commit_specifics=*/false);
sync_pb::ClientToServerMessage msg;
contribution.AddToCommitMessage(&msg);
contribution.CleanUp();
ASSERT_EQ(1, msg.commit().entries().size());
SyncEntity entity = msg.commit().entries(0);
// Exhaustively verify the populated SyncEntity.
EXPECT_TRUE(entity.id_string().empty());
EXPECT_EQ(7, entity.version());
EXPECT_EQ("encrypted", entity.name());
EXPECT_EQ(kTag, entity.client_defined_unique_tag());
EXPECT_FALSE(entity.deleted());
EXPECT_FALSE(entity.specifics().has_encrypted());
EXPECT_TRUE(entity.specifics().has_password());
EXPECT_EQ(kSignonRealm,
entity.specifics().password().unencrypted_metadata().url());
EXPECT_FALSE(entity.specifics().password().encrypted().blob().empty());
EXPECT_TRUE(entity.parent_id_string().empty());
EXPECT_FALSE(entity.unique_position().has_custom_compressed_v1());
EXPECT_EQ(0, entity.position_in_parent());
}
// Same as above but uses CUSTOM_PASSPHRASE. In this case, field
// |unencrypted_metadata| should be cleared.
TEST(NonBlockingTypeCommitContributionTest,
PopulateCommitProtoPasswordWithCustomPassphrase) {
const std::string kMetadataUrl = "http://foo.com";
const std::string kSignonRealm = "signon_realm";
const int64_t kBaseVersion = 7;
EntityData data;
data.client_tag_hash = kTag;
sync_pb::PasswordSpecificsData* password_data =
data.specifics.mutable_password()->mutable_client_only_encrypted_data();
password_data->set_signon_realm(kSignonRealm);
data.specifics.mutable_password()->mutable_unencrypted_metadata()->set_url(
kMetadataUrl);
CommitRequestData request_data;
request_data.entity = data.PassToPtr();
request_data.sequence_number = 2;
request_data.base_version = kBaseVersion;
base::Base64Encode(base::SHA1HashString(data.specifics.SerializeAsString()),
&request_data.specifics_hash);
base::ObserverList<TypeDebugInfoObserver>::Unchecked observers;
DataTypeDebugInfoEmitter debug_info_emitter(PASSWORDS, &observers);
FakeEncryptor fake_encryptor;
Cryptographer cryptographer(&fake_encryptor);
cryptographer.AddKey({KeyDerivationParams::CreateForPbkdf2(), "dummy"});
NonBlockingTypeCommitContribution contribution(
PASSWORDS, sync_pb::DataTypeContext(), {request_data},
/*worker=*/nullptr, &cryptographer, PassphraseType::CUSTOM_PASSPHRASE,
&debug_info_emitter,
/*only_commit_specifics=*/false);
sync_pb::ClientToServerMessage msg;
contribution.AddToCommitMessage(&msg);
contribution.CleanUp();
ASSERT_EQ(1, msg.commit().entries().size());
SyncEntity entity = msg.commit().entries(0);
// Exhaustively verify the populated SyncEntity.
EXPECT_TRUE(entity.id_string().empty());
EXPECT_EQ(7, entity.version());
EXPECT_EQ("encrypted", entity.name());
EXPECT_EQ(kTag, entity.client_defined_unique_tag());
EXPECT_FALSE(entity.deleted());
EXPECT_FALSE(entity.specifics().has_encrypted());
EXPECT_TRUE(entity.specifics().has_password());
EXPECT_FALSE(entity.specifics().password().encrypted().blob().empty());
EXPECT_FALSE(entity.specifics().password().has_unencrypted_metadata());
EXPECT_TRUE(entity.parent_id_string().empty());
EXPECT_FALSE(entity.unique_position().has_custom_compressed_v1());
EXPECT_EQ(0, entity.position_in_parent());
}
} // namespace
} // namespace syncer