Commit 475bfe9d authored by David Davidović's avatar David Davidović Committed by Commit Bot

[sync::test] Add single client integration test for custom passphrase

Add a Sync integration test which exercises custom passphrase flows, including
the handling and proper functioning of the newly added key derivation method
(scrypt).

Add an integration test helper file for encryption-related tasks and modify
FakeServer and LoopbackServer to allow easier modification of persistent
entities such as Nigori. Add passphrase-related functionality to
ProfileSyncServiceHarness to give tests better control over how and when
passphrase-based encryption is enabled.

The test uses a gray-box approach, where it tests the client at the
ProfileSyncService granularity, but injects and inspects entities on the (fake)
server to ensure that encryption is performed properly. This is necessary
because, when it comes to encryption, we are not interested merely in that it
does not hinder existing functionality (e.g. two clients are syncing data
properly in the presence of a custom passphrase), but also that it provides the
expected security to the user. For this reason, we use our knowledge of the
encryption architecture internals to ensure that the data committed to the
server is encrypted in the expected way.

Bug: 894148
Change-Id: I728f7f18cc0db7b1da50f747a87a640877d0b023
Reviewed-on: https://chromium-review.googlesource.com/c/1274205
Commit-Queue: David Davidović <davidovic@google.com>
Reviewed-by: default avatarvitaliii <vitaliii@chromium.org>
Reviewed-by: default avatarMarc Treib <treib@chromium.org>
Cr-Commit-Position: refs/heads/master@{#598912}
parent a5a40ab6
......@@ -45,6 +45,7 @@
#include "components/history/core/browser/history_db_task.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_types.h"
#include "components/sync/test/fake_server/entity_builder_factory.h"
#include "components/sync_bookmarks/bookmark_change_processor.h"
#include "content/public/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -957,6 +958,15 @@ std::string IndexedSubsubfolderName(int i) {
return base::StringPrintf("Subsubfolder Name %d", i);
}
std::unique_ptr<syncer::LoopbackServerEntity> CreateBookmarkServerEntity(
const std::string& title,
const GURL& url) {
fake_server::EntityBuilderFactory entity_builder_factory;
fake_server::BookmarkEntityBuilder bookmark_builder =
entity_builder_factory.NewBookmarkEntityBuilder(title);
return bookmark_builder.BuildBookmark(url);
}
} // namespace bookmarks_helper
BookmarksMatchChecker::BookmarksMatchChecker()
......@@ -1004,6 +1014,64 @@ std::string BookmarksTitleChecker::GetDebugMessage() const {
return "Waiting for bookmark count to match";
}
ServerBookmarksEqualityChecker::ServerBookmarksEqualityChecker(
browser_sync::ProfileSyncService* service,
fake_server::FakeServer* fake_server,
const std::vector<ExpectedBookmark>& expected_bookmarks,
syncer::Cryptographer* cryptographer)
: SingleClientStatusChangeChecker(service),
fake_server_(fake_server),
cryptographer_(cryptographer),
expected_bookmarks_(expected_bookmarks) {}
bool ServerBookmarksEqualityChecker::IsExitConditionSatisfied() {
std::vector<sync_pb::SyncEntity> entities =
fake_server_->GetSyncEntitiesByModelType(syncer::BOOKMARKS);
if (expected_bookmarks_.size() != entities.size()) {
return false;
}
// Make a copy so we can remove bookmarks that were found.
std::vector<ExpectedBookmark> expected = expected_bookmarks_;
for (const sync_pb::SyncEntity& entity : entities) {
// If the cryptographer was provided, we expect the specifics to have
// encrypted data.
EXPECT_EQ(entity.specifics().has_encrypted(), cryptographer_ != nullptr);
sync_pb::BookmarkSpecifics actual_specifics;
if (entity.specifics().has_encrypted()) {
sync_pb::EntitySpecifics entity_specifics;
EXPECT_TRUE(cryptographer_->Decrypt(entity.specifics().encrypted(),
&entity_specifics));
actual_specifics = entity_specifics.bookmark();
} else {
actual_specifics = entity.specifics().bookmark();
}
auto it =
std::find_if(expected.begin(), expected.end(),
[actual_specifics](const ExpectedBookmark& bookmark) {
return actual_specifics.title() == bookmark.title &&
actual_specifics.url() == bookmark.url;
});
if (it != expected.end()) {
expected.erase(it);
} else {
ADD_FAILURE() << "Could not find expected bookmark with title '"
<< actual_specifics.title() << "' and URL '"
<< actual_specifics.url() << "'";
}
}
return true;
}
std::string ServerBookmarksEqualityChecker::GetDebugMessage() const {
return "Waiting for server-side bookmarks to match expected.";
}
ServerBookmarksEqualityChecker::~ServerBookmarksEqualityChecker() {}
namespace {
bool BookmarkCountsByUrlMatch(int profile,
......
......@@ -5,13 +5,19 @@
#ifndef CHROME_BROWSER_SYNC_TEST_INTEGRATION_BOOKMARKS_HELPER_H_
#define CHROME_BROWSER_SYNC_TEST_INTEGRATION_BOOKMARKS_HELPER_H_
#include <memory>
#include <string>
#include <vector>
#include "base/compiler_specific.h"
#include "chrome/browser/sync/test/integration/await_match_status_change_checker.h"
#include "chrome/browser/sync/test/integration/multi_client_status_change_checker.h"
#include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
#include "components/sync/base/cryptographer.h"
#include "components/sync/engine_impl/loopback_server/loopback_server_entity.h"
#include "components/sync/test/fake_server/fake_server.h"
#include "third_party/skia/include/core/SkColor.h"
#include "url/gurl.h"
class GURL;
......@@ -223,6 +229,12 @@ std::string IndexedSubfolderName(int i);
// Returns a subsubfolder name identifiable by |i|.
std::string IndexedSubsubfolderName(int i);
// Creates a server-side entity representing a bookmark with the given title and
// URL.
std::unique_ptr<syncer::LoopbackServerEntity> CreateBookmarkServerEntity(
const std::string& title,
const GURL& url);
} // namespace bookmarks_helper
// Checker used to block until bookmarks match on all clients.
......@@ -265,6 +277,37 @@ class BookmarksTitleChecker : public SingleClientStatusChangeChecker {
const int expected_count_;
};
// Checker used to block until the bookmarks on the server match a given set of
// expected bookmarks.
class ServerBookmarksEqualityChecker : public SingleClientStatusChangeChecker {
public:
struct ExpectedBookmark {
std::string title;
GURL url;
};
// If a |cryptographer| is provided (i.e. is not nullptr), it is assumed that
// the server-side data should be encrypted, and the provided cryptographer
// will be used to decrypt the data prior to checking for equality.
ServerBookmarksEqualityChecker(
browser_sync::ProfileSyncService* service,
fake_server::FakeServer* fake_server,
const std::vector<ExpectedBookmark>& expected_bookmarks,
syncer::Cryptographer* cryptographer);
bool IsExitConditionSatisfied() override;
std::string GetDebugMessage() const override;
~ServerBookmarksEqualityChecker() override;
private:
fake_server::FakeServer* fake_server_;
syncer::Cryptographer* cryptographer_;
const std::vector<ExpectedBookmark> expected_bookmarks_;
DISALLOW_COPY_AND_ASSIGN(ServerBookmarksEqualityChecker);
};
// Checker used to block until the actual number of bookmarks with the given url
// match the expected count.
class BookmarksUrlChecker : public AwaitMatchStatusChangeChecker {
......
// 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 <string>
#include <vector>
#include "base/base64.h"
#include "chrome/browser/sync/test/integration/encryption_helper.h"
#include "components/browser_sync/profile_sync_service.h"
#include "components/sync/base/passphrase_enums.h"
#include "components/sync/base/system_encryptor.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace encryption_helper {
bool GetServerNigori(fake_server::FakeServer* fake_server,
sync_pb::NigoriSpecifics* nigori) {
std::vector<sync_pb::SyncEntity> entity_list =
fake_server->GetPermanentSyncEntitiesByModelType(syncer::NIGORI);
if (entity_list.size() != 1U) {
return false;
}
*nigori = entity_list[0].specifics().nigori();
return true;
}
void InitCustomPassphraseCryptographerFromNigori(
const sync_pb::NigoriSpecifics& nigori,
syncer::Cryptographer* cryptographer,
const std::string& passphrase) {
sync_pb::EncryptedData keybag = nigori.encryption_keybag();
cryptographer->SetPendingKeys(keybag);
std::string decoded_salt;
switch (syncer::ProtoKeyDerivationMethodToEnum(
nigori.custom_passphrase_key_derivation_method())) {
case syncer::KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003:
ASSERT_TRUE(cryptographer->DecryptPendingKeys(
{syncer::KeyDerivationParams::CreateForPbkdf2(), passphrase}));
break;
case syncer::KeyDerivationMethod::SCRYPT_8192_8_11:
ASSERT_TRUE(base::Base64Decode(
nigori.custom_passphrase_key_derivation_salt(), &decoded_salt));
ASSERT_TRUE(cryptographer->DecryptPendingKeys(
{syncer::KeyDerivationParams::CreateForScrypt(decoded_salt),
passphrase}));
break;
case syncer::KeyDerivationMethod::UNSUPPORTED:
// This test cannot pass since we wouldn't know how to decrypt data
// encrypted using an unsupported method.
FAIL() << "Unsupported key derivation method encountered: "
<< nigori.custom_passphrase_key_derivation_method();
}
}
sync_pb::NigoriSpecifics CreateCustomPassphraseNigori(
const syncer::KeyParams& params) {
syncer::KeyDerivationMethod method = params.derivation_params.method();
sync_pb::NigoriSpecifics nigori;
nigori.set_keybag_is_frozen(true);
nigori.set_keystore_migration_time(1U);
nigori.set_encrypt_everything(true);
nigori.set_passphrase_type(sync_pb::NigoriSpecifics::CUSTOM_PASSPHRASE);
nigori.set_custom_passphrase_key_derivation_method(
EnumKeyDerivationMethodToProto(method));
std::string encoded_salt;
switch (method) {
case syncer::KeyDerivationMethod::PBKDF2_HMAC_SHA1_1003:
// Nothing to do; no further information needs to be extracted from
// Nigori.
break;
case syncer::KeyDerivationMethod::SCRYPT_8192_8_11:
base::Base64Encode(params.derivation_params.scrypt_salt(), &encoded_salt);
nigori.set_custom_passphrase_key_derivation_salt(encoded_salt);
break;
case syncer::KeyDerivationMethod::UNSUPPORTED:
ADD_FAILURE()
<< "Unsupported method in KeyParams, cannot construct Nigori.";
break;
}
// Nigori also contains a keybag, which is an encrypted collection of all keys
// that the data might be encrypted with. To create it, we construct a
// cryptographer, add our key to it, and use GetKeys() to dump it to the
// keybag (in encrypted form). So, in our case, the keybag is simply the
// passphrase-derived key encrypted with itself. Note that this is usually
// also the case during normal Sync operation, and so the keybag from Nigori
// only helps the encryption machinery to know if a given key is correct (e.g.
// checking if a user's passphrase is correct is done by trying to decrypt the
// keybag using a key derived from that passphrase). However, in some migrated
// states, the keybag might also additionally contain an old, pre-migration
// key.
syncer::SystemEncryptor encryptor;
syncer::Cryptographer cryptographer(&encryptor);
DCHECK(cryptographer.AddKey(params));
DCHECK(cryptographer.GetKeys(nigori.mutable_encryption_keybag()));
return nigori;
}
sync_pb::EntitySpecifics GetEncryptedBookmarkEntitySpecifics(
const sync_pb::BookmarkSpecifics& bookmark_specifics,
const syncer::KeyParams& key_params) {
sync_pb::EntitySpecifics new_specifics;
sync_pb::EntitySpecifics wrapped_entity_specifics;
*wrapped_entity_specifics.mutable_bookmark() = bookmark_specifics;
syncer::SystemEncryptor encryptor;
syncer::Cryptographer cryptographer(&encryptor);
DCHECK(cryptographer.AddKey(key_params));
DCHECK(cryptographer.Encrypt(wrapped_entity_specifics,
new_specifics.mutable_encrypted()));
new_specifics.mutable_bookmark()->set_title("encrypted");
new_specifics.mutable_bookmark()->set_url("encrypted");
return new_specifics;
}
void SetNigoriInFakeServer(fake_server::FakeServer* fake_server,
const sync_pb::NigoriSpecifics& nigori) {
std::string nigori_entity_id =
fake_server->GetTopLevelPermanentItemId(syncer::NIGORI);
ASSERT_NE(nigori_entity_id, "");
sync_pb::EntitySpecifics nigori_entity_specifics;
*nigori_entity_specifics.mutable_nigori() = nigori;
fake_server->ModifyEntitySpecifics(nigori_entity_id, nigori_entity_specifics);
}
} // namespace encryption_helper
ServerNigoriChecker::ServerNigoriChecker(
browser_sync::ProfileSyncService* service,
fake_server::FakeServer* fake_server,
syncer::PassphraseType expected_passphrase_type)
: SingleClientStatusChangeChecker(service),
fake_server_(fake_server),
expected_passphrase_type_(expected_passphrase_type) {}
bool ServerNigoriChecker::IsExitConditionSatisfied() {
std::vector<sync_pb::SyncEntity> nigori_entities =
fake_server_->GetPermanentSyncEntitiesByModelType(syncer::NIGORI);
EXPECT_LE(nigori_entities.size(), 1U);
return !nigori_entities.empty() &&
syncer::ProtoPassphraseTypeToEnum(
nigori_entities[0].specifics().nigori().passphrase_type()) ==
expected_passphrase_type_;
}
std::string ServerNigoriChecker::GetDebugMessage() const {
return "Waiting for a Nigori node with the proper passphrase type to become "
"available on the server.";
}
PassphraseRequiredStateChecker::PassphraseRequiredStateChecker(
browser_sync::ProfileSyncService* service,
bool desired_state)
: SingleClientStatusChangeChecker(service), desired_state_(desired_state) {}
bool PassphraseRequiredStateChecker::IsExitConditionSatisfied() {
return service()->IsPassphraseRequiredForDecryption() == desired_state_;
}
std::string PassphraseRequiredStateChecker::GetDebugMessage() const {
return "Waiting until decryption passphrase is " +
std::string(desired_state_ ? "required" : "not required");
}
// 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.
#ifndef CHROME_BROWSER_SYNC_TEST_INTEGRATION_ENCRYPTION_HELPER_H_
#define CHROME_BROWSER_SYNC_TEST_INTEGRATION_ENCRYPTION_HELPER_H_
#include <string>
#include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
#include "components/sync/base/cryptographer.h"
#include "components/sync/protocol/nigori_specifics.pb.h"
#include "components/sync/test/fake_server/fake_server.h"
namespace encryption_helper {
// Given a |fake_server|, fetches its Nigori node and writes it to the
// proto pointed to by |nigori|. Returns false if the server does not contain
// exactly one Nigori node.
bool GetServerNigori(fake_server::FakeServer* fake_server,
sync_pb::NigoriSpecifics* nigori);
// Given a |fake_server|, sets the Nigori instance stored in it to |nigori|.
void SetNigoriInFakeServer(fake_server::FakeServer* fake_server,
const sync_pb::NigoriSpecifics& nigori);
// Given a |nigori| with CUSTOM_PASSPHRASE passphrase type, initializes the
// given |cryptographer| with the key described in it. Since the key inside the
// Nigori is encrypted (by design), the provided |passphrase| will be used to
// decrypt it. This function will fail the test (using ASSERT) if the Nigori is
// not a custom passphrase one, or if the key cannot be decrypted.
void InitCustomPassphraseCryptographerFromNigori(
const sync_pb::NigoriSpecifics& nigori,
syncer::Cryptographer* cryptographer,
const std::string& passphrase);
// Returns an EntitySpecifics containing encrypted data corresponding to the
// provided BookmarkSpecifics and encrypted using the given |key_params|.
sync_pb::EntitySpecifics GetEncryptedBookmarkEntitySpecifics(
const sync_pb::BookmarkSpecifics& specifics,
const syncer::KeyParams& key_params);
// Creates a NigoriSpecifics that describes encryption using a custom passphrase
// with the given key parameters.
sync_pb::NigoriSpecifics CreateCustomPassphraseNigori(
const syncer::KeyParams& params);
} // namespace encryption_helper
// Checker used to block until a Nigori with a given passphrase type is
// available on the server.
class ServerNigoriChecker : public SingleClientStatusChangeChecker {
public:
ServerNigoriChecker(browser_sync::ProfileSyncService* service,
fake_server::FakeServer* fake_server,
syncer::PassphraseType expected_passphrase_type);
bool IsExitConditionSatisfied() override;
std::string GetDebugMessage() const override;
private:
fake_server::FakeServer* fake_server_;
syncer::PassphraseType expected_passphrase_type_;
};
// Checker used to block until Sync requires or stops requiring a passphrase.
class PassphraseRequiredStateChecker : public SingleClientStatusChangeChecker {
public:
PassphraseRequiredStateChecker(browser_sync::ProfileSyncService* service,
bool desired_state);
bool IsExitConditionSatisfied() override;
std::string GetDebugMessage() const override;
private:
bool desired_state_;
};
#endif // CHROME_BROWSER_SYNC_TEST_INTEGRATION_ENCRYPTION_HELPER_H_
......@@ -182,7 +182,7 @@ void ProfileSyncServiceHarness::SignOutPrimaryAccount() {
#endif // !OS_CHROMEOS
bool ProfileSyncServiceHarness::SetupSync() {
bool result = SetupSync(syncer::UserSelectableTypes(), false);
bool result = SetupSync(syncer::UserSelectableTypes());
if (!result) {
LOG(ERROR) << profile_debug_name_ << ": SetupSync failed. Syncer status:\n"
<< GetServiceStatus();
......@@ -193,7 +193,9 @@ bool ProfileSyncServiceHarness::SetupSync() {
}
bool ProfileSyncServiceHarness::SetupSyncForClearingServerData() {
bool result = SetupSync(syncer::UserSelectableTypes(), true);
bool result = SetupSyncImpl(syncer::UserSelectableTypes(),
/*skip_passphrase_verification=*/true,
/*encryption_passphrase=*/base::nullopt);
if (!result) {
LOG(ERROR) << profile_debug_name_
<< ": SetupSyncForClear failed. Syncer status:\n"
......@@ -204,8 +206,47 @@ bool ProfileSyncServiceHarness::SetupSyncForClearingServerData() {
return result;
}
bool ProfileSyncServiceHarness::SetupSync(syncer::ModelTypeSet synced_datatypes,
bool skip_passphrase_verification) {
bool ProfileSyncServiceHarness::SetupSync(
syncer::ModelTypeSet synced_datatypes) {
return SetupSyncImpl(synced_datatypes, /*skip_passphrase_verification=*/false,
/*encryption_passphrase=*/base::nullopt);
}
bool ProfileSyncServiceHarness::SetupSyncWithEncryptionPassphrase(
syncer::ModelTypeSet synced_datatypes,
const std::string& passphrase) {
return SetupSyncImpl(synced_datatypes, /*skip_passphrase_verification=*/false,
passphrase);
}
bool ProfileSyncServiceHarness::SetupSyncWithDecryptionPassphrase(
syncer::ModelTypeSet synced_datatypes,
const std::string& passphrase) {
if (!SetupSyncImpl(synced_datatypes, /*skip_passphrase_verification=*/true,
/*encryption_passphrase=*/base::nullopt)) {
return false;
}
DVLOG(1) << "Setting decryption passphrase.";
if (!service_->SetDecryptionPassphrase(passphrase)) {
// This is not a fatal failure, as some tests intentionally pass an
// incorrect passphrase. If this happens, Sync will be set up but will have
// encountered cryptographer errors for the passphrase-encrypted datatypes.
LOG(INFO) << "SetDecryptionPassphrase() failed.";
}
// Since SetupSyncImpl() was called with skip_passphrase_verification == true,
// it will not have called FinishSyncSetup(). FinishSyncSetup() is in charge
// of calling ProfileSyncService::SetFirstSetupComplete(), and without that,
// Sync will still be in setup mode and Sync-the-feature will be disabled.
// Therefore, we call FinishSyncSetup() here explicitly.
FinishSyncSetup();
return true;
}
bool ProfileSyncServiceHarness::SetupSyncImpl(
syncer::ModelTypeSet synced_datatypes,
bool skip_passphrase_verification,
const base::Optional<std::string>& encryption_passphrase) {
DCHECK(!profile_->IsLegacySupervised())
<< "SetupSync should not be used for legacy supervised users.";
......@@ -243,6 +284,12 @@ bool ProfileSyncServiceHarness::SetupSync(syncer::ModelTypeSet synced_datatypes,
service()->OnUserChoseDatatypes(sync_everything, synced_datatypes);
}
if (encryption_passphrase.has_value()) {
service()->SetEncryptionPassphrase(
encryption_passphrase.value(),
syncer::SyncService::PassphraseType::EXPLICIT);
}
// Notify ProfileSyncService that we are done with configuration.
if (skip_passphrase_verification) {
sync_blocker_.reset();
......
......@@ -11,6 +11,7 @@
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/optional.h"
#include "components/browser_sync/profile_sync_service.h"
#include "components/sync/base/model_type.h"
#include "components/sync/engine/cycle/sync_cycle_snapshot.h"
......@@ -62,11 +63,21 @@ class ProfileSyncServiceHarness {
// StopSyncService(), StartSyncService() directly after.
bool SetupSyncForClearingServerData();
// Both SetupSync and SetupSyncForClearingServerData call into this method.
// Same as the above method, but enables sync only for the datatypes contained
// in |synced_datatypes|.
bool SetupSync(syncer::ModelTypeSet synced_datatypes,
bool skip_passphrase_verification = false);
// Enables and configures sync only for the given |synced_datatypes|. Returns
// true only after sync has been fully initialized and authenticated, and we
// are ready to process changes.
bool SetupSync(syncer::ModelTypeSet synced_datatypes);
// Same as SetupSync(), but also sets the given encryption passphrase during
// setup.
bool SetupSyncWithEncryptionPassphrase(syncer::ModelTypeSet synced_datatypes,
const std::string& passphrase);
// Same as SetupSync(), but also sets the given decryption passphrase during
// setup. If the passphrase is incorrect, this method will still return true
// and Sync will be operational but with undecryptable datatypes disabled.
bool SetupSyncWithDecryptionPassphrase(syncer::ModelTypeSet synced_datatypes,
const std::string& passphrase);
// Signals that sync setup is complete, and that PSS may begin syncing.
// Typically SetupSync does this automatically, but if that returned false,
......@@ -146,6 +157,16 @@ class ProfileSyncServiceHarness {
const std::string& password,
SigninType signin_type);
// If |encryption_passphrase| has a value, it will be set during setup. If
// not, no custom passphrase will be set. If |skip_passphrase_verification| is
// true and Sync requires a passphrase, FinishSyncSetup() will not be called,
// in order to give the caller a chance to provide the passphrase using
// SetDecryptionPassphrase(). After that, the caller needs to call
// FinishSyncSetup() manually.
bool SetupSyncImpl(syncer::ModelTypeSet synced_datatypes,
bool skip_passphrase_verification,
const base::Optional<std::string>& encryption_passphrase);
// Gets detailed status from |service_| in pretty-printable form.
std::string GetServiceStatus();
......
......@@ -643,6 +643,20 @@ void SyncTest::DisableNotificationsForClient(int index) {
fake_server_->RemoveObserver(fake_server_invalidation_services_[index]);
}
void SyncTest::SetEncryptionPassphraseForClient(int index,
const std::string& passphrase) {
// Must be called before client initialization.
DCHECK(clients_.empty());
client_encryption_passphrases_[index] = passphrase;
}
void SyncTest::SetDecryptionPassphraseForClient(int index,
const std::string& passphrase) {
// Must be called before client initialization.
DCHECK(clients_.empty());
client_decryption_passphrases_[index] = passphrase;
}
void SyncTest::SetupMockGaiaResponsesForProfile(Profile* profile) {
ChromeSigninClient* signin_client = static_cast<ChromeSigninClient*>(
ChromeSigninClientFactory::GetForProfile(profile));
......@@ -782,8 +796,35 @@ bool SyncTest::SetupSync() {
// Sync each of the profiles.
for (; clientIndex < num_clients_; clientIndex++) {
ProfileSyncServiceHarness* client = GetClient(clientIndex);
DVLOG(1) << "Setting up " << clientIndex << " client";
if (!GetClient(clientIndex)->SetupSync()) {
auto decryption_passphrase_it =
client_decryption_passphrases_.find(clientIndex);
auto encryption_passphrase_it =
client_encryption_passphrases_.find(clientIndex);
bool decryption_passphrase_provided =
(decryption_passphrase_it != client_decryption_passphrases_.end());
bool encryption_passphrase_provided =
(encryption_passphrase_it != client_encryption_passphrases_.end());
if (decryption_passphrase_provided && encryption_passphrase_provided) {
LOG(FATAL) << "Both an encryption and decryption passphrase were "
"provided for the client. This is disallowed.";
return false;
}
bool setup_succeeded;
if (encryption_passphrase_provided) {
setup_succeeded = client->SetupSyncWithEncryptionPassphrase(
syncer::UserSelectableTypes(), encryption_passphrase_it->second);
} else if (decryption_passphrase_provided) {
setup_succeeded = client->SetupSyncWithDecryptionPassphrase(
syncer::UserSelectableTypes(), decryption_passphrase_it->second);
} else {
setup_succeeded = client->SetupSync(syncer::UserSelectableTypes());
}
if (!setup_succeeded) {
LOG(FATAL) << "SetupSync() failed.";
return false;
}
......
......@@ -292,6 +292,23 @@ class SyncTest : public InProcessBrowserTest {
// Stops notificatinos being sent to a client.
void DisableNotificationsForClient(int index);
// Sets a decryption passphrase to be used for a client. The passphrase will
// be provided to the client during initialization, before Sync starts. It is
// an error to provide both a decryption and encryption passphrases for one
// client.
void SetDecryptionPassphraseForClient(int index,
const std::string& passphrase);
// Sets an explicit encryption passphrase to be used for a client. The
// passphrase will be set for the client during initialization, before Sync
// starts. An encryption passphrase can be also enabled after initialization,
// but using this method ensures that Sync is never enabled when there is no
// passphrase, which allows tests to check for unencrypted data leaks. It is
// an error to provide both a decryption and encryption passphrases for one
// client.
void SetEncryptionPassphraseForClient(int index,
const std::string& passphrase);
// Sets up fake responses for kClientLoginUrl, kIssueAuthTokenUrl,
// kGetUserInfoUrl and kSearchDomainCheckUrl in order to mock out calls to
// GAIA servers.
......@@ -443,6 +460,12 @@ class SyncTest : public InProcessBrowserTest {
// profile with the server.
std::vector<std::unique_ptr<ProfileSyncServiceHarness>> clients_;
// Mapping from client indexes to encryption passphrases to use for them.
std::map<int, std::string> client_encryption_passphrases_;
// Mapping from client indexes to decryption passphrases to use for them.
std::map<int, std::string> client_decryption_passphrases_;
// A set of objects to listen for commit activity and broadcast notifications
// of this activity to its peer sync clients.
std::vector<std::unique_ptr<P2PInvalidationForwarder>>
......
......@@ -5187,6 +5187,8 @@ if (!is_android && !is_fuchsia) {
"../browser/sync/test/integration/dictionary_helper.h",
"../browser/sync/test/integration/dictionary_load_observer.cc",
"../browser/sync/test/integration/dictionary_load_observer.h",
"../browser/sync/test/integration/encryption_helper.cc",
"../browser/sync/test/integration/encryption_helper.h",
"../browser/sync/test/integration/extension_settings_helper.cc",
"../browser/sync/test/integration/extension_settings_helper.h",
"../browser/sync/test/integration/extensions_helper.cc",
......@@ -5322,6 +5324,7 @@ if (!is_android && !is_fuchsia) {
"../browser/sync/test/integration/single_client_apps_sync_test.cc",
"../browser/sync/test/integration/single_client_arc_package_sync_test.cc",
"../browser/sync/test/integration/single_client_bookmarks_sync_test.cc",
"../browser/sync/test/integration/single_client_custom_passphrase_sync_test.cc",
"../browser/sync/test/integration/single_client_dictionary_sync_test.cc",
"../browser/sync/test/integration/single_client_directory_sync_test.cc",
"../browser/sync/test/integration/single_client_extensions_sync_test.cc",
......
......@@ -185,6 +185,7 @@ bool LoopbackServer::CreateDefaultPermanentItems() {
if (!top_level_entity) {
return false;
}
top_level_permanent_item_ids_[model_type] = top_level_entity->GetId();
SaveEntity(std::move(top_level_entity));
if (model_type == syncer::BOOKMARKS) {
......@@ -200,6 +201,15 @@ bool LoopbackServer::CreateDefaultPermanentItems() {
return true;
}
std::string LoopbackServer::GetTopLevelPermanentItemId(
syncer::ModelType model_type) {
auto it = top_level_permanent_item_ids_.find(model_type);
if (it == top_level_permanent_item_ids_.end()) {
return std::string();
}
return it->second;
}
void LoopbackServer::UpdateEntityVersion(LoopbackServerEntity* entity) {
entity->SetVersion(++version_);
}
......@@ -506,6 +516,22 @@ std::vector<sync_pb::SyncEntity> LoopbackServer::GetSyncEntitiesByModelType(
return sync_entities;
}
std::vector<sync_pb::SyncEntity>
LoopbackServer::GetPermanentSyncEntitiesByModelType(ModelType model_type) {
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<sync_pb::SyncEntity> sync_entities;
for (const auto& kv : entities_) {
const LoopbackServerEntity& entity = *kv.second;
if (!entity.IsDeleted() && entity.IsPermanent() &&
entity.GetModelType() == model_type) {
sync_pb::SyncEntity sync_entity;
entity.SerializeAsProto(&sync_entity);
sync_entities.push_back(sync_entity);
}
}
return sync_entities;
}
std::unique_ptr<base::DictionaryValue>
LoopbackServer::GetEntitiesAsDictionaryValue() {
DCHECK(thread_checker_.CalledOnValidThread());
......
......@@ -88,6 +88,10 @@ class LoopbackServer {
// Inserts the default permanent items in |entities_|.
bool CreateDefaultPermanentItems();
// Returns an empty string if no top-level permanent item of the given type
// was created.
std::string GetTopLevelPermanentItemId(syncer::ModelType model_type);
std::string GenerateNewKeystoreKey() const;
// Saves a |entity| to |entities_|.
......@@ -127,14 +131,20 @@ class LoopbackServer {
std::string GetStoreBirthday() const;
// Returns all entities stored by the server of the given |model_type|.
// This method is only used in tests.
// Permanent entities are excluded. This method is only used in tests.
std::vector<sync_pb::SyncEntity> GetSyncEntitiesByModelType(
syncer::ModelType model_type);
// Returns a list of permanent entities of the given |model_type|. This method
// is only used in tests.
std::vector<sync_pb::SyncEntity> GetPermanentSyncEntitiesByModelType(
syncer::ModelType model_type);
// Creates a DicionaryValue representation of all entities present in the
// server. The dictionary keys are the strings generated by ModelTypeToString
// and the values are ListValues containing StringValue versions of entity
// names. Used by test to verify the contents of the server state.
// names. Permanent entities are excluded. Used by test to verify the contents
// of the server state.
std::unique_ptr<base::DictionaryValue> GetEntitiesAsDictionaryValue();
// Modifies the entity on the server with the given |id|. The entity's
......@@ -189,6 +199,7 @@ class LoopbackServer {
int64_t store_birthday_;
EntityMap entities_;
std::map<ModelType, std::string> top_level_permanent_item_ids_;
std::vector<std::string> keystore_keys_;
// The file used to store the local sync data.
......
......@@ -306,6 +306,18 @@ std::vector<sync_pb::SyncEntity> FakeServer::GetSyncEntitiesByModelType(
return loopback_server_->GetSyncEntitiesByModelType(model_type);
}
std::vector<sync_pb::SyncEntity>
FakeServer::GetPermanentSyncEntitiesByModelType(ModelType model_type) {
DCHECK(thread_checker_.CalledOnValidThread());
return loopback_server_->GetPermanentSyncEntitiesByModelType(model_type);
}
std::string FakeServer::GetTopLevelPermanentItemId(
syncer::ModelType model_type) {
DCHECK(thread_checker_.CalledOnValidThread());
return loopback_server_->GetTopLevelPermanentItemId(model_type);
}
void FakeServer::InjectEntity(std::unique_ptr<LoopbackServerEntity> entity) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(entity->GetModelType() != syncer::AUTOFILL_WALLET_DATA)
......
......@@ -81,10 +81,21 @@ class FakeServer : public syncer::LoopbackServer::ObserverForTests {
// Returns all entities stored by the server of the given |model_type|.
// This method returns SyncEntity protocol buffer objects (instead of
// LoopbackServerEntity) so that callers can inspect datatype-specific data
// (e.g., the URL of a session tab).
// (e.g., the URL of a session tab). Permanent entities are excluded.
std::vector<sync_pb::SyncEntity> GetSyncEntitiesByModelType(
syncer::ModelType model_type);
// Returns all permanent entities stored by the server of the given
// |model_type|. This method returns SyncEntity protocol buffer objects
// (instead of LoopbackServerEntity) so that callers can inspect
// datatype-specific data (e.g., the URL of a session tab).
std::vector<sync_pb::SyncEntity> GetPermanentSyncEntitiesByModelType(
syncer::ModelType model_type);
// Returns an empty string if no top-level permanent item of the given type
// was created.
std::string GetTopLevelPermanentItemId(syncer::ModelType model_type);
// Adds |entity| to the server's collection of entities. This method makes no
// guarantees that the added entity will result in successful server
// operations.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment