Commit d1df3c49 authored by David Davidović's avatar David Davidović Committed by Commit Bot

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

This is a reland of 475bfe9d. Fixed test
failures which happened because some DCHECKs had side effects.

Original change's description:
> [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: vitaliii <vitaliii@chromium.org>
> Reviewed-by: Marc Treib <treib@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#598912}

Bug: 894148
Change-Id: I752acff5815531c0c83ebefb43fb620a785e3ca3
Reviewed-on: https://chromium-review.googlesource.com/c/1278790
Commit-Queue: David Davidović <davidovic@google.com>
Reviewed-by: default avatarMarc Treib <treib@chromium.org>
Reviewed-by: default avatarvitaliii <vitaliii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599182}
parent 369b6fca
......@@ -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);
bool add_key_result = cryptographer.AddKey(params);
DCHECK(add_key_result);
bool get_keys_result =
cryptographer.GetKeys(nigori.mutable_encryption_keybag());
DCHECK(get_keys_result);
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);
bool add_key_result = cryptographer.AddKey(key_params);
DCHECK(add_key_result);
bool encrypt_result = cryptographer.Encrypt(
wrapped_entity_specifics, new_specifics.mutable_encrypted());
DCHECK(encrypt_result);
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>>
......
......@@ -5188,6 +5188,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",
......@@ -5323,6 +5325,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