diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-07-12 14:07:37 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2017-07-17 10:29:26 +0000 |
commit | ec02ee4181c49b61fce1c8fb99292dbb8139cc90 (patch) | |
tree | 25cde714b2b71eb639d1cd53f5a22e9ba76e14ef /chromium/services/preferences | |
parent | bb09965444b5bb20b096a291445170876225268d (diff) | |
download | qtwebengine-chromium-ec02ee4181c49b61fce1c8fb99292dbb8139cc90.tar.gz |
BASELINE: Update Chromium to 59.0.3071.134
Change-Id: Id02ef6fb2204c5fd21668a1c3e6911c83b17585a
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/services/preferences')
95 files changed, 10060 insertions, 217 deletions
diff --git a/chromium/services/preferences/BUILD.gn b/chromium/services/preferences/BUILD.gn index 8640a9194ee..12b41820c89 100644 --- a/chromium/services/preferences/BUILD.gn +++ b/chromium/services/preferences/BUILD.gn @@ -2,9 +2,70 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -group("tests") { +import("//services/catalog/public/tools/catalog.gni") +import("//services/service_manager/public/service_manifest.gni") +import("//testing/test.gni") + +service_manifest("manifest") { + name = "preferences" + source = "manifest.json" +} + +source_set("preferences") { + visibility = [ + ":*", + "//services/preferences/public/cpp:service_main", + ] + deps = [ + "//components/prefs", + "//services/preferences/public/cpp", + "//services/preferences/public/interfaces", + "//services/preferences/tracked", + "//services/service_manager/public/cpp", + ] + sources = [ + "persistent_pref_store_factory.cc", + "persistent_pref_store_factory.h", + "persistent_pref_store_impl.cc", + "persistent_pref_store_impl.h", + "pref_store_manager_impl.cc", + "pref_store_manager_impl.h", + ] +} + +source_set("tests") { testonly = true deps = [ + ":preferences", + "//base", + "//base/test:test_support", + "//components/prefs:test_support", + "//mojo/public/cpp/bindings:bindings", + "//services/preferences/public/cpp", + "//services/preferences/public/cpp:service_main", "//services/preferences/public/cpp/tests", + "//services/preferences/public/interfaces", + "//services/preferences/tracked:unit_tests", + "//services/service_manager/public/cpp", + "//testing/gmock", + "//testing/gtest", + ] + sources = [ + "persistent_pref_store_impl_unittest.cc", ] + if (!is_ios) { + sources += [ "pref_service_factory_unittest.cc" ] + deps += [ "//services/service_manager/public/cpp:service_test_support" ] + } +} + +service_manifest("unittest_manifest") { + name = "prefs_unittests" + source = "unittest_manifest.json" + packaged_services = [ ":manifest" ] +} + +catalog("tests_catalog") { + testonly = true + embedded_services = [ ":unittest_manifest" ] } diff --git a/chromium/services/preferences/DEPS b/chromium/services/preferences/DEPS new file mode 100644 index 00000000000..eac076192dc --- /dev/null +++ b/chromium/services/preferences/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+components/prefs", +] diff --git a/chromium/services/preferences/manifest.json b/chromium/services/preferences/manifest.json new file mode 100644 index 00000000000..40473465595 --- /dev/null +++ b/chromium/services/preferences/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "preferences", + "display_name": "Preferences", + "interface_provider_specs": { + "service_manager:connector": { + "provides": { + "pref_client": [ + "prefs::mojom::PrefStoreConnector", + "prefs::mojom::PrefStoreRegistry" + ], + "pref_control": [ + "prefs::mojom::PrefServiceControl" + ] + }, + "requires": { + } + } + } +} diff --git a/chromium/services/preferences/persistent_pref_store_factory.cc b/chromium/services/preferences/persistent_pref_store_factory.cc new file mode 100644 index 00000000000..587ac96d182 --- /dev/null +++ b/chromium/services/preferences/persistent_pref_store_factory.cc @@ -0,0 +1,51 @@ +// Copyright 2017 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 "services/preferences/persistent_pref_store_factory.h" + +#include <memory> +#include <utility> + +#include "components/prefs/json_pref_store.h" +#include "components/prefs/pref_filter.h" +#include "services/preferences/persistent_pref_store_impl.h" +#include "services/preferences/tracked/tracked_persistent_pref_store_factory.h" + +namespace prefs { +namespace { + +std::unique_ptr<PersistentPrefStoreImpl> CreateSimplePersistentPrefStore( + mojom::SimplePersistentPrefStoreConfigurationPtr config, + base::SequencedWorkerPool* worker_pool, + base::Closure on_initialized) { + return base::MakeUnique<PersistentPrefStoreImpl>( + new JsonPrefStore(config->pref_filename, + JsonPrefStore::GetTaskRunnerForFile( + config->pref_filename.DirName(), worker_pool), + nullptr), + std::move(on_initialized)); +} + +} // namespace + +std::unique_ptr<PersistentPrefStoreImpl> CreatePersistentPrefStore( + mojom::PersistentPrefStoreConfigurationPtr configuration, + base::SequencedWorkerPool* worker_pool, + base::Closure on_initialized) { + if (configuration->is_simple_configuration()) { + return CreateSimplePersistentPrefStore( + std::move(configuration->get_simple_configuration()), worker_pool, + std::move(on_initialized)); + } + if (configuration->is_tracked_configuration()) { + return base::MakeUnique<PersistentPrefStoreImpl>( + CreateTrackedPersistentPrefStore( + std::move(configuration->get_tracked_configuration()), worker_pool), + std::move(on_initialized)); + } + NOTREACHED(); + return nullptr; +} + +} // namespace prefs diff --git a/chromium/services/preferences/persistent_pref_store_factory.h b/chromium/services/preferences/persistent_pref_store_factory.h new file mode 100644 index 00000000000..06cf8f701b0 --- /dev/null +++ b/chromium/services/preferences/persistent_pref_store_factory.h @@ -0,0 +1,28 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_FACTORY_H_ +#define SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_FACTORY_H_ + +#include <memory> + +#include "services/preferences/public/interfaces/preferences.mojom.h" + +namespace base { +class SequencedWorkerPool; +} + +namespace prefs { +class PersistentPrefStoreImpl; + +// Create a new mojom::PersistentPrefStore impl that runs on |worker_pool| +// configured by |configuration|. +std::unique_ptr<PersistentPrefStoreImpl> CreatePersistentPrefStore( + mojom::PersistentPrefStoreConfigurationPtr configuration, + base::SequencedWorkerPool* worker_pool, + base::Closure on_initialized); + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_FACTORY_H_ diff --git a/chromium/services/preferences/persistent_pref_store_impl.cc b/chromium/services/preferences/persistent_pref_store_impl.cc new file mode 100644 index 00000000000..95930387654 --- /dev/null +++ b/chromium/services/preferences/persistent_pref_store_impl.cc @@ -0,0 +1,161 @@ +// Copyright 2017 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 "services/preferences/persistent_pref_store_impl.h" + +#include <utility> + +#include "base/auto_reset.h" +#include "base/memory/ptr_util.h" +#include "base/stl_util.h" +#include "base/values.h" +#include "components/prefs/persistent_pref_store.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace prefs { + +class PersistentPrefStoreImpl::Connection : public mojom::PersistentPrefStore { + public: + Connection(PersistentPrefStoreImpl* pref_store, + mojom::PersistentPrefStoreRequest request, + mojom::PrefStoreObserverPtr observer, + ObservedPrefs observed_keys) + : pref_store_(pref_store), + binding_(this, std::move(request)), + observer_(std::move(observer)), + observed_keys_(std::move(observed_keys)) { + auto error_callback = + base::Bind(&PersistentPrefStoreImpl::Connection::OnConnectionError, + base::Unretained(this)); + binding_.set_connection_error_handler(error_callback); + observer_.set_connection_error_handler(error_callback); + } + + ~Connection() override = default; + + void OnPrefValuesChanged(const std::vector<mojom::PrefUpdatePtr>& updates) { + if (write_in_progress_) + return; + + std::vector<mojom::PrefUpdatePtr> filtered_updates; + for (const auto& update : updates) { + if (base::ContainsKey(observed_keys_, update->key)) { + filtered_updates.push_back(mojom::PrefUpdate::New( + update->key, + update->value ? update->value->CreateDeepCopy() : nullptr, 0)); + } + } + if (!filtered_updates.empty()) + observer_->OnPrefsChanged(std::move(filtered_updates)); + } + + private: + // mojom::PersistentPrefStore: + void SetValues(std::vector<mojom::PrefUpdatePtr> updates) override { + base::AutoReset<bool> scoped_call_in_progress(&write_in_progress_, true); + pref_store_->SetValues(std::move(updates)); + } + + void CommitPendingWrite() override { pref_store_->CommitPendingWrite(); } + void SchedulePendingLossyWrites() override { + pref_store_->SchedulePendingLossyWrites(); + } + void ClearMutableValues() override { pref_store_->ClearMutableValues(); } + + void OnConnectionError() { pref_store_->OnConnectionError(this); } + + // Owns |this|. + PersistentPrefStoreImpl* const pref_store_; + + mojo::Binding<mojom::PersistentPrefStore> binding_; + mojom::PrefStoreObserverPtr observer_; + const ObservedPrefs observed_keys_; + + // If true then a write is in progress and any update notifications should be + // ignored, as those updates would originate from ourselves. + bool write_in_progress_ = false; + + DISALLOW_COPY_AND_ASSIGN(Connection); +}; + +PersistentPrefStoreImpl::PersistentPrefStoreImpl( + scoped_refptr<PersistentPrefStore> backing_pref_store, + base::OnceClosure on_initialized) + : backing_pref_store_(backing_pref_store) { + if (!backing_pref_store_->IsInitializationComplete()) { + backing_pref_store_->AddObserver(this); + on_initialized_ = std::move(on_initialized); + initializing_ = true; + backing_pref_store_->ReadPrefsAsync(nullptr); + } +} + +PersistentPrefStoreImpl::~PersistentPrefStoreImpl() = default; + +mojom::PersistentPrefStoreConnectionPtr +PersistentPrefStoreImpl::CreateConnection(ObservedPrefs observed_prefs) { + DCHECK(!initializing_); + if (!backing_pref_store_->IsInitializationComplete()) { + // |backing_pref_store_| initialization failed. + return mojom::PersistentPrefStoreConnection::New( + nullptr, nullptr, backing_pref_store_->GetReadError(), + backing_pref_store_->ReadOnly()); + } + mojom::PersistentPrefStorePtr pref_store_ptr; + mojom::PrefStoreObserverPtr observer; + mojom::PrefStoreObserverRequest observer_request = + mojo::MakeRequest(&observer); + auto connection = base::MakeUnique<Connection>( + this, mojo::MakeRequest(&pref_store_ptr), std::move(observer), + std::move(observed_prefs)); + auto* connection_ptr = connection.get(); + connections_.insert(std::make_pair(connection_ptr, std::move(connection))); + return mojom::PersistentPrefStoreConnection::New( + mojom::PrefStoreConnection::New(std::move(observer_request), + backing_pref_store_->GetValues(), true), + std::move(pref_store_ptr), backing_pref_store_->GetReadError(), + backing_pref_store_->ReadOnly()); +} + +void PersistentPrefStoreImpl::OnPrefValueChanged(const std::string& key) {} + +void PersistentPrefStoreImpl::OnInitializationCompleted(bool succeeded) { + DCHECK(initializing_); + backing_pref_store_->RemoveObserver(this); + initializing_ = false; + std::move(on_initialized_).Run(); +} + +void PersistentPrefStoreImpl::SetValues( + std::vector<mojom::PrefUpdatePtr> updates) { + for (auto& entry : connections_) + entry.first->OnPrefValuesChanged(updates); + + for (auto& update : updates) { + if (update->value) { + backing_pref_store_->SetValue(update->key, std::move(update->value), + update->flags); + } else { + backing_pref_store_->RemoveValue(update->key, update->flags); + } + } +} + +void PersistentPrefStoreImpl::CommitPendingWrite() { + backing_pref_store_->CommitPendingWrite(); +} + +void PersistentPrefStoreImpl::SchedulePendingLossyWrites() { + backing_pref_store_->SchedulePendingLossyWrites(); +} + +void PersistentPrefStoreImpl::ClearMutableValues() { + backing_pref_store_->ClearMutableValues(); +} + +void PersistentPrefStoreImpl::OnConnectionError(Connection* connection) { + connections_.erase(connection); +} + +} // namespace prefs diff --git a/chromium/services/preferences/persistent_pref_store_impl.h b/chromium/services/preferences/persistent_pref_store_impl.h new file mode 100644 index 00000000000..5111ed7f4e2 --- /dev/null +++ b/chromium/services/preferences/persistent_pref_store_impl.h @@ -0,0 +1,64 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_IMPL_H_ +#define SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_IMPL_H_ + +#include <memory> +#include <string> +#include <unordered_map> +#include <vector> + +#include "base/macros.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" +#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h" + +namespace prefs { + +class PersistentPrefStoreImpl : public PrefStore::Observer { + public: + using ObservedPrefs = std::set<std::string>; + + // If |initialized()| is false after construction, |on_initialized| will be + // called when it becomes true. + PersistentPrefStoreImpl( + scoped_refptr<PersistentPrefStore> backing_pref_store, + base::OnceClosure on_initialized); + + ~PersistentPrefStoreImpl() override; + + mojom::PersistentPrefStoreConnectionPtr CreateConnection( + ObservedPrefs observed_prefs); + + bool initialized() { return !initializing_; } + + private: + class Connection; + + void SetValues(std::vector<mojom::PrefUpdatePtr> updates); + + void CommitPendingWrite(); + void SchedulePendingLossyWrites(); + void ClearMutableValues(); + + // PrefStore::Observer: + void OnPrefValueChanged(const std::string& key) override; + void OnInitializationCompleted(bool succeeded) override; + + void OnConnectionError(Connection* connection); + + scoped_refptr<PersistentPrefStore> backing_pref_store_; + + bool initializing_ = false; + + std::unordered_map<Connection*, std::unique_ptr<Connection>> connections_; + + base::OnceClosure on_initialized_; + + DISALLOW_COPY_AND_ASSIGN(PersistentPrefStoreImpl); +}; + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PERSISTENT_PREF_STORE_IMPL_H_ diff --git a/chromium/services/preferences/persistent_pref_store_impl_unittest.cc b/chromium/services/preferences/persistent_pref_store_impl_unittest.cc new file mode 100644 index 00000000000..7a326276c0a --- /dev/null +++ b/chromium/services/preferences/persistent_pref_store_impl_unittest.cc @@ -0,0 +1,336 @@ +// Copyright 2017 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 "services/preferences/persistent_pref_store_impl.h" + +#include <utility> + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/values.h" +#include "components/prefs/in_memory_pref_store.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "services/preferences/public/cpp/persistent_pref_store_client.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::Invoke; +using testing::WithoutArgs; + +namespace prefs { +namespace { + +class PrefStoreObserverMock : public PrefStore::Observer { + public: + MOCK_METHOD1(OnPrefValueChanged, void(const std::string&)); + MOCK_METHOD1(OnInitializationCompleted, void(bool)); +}; + +class PersistentPrefStoreMock : public InMemoryPrefStore { + public: + MOCK_METHOD0(CommitPendingWrite, void()); + MOCK_METHOD0(SchedulePendingLossyWrites, void()); + MOCK_METHOD0(ClearMutableValues, void()); + + private: + ~PersistentPrefStoreMock() override = default; +}; + +void ExpectPrefChange(PrefStore* pref_store, base::StringPiece key) { + PrefStoreObserverMock observer; + pref_store->AddObserver(&observer); + base::RunLoop run_loop; + EXPECT_CALL(observer, OnPrefValueChanged(key.as_string())) + .WillOnce(WithoutArgs(Invoke([&run_loop]() { run_loop.Quit(); }))); + run_loop.Run(); + pref_store->RemoveObserver(&observer); +} + +class InitializationMockPersistentPrefStore : public InMemoryPrefStore { + public: + InitializationMockPersistentPrefStore( + bool success, + PersistentPrefStore::PrefReadError error, + bool read_only) + : success_(success), read_error_(error), read_only_(read_only) {} + + bool IsInitializationComplete() const override { + return initialized_ && success_; + } + + void AddObserver(PrefStore::Observer* observer) override { + observers_.AddObserver(observer); + } + + void RemoveObserver(PrefStore::Observer* observer) override { + observers_.RemoveObserver(observer); + } + + void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override { + DCHECK(!error_delegate); + DCHECK(!initialized_); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&InitializationMockPersistentPrefStore::CompleteRead, this)); + } + + void CompleteRead() { + initialized_ = true; + for (auto& observer : observers_) { + observer.OnInitializationCompleted(success_); + } + } + + PersistentPrefStore::PrefReadError GetReadError() const override { + return read_error_; + } + bool ReadOnly() const override { return read_only_; } + + private: + ~InitializationMockPersistentPrefStore() override = default; + + bool initialized_ = false; + bool success_; + PersistentPrefStore::PrefReadError read_error_; + bool read_only_; + base::ObserverList<PrefStore::Observer, true> observers_; +}; + +constexpr char kKey[] = "path.to.key"; +constexpr char kOtherKey[] = "path.to.other_key"; + +class PersistentPrefStoreImplTest : public testing::Test { + public: + PersistentPrefStoreImplTest() = default; + + // testing::Test: + void TearDown() override { + pref_store_ = nullptr; + base::RunLoop().RunUntilIdle(); + impl_.reset(); + base::RunLoop().RunUntilIdle(); + } + + void CreateImpl(scoped_refptr<PersistentPrefStore> backing_pref_store) { + base::RunLoop run_loop; + bool initialized = backing_pref_store->IsInitializationComplete(); + impl_ = base::MakeUnique<PersistentPrefStoreImpl>( + std::move(backing_pref_store), run_loop.QuitClosure()); + if (!initialized) + run_loop.Run(); + pref_store_ = CreateConnection(); + } + + scoped_refptr<PersistentPrefStore> CreateConnection( + PersistentPrefStoreImpl::ObservedPrefs observed_prefs = + PersistentPrefStoreImpl::ObservedPrefs()) { + if (observed_prefs.empty()) + observed_prefs.insert({kKey, kOtherKey}); + return make_scoped_refptr(new PersistentPrefStoreClient( + impl_->CreateConnection(std::move(observed_prefs)))); + } + + PersistentPrefStore* pref_store() { return pref_store_.get(); } + + private: + base::MessageLoop message_loop_; + + std::unique_ptr<PersistentPrefStoreImpl> impl_; + + scoped_refptr<PersistentPrefStore> pref_store_; + + DISALLOW_COPY_AND_ASSIGN(PersistentPrefStoreImplTest); +}; + +TEST_F(PersistentPrefStoreImplTest, InitializationSuccess) { + auto backing_pref_store = + make_scoped_refptr(new InitializationMockPersistentPrefStore( + true, PersistentPrefStore::PREF_READ_ERROR_NONE, false)); + CreateImpl(backing_pref_store); + EXPECT_TRUE(pref_store()->IsInitializationComplete()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, + pref_store()->GetReadError()); + EXPECT_FALSE(pref_store()->ReadOnly()); +} + +TEST_F(PersistentPrefStoreImplTest, InitializationFailure) { + auto backing_pref_store = + make_scoped_refptr(new InitializationMockPersistentPrefStore( + false, PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE, true)); + CreateImpl(backing_pref_store); + EXPECT_FALSE(pref_store()->IsInitializationComplete()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE, + pref_store()->GetReadError()); + EXPECT_TRUE(pref_store()->ReadOnly()); +} + +TEST_F(PersistentPrefStoreImplTest, InitialValue) { + auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore()); + const base::Value value("value"); + backing_pref_store->SetValue(kKey, value.CreateDeepCopy(), 0); + CreateImpl(backing_pref_store); + EXPECT_TRUE(pref_store()->IsInitializationComplete()); + const base::Value* output = nullptr; + ASSERT_TRUE(pref_store()->GetValue(kKey, &output)); + EXPECT_TRUE(value.Equals(output)); +} + +TEST_F(PersistentPrefStoreImplTest, InitialValueWithoutPathExpansion) { + auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore()); + base::DictionaryValue dict; + dict.SetStringWithoutPathExpansion(kKey, "value"); + backing_pref_store->SetValue(kKey, dict.CreateDeepCopy(), 0); + CreateImpl(backing_pref_store); + EXPECT_TRUE(pref_store()->IsInitializationComplete()); + const base::Value* output = nullptr; + ASSERT_TRUE(pref_store()->GetValue(kKey, &output)); + EXPECT_TRUE(dict.Equals(output)); +} + +TEST_F(PersistentPrefStoreImplTest, WriteObservedByOtherClient) { + auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore()); + CreateImpl(backing_pref_store); + EXPECT_TRUE(pref_store()->IsInitializationComplete()); + + auto other_pref_store = CreateConnection(); + EXPECT_TRUE(other_pref_store->IsInitializationComplete()); + + const base::Value value("value"); + pref_store()->SetValueSilently(kKey, value.CreateDeepCopy(), 0); + + ExpectPrefChange(other_pref_store.get(), kKey); + const base::Value* output = nullptr; + ASSERT_TRUE(other_pref_store->GetValue(kKey, &output)); + EXPECT_TRUE(value.Equals(output)); +} + +TEST_F(PersistentPrefStoreImplTest, UnregisteredPrefNotObservedByOtherClient) { + auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore()); + CreateImpl(backing_pref_store); + EXPECT_TRUE(pref_store()->IsInitializationComplete()); + + PersistentPrefStoreImpl::ObservedPrefs observed_prefs; + observed_prefs.insert(kKey); + + auto other_pref_store = CreateConnection(std::move(observed_prefs)); + EXPECT_TRUE(other_pref_store->IsInitializationComplete()); + + pref_store()->SetValue(kOtherKey, base::MakeUnique<base::Value>(123), 0); + pref_store()->SetValue(kKey, base::MakeUnique<base::Value>("value"), 0); + + ExpectPrefChange(other_pref_store.get(), kKey); + EXPECT_FALSE(other_pref_store->GetValue(kOtherKey, nullptr)); +} + +TEST_F(PersistentPrefStoreImplTest, + WriteWithoutPathExpansionObservedByOtherClient) { + auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore()); + CreateImpl(backing_pref_store); + EXPECT_TRUE(pref_store()->IsInitializationComplete()); + + auto other_pref_store = CreateConnection(); + EXPECT_TRUE(other_pref_store->IsInitializationComplete()); + + base::DictionaryValue dict; + dict.SetStringWithoutPathExpansion(kKey, "value"); + pref_store()->SetValue(kKey, dict.CreateDeepCopy(), 0); + + ExpectPrefChange(other_pref_store.get(), kKey); + const base::Value* output = nullptr; + ASSERT_TRUE(other_pref_store->GetValue(kKey, &output)); + EXPECT_TRUE(dict.Equals(output)); +} + +TEST_F(PersistentPrefStoreImplTest, RemoveObservedByOtherClient) { + auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore()); + const base::Value value("value"); + backing_pref_store->SetValue(kKey, value.CreateDeepCopy(), 0); + CreateImpl(backing_pref_store); + EXPECT_TRUE(pref_store()->IsInitializationComplete()); + + auto other_pref_store = CreateConnection(); + EXPECT_TRUE(other_pref_store->IsInitializationComplete()); + + const base::Value* output = nullptr; + ASSERT_TRUE(other_pref_store->GetValue(kKey, &output)); + EXPECT_TRUE(value.Equals(output)); + pref_store()->RemoveValue(kKey, 0); + + // This should be a no-op and shouldn't trigger a notification for the other + // client. + pref_store()->RemoveValue(kKey, 0); + + ExpectPrefChange(other_pref_store.get(), kKey); + EXPECT_FALSE(other_pref_store->GetValue(kKey, &output)); +} + +TEST_F(PersistentPrefStoreImplTest, + RemoveWithoutPathExpansionObservedByOtherClient) { + auto backing_pref_store = make_scoped_refptr(new InMemoryPrefStore()); + base::DictionaryValue dict; + dict.SetStringWithoutPathExpansion(kKey, "value"); + backing_pref_store->SetValue(kKey, dict.CreateDeepCopy(), 0); + CreateImpl(backing_pref_store); + EXPECT_TRUE(pref_store()->IsInitializationComplete()); + + auto other_pref_store = CreateConnection(); + EXPECT_TRUE(other_pref_store->IsInitializationComplete()); + + const base::Value* output = nullptr; + ASSERT_TRUE(other_pref_store->GetValue(kKey, &output)); + EXPECT_TRUE(dict.Equals(output)); + + base::Value* mutable_value = nullptr; + dict.SetStringWithoutPathExpansion(kKey, "value"); + ASSERT_TRUE(pref_store()->GetMutableValue(kKey, &mutable_value)); + base::DictionaryValue* mutable_dict = nullptr; + ASSERT_TRUE(mutable_value->GetAsDictionary(&mutable_dict)); + mutable_dict->RemoveWithoutPathExpansion(kKey, nullptr); + pref_store()->ReportValueChanged(kKey, 0); + + ExpectPrefChange(other_pref_store.get(), kKey); + ASSERT_TRUE(other_pref_store->GetValue(kKey, &output)); + const base::DictionaryValue* dict_value = nullptr; + ASSERT_TRUE(output->GetAsDictionary(&dict_value)); + EXPECT_TRUE(dict_value->empty()); +} + +TEST_F(PersistentPrefStoreImplTest, CommitPendingWrite) { + auto backing_store = make_scoped_refptr(new PersistentPrefStoreMock); + CreateImpl(backing_store); + base::RunLoop run_loop; + EXPECT_CALL(*backing_store, CommitPendingWrite()) + .Times(2) + .WillOnce(WithoutArgs(Invoke([&run_loop]() { run_loop.Quit(); }))); + pref_store()->CommitPendingWrite(); + run_loop.Run(); +} + +TEST_F(PersistentPrefStoreImplTest, SchedulePendingLossyWrites) { + auto backing_store = make_scoped_refptr(new PersistentPrefStoreMock); + CreateImpl(backing_store); + base::RunLoop run_loop; + EXPECT_CALL(*backing_store, SchedulePendingLossyWrites()) + .WillOnce(WithoutArgs(Invoke([&run_loop]() { run_loop.Quit(); }))); + EXPECT_CALL(*backing_store, CommitPendingWrite()).Times(1); + pref_store()->SchedulePendingLossyWrites(); + run_loop.Run(); +} + +TEST_F(PersistentPrefStoreImplTest, ClearMutableValues) { + auto backing_store = make_scoped_refptr(new PersistentPrefStoreMock); + CreateImpl(backing_store); + base::RunLoop run_loop; + EXPECT_CALL(*backing_store, ClearMutableValues()) + .WillOnce(WithoutArgs(Invoke([&run_loop]() { run_loop.Quit(); }))); + EXPECT_CALL(*backing_store, CommitPendingWrite()).Times(1); + pref_store()->ClearMutableValues(); + run_loop.Run(); +} + +} // namespace +} // namespace prefs diff --git a/chromium/services/preferences/pref_service_factory_unittest.cc b/chromium/services/preferences/pref_service_factory_unittest.cc new file mode 100644 index 00000000000..21094e823ad --- /dev/null +++ b/chromium/services/preferences/pref_service_factory_unittest.cc @@ -0,0 +1,269 @@ +// Copyright (c) 2017 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 "services/preferences/public/cpp/pref_service_factory.h" + +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/test/sequenced_worker_pool_owner.h" +#include "components/prefs/pref_change_registrar.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/value_map_pref_store.h" +#include "components/prefs/writeable_pref_store.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "services/preferences/public/cpp/pref_service_main.h" +#include "services/preferences/public/cpp/pref_store_impl.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" +#include "services/service_manager/public/cpp/binder_registry.h" +#include "services/service_manager/public/cpp/interface_factory.h" +#include "services/service_manager/public/cpp/service_context.h" +#include "services/service_manager/public/cpp/service_test.h" +#include "services/service_manager/public/interfaces/service_factory.mojom.h" + +namespace prefs { +namespace { + +class ServiceTestClient : public service_manager::test::ServiceTestClient, + public service_manager::mojom::ServiceFactory, + public service_manager::InterfaceFactory< + service_manager::mojom::ServiceFactory> { + public: + ServiceTestClient(service_manager::test::ServiceTest* test, + scoped_refptr<base::SequencedWorkerPool> worker_pool) + : service_manager::test::ServiceTestClient(test), + worker_pool_(std::move(worker_pool)) { + registry_.AddInterface<service_manager::mojom::ServiceFactory>(this); + } + + protected: + void OnBindInterface(const service_manager::ServiceInfo& source_info, + const std::string& interface_name, + mojo::ScopedMessagePipeHandle interface_pipe) override { + registry_.BindInterface(source_info.identity, interface_name, + std::move(interface_pipe)); + } + + void CreateService(service_manager::mojom::ServiceRequest request, + const std::string& name) override { + if (name == prefs::mojom::kServiceName) { + pref_service_context_.reset(new service_manager::ServiceContext( + CreatePrefService( + {PrefValueStore::COMMAND_LINE_STORE, + PrefValueStore::RECOMMENDED_STORE, PrefValueStore::USER_STORE, + PrefValueStore::DEFAULT_STORE}, + worker_pool_), + std::move(request))); + } + } + + void Create(const service_manager::Identity& remote_identity, + service_manager::mojom::ServiceFactoryRequest request) override { + service_factory_bindings_.AddBinding(this, std::move(request)); + } + + private: + scoped_refptr<base::SequencedWorkerPool> worker_pool_; + service_manager::BinderRegistry registry_; + mojo::BindingSet<service_manager::mojom::ServiceFactory> + service_factory_bindings_; + std::unique_ptr<service_manager::ServiceContext> pref_service_context_; +}; + +constexpr int kInitialValue = 1; +constexpr int kUpdatedValue = 2; +constexpr char kKey[] = "some_key"; +constexpr char kOtherKey[] = "some_other_key"; + +class PrefServiceFactoryTest : public base::MessageLoop::DestructionObserver, + public service_manager::test::ServiceTest { + public: + PrefServiceFactoryTest() : ServiceTest("prefs_unittests", false) {} + + protected: + void SetUp() override { + ServiceTest::SetUp(); + ASSERT_TRUE(profile_dir_.CreateUniqueTempDir()); + + // Init the pref service (in production Chrome startup would do this.) + mojom::PrefServiceControlPtr control; + connector()->BindInterface(mojom::kServiceName, &control); + auto config = mojom::PersistentPrefStoreConfiguration::New(); + config->set_simple_configuration( + mojom::SimplePersistentPrefStoreConfiguration::New( + profile_dir_.GetPath().AppendASCII("Preferences"))); + control->Init(std::move(config)); + above_user_prefs_pref_store_ = new ValueMapPrefStore(); + below_user_prefs_pref_store_ = new ValueMapPrefStore(); + mojom::PrefStoreRegistryPtr registry; + connector()->BindInterface(mojom::kServiceName, ®istry); + above_user_prefs_impl_ = + PrefStoreImpl::Create(registry.get(), above_user_prefs_pref_store_, + PrefValueStore::COMMAND_LINE_STORE); + below_user_prefs_impl_ = + PrefStoreImpl::Create(registry.get(), below_user_prefs_pref_store_, + PrefValueStore::RECOMMENDED_STORE); + } + + // service_manager::test::ServiceTest: + std::unique_ptr<service_manager::Service> CreateService() override { + return base::MakeUnique<ServiceTestClient>(this, + worker_pool_owner_->pool()); + } + + std::unique_ptr<base::MessageLoop> CreateMessageLoop() override { + auto loop = ServiceTest::CreateMessageLoop(); + worker_pool_owner_ = base::MakeUnique<base::SequencedWorkerPoolOwner>( + 2, "PrefServiceFactoryTest"); + loop->AddDestructionObserver(this); + return loop; + } + + // base::MessageLoop::DestructionObserver + void WillDestroyCurrentMessageLoop() override { worker_pool_owner_.reset(); } + + // Create a fully initialized PrefService synchronously. + std::unique_ptr<PrefService> Create() { + std::unique_ptr<PrefService> pref_service; + base::RunLoop run_loop; + auto pref_registry = make_scoped_refptr(new PrefRegistrySimple()); + pref_registry->RegisterIntegerPref(kKey, kInitialValue); + pref_registry->RegisterIntegerPref(kOtherKey, kInitialValue); + ConnectToPrefService(connector(), pref_registry, + std::vector<PrefValueStore::PrefStoreType>(), + base::Bind(&PrefServiceFactoryTest::OnCreate, + run_loop.QuitClosure(), &pref_service)); + run_loop.Run(); + return pref_service; + } + + // Wait until first update of the pref |key| in |pref_service| synchronously. + void WaitForPrefChange(PrefService* pref_service, const std::string& key) { + PrefChangeRegistrar registrar; + registrar.Init(pref_service); + base::RunLoop run_loop; + registrar.Add(key, base::Bind(&OnPrefChanged, run_loop.QuitClosure(), key)); + run_loop.Run(); + } + + WriteablePrefStore* above_user_prefs_pref_store() { + return above_user_prefs_pref_store_.get(); + } + WriteablePrefStore* below_user_prefs_pref_store() { + return below_user_prefs_pref_store_.get(); + } + + private: + // Called when the PrefService has been initialized. + static void OnInit(const base::Closure& quit_closure, bool success) { + quit_closure.Run(); + } + + // Called when the PrefService has been created. + static void OnCreate(const base::Closure& quit_closure, + std::unique_ptr<PrefService>* out, + std::unique_ptr<PrefService> pref_service) { + DCHECK(pref_service); + *out = std::move(pref_service); + if ((*out)->GetInitializationStatus() == + PrefService::INITIALIZATION_STATUS_WAITING) { + (*out)->AddPrefInitObserver( + base::Bind(PrefServiceFactoryTest::OnInit, quit_closure)); + return; + } + quit_closure.Run(); + } + + static void OnPrefChanged(const base::Closure& quit_closure, + const std::string& expected_path, + const std::string& path) { + if (path == expected_path) + quit_closure.Run(); + } + + base::ScopedTempDir profile_dir_; + std::unique_ptr<base::SequencedWorkerPoolOwner> worker_pool_owner_; + scoped_refptr<WriteablePrefStore> above_user_prefs_pref_store_; + std::unique_ptr<PrefStoreImpl> above_user_prefs_impl_; + scoped_refptr<WriteablePrefStore> below_user_prefs_pref_store_; + std::unique_ptr<PrefStoreImpl> below_user_prefs_impl_; + + DISALLOW_COPY_AND_ASSIGN(PrefServiceFactoryTest); +}; + +// Check that a single client can set and read back values. +TEST_F(PrefServiceFactoryTest, Basic) { + auto pref_service = Create(); + + EXPECT_EQ(kInitialValue, pref_service->GetInteger(kKey)); + pref_service->SetInteger(kKey, kUpdatedValue); + EXPECT_EQ(kUpdatedValue, pref_service->GetInteger(kKey)); +} + +// Check that updates in one client eventually propagates to the other. +TEST_F(PrefServiceFactoryTest, MultipleClients) { + auto pref_service = Create(); + auto pref_service2 = Create(); + + EXPECT_EQ(kInitialValue, pref_service->GetInteger(kKey)); + EXPECT_EQ(kInitialValue, pref_service2->GetInteger(kKey)); + pref_service->SetInteger(kKey, kUpdatedValue); + WaitForPrefChange(pref_service2.get(), kKey); + EXPECT_EQ(kUpdatedValue, pref_service2->GetInteger(kKey)); +} + +// Check that read-only pref store changes are observed. +TEST_F(PrefServiceFactoryTest, ReadOnlyPrefStore) { + auto pref_service = Create(); + + EXPECT_EQ(kInitialValue, pref_service->GetInteger(kKey)); + + below_user_prefs_pref_store()->SetValue( + kKey, base::MakeUnique<base::Value>(kUpdatedValue), 0); + WaitForPrefChange(pref_service.get(), kKey); + EXPECT_EQ(kUpdatedValue, pref_service->GetInteger(kKey)); + pref_service->SetInteger(kKey, 3); + EXPECT_EQ(3, pref_service->GetInteger(kKey)); + above_user_prefs_pref_store()->SetValue(kKey, + base::MakeUnique<base::Value>(4), 0); + WaitForPrefChange(pref_service.get(), kKey); + EXPECT_EQ(4, pref_service->GetInteger(kKey)); +} + +// Check that updates to read-only pref stores are correctly layered. +TEST_F(PrefServiceFactoryTest, ReadOnlyPrefStore_Layering) { + auto pref_service = Create(); + + above_user_prefs_pref_store()->SetValue( + kKey, base::MakeUnique<base::Value>(kInitialValue), 0); + WaitForPrefChange(pref_service.get(), kKey); + EXPECT_EQ(kInitialValue, pref_service->GetInteger(kKey)); + + below_user_prefs_pref_store()->SetValue( + kKey, base::MakeUnique<base::Value>(kUpdatedValue), 0); + // This update is needed to check that the change to kKey has propagated even + // though we will not observe it change. + below_user_prefs_pref_store()->SetValue( + kOtherKey, base::MakeUnique<base::Value>(kUpdatedValue), 0); + WaitForPrefChange(pref_service.get(), kOtherKey); + EXPECT_EQ(kInitialValue, pref_service->GetInteger(kKey)); +} + +// Check that writes to user prefs are correctly layered with read-only +// pref stores. +TEST_F(PrefServiceFactoryTest, ReadOnlyPrefStore_UserPrefStoreLayering) { + auto pref_service = Create(); + + above_user_prefs_pref_store()->SetValue(kKey, + base::MakeUnique<base::Value>(2), 0); + WaitForPrefChange(pref_service.get(), kKey); + EXPECT_EQ(2, pref_service->GetInteger(kKey)); + + pref_service->SetInteger(kKey, 3); + EXPECT_EQ(2, pref_service->GetInteger(kKey)); +} + +} // namespace +} // namespace prefs diff --git a/chromium/services/preferences/pref_store_manager_impl.cc b/chromium/services/preferences/pref_store_manager_impl.cc new file mode 100644 index 00000000000..f418792f29e --- /dev/null +++ b/chromium/services/preferences/pref_store_manager_impl.cc @@ -0,0 +1,280 @@ +// Copyright 2017 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 "services/preferences/pref_store_manager_impl.h" + +#include <algorithm> +#include <utility> + +#include "base/memory/ref_counted.h" +#include "base/stl_util.h" +#include "base/threading/sequenced_worker_pool.h" +#include "components/prefs/default_pref_store.h" +#include "components/prefs/pref_value_store.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "services/preferences/persistent_pref_store_factory.h" +#include "services/preferences/persistent_pref_store_impl.h" +#include "services/preferences/public/cpp/pref_store_impl.h" +#include "services/service_manager/public/cpp/service_info.h" + +namespace prefs { +namespace { + +using ConnectCallback = mojom::PrefStoreConnector::ConnectCallback; +using PrefStorePtrs = + std::unordered_map<PrefValueStore::PrefStoreType, mojom::PrefStore*>; + +// Used to make sure all pref stores have been registered before replying to any +// Connect calls. +class ConnectionBarrier : public base::RefCounted<ConnectionBarrier> { + public: + static void Create( + const PrefStorePtrs& pref_store_ptrs, + mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection, + const std::vector<std::string>& observed_prefs, + const ConnectCallback& callback); + + void Init(const PrefStorePtrs& pref_store_ptrs, + const std::vector<std::string>& observed_prefs); + + private: + friend class base::RefCounted<ConnectionBarrier>; + ConnectionBarrier( + const PrefStorePtrs& pref_store_ptrs, + mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection, + const ConnectCallback& callback); + ~ConnectionBarrier() = default; + + void OnConnect(PrefValueStore::PrefStoreType type, + mojom::PrefStoreConnectionPtr connection_ptr); + + ConnectCallback callback_; + + std::unordered_map<PrefValueStore::PrefStoreType, + mojom::PrefStoreConnectionPtr> + connections_; + + const size_t expected_connections_; + + mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection_; + + DISALLOW_COPY_AND_ASSIGN(ConnectionBarrier); +}; + +// static +void ConnectionBarrier::Create( + const PrefStorePtrs& pref_store_ptrs, + mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection, + const std::vector<std::string>& observed_prefs, + const ConnectCallback& callback) { + make_scoped_refptr(new ConnectionBarrier( + pref_store_ptrs, + std::move(persistent_pref_store_connection), callback)) + ->Init(pref_store_ptrs, observed_prefs); +} + +void ConnectionBarrier::Init(const PrefStorePtrs& pref_store_ptrs, + const std::vector<std::string>& observed_prefs) { + for (const auto& ptr : pref_store_ptrs) { + ptr.second->AddObserver( + observed_prefs, + base::Bind(&ConnectionBarrier::OnConnect, this, ptr.first)); + } +} + +ConnectionBarrier::ConnectionBarrier( + const PrefStorePtrs& pref_store_ptrs, + mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection, + const ConnectCallback& callback) + : callback_(callback), + expected_connections_(pref_store_ptrs.size()), + persistent_pref_store_connection_( + std::move(persistent_pref_store_connection)) {} + +void ConnectionBarrier::OnConnect( + PrefValueStore::PrefStoreType type, + mojom::PrefStoreConnectionPtr connection_ptr) { + connections_.insert(std::make_pair(type, std::move(connection_ptr))); + if (connections_.size() == expected_connections_) { + // After this method exits |this| will get destroyed so it's safe to move + // out the members. + callback_.Run(std::move(persistent_pref_store_connection_), + std::move(connections_)); + } +} + +} // namespace + +PrefStoreManagerImpl::PrefStoreManagerImpl( + std::set<PrefValueStore::PrefStoreType> expected_pref_stores, + scoped_refptr<base::SequencedWorkerPool> worker_pool) + : expected_pref_stores_(std::move(expected_pref_stores)), + init_binding_(this), + defaults_(new DefaultPrefStore), + defaults_wrapper_(base::MakeUnique<PrefStoreImpl>( + defaults_, + mojo::MakeRequest(&pref_store_ptrs_[PrefValueStore::DEFAULT_STORE]))), + worker_pool_(std::move(worker_pool)) { + DCHECK( + base::ContainsValue(expected_pref_stores_, PrefValueStore::USER_STORE) && + base::ContainsValue(expected_pref_stores_, PrefValueStore::DEFAULT_STORE)) + << "expected_pref_stores must always include PrefValueStore::USER_STORE " + "and PrefValueStore::DEFAULT_STORE."; + // The user store is not actually connected to in the implementation, but + // accessed directly. + expected_pref_stores_.erase(PrefValueStore::USER_STORE); + registry_.AddInterface<prefs::mojom::PrefStoreConnector>(this); + registry_.AddInterface<prefs::mojom::PrefStoreRegistry>(this); + registry_.AddInterface<prefs::mojom::PrefServiceControl>(this); +} + +PrefStoreManagerImpl::~PrefStoreManagerImpl() = default; + +struct PrefStoreManagerImpl::PendingConnect { + mojom::PrefRegistryPtr pref_registry; + // Pref stores the caller already is connected to (and hence we won't + // connect to these). + std::vector<PrefValueStore::PrefStoreType> already_connected_types; + ConnectCallback callback; +}; + +void PrefStoreManagerImpl::Register(PrefValueStore::PrefStoreType type, + mojom::PrefStorePtr pref_store_ptr) { + if (expected_pref_stores_.count(type) == 0) { + LOG(WARNING) << "Not registering unexpected pref store: " << type; + return; + } + DVLOG(1) << "Registering pref store: " << type; + pref_store_ptr.set_connection_error_handler( + base::Bind(&PrefStoreManagerImpl::OnPrefStoreDisconnect, + base::Unretained(this), type)); + const bool success = + pref_store_ptrs_.insert(std::make_pair(type, std::move(pref_store_ptr))) + .second; + DCHECK(success) << "The same pref store registered twice: " << type; + if (AllConnected()) { + DVLOG(1) << "All pref stores registered."; + ProcessPendingConnects(); + } +} + +void PrefStoreManagerImpl::Connect( + mojom::PrefRegistryPtr pref_registry, + const std::vector<PrefValueStore::PrefStoreType>& already_connected_types, + const ConnectCallback& callback) { + if (AllConnected()) { + ConnectImpl(std::move(pref_registry), already_connected_types, callback); + } else { + pending_connects_.push_back( + {std::move(pref_registry), already_connected_types, callback}); + } +} + +void PrefStoreManagerImpl::Create( + const service_manager::Identity& remote_identity, + prefs::mojom::PrefStoreConnectorRequest request) { + connector_bindings_.AddBinding(this, std::move(request)); +} + +void PrefStoreManagerImpl::Create( + const service_manager::Identity& remote_identity, + prefs::mojom::PrefStoreRegistryRequest request) { + registry_bindings_.AddBinding(this, std::move(request)); +} + +void PrefStoreManagerImpl::Create( + const service_manager::Identity& remote_identity, + prefs::mojom::PrefServiceControlRequest request) { + if (init_binding_.is_bound()) { + LOG(ERROR) + << "Pref service received unexpected control interface connection from " + << remote_identity.name(); + return; + } + + init_binding_.Bind(std::move(request)); +} + +void PrefStoreManagerImpl::Init( + mojom::PersistentPrefStoreConfigurationPtr configuration) { + DCHECK(!persistent_pref_store_); + + persistent_pref_store_ = CreatePersistentPrefStore( + std::move(configuration), worker_pool_.get(), + base::Bind(&PrefStoreManagerImpl::OnPersistentPrefStoreReady, + base::Unretained(this))); + DCHECK(persistent_pref_store_); + if (AllConnected()) + ProcessPendingConnects(); +} + +void PrefStoreManagerImpl::OnStart() {} + +void PrefStoreManagerImpl::OnBindInterface( + const service_manager::ServiceInfo& source_info, + const std::string& interface_name, + mojo::ScopedMessagePipeHandle interface_pipe) { + registry_.BindInterface(source_info.identity, interface_name, + std::move(interface_pipe)); +} + +void PrefStoreManagerImpl::OnPrefStoreDisconnect( + PrefValueStore::PrefStoreType type) { + DVLOG(1) << "Deregistering pref store: " << type; + pref_store_ptrs_.erase(type); +} + +bool PrefStoreManagerImpl::AllConnected() const { + return pref_store_ptrs_.size() == expected_pref_stores_.size() && + persistent_pref_store_ && persistent_pref_store_->initialized(); +} + +void PrefStoreManagerImpl::ProcessPendingConnects() { + for (auto& connect : pending_connects_) + ConnectImpl(std::move(connect.pref_registry), + connect.already_connected_types, connect.callback); + pending_connects_.clear(); +} + +void PrefStoreManagerImpl::ConnectImpl( + mojom::PrefRegistryPtr pref_registry, + const std::vector<PrefValueStore::PrefStoreType>& already_connected_types, + const ConnectCallback& callback) { + std::vector<std::string> observed_prefs; + for (auto& registration : pref_registry->registrations) { + observed_prefs.push_back(registration.first); + const auto& key = registration.first; + auto& default_value = registration.second->default_value; + const base::Value* old_default = nullptr; + // TODO(sammc): Once non-owning registrations are supported, disallow + // multiple owners instead of just checking for consistent defaults. + if (defaults_->GetValue(key, &old_default)) + DCHECK(old_default->Equals(default_value.get())); + else + defaults_->SetDefaultValue(key, std::move(default_value)); + } + + // Only connect to pref stores the client isn't already connected to. + PrefStorePtrs ptrs; + for (const auto& entry : pref_store_ptrs_) { + if (!base::ContainsValue(already_connected_types, entry.first)) { + ptrs.insert(std::make_pair(entry.first, entry.second.get())); + } + } + ConnectionBarrier::Create( + ptrs, + persistent_pref_store_->CreateConnection( + PersistentPrefStoreImpl::ObservedPrefs(observed_prefs.begin(), + observed_prefs.end())), + observed_prefs, callback); +} + +void PrefStoreManagerImpl::OnPersistentPrefStoreReady() { + DVLOG(1) << "PersistentPrefStore ready"; + if (AllConnected()) { + ProcessPendingConnects(); + } +} + +} // namespace prefs diff --git a/chromium/services/preferences/pref_store_manager_impl.h b/chromium/services/preferences/pref_store_manager_impl.h new file mode 100644 index 00000000000..bc7eb1e6c45 --- /dev/null +++ b/chromium/services/preferences/pref_store_manager_impl.h @@ -0,0 +1,136 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PREF_STORE_MANAGER_IMPL_H_ +#define SERVICES_PREFERENCES_PREF_STORE_MANAGER_IMPL_H_ + +#include <memory> +#include <set> +#include <unordered_map> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "components/prefs/pref_value_store.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" +#include "services/service_manager/public/cpp/binder_registry.h" +#include "services/service_manager/public/cpp/interface_factory.h" +#include "services/service_manager/public/cpp/service.h" + +class DefaultPrefStore; + +namespace base { +class SequencedWorkerPool; +} + +namespace prefs { +class PersistentPrefStoreImpl; +class PrefStoreImpl; + +// This class mediates the connection of clients who wants to read preferences +// and the pref stores that store those preferences. Pref stores use the +// |PrefStoreRegistry| interface to register themselves with the manager and +// clients use the |PrefStoreConnector| interface to connect to these stores. +class PrefStoreManagerImpl + : public mojom::PrefStoreRegistry, + public mojom::PrefStoreConnector, + public service_manager::InterfaceFactory<mojom::PrefStoreConnector>, + public service_manager::InterfaceFactory<mojom::PrefStoreRegistry>, + public mojom::PrefServiceControl, + public service_manager::InterfaceFactory<mojom::PrefServiceControl>, + public service_manager::Service { + public: + // Only replies to Connect calls when all |expected_pref_stores| have + // registered. |expected_pref_stores| must contain + // PrefValueStore::DEFAULT_STORE and PrefValueStore::USER_STORE for + // consistency, as the service always registers these + // internally. |worker_pool| is used for any I/O performed by the service. + PrefStoreManagerImpl( + std::set<PrefValueStore::PrefStoreType> expected_pref_stores, + scoped_refptr<base::SequencedWorkerPool> worker_pool); + ~PrefStoreManagerImpl() override; + + private: + struct PendingConnect; + + // mojom::PrefStoreRegistry: + void Register(PrefValueStore::PrefStoreType type, + mojom::PrefStorePtr pref_store_ptr) override; + + // mojom::PrefStoreConnector: |already_connected_types| must not include + // PrefValueStore::DEFAULT_STORE and PrefValueStore::USER_STORE as these must + // always be accessed through the service. + void Connect( + mojom::PrefRegistryPtr pref_registry, + const std::vector<PrefValueStore::PrefStoreType>& already_connected_types, + const ConnectCallback& callback) override; + + // service_manager::InterfaceFactory<PrefStoreConnector>: + void Create(const service_manager::Identity& remote_identity, + prefs::mojom::PrefStoreConnectorRequest request) override; + + // service_manager::InterfaceFactory<PrefStoreRegistry>: + void Create(const service_manager::Identity& remote_identity, + prefs::mojom::PrefStoreRegistryRequest request) override; + + // service_manager::InterfaceFactory<PrefServiceControl>: + void Create(const service_manager::Identity& remote_identity, + prefs::mojom::PrefServiceControlRequest request) override; + + // PrefServiceControl: + void Init(mojom::PersistentPrefStoreConfigurationPtr configuration) override; + + // service_manager::Service: + void OnStart() override; + void OnBindInterface(const service_manager::ServiceInfo& source_info, + const std::string& interface_name, + mojo::ScopedMessagePipeHandle interface_pipe) override; + + // Called when a PrefStore previously registered using |Register| disconnects. + void OnPrefStoreDisconnect(PrefValueStore::PrefStoreType type); + + // Have all the expected PrefStores connected? + bool AllConnected() const; + + void ProcessPendingConnects(); + + void ConnectImpl( + mojom::PrefRegistryPtr pref_registry, + const std::vector<PrefValueStore::PrefStoreType>& already_connected_types, + const ConnectCallback& callback); + + void OnPersistentPrefStoreReady(); + + // PrefStores that need to register before replying to any Connect calls. This + // does not include the PersistentPrefStore, which is handled separately. + std::set<PrefValueStore::PrefStoreType> expected_pref_stores_; + + // Registered pref stores. + std::unordered_map<PrefValueStore::PrefStoreType, mojom::PrefStorePtr> + pref_store_ptrs_; + + // We hold on to the connection request callbacks until all expected + // PrefStores have registered. + std::vector<PendingConnect> pending_connects_; + + mojo::BindingSet<mojom::PrefStoreConnector> connector_bindings_; + mojo::BindingSet<mojom::PrefStoreRegistry> registry_bindings_; + std::unique_ptr<PersistentPrefStoreImpl> persistent_pref_store_; + mojo::Binding<mojom::PrefServiceControl> init_binding_; + + const scoped_refptr<DefaultPrefStore> defaults_; + const std::unique_ptr<PrefStoreImpl> defaults_wrapper_; + + const scoped_refptr<base::SequencedWorkerPool> worker_pool_; + + service_manager::BinderRegistry registry_; + + DISALLOW_COPY_AND_ASSIGN(PrefStoreManagerImpl); +}; + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PREF_STORE_MANAGER_IMPL_H_ diff --git a/chromium/services/preferences/public/cpp/BUILD.gn b/chromium/services/preferences/public/cpp/BUILD.gn index 727169326a4..1499131617c 100644 --- a/chromium/services/preferences/public/cpp/BUILD.gn +++ b/chromium/services/preferences/public/cpp/BUILD.gn @@ -4,18 +4,43 @@ source_set("cpp") { sources = [ - "pref_client_store.cc", - "pref_client_store.h", + "persistent_pref_store_client.cc", + "persistent_pref_store_client.h", + "pref_registry_serializer.cc", + "pref_registry_serializer.h", + "pref_service_factory.cc", + "pref_service_factory.h", + "pref_store_adapter.cc", + "pref_store_adapter.h", + "pref_store_client.cc", + "pref_store_client.h", + "pref_store_client_mixin.cc", + "pref_store_client_mixin.h", + "pref_store_impl.cc", + "pref_store_impl.h", ] public_deps = [ "//base", - "//components/prefs:prefs", + "//components/prefs", "//services/preferences/public/interfaces", "//services/service_manager/public/cpp", ] deps = [ - "//mojo/public/cpp/bindings:bindings", + "//mojo/public/cpp/bindings", + ] +} + +source_set("service_main") { + deps = [ + "//base", + "//components/prefs", + "//services/preferences", + "//services/service_manager/public/cpp", + ] + sources = [ + "pref_service_main.cc", + "pref_service_main.h", ] } diff --git a/chromium/services/preferences/public/cpp/DEPS b/chromium/services/preferences/public/cpp/DEPS index eac076192dc..45e227dd6d0 100644 --- a/chromium/services/preferences/public/cpp/DEPS +++ b/chromium/services/preferences/public/cpp/DEPS @@ -1,3 +1,10 @@ include_rules = [ "+components/prefs", + "-services/preferences", + "+services/preferences/public", ] +specific_include_rules = { + "pref_service_main\.cc": [ + "+services/preferences", + ], +} diff --git a/chromium/services/preferences/public/cpp/OWNERS b/chromium/services/preferences/public/cpp/OWNERS new file mode 100644 index 00000000000..4df0c71cc7d --- /dev/null +++ b/chromium/services/preferences/public/cpp/OWNERS @@ -0,0 +1,4 @@ +per-file *_struct_traits*.*=set noparent +per-file *_struct_traits*.*=file://ipc/SECURITY_OWNERS +per-file *.typemap=set noparent +per-file *.typemap=file://ipc/SECURITY_OWNERS diff --git a/chromium/services/preferences/public/cpp/persistent_pref_store_client.cc b/chromium/services/preferences/public/cpp/persistent_pref_store_client.cc new file mode 100644 index 00000000000..9bcb92fb1b6 --- /dev/null +++ b/chromium/services/preferences/public/cpp/persistent_pref_store_client.cc @@ -0,0 +1,184 @@ +// Copyright 2017 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 "services/preferences/public/cpp/persistent_pref_store_client.h" + +#include <utility> + +#include "base/values.h" +#include "components/prefs/pref_registry.h" +#include "mojo/public/cpp/bindings/sync_call_restrictions.h" +#include "services/preferences/public/cpp/pref_registry_serializer.h" + +namespace prefs { + +PersistentPrefStoreClient::PersistentPrefStoreClient( + mojom::PrefStoreConnectorPtr connector, + scoped_refptr<PrefRegistry> pref_registry, + std::vector<PrefValueStore::PrefStoreType> already_connected_types) + : connector_(std::move(connector)), + pref_registry_(std::move(pref_registry)), + already_connected_types_(std::move(already_connected_types)), + weak_factory_(this) { + DCHECK(connector_); +} + +PersistentPrefStoreClient::PersistentPrefStoreClient( + mojom::PersistentPrefStoreConnectionPtr connection) + : weak_factory_(this) { + OnConnect(std::move(connection), + std::unordered_map<PrefValueStore::PrefStoreType, + prefs::mojom::PrefStoreConnectionPtr>()); +} + +void PersistentPrefStoreClient::SetValue(const std::string& key, + std::unique_ptr<base::Value> value, + uint32_t flags) { + base::Value* old_value = nullptr; + GetMutableValues().Get(key, &old_value); + if (!old_value || !value->Equals(old_value)) { + GetMutableValues().Set(key, std::move(value)); + ReportValueChanged(key, flags); + } +} + +void PersistentPrefStoreClient::RemoveValue(const std::string& key, + uint32_t flags) { + if (GetMutableValues().RemovePath(key, nullptr)) + ReportValueChanged(key, flags); +} + +bool PersistentPrefStoreClient::GetMutableValue(const std::string& key, + base::Value** result) { + return GetMutableValues().Get(key, result); +} + +void PersistentPrefStoreClient::ReportValueChanged(const std::string& key, + uint32_t flags) { + DCHECK(pref_store_); + const base::Value* local_value = nullptr; + GetMutableValues().Get(key, &local_value); + + QueueWrite(key, flags); + ReportPrefValueChanged(key); +} + +void PersistentPrefStoreClient::SetValueSilently( + const std::string& key, + std::unique_ptr<base::Value> value, + uint32_t flags) { + DCHECK(pref_store_); + QueueWrite(key, flags); + GetMutableValues().Set(key, std::move(value)); +} + +bool PersistentPrefStoreClient::ReadOnly() const { + return read_only_; +} + +PersistentPrefStore::PrefReadError PersistentPrefStoreClient::GetReadError() + const { + return read_error_; +} + +PersistentPrefStore::PrefReadError PersistentPrefStoreClient::ReadPrefs() { + mojom::PersistentPrefStoreConnectionPtr connection; + std::unordered_map<PrefValueStore::PrefStoreType, + prefs::mojom::PrefStoreConnectionPtr> + other_pref_stores; + mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_calls; + if (!connector_->Connect(SerializePrefRegistry(*pref_registry_), + already_connected_types_, &connection, + &other_pref_stores)) { + NOTREACHED(); + } + pref_registry_ = nullptr; + OnConnect(std::move(connection), std::move(other_pref_stores)); + return read_error_; +} + +void PersistentPrefStoreClient::ReadPrefsAsync( + ReadErrorDelegate* error_delegate) { + error_delegate_.reset(error_delegate); + connector_->Connect(SerializePrefRegistry(*pref_registry_), + already_connected_types_, + base::Bind(&PersistentPrefStoreClient::OnConnect, + base::Unretained(this))); + pref_registry_ = nullptr; +} + +void PersistentPrefStoreClient::CommitPendingWrite() { + DCHECK(pref_store_); + if (!pending_writes_.empty()) + FlushPendingWrites(); + pref_store_->CommitPendingWrite(); +} + +void PersistentPrefStoreClient::SchedulePendingLossyWrites() { + DCHECK(pref_store_); + return pref_store_->SchedulePendingLossyWrites(); +} + +void PersistentPrefStoreClient::ClearMutableValues() { + DCHECK(pref_store_); + return pref_store_->ClearMutableValues(); +} + +PersistentPrefStoreClient::~PersistentPrefStoreClient() { + if (!pref_store_) + return; + + CommitPendingWrite(); +} + +void PersistentPrefStoreClient::OnConnect( + mojom::PersistentPrefStoreConnectionPtr connection, + std::unordered_map<PrefValueStore::PrefStoreType, + prefs::mojom::PrefStoreConnectionPtr> + other_pref_stores) { + connector_.reset(); + read_error_ = connection->read_error; + read_only_ = connection->read_only; + pref_store_ = std::move(connection->pref_store); + if (error_delegate_ && read_error_ != PREF_READ_ERROR_NONE) + error_delegate_->OnError(read_error_); + error_delegate_.reset(); + + if (connection->pref_store_connection) { + Init(std::move(connection->pref_store_connection->initial_prefs), true, + std::move(connection->pref_store_connection->observer)); + } else { + Init(nullptr, false, nullptr); + } +} + +void PersistentPrefStoreClient::QueueWrite(const std::string& key, + uint32_t flags) { + if (pending_writes_.empty()) { + // Use a weak pointer since a pending write should not prolong the life of + // |this|. Instead, the destruction of |this| will flush any pending writes. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&PersistentPrefStoreClient::FlushPendingWrites, + weak_factory_.GetWeakPtr())); + } + pending_writes_.insert(std::make_pair(key, flags)); +} + +void PersistentPrefStoreClient::FlushPendingWrites() { + std::vector<mojom::PrefUpdatePtr> updates; + for (const auto& pref : pending_writes_) { + const base::Value* value = nullptr; + if (GetValue(pref.first, &value)) { + updates.push_back(mojom::PrefUpdate::New( + pref.first, value->CreateDeepCopy(), pref.second)); + } else { + updates.push_back( + mojom::PrefUpdate::New(pref.first, nullptr, pref.second)); + } + } + pref_store_->SetValues(std::move(updates)); + pending_writes_.clear(); +} + +} // namespace prefs diff --git a/chromium/services/preferences/public/cpp/persistent_pref_store_client.h b/chromium/services/preferences/public/cpp/persistent_pref_store_client.h new file mode 100644 index 00000000000..e317ec9f163 --- /dev/null +++ b/chromium/services/preferences/public/cpp/persistent_pref_store_client.h @@ -0,0 +1,89 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PERSISTENT_PREF_STORE_CLIENT_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_PERSISTENT_PREF_STORE_CLIENT_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "components/prefs/persistent_pref_store.h" +#include "components/prefs/pref_value_store.h" +#include "services/preferences/public/cpp/pref_store_client_mixin.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" + +namespace base { +class Value; +} + +class PrefRegistry; + +namespace prefs { + +// An implementation of PersistentPrefStore backed by a +// mojom::PersistentPrefStore and a mojom::PrefStoreObserver. +class PersistentPrefStoreClient + : public PrefStoreClientMixin<PersistentPrefStore> { + public: + PersistentPrefStoreClient( + mojom::PrefStoreConnectorPtr connector, + scoped_refptr<PrefRegistry> pref_registry, + std::vector<PrefValueStore::PrefStoreType> already_connected_types); + + explicit PersistentPrefStoreClient( + mojom::PersistentPrefStoreConnectionPtr connection); + + // WriteablePrefStore: + void SetValue(const std::string& key, + std::unique_ptr<base::Value> value, + uint32_t flags) override; + void RemoveValue(const std::string& key, uint32_t flags) override; + bool GetMutableValue(const std::string& key, base::Value** result) override; + void ReportValueChanged(const std::string& key, uint32_t flags) override; + void SetValueSilently(const std::string& key, + std::unique_ptr<base::Value> value, + uint32_t flags) override; + + // PersistentPrefStore: + bool ReadOnly() const override; + PrefReadError GetReadError() const override; + PrefReadError ReadPrefs() override; + void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override; + void CommitPendingWrite() override; + void SchedulePendingLossyWrites() override; + void ClearMutableValues() override; + + protected: + // base::RefCounted<PrefStore>: + ~PersistentPrefStoreClient() override; + + private: + void OnConnect(mojom::PersistentPrefStoreConnectionPtr connection, + std::unordered_map<PrefValueStore::PrefStoreType, + prefs::mojom::PrefStoreConnectionPtr> + other_pref_stores); + + void QueueWrite(const std::string& key, uint32_t flags); + void FlushPendingWrites(); + + mojom::PrefStoreConnectorPtr connector_; + scoped_refptr<PrefRegistry> pref_registry_; + bool read_only_ = false; + PrefReadError read_error_ = PersistentPrefStore::PREF_READ_ERROR_NONE; + mojom::PersistentPrefStorePtr pref_store_; + std::map<std::string, uint32_t> pending_writes_; + + std::unique_ptr<ReadErrorDelegate> error_delegate_; + std::vector<PrefValueStore::PrefStoreType> already_connected_types_; + + base::WeakPtrFactory<PersistentPrefStoreClient> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(PersistentPrefStoreClient); +}; + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PERSISTENT_PREF_STORE_CLIENT_H_ diff --git a/chromium/services/preferences/public/cpp/pref_client_store.cc b/chromium/services/preferences/public/cpp/pref_client_store.cc deleted file mode 100644 index 289aaaadd20..00000000000 --- a/chromium/services/preferences/public/cpp/pref_client_store.cc +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2016 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 "services/preferences/public/cpp/pref_client_store.h" - -#include "base/memory/ptr_util.h" -#include "base/values.h" -#include "services/service_manager/public/cpp/connector.h" - -namespace preferences { - -PrefClientStore::PrefClientStore( - prefs::mojom::PreferencesServiceFactoryPtr pref_factory_ptr) - : prefs_binding_(this), - pref_factory_ptr_(std::move(pref_factory_ptr)), - initialized_(false) { - pref_factory_ptr_->Create(prefs_binding_.CreateInterfacePtrAndBind(), - mojo::MakeRequest(&prefs_service_ptr_)); -} - -void PrefClientStore::Subscribe(const std::set<std::string>& keys) { - keys_.insert(keys.begin(), keys.end()); - - std::vector<std::string> pref_array; - std::copy(keys_.begin(), keys_.end(), std::back_inserter(pref_array)); - prefs_service_ptr_->Subscribe(pref_array); -} - -bool PrefClientStore::GetValue(const std::string& key, - const base::Value** value) const { - DCHECK(initialized_); - DCHECK(keys_.find(key) != keys_.end()); - - return ValueMapPrefStore::GetValue(key, value); -} - -void PrefClientStore::SetValue(const std::string& key, - std::unique_ptr<base::Value> value, - uint32_t flags) { - DCHECK(keys_.find(key) != keys_.end()); - - // TODO(jonross): only notify the server if the value changed. - SetValueOnPreferenceManager(key, *value); - ValueMapPrefStore::SetValue(key, std::move(value), flags); -} - -void PrefClientStore::RemoveValue(const std::string& key, uint32_t flags) { - // TODO(jonross): add preference removal to preferences.mojom - NOTIMPLEMENTED(); -} - -bool PrefClientStore::GetMutableValue(const std::string& key, - base::Value** value) { - DCHECK(initialized_); - DCHECK(keys_.find(key) != keys_.end()); - - return ValueMapPrefStore::GetMutableValue(key, value); -} - -void PrefClientStore::ReportValueChanged(const std::string& key, - uint32_t flags) { - DCHECK(keys_.find(key) != keys_.end()); - const base::Value* value = nullptr; - ValueMapPrefStore::GetValue(key, &value); - SetValueOnPreferenceManager(key, *value); - ValueMapPrefStore::ReportValueChanged(key, flags); -} - -void PrefClientStore::SetValueSilently(const std::string& key, - std::unique_ptr<base::Value> value, - uint32_t flags) { - SetValueOnPreferenceManager(key, *value); - ValueMapPrefStore::SetValueSilently(key, std::move(value), flags); -} - -PrefClientStore::~PrefClientStore() {} - -void PrefClientStore::SetValueOnPreferenceManager(const std::string& key, - const base::Value& value) { - if (keys_.find(key) == keys_.end()) - return; - - auto prefs = base::MakeUnique<base::DictionaryValue>(); - prefs->SetWithoutPathExpansion(key, value.CreateDeepCopy()); - prefs_service_ptr_->SetPreferences(std::move(prefs)); -} - -void PrefClientStore::OnPreferencesChanged( - std::unique_ptr<base::DictionaryValue> preferences) { - if (!initialized_) { - initialized_ = true; - NotifyInitializationCompleted(); - } - - for (base::DictionaryValue::Iterator it(*preferences); !it.IsAtEnd(); - it.Advance()) { - if (keys_.find(it.key()) == keys_.end()) - continue; - ValueMapPrefStore::SetValue(it.key(), it.value().CreateDeepCopy(), 0); - } -} - -} // namespace preferences diff --git a/chromium/services/preferences/public/cpp/pref_client_store.h b/chromium/services/preferences/public/cpp/pref_client_store.h deleted file mode 100644 index c2ac032366c..00000000000 --- a/chromium/services/preferences/public/cpp/pref_client_store.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2016 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREFS_CLIENT_STORE_H_ -#define SERVICES_PREFERENCES_PUBLIC_CPP_PREFS_CLIENT_STORE_H_ - -#include <set> - -#include "base/macros.h" -#include "components/prefs/value_map_pref_store.h" -#include "mojo/public/cpp/bindings/binding.h" -#include "services/preferences/public/interfaces/preferences.mojom.h" - -namespace preferences { - -class PrefClientStoreTest; - -// An implementation of PrefStore which uses prefs::mojom::PreferenceManager as -// the backing of the preferences. -// -// PrefClientStore caches the values locally to provide synchronous access. -// The cache is empty until the first notification of OnPreferencesChanged is -// received from the prefs::mojom::PreferenceManager. Upon recieving an initial -// OnPreferencesChanged initialization will be considered as completed, and any -// PrefStore::Observer will be notified of OnInitializationCompleted. -// -// Currently this does not support RemoveValue. -class PrefClientStore : public ValueMapPrefStore, - public prefs::mojom::PreferencesServiceClient { - public: - explicit PrefClientStore( - prefs::mojom::PreferencesServiceFactoryPtr pref_factory_ptr); - - // Adds a set of |keys| which PrefClientStore will handle. Begins listening - // for changes to these from |prefs_service_|. - void Subscribe(const std::set<std::string>& keys); - - // PrefStore: - bool GetValue(const std::string& key, - const base::Value** value) const override; - - // ValueMapPrefStore: - void SetValue(const std::string& key, - std::unique_ptr<base::Value> value, - uint32_t flags) override; - void RemoveValue(const std::string& key, uint32_t flags) override; - bool GetMutableValue(const std::string& key, base::Value** value) override; - void ReportValueChanged(const std::string& key, uint32_t flags) override; - void SetValueSilently(const std::string& key, - std::unique_ptr<base::Value> value, - uint32_t flags) override; - - protected: - // base::RefCounted<PrefStore>: - ~PrefClientStore() override; - - private: - friend class PrefClientStoreTest; - - // Notifies |prefs_service_| of the change in |key| - |value| pair. - void SetValueOnPreferenceManager(const std::string& key, - base::Value const& value); - - // prefs::mojom::PreferencesServiceClient: - void OnPreferencesChanged( - std::unique_ptr<base::DictionaryValue> preferences) override; - - mojo::Binding<prefs::mojom::PreferencesServiceClient> prefs_binding_; - prefs::mojom::PreferencesServiceFactoryPtr pref_factory_ptr_; - prefs::mojom::PreferencesServicePtr prefs_service_ptr_; - - std::set<std::string> keys_; - - // True upon the first OnPreferencesChanged received after Init. - bool initialized_; - - DISALLOW_COPY_AND_ASSIGN(PrefClientStore); -}; - -} // namespace preferences -#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREFS_CLIENT_STORE_H_ diff --git a/chromium/services/preferences/public/cpp/pref_registry_serializer.cc b/chromium/services/preferences/public/cpp/pref_registry_serializer.cc new file mode 100644 index 00000000000..b64e2c787d4 --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_registry_serializer.cc @@ -0,0 +1,21 @@ +// Copyright 2017 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 "services/preferences/public/cpp/pref_registry_serializer.h" + +#include "components/prefs/pref_registry.h" + +namespace prefs { + +mojom::PrefRegistryPtr SerializePrefRegistry(PrefRegistry& pref_registry) { + auto registry = mojom::PrefRegistry::New(); + for (auto& pref : pref_registry) { + registry->registrations[pref.first] = mojom::PrefRegistration::New( + pref.second->CreateDeepCopy(), + pref_registry.GetRegistrationFlags(pref.first)); + } + return registry; +} + +} // namespace prefs diff --git a/chromium/services/preferences/public/cpp/pref_registry_serializer.h b/chromium/services/preferences/public/cpp/pref_registry_serializer.h new file mode 100644 index 00000000000..4e016ce9519 --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_registry_serializer.h @@ -0,0 +1,18 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_REGISTRY_SERIALIZER_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_REGISTRY_SERIALIZER_H_ + +#include "services/preferences/public/interfaces/preferences.mojom.h" + +class PrefRegistry; + +namespace prefs { + +mojom::PrefRegistryPtr SerializePrefRegistry(PrefRegistry& pref_registry); + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_REGISTRY_SERIALIZER_H_ diff --git a/chromium/services/preferences/public/cpp/pref_service_factory.cc b/chromium/services/preferences/public/cpp/pref_service_factory.cc new file mode 100644 index 00000000000..58ff031c081 --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_service_factory.cc @@ -0,0 +1,116 @@ +// Copyright 2017 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 "services/preferences/public/cpp/pref_service_factory.h" + +#include "base/callback_helpers.h" +#include "components/prefs/persistent_pref_store.h" +#include "components/prefs/pref_notifier_impl.h" +#include "components/prefs/pref_registry.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/pref_value_store.h" +#include "services/preferences/public/cpp/persistent_pref_store_client.h" +#include "services/preferences/public/cpp/pref_registry_serializer.h" +#include "services/preferences/public/cpp/pref_store_client.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" +#include "services/service_manager/public/cpp/connector.h" + +namespace prefs { +namespace { + +// Used to implement a "fire and forget" pattern where we call an interface +// method, with an attached error handler, but don't care to hold on to the +// InterfacePtr after. +template <typename Interface> +class RefCountedInterfacePtr + : public base::RefCounted<RefCountedInterfacePtr<Interface>> { + public: + mojo::InterfacePtr<Interface>& get() { return ptr_; } + void reset() { ptr_.reset(); } + + private: + friend class base::RefCounted<RefCountedInterfacePtr<Interface>>; + ~RefCountedInterfacePtr() = default; + + mojo::InterfacePtr<Interface> ptr_; +}; + +void DoNothingHandleReadError(PersistentPrefStore::PrefReadError error) {} + +scoped_refptr<PrefStore> CreatePrefStore( + PrefValueStore::PrefStoreType store_type, + std::unordered_map<PrefValueStore::PrefStoreType, + mojom::PrefStoreConnectionPtr>* connections) { + auto pref_store_it = connections->find(store_type); + if (pref_store_it != connections->end()) { + return make_scoped_refptr( + new PrefStoreClient(std::move(pref_store_it->second))); + } else { + return nullptr; + } +} + +void OnConnect( + scoped_refptr<RefCountedInterfacePtr<mojom::PrefStoreConnector>> + connector_ptr, + scoped_refptr<PrefRegistry> pref_registry, + ConnectCallback callback, + mojom::PersistentPrefStoreConnectionPtr persistent_pref_store_connection, + std::unordered_map<PrefValueStore::PrefStoreType, + mojom::PrefStoreConnectionPtr> connections) { + scoped_refptr<PrefStore> managed_prefs = + CreatePrefStore(PrefValueStore::MANAGED_STORE, &connections); + scoped_refptr<PrefStore> supervised_user_prefs = + CreatePrefStore(PrefValueStore::SUPERVISED_USER_STORE, &connections); + scoped_refptr<PrefStore> extension_prefs = + CreatePrefStore(PrefValueStore::EXTENSION_STORE, &connections); + scoped_refptr<PrefStore> command_line_prefs = + CreatePrefStore(PrefValueStore::COMMAND_LINE_STORE, &connections); + scoped_refptr<PrefStore> recommended_prefs = + CreatePrefStore(PrefValueStore::RECOMMENDED_STORE, &connections); + scoped_refptr<PrefStore> default_prefs = + CreatePrefStore(PrefValueStore::DEFAULT_STORE, &connections); + scoped_refptr<PersistentPrefStore> persistent_pref_store( + new PersistentPrefStoreClient( + std::move(persistent_pref_store_connection))); + PrefNotifierImpl* pref_notifier = new PrefNotifierImpl(); + auto* pref_value_store = new PrefValueStore( + managed_prefs.get(), supervised_user_prefs.get(), extension_prefs.get(), + command_line_prefs.get(), persistent_pref_store.get(), + recommended_prefs.get(), default_prefs.get(), pref_notifier); + callback.Run(base::MakeUnique<::PrefService>( + pref_notifier, pref_value_store, persistent_pref_store.get(), + pref_registry.get(), base::Bind(&DoNothingHandleReadError), true)); + connector_ptr->reset(); +} + +void OnConnectError( + scoped_refptr<RefCountedInterfacePtr<mojom::PrefStoreConnector>> + connector_ptr, + ConnectCallback callback) { + callback.Run(nullptr); + connector_ptr->reset(); +} + +} // namespace + +void ConnectToPrefService( + service_manager::Connector* connector, + scoped_refptr<PrefRegistry> pref_registry, + const std::vector<PrefValueStore::PrefStoreType>& already_connected_types, + ConnectCallback callback, + base::StringPiece service_name) { + auto connector_ptr = make_scoped_refptr( + new RefCountedInterfacePtr<mojom::PrefStoreConnector>()); + connector->BindInterface(service_name.as_string(), &connector_ptr->get()); + connector_ptr->get().set_connection_error_handler(base::Bind( + &OnConnectError, connector_ptr, base::Passed(ConnectCallback{callback}))); + auto serialized_pref_registry = SerializePrefRegistry(*pref_registry); + connector_ptr->get()->Connect( + std::move(serialized_pref_registry), already_connected_types, + base::Bind(&OnConnect, connector_ptr, base::Passed(&pref_registry), + base::Passed(&callback))); +} + +} // namespace prefs diff --git a/chromium/services/preferences/public/cpp/pref_service_factory.h b/chromium/services/preferences/public/cpp/pref_service_factory.h new file mode 100644 index 00000000000..d138ad3583d --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_service_factory.h @@ -0,0 +1,48 @@ +// Copyright 2017 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. + +// This provides a way for any service to connect to the pref service to access +// the application's current preferences. + +// Access is provided through a synchronous interface, exposed using the +// |PrefService| class. + +#ifndef SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_FACTORY_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_FACTORY_H_ + +#include <memory> + +#include "base/callback.h" +#include "components/prefs/pref_value_store.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" + +class PrefRegistry; +class PrefService; + +namespace service_manager { +class Connector; +} + +namespace prefs { + +// Note that |PrefService| might not be fully initialized yet and thus you need +// to call |AddPrefInitObserver| on it before using it. Passed |nullptr| on +// failure. +using ConnectCallback = base::Callback<void(std::unique_ptr<::PrefService>)>; + +// Create a |PrefService| object acting as a client library for the pref +// service, by connecting to the service using |connector|. Connecting is +// asynchronous and |callback| will be called when it has been established. All +// preferences that will be accessed need to be registered in |pref_registry| +// first. +void ConnectToPrefService( + service_manager::Connector* connector, + scoped_refptr<PrefRegistry> pref_registry, + const std::vector<PrefValueStore::PrefStoreType>& already_connected_types, + ConnectCallback callback, + base::StringPiece service_name = mojom::kServiceName); + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_FACTORY_H_ diff --git a/chromium/services/preferences/public/cpp/pref_service_main.cc b/chromium/services/preferences/public/cpp/pref_service_main.cc new file mode 100644 index 00000000000..b4c92c53060 --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_service_main.cc @@ -0,0 +1,20 @@ +// Copyright 2017 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 "services/preferences/public/cpp/pref_service_main.h" + +#include "base/threading/sequenced_worker_pool.h" +#include "services/preferences/pref_store_manager_impl.h" +#include "services/service_manager/public/cpp/service.h" + +namespace prefs { + +std::unique_ptr<service_manager::Service> CreatePrefService( + std::set<PrefValueStore::PrefStoreType> expected_pref_stores, + scoped_refptr<base::SequencedWorkerPool> worker_pool) { + return base::MakeUnique<PrefStoreManagerImpl>(expected_pref_stores, + std::move(worker_pool)); +} + +} // namespace prefs diff --git a/chromium/services/preferences/public/cpp/pref_service_main.h b/chromium/services/preferences/public/cpp/pref_service_main.h new file mode 100644 index 00000000000..6b51efaf3c1 --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_service_main.h @@ -0,0 +1,30 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_MAIN_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_MAIN_H_ + +#include <memory> +#include <set> + +#include "base/memory/ref_counted.h" +#include "components/prefs/pref_value_store.h" + +namespace base { +class SequencedWorkerPool; +} + +namespace service_manager { +class Service; +} + +namespace prefs { + +std::unique_ptr<service_manager::Service> CreatePrefService( + std::set<PrefValueStore::PrefStoreType> expected_pref_stores, + scoped_refptr<base::SequencedWorkerPool> worker_pool); + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_SERVICE_MAIN_H_ diff --git a/chromium/services/preferences/public/cpp/pref_store_adapter.cc b/chromium/services/preferences/public/cpp/pref_store_adapter.cc new file mode 100644 index 00000000000..2cfe85a9167 --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_store_adapter.cc @@ -0,0 +1,40 @@ +// Copyright 2017 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 "services/preferences/public/cpp/pref_store_adapter.h" + +namespace prefs { + +PrefStoreAdapter::PrefStoreAdapter(scoped_refptr<PrefStore> pref_store, + std::unique_ptr<PrefStoreImpl> impl) + : pref_store_(std::move(pref_store)), impl_(std::move(impl)) {} + +void PrefStoreAdapter::AddObserver(PrefStore::Observer* observer) { + pref_store_->AddObserver(observer); +} + +void PrefStoreAdapter::RemoveObserver(PrefStore::Observer* observer) { + pref_store_->RemoveObserver(observer); +} + +bool PrefStoreAdapter::HasObservers() const { + return pref_store_->HasObservers(); +} + +bool PrefStoreAdapter::IsInitializationComplete() const { + return pref_store_->IsInitializationComplete(); +} + +bool PrefStoreAdapter::GetValue(const std::string& key, + const base::Value** result) const { + return pref_store_->GetValue(key, result); +} + +std::unique_ptr<base::DictionaryValue> PrefStoreAdapter::GetValues() const { + return pref_store_->GetValues(); +} + +PrefStoreAdapter::~PrefStoreAdapter() = default; + +} // namespace prefs diff --git a/chromium/services/preferences/public/cpp/pref_store_adapter.h b/chromium/services/preferences/public/cpp/pref_store_adapter.h new file mode 100644 index 00000000000..c734606344e --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_store_adapter.h @@ -0,0 +1,44 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_ADAPTER_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_ADAPTER_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/values.h" +#include "components/prefs/pref_store.h" +#include "services/preferences/public/cpp/pref_store_impl.h" + +namespace prefs { + +// Ties the lifetime of a |PrefStoreImpl| to a |PrefStore|. Otherwise acts as a +// |PrefStore|, forwarding all calls to the wrapped |PrefStore|. +class PrefStoreAdapter : public PrefStore { + public: + PrefStoreAdapter(scoped_refptr<PrefStore> pref_store, + std::unique_ptr<PrefStoreImpl> impl); + + // PrefStore: + void AddObserver(PrefStore::Observer* observer) override; + void RemoveObserver(PrefStore::Observer* observer) override; + bool HasObservers() const override; + bool IsInitializationComplete() const override; + bool GetValue(const std::string& key, + const base::Value** result) const override; + std::unique_ptr<base::DictionaryValue> GetValues() const override; + + private: + ~PrefStoreAdapter() override; + + scoped_refptr<PrefStore> pref_store_; + std::unique_ptr<PrefStoreImpl> impl_; + + DISALLOW_COPY_AND_ASSIGN(PrefStoreAdapter); +}; + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_ADAPTER_H_ diff --git a/chromium/services/preferences/public/cpp/pref_store_client.cc b/chromium/services/preferences/public/cpp/pref_store_client.cc new file mode 100644 index 00000000000..441e8154589 --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_store_client.cc @@ -0,0 +1,18 @@ +// Copyright 2017 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 "services/preferences/public/cpp/pref_store_client.h" + +#include <utility> + +namespace prefs { + +PrefStoreClient::PrefStoreClient(mojom::PrefStoreConnectionPtr connection) { + Init(std::move(connection->initial_prefs), connection->is_initialized, + std::move(connection->observer)); +} + +PrefStoreClient::~PrefStoreClient() = default; + +} // namespace prefs diff --git a/chromium/services/preferences/public/cpp/pref_store_client.h b/chromium/services/preferences/public/cpp/pref_store_client.h new file mode 100644 index 00000000000..b4d82451100 --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_store_client.h @@ -0,0 +1,34 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_H_ + +#include "base/macros.h" +#include "components/prefs/pref_store.h" +#include "services/preferences/public/cpp/pref_store_client_mixin.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" + +namespace prefs { + +// TODO(tibell): Make PrefObserverStore use PrefStoreClient as a base class. + +// An implementation of PrefStore which uses prefs::mojom::PrefStore as +// the backing store of the preferences. +// +// PrefStoreClient provides synchronous access to the preferences stored by the +// backing store by caching them locally. +class PrefStoreClient : public PrefStoreClientMixin<::PrefStore> { + public: + explicit PrefStoreClient(mojom::PrefStoreConnectionPtr connection); + + private: + ~PrefStoreClient() override; + + DISALLOW_COPY_AND_ASSIGN(PrefStoreClient); +}; + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_H_ diff --git a/chromium/services/preferences/public/cpp/pref_store_client_mixin.cc b/chromium/services/preferences/public/cpp/pref_store_client_mixin.cc new file mode 100644 index 00000000000..0387883265f --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_store_client_mixin.cc @@ -0,0 +1,129 @@ +// Copyright 2017 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 "services/preferences/public/cpp/pref_store_client_mixin.h" + +#include <utility> + +#include "base/values.h" +#include "services/preferences/public/cpp/pref_store_client.h" + +namespace prefs { + +template <typename BasePrefStore> +PrefStoreClientMixin<BasePrefStore>::PrefStoreClientMixin() + : observer_binding_(this) {} + +template <typename BasePrefStore> +void PrefStoreClientMixin<BasePrefStore>::AddObserver( + PrefStore::Observer* observer) { + observers_.AddObserver(observer); +} + +template <typename BasePrefStore> +void PrefStoreClientMixin<BasePrefStore>::RemoveObserver( + PrefStore::Observer* observer) { + observers_.RemoveObserver(observer); +} + +template <typename BasePrefStore> +bool PrefStoreClientMixin<BasePrefStore>::HasObservers() const { + return observers_.might_have_observers(); +} + +template <typename BasePrefStore> +bool PrefStoreClientMixin<BasePrefStore>::IsInitializationComplete() const { + return initialized_ && static_cast<bool>(cached_prefs_); +} + +template <typename BasePrefStore> +bool PrefStoreClientMixin<BasePrefStore>::GetValue( + const std::string& key, + const base::Value** result) const { + DCHECK(initialized_); + DCHECK(cached_prefs_); + return cached_prefs_->Get(key, result); +} + +template <typename BasePrefStore> +std::unique_ptr<base::DictionaryValue> +PrefStoreClientMixin<BasePrefStore>::GetValues() const { + DCHECK(initialized_); + DCHECK(cached_prefs_); + return cached_prefs_->CreateDeepCopy(); +} + +template <typename BasePrefStore> +PrefStoreClientMixin<BasePrefStore>::~PrefStoreClientMixin() = default; + +template <typename BasePrefStore> +void PrefStoreClientMixin<BasePrefStore>::Init( + std::unique_ptr<base::DictionaryValue> initial_prefs, + bool initialized, + mojom::PrefStoreObserverRequest observer_request) { + cached_prefs_ = std::move(initial_prefs); + observer_binding_.Bind(std::move(observer_request)); + if (initialized) + OnInitializationCompleted(static_cast<bool>(cached_prefs_)); +} + +template <typename BasePrefStore> +base::DictionaryValue& PrefStoreClientMixin<BasePrefStore>::GetMutableValues() { + DCHECK(cached_prefs_); + return *cached_prefs_; +} + +template <typename BasePrefStore> +void PrefStoreClientMixin<BasePrefStore>::ReportPrefValueChanged( + const std::string& key) { + for (auto& observer : observers_) + observer.OnPrefValueChanged(key); +} + +template <typename BasePrefStore> +void PrefStoreClientMixin<BasePrefStore>::OnPrefsChanged( + std::vector<mojom::PrefUpdatePtr> updates) { + for (const auto& update : updates) + OnPrefChanged(update->key, std::move(update->value)); +} + +template <typename BasePrefStore> +void PrefStoreClientMixin<BasePrefStore>::OnInitializationCompleted( + bool succeeded) { + if (!initialized_) { + initialized_ = true; + for (auto& observer : observers_) + observer.OnInitializationCompleted(succeeded); + } +} + +template <typename BasePrefStore> +void PrefStoreClientMixin<BasePrefStore>::OnPrefChanged( + const std::string& key, + std::unique_ptr<base::Value> value) { + DCHECK(cached_prefs_); + bool changed = false; + if (!value) { // Delete + if (cached_prefs_->RemovePath(key, nullptr)) + changed = true; + } else { + const base::Value* prev; + if (cached_prefs_->Get(key, &prev)) { + if (!prev->Equals(value.get())) { + cached_prefs_->Set(key, std::move(value)); + changed = true; + } + } else { + cached_prefs_->Set(key, std::move(value)); + changed = true; + } + } + if (changed && initialized_) + ReportPrefValueChanged(key); +} + +template class PrefStoreClientMixin<::PrefStore>; +template class PrefStoreClientMixin<::PersistentPrefStore>; + +} // namespace prefs diff --git a/chromium/services/preferences/public/cpp/pref_store_client_mixin.h b/chromium/services/preferences/public/cpp/pref_store_client_mixin.h new file mode 100644 index 00000000000..19cf250c586 --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_store_client_mixin.h @@ -0,0 +1,82 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_MIXIN_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_MIXIN_H_ + +#include <memory> +#include <string> + +#include "base/macros.h" +#include "base/observer_list.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" + +namespace base { +class DictionaryValue; +class Value; +} + +namespace prefs { + +// An mixin implementation of PrefStore which uses prefs::mojom::PrefStore as +// the backing store of the preferences. +// +// PrefStoreClientMixin provides synchronous access to the preferences stored by +// the backing store by caching them locally. +template <typename BasePrefStore> +class PrefStoreClientMixin : public BasePrefStore, + public mojom::PrefStoreObserver { + public: + PrefStoreClientMixin(); + + // BasePrefStore: + void AddObserver(PrefStore::Observer* observer) override; + void RemoveObserver(PrefStore::Observer* observer) override; + bool HasObservers() const override; + bool IsInitializationComplete() const override; + bool GetValue(const std::string& key, + const base::Value** result) const override; + std::unique_ptr<base::DictionaryValue> GetValues() const override; + + protected: + ~PrefStoreClientMixin() override; + + // Initializes |this|. This must be called before any of the other public or + // protected member functions. + void Init(std::unique_ptr<base::DictionaryValue> initial_prefs, + bool initialized, + mojom::PrefStoreObserverRequest observer_request); + + base::DictionaryValue& GetMutableValues(); + void ReportPrefValueChanged(const std::string& key); + + private: + // prefs::mojom::PreferenceObserver: + void OnPrefsChanged(std::vector<mojom::PrefUpdatePtr> updates) override; + void OnInitializationCompleted(bool succeeded) override; + + void OnPrefChanged(const std::string& key, + std::unique_ptr<base::Value> value); + + // Cached preferences. + // If null, indicates that initialization failed. + std::unique_ptr<base::DictionaryValue> cached_prefs_; + + base::ObserverList<PrefStore::Observer, true> observers_; + + // Has the PrefStore we're observing been initialized? + bool initialized_ = false; + + mojo::Binding<mojom::PrefStoreObserver> observer_binding_; + + DISALLOW_COPY_AND_ASSIGN(PrefStoreClientMixin); +}; + +extern template class PrefStoreClientMixin<::PrefStore>; +extern template class PrefStoreClientMixin<::PersistentPrefStore>; + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_CLIENT_MIXIN_H_ diff --git a/chromium/services/preferences/public/cpp/pref_store_impl.cc b/chromium/services/preferences/public/cpp/pref_store_impl.cc new file mode 100644 index 00000000000..c2289621e7e --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_store_impl.cc @@ -0,0 +1,113 @@ +// Copyright 2017 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 "services/preferences/public/cpp/pref_store_impl.h" + +#include <memory> +#include <unordered_set> + +#include "base/stl_util.h" +#include "base/values.h" + +namespace prefs { + +class PrefStoreImpl::Observer { + public: + Observer(mojom::PrefStoreObserverPtr observer, + std::unordered_set<std::string> prefs) + : observer_(std::move(observer)), prefs_(std::move(prefs)) {} + + void OnInitializationCompleted(bool succeeded) { + observer_->OnInitializationCompleted(succeeded); + } + + void OnPrefChanged(const std::string& key, const base::Value& value) const { + if (!base::ContainsKey(prefs_, key)) + return; + + std::vector<mojom::PrefUpdatePtr> updates; + updates.push_back(mojom::PrefUpdate::New(key, value.CreateDeepCopy(), 0)); + observer_->OnPrefsChanged(std::move(updates)); + } + + void OnPrefRemoved(const std::string& key) const { + if (!base::ContainsKey(prefs_, key)) + return; + + std::vector<mojom::PrefUpdatePtr> updates; + updates.push_back(mojom::PrefUpdate::New(key, nullptr, 0)); + observer_->OnPrefsChanged(std::move(updates)); + } + + private: + mojom::PrefStoreObserverPtr observer_; + const std::unordered_set<std::string> prefs_; + + DISALLOW_COPY_AND_ASSIGN(Observer); +}; + +PrefStoreImpl::PrefStoreImpl(scoped_refptr<::PrefStore> pref_store, + mojom::PrefStoreRequest request) + : backing_pref_store_(std::move(pref_store)), + backing_pref_store_initialized_(false), + binding_(this, std::move(request)) { + DCHECK(backing_pref_store_); + if (backing_pref_store_->IsInitializationComplete()) + OnInitializationCompleted(true); + backing_pref_store_->AddObserver(this); +} + +PrefStoreImpl::~PrefStoreImpl() { + backing_pref_store_->RemoveObserver(this); +} + +// static +std::unique_ptr<PrefStoreImpl> PrefStoreImpl::Create( + mojom::PrefStoreRegistry* registry_ptr, + scoped_refptr<::PrefStore> pref_store, + PrefValueStore::PrefStoreType type) { + mojom::PrefStorePtr ptr; + auto impl = base::MakeUnique<PrefStoreImpl>(std::move(pref_store), + mojo::MakeRequest(&ptr)); + registry_ptr->Register(type, std::move(ptr)); + return impl; +} + +void PrefStoreImpl::OnPrefValueChanged(const std::string& key) { + auto dictionary = base::MakeUnique<base::DictionaryValue>(); + const base::Value* value = nullptr; + if (backing_pref_store_->GetValue(key, &value)) { + for (const auto& observer : observers_) + observer->OnPrefChanged(key, *value); + } else { + for (const auto& observer : observers_) + observer->OnPrefRemoved(key); + } +} + +void PrefStoreImpl::OnInitializationCompleted(bool succeeded) { + // Some pref stores call this more than once. We just ignore all calls after + // the first. + if (backing_pref_store_initialized_) + DCHECK(succeeded); + backing_pref_store_initialized_ = succeeded; + for (const auto& observer : observers_) + observer->OnInitializationCompleted(succeeded); +} + +void PrefStoreImpl::AddObserver( + const std::vector<std::string>& prefs_to_observe, + const AddObserverCallback& callback) { + mojom::PrefStoreObserverPtr observer_ptr; + auto request = mojo::MakeRequest(&observer_ptr); + observers_.push_back(base::MakeUnique<Observer>( + std::move(observer_ptr), + std::unordered_set<std::string>(prefs_to_observe.begin(), + prefs_to_observe.end()))); + callback.Run(mojom::PrefStoreConnection::New( + std::move(request), backing_pref_store_->GetValues(), + backing_pref_store_->IsInitializationComplete())); +} + +} // namespace prefs diff --git a/chromium/services/preferences/public/cpp/pref_store_impl.h b/chromium/services/preferences/public/cpp/pref_store_impl.h new file mode 100644 index 00000000000..62146a648c7 --- /dev/null +++ b/chromium/services/preferences/public/cpp/pref_store_impl.h @@ -0,0 +1,63 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_IMPL_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_IMPL_H_ + +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "components/prefs/pref_store.h" +#include "components/prefs/pref_value_store.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" + +namespace prefs { + +// Wraps an actual PrefStore implementation and exposes it as a +// mojom::PrefStore interface. +class PrefStoreImpl : public ::PrefStore::Observer, public mojom::PrefStore { + public: + PrefStoreImpl(scoped_refptr<::PrefStore> pref_store, + mojom::PrefStoreRequest request); + ~PrefStoreImpl() override; + + // The created instance is registered in and owned by the + // |mojom::PrefStoreRegistry|. + static std::unique_ptr<PrefStoreImpl> Create( + mojom::PrefStoreRegistry* registry_ptr, + scoped_refptr<::PrefStore> pref_store, + PrefValueStore::PrefStoreType type); + + private: + class Observer; + + // PrefStore::Observer: + void OnPrefValueChanged(const std::string& key) override; + void OnInitializationCompleted(bool succeeded) override; + + // prefs::mojom::PrefStore: + void AddObserver(const std::vector<std::string>& prefs_to_observe, + const AddObserverCallback& callback) override; + + // The backing store we observer for changes. + scoped_refptr<::PrefStore> backing_pref_store_; + + // Observers we notify when |backing_pref_store_| changes. + std::vector<std::unique_ptr<Observer>> observers_; + + // True when the |backing_pref_store_| is initialized, either because it was + // passed already initialized in the constructor or after + // OnInitializationCompleted was called. + bool backing_pref_store_initialized_; + + mojo::Binding<mojom::PrefStore> binding_; + + DISALLOW_COPY_AND_ASSIGN(PrefStoreImpl); +}; + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_IMPL_H_ diff --git a/chromium/services/preferences/public/cpp/preferences.typemap b/chromium/services/preferences/public/cpp/preferences.typemap new file mode 100644 index 00000000000..d438bedb829 --- /dev/null +++ b/chromium/services/preferences/public/cpp/preferences.typemap @@ -0,0 +1,22 @@ +# Copyright 2017 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. + +mojom = "//services/preferences/public/interfaces/preferences.mojom" +public_headers = [ + "//components/prefs/persistent_pref_store.h", + "//components/prefs/pref_value_store.h", +] +traits_headers = + [ "//services/preferences/public/cpp/preferences_struct_traits.h" ] +sources = [ + "//services/preferences/public/cpp/preferences_struct_traits.cc", +] +deps = [ + "//components/prefs", +] + +type_mappings = [ + "prefs.mojom.PersistentPrefStoreConnection.ReadError=::PersistentPrefStore::PrefReadError", + "prefs.mojom.PrefStoreType=::PrefValueStore::PrefStoreType", +] diff --git a/chromium/services/preferences/public/cpp/preferences_struct_traits.cc b/chromium/services/preferences/public/cpp/preferences_struct_traits.cc new file mode 100644 index 00000000000..4d22db121f9 --- /dev/null +++ b/chromium/services/preferences/public/cpp/preferences_struct_traits.cc @@ -0,0 +1,136 @@ +// Copyright 2017 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 "services/preferences/public/cpp/preferences_struct_traits.h" + +namespace mojo { + +using PrefStoreType = prefs::mojom::PrefStoreType; + +PrefStoreType EnumTraits<PrefStoreType, PrefValueStore::PrefStoreType>::ToMojom( + PrefValueStore::PrefStoreType input) { + switch (input) { + case PrefValueStore::INVALID_STORE: + break; + case PrefValueStore::MANAGED_STORE: + return PrefStoreType::MANAGED; + case PrefValueStore::SUPERVISED_USER_STORE: + return PrefStoreType::SUPERVISED_USER; + case PrefValueStore::EXTENSION_STORE: + return PrefStoreType::EXTENSION; + case PrefValueStore::COMMAND_LINE_STORE: + return PrefStoreType::COMMAND_LINE; + case PrefValueStore::USER_STORE: + return PrefStoreType::USER; + case PrefValueStore::RECOMMENDED_STORE: + return PrefStoreType::RECOMMENDED; + case PrefValueStore::DEFAULT_STORE: + return PrefStoreType::DEFAULT; + } + NOTREACHED(); + return {}; +} + +bool EnumTraits<PrefStoreType, PrefValueStore::PrefStoreType>::FromMojom( + PrefStoreType input, + PrefValueStore::PrefStoreType* output) { + switch (input) { + case PrefStoreType::MANAGED: + *output = PrefValueStore::MANAGED_STORE; + return true; + case PrefStoreType::SUPERVISED_USER: + *output = PrefValueStore::SUPERVISED_USER_STORE; + return true; + case PrefStoreType::EXTENSION: + *output = PrefValueStore::EXTENSION_STORE; + return true; + case PrefStoreType::COMMAND_LINE: + *output = PrefValueStore::COMMAND_LINE_STORE; + return true; + case PrefStoreType::USER: + *output = PrefValueStore::USER_STORE; + return true; + case PrefStoreType::RECOMMENDED: + *output = PrefValueStore::RECOMMENDED_STORE; + return true; + case PrefStoreType::DEFAULT: + *output = PrefValueStore::DEFAULT_STORE; + return true; + } + return false; +} + +using MojomReadError = prefs::mojom::PersistentPrefStoreConnection_ReadError; + +MojomReadError +EnumTraits<MojomReadError, PersistentPrefStore::PrefReadError>::ToMojom( + PersistentPrefStore::PrefReadError input) { + switch (input) { + case PersistentPrefStore::PREF_READ_ERROR_NONE: + return MojomReadError::NONE; + case PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE: + return MojomReadError::JSON_PARSE; + case PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE: + return MojomReadError::JSON_TYPE; + case PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED: + return MojomReadError::ACCESS_DENIED; + case PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER: + return MojomReadError::FILE_OTHER; + case PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED: + return MojomReadError::FILE_LOCKED; + case PersistentPrefStore::PREF_READ_ERROR_NO_FILE: + return MojomReadError::NO_FILE; + case PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT: + return MojomReadError::JSON_REPEAT; + case PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED: + return MojomReadError::FILE_NOT_SPECIFIED; + case PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE: + return MojomReadError::ASYNCHRONOUS_TASK_INCOMPLETE; + case PersistentPrefStore::PREF_READ_ERROR_MAX_ENUM: + break; + } + NOTREACHED(); + return {}; +} + +bool EnumTraits<MojomReadError, PersistentPrefStore::PrefReadError>::FromMojom( + MojomReadError input, + PersistentPrefStore::PrefReadError* output) { + switch (input) { + case MojomReadError::NONE: + *output = PersistentPrefStore::PREF_READ_ERROR_NONE; + return true; + case MojomReadError::JSON_PARSE: + *output = PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE; + return true; + case MojomReadError::JSON_TYPE: + *output = PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE; + return true; + case MojomReadError::ACCESS_DENIED: + *output = PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED; + return true; + case MojomReadError::FILE_OTHER: + *output = PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER; + return true; + case MojomReadError::FILE_LOCKED: + *output = PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED; + return true; + case MojomReadError::NO_FILE: + *output = PersistentPrefStore::PREF_READ_ERROR_NO_FILE; + return true; + case MojomReadError::JSON_REPEAT: + *output = PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT; + return true; + case MojomReadError::FILE_NOT_SPECIFIED: + *output = PersistentPrefStore::PREF_READ_ERROR_FILE_NOT_SPECIFIED; + return true; + case MojomReadError::ASYNCHRONOUS_TASK_INCOMPLETE: + *output = + PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE; + return true; + } + return false; +} + +} // namespace mojo diff --git a/chromium/services/preferences/public/cpp/preferences_struct_traits.h b/chromium/services/preferences/public/cpp/preferences_struct_traits.h new file mode 100644 index 00000000000..194d2748b24 --- /dev/null +++ b/chromium/services/preferences/public/cpp/preferences_struct_traits.h @@ -0,0 +1,38 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_PREFERENCES_STRUCT_TRAITS_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_PREFERENCES_STRUCT_TRAITS_H_ + +#include "components/prefs/persistent_pref_store.h" +#include "components/prefs/pref_value_store.h" +#include "mojo/public/cpp/bindings/enum_traits.h" +#include "services/preferences/public/interfaces/preferences.mojom-shared.h" + +namespace mojo { + +template <> +struct EnumTraits<::prefs::mojom::PrefStoreType, + ::PrefValueStore::PrefStoreType> { + static prefs::mojom::PrefStoreType ToMojom( + PrefValueStore::PrefStoreType input); + + static bool FromMojom(prefs::mojom::PrefStoreType input, + PrefValueStore::PrefStoreType* output); +}; + +template <> +struct EnumTraits<::prefs::mojom::PersistentPrefStoreConnection_ReadError, + ::PersistentPrefStore::PrefReadError> { + static prefs::mojom::PersistentPrefStoreConnection_ReadError ToMojom( + PersistentPrefStore::PrefReadError input); + + static bool FromMojom( + prefs::mojom::PersistentPrefStoreConnection_ReadError input, + PersistentPrefStore::PrefReadError* output); +}; + +} // namespace mojo + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_PREFERENCES_STRUCT_TRAITS_H_ diff --git a/chromium/services/preferences/public/cpp/tests/BUILD.gn b/chromium/services/preferences/public/cpp/tests/BUILD.gn index 1c818cbff4d..bbe7e02055a 100644 --- a/chromium/services/preferences/public/cpp/tests/BUILD.gn +++ b/chromium/services/preferences/public/cpp/tests/BUILD.gn @@ -2,28 +2,20 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//build/config/ui.gni") -import("//testing/test.gni") - -group("tests") { +source_set("tests") { testonly = true - deps = [ - ":preferences_unittests", - ] -} - -test("preferences_unittests") { sources = [ - "pref_client_store_unittest.cc", + "pref_store_client_unittest.cc", + "pref_store_impl_unittest.cc", ] deps = [ "//base", "//base/test:test_support", "//components/prefs:test_support", - "//mojo/edk/test:run_all_unittests", "//mojo/public/cpp/bindings:bindings", "//services/preferences/public/cpp", "//services/preferences/public/interfaces", + "//testing/gmock", "//testing/gtest", ] } diff --git a/chromium/services/preferences/public/cpp/tracked/BUILD.gn b/chromium/services/preferences/public/cpp/tracked/BUILD.gn new file mode 100644 index 00000000000..ddcc14d0e40 --- /dev/null +++ b/chromium/services/preferences/public/cpp/tracked/BUILD.gn @@ -0,0 +1,33 @@ +# Copyright 2014 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. + +static_library("tracked") { + sources = [ + "configuration.cc", + "configuration.h", + "pref_names.cc", + "pref_names.h", + "tracked_preference_histogram_names.cc", + "tracked_preference_histogram_names.h", + ] + public_deps = [ + "//services/preferences/public/interfaces", + ] + deps = [ + "//base", + ] +} + +static_library("test_support") { + testonly = true + sources = [ + "mock_validation_delegate.cc", + "mock_validation_delegate.h", + ] + + deps = [ + "//base", + "//services/preferences/public/interfaces", + ] +} diff --git a/chromium/services/preferences/public/cpp/tracked/OWNERS b/chromium/services/preferences/public/cpp/tracked/OWNERS new file mode 100644 index 00000000000..2845b0f366d --- /dev/null +++ b/chromium/services/preferences/public/cpp/tracked/OWNERS @@ -0,0 +1,2 @@ +bauerb@chromium.org +gab@chromium.org diff --git a/chromium/services/preferences/public/cpp/tracked/configuration.cc b/chromium/services/preferences/public/cpp/tracked/configuration.cc new file mode 100644 index 00000000000..f49adeab640 --- /dev/null +++ b/chromium/services/preferences/public/cpp/tracked/configuration.cc @@ -0,0 +1,25 @@ +// Copyright 2017 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 "services/preferences/public/cpp/tracked/configuration.h" + +namespace prefs { + +mojom::TrackedPreferenceMetadataPtr ConstructTrackedMetadata( + const TrackedPreferenceMetadata& metadata) { + return mojom::TrackedPreferenceMetadata::New( + metadata.reporting_id, metadata.name, metadata.enforcement_level, + metadata.strategy, metadata.value_type); +} + +std::vector<mojom::TrackedPreferenceMetadataPtr> CloneTrackedConfiguration( + const std::vector<mojom::TrackedPreferenceMetadataPtr>& configuration) { + std::vector<mojom::TrackedPreferenceMetadataPtr> result; + for (const auto& metadata : configuration) { + result.push_back(metadata.Clone()); + } + return result; +} + +} // namespace prefs diff --git a/chromium/services/preferences/public/cpp/tracked/configuration.h b/chromium/services/preferences/public/cpp/tracked/configuration.h new file mode 100644 index 00000000000..bbb0ebff4ca --- /dev/null +++ b/chromium/services/preferences/public/cpp/tracked/configuration.h @@ -0,0 +1,37 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_CONFIGURATION_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_CONFIGURATION_H_ + +#include "services/preferences/public/interfaces/preferences_configuration.mojom.h" + +namespace prefs { + +struct TrackedPreferenceMetadata { + size_t reporting_id; + const char* name; + mojom::TrackedPreferenceMetadata::EnforcementLevel enforcement_level; + mojom::TrackedPreferenceMetadata::PrefTrackingStrategy strategy; + mojom::TrackedPreferenceMetadata::ValueType value_type; +}; + +mojom::TrackedPreferenceMetadataPtr ConstructTrackedMetadata( + const TrackedPreferenceMetadata& metadata); + +template <typename ConfigurationContainer> +std::vector<mojom::TrackedPreferenceMetadataPtr> ConstructTrackedConfiguration( + const ConfigurationContainer& configuration) { + std::vector<mojom::TrackedPreferenceMetadataPtr> result; + for (auto metadata : configuration) { + result.push_back(ConstructTrackedMetadata(metadata)); + } + return result; +} + +std::vector<mojom::TrackedPreferenceMetadataPtr> CloneTrackedConfiguration( + const std::vector<mojom::TrackedPreferenceMetadataPtr>& configuration); + +} // namespace prefs +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_CONFIGURATION_H_ diff --git a/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.cc b/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.cc new file mode 100644 index 00000000000..f26ff4f001f --- /dev/null +++ b/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.cc @@ -0,0 +1,84 @@ +// Copyright 2014 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 "services/preferences/public/cpp/tracked/mock_validation_delegate.h" + +MockValidationDelegateRecord::MockValidationDelegateRecord() = default; + +MockValidationDelegateRecord::~MockValidationDelegateRecord() = default; + +size_t MockValidationDelegateRecord::CountValidationsOfState( + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state) + const { + size_t count = 0; + for (size_t i = 0; i < validations_.size(); ++i) { + if (validations_[i].value_state == value_state) + ++count; + } + return count; +} + +size_t MockValidationDelegateRecord::CountExternalValidationsOfState( + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state) + const { + size_t count = 0; + for (size_t i = 0; i < validations_.size(); ++i) { + if (validations_[i].external_validation_value_state == value_state) + ++count; + } + return count; +} + +const MockValidationDelegateRecord::ValidationEvent* +MockValidationDelegateRecord::GetEventForPath( + const std::string& pref_path) const { + for (size_t i = 0; i < validations_.size(); ++i) { + if (validations_[i].pref_path == pref_path) + return &validations_[i]; + } + return NULL; +} + +void MockValidationDelegateRecord::RecordValidation( + const std::string& pref_path, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + external_validation_value_state, + bool is_personal, + prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy strategy) { + validations_.push_back(ValidationEvent(pref_path, value_state, + external_validation_value_state, + is_personal, strategy)); +} + +MockValidationDelegate::MockValidationDelegate( + scoped_refptr<MockValidationDelegateRecord> record) + : record_(std::move(record)) {} + +MockValidationDelegate::~MockValidationDelegate() = default; + +void MockValidationDelegate::OnAtomicPreferenceValidation( + const std::string& pref_path, + std::unique_ptr<base::Value> value, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + external_validation_value_state, + bool is_personal) { + record_->RecordValidation( + pref_path, value_state, external_validation_value_state, is_personal, + prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy::ATOMIC); +} + +void MockValidationDelegate::OnSplitPreferenceValidation( + const std::string& pref_path, + const std::vector<std::string>& invalid_keys, + const std::vector<std::string>& external_validation_invalid_keys, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + external_validation_value_state, + bool is_personal) { + record_->RecordValidation( + pref_path, value_state, external_validation_value_state, is_personal, + prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy::SPLIT); +} diff --git a/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.h b/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.h new file mode 100644 index 00000000000..5538c69301f --- /dev/null +++ b/chromium/services/preferences/public/cpp/tracked/mock_validation_delegate.h @@ -0,0 +1,124 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_MOCK_VALIDATION_DELEGATE_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_MOCK_VALIDATION_DELEGATE_H_ + +#include <stddef.h> + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "services/preferences/public/interfaces/preferences_configuration.mojom.h" +#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h" + +class MockValidationDelegate; + +// A mock tracked preference validation delegate for use by tests. +class MockValidationDelegateRecord + : public base::RefCounted<MockValidationDelegateRecord> { + public: + struct ValidationEvent { + ValidationEvent( + const std::string& path, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState state, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + external_validation_state, + bool is_personal, + prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy + tracking_strategy) + : pref_path(path), + value_state(state), + external_validation_value_state(external_validation_state), + is_personal(is_personal), + strategy(tracking_strategy) {} + + std::string pref_path; + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state; + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + external_validation_value_state; + bool is_personal; + prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy strategy; + }; + + MockValidationDelegateRecord(); + + // Returns the number of recorded validations. + size_t recorded_validations_count() const { return validations_.size(); } + + // Returns the number of validations of a given value state. + size_t CountValidationsOfState( + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state) + const; + + // Returns the number of external validations of a given value state. + size_t CountExternalValidationsOfState( + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state) + const; + + // Returns the event for the preference with a given path. + const ValidationEvent* GetEventForPath(const std::string& pref_path) const; + + private: + friend class MockValidationDelegate; + friend class base::RefCounted<MockValidationDelegateRecord>; + + ~MockValidationDelegateRecord(); + + // Adds a new validation event. + void RecordValidation( + const std::string& pref_path, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + external_validation_value_state, + bool is_personal, + prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy strategy); + + std::vector<ValidationEvent> validations_; + + DISALLOW_COPY_AND_ASSIGN(MockValidationDelegateRecord); +}; + +class MockValidationDelegate + : public prefs::mojom::TrackedPreferenceValidationDelegate { + public: + explicit MockValidationDelegate( + scoped_refptr<MockValidationDelegateRecord> record); + ~MockValidationDelegate() override; + + // TrackedPreferenceValidationDelegate implementation. + void OnAtomicPreferenceValidation( + const std::string& pref_path, + std::unique_ptr<base::Value> value, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + external_validation_value_state, + bool is_personal) override; + void OnSplitPreferenceValidation( + const std::string& pref_path, + const std::vector<std::string>& invalid_keys, + const std::vector<std::string>& external_validation_invalid_keys, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + external_validation_value_state, + bool is_personal) override; + + private: + // Adds a new validation event. + void RecordValidation( + const std::string& pref_path, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state, + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + external_validation_value_state, + bool is_personal, + prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy strategy); + + scoped_refptr<MockValidationDelegateRecord> record_; + + DISALLOW_COPY_AND_ASSIGN(MockValidationDelegate); +}; + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_MOCK_VALIDATION_DELEGATE_H_ diff --git a/chromium/services/preferences/public/cpp/tracked/pref_names.cc b/chromium/services/preferences/public/cpp/tracked/pref_names.cc new file mode 100644 index 00000000000..59446c8a3f5 --- /dev/null +++ b/chromium/services/preferences/public/cpp/tracked/pref_names.cc @@ -0,0 +1,13 @@ +// Copyright 2015 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 "services/preferences/public/cpp/tracked/pref_names.h" + +namespace user_prefs { + +// A timestamp (stored in base::Time::ToInternalValue format) of the last time +// a preference was reset. +const char kPreferenceResetTime[] = "prefs.preference_reset_time"; + +} // namespace user_prefs diff --git a/chromium/services/preferences/public/cpp/tracked/pref_names.h b/chromium/services/preferences/public/cpp/tracked/pref_names.h new file mode 100644 index 00000000000..2f594f5059f --- /dev/null +++ b/chromium/services/preferences/public/cpp/tracked/pref_names.h @@ -0,0 +1,14 @@ +// Copyright 2015 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 SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_PREF_NAMES_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_PREF_NAMES_H_ + +namespace user_prefs { + +extern const char kPreferenceResetTime[]; + +} // namespace user_prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_PREF_NAMES_H_ diff --git a/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.cc b/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.cc new file mode 100644 index 00000000000..7478db56b7f --- /dev/null +++ b/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.cc @@ -0,0 +1,31 @@ +// Copyright 2015 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 "services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h" + +namespace user_prefs { +namespace tracked { + +// Tracked pref histogram names. +const char kTrackedPrefHistogramUnchanged[] = + "Settings.TrackedPreferenceUnchanged"; +const char kTrackedPrefHistogramCleared[] = "Settings.TrackedPreferenceCleared"; +const char kTrackedPrefHistogramMigratedLegacyDeviceId[] = + "Settings.TrackedPreferenceMigratedLegacyDeviceId"; +const char kTrackedPrefHistogramChanged[] = "Settings.TrackedPreferenceChanged"; +const char kTrackedPrefHistogramInitialized[] = + "Settings.TrackedPreferenceInitialized"; +const char kTrackedPrefHistogramTrustedInitialized[] = + "Settings.TrackedPreferenceTrustedInitialized"; +const char kTrackedPrefHistogramNullInitialized[] = + "Settings.TrackedPreferenceNullInitialized"; +const char kTrackedPrefHistogramWantedReset[] = + "Settings.TrackedPreferenceWantedReset"; +const char kTrackedPrefHistogramReset[] = "Settings.TrackedPreferenceReset"; +const char kTrackedSplitPrefHistogramChanged[] = + "Settings.TrackedSplitPreferenceChanged."; +const char kTrackedPrefRegistryValidationSuffix[] = "FromRegistry"; + +} // namespace tracked +} // namespace user_prefs diff --git a/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h b/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h new file mode 100644 index 00000000000..ec0a219db58 --- /dev/null +++ b/chromium/services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h @@ -0,0 +1,26 @@ +// Copyright 2015 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 SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_TRACKED_PREFERENCE_HISTOGRAM_NAMES_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_TRACKED_PREFERENCE_HISTOGRAM_NAMES_H_ + +namespace user_prefs { +namespace tracked { + +extern const char kTrackedPrefHistogramUnchanged[]; +extern const char kTrackedPrefHistogramCleared[]; +extern const char kTrackedPrefHistogramMigratedLegacyDeviceId[]; +extern const char kTrackedPrefHistogramChanged[]; +extern const char kTrackedPrefHistogramInitialized[]; +extern const char kTrackedPrefHistogramTrustedInitialized[]; +extern const char kTrackedPrefHistogramNullInitialized[]; +extern const char kTrackedPrefHistogramWantedReset[]; +extern const char kTrackedPrefHistogramReset[]; +extern const char kTrackedSplitPrefHistogramChanged[]; +extern const char kTrackedPrefRegistryValidationSuffix[]; + +} // namespace tracked +} // namespace user_prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_TRACKED_TRACKED_PREFERENCE_HISTOGRAM_NAMES_H_ diff --git a/chromium/services/preferences/public/cpp/typemaps.gni b/chromium/services/preferences/public/cpp/typemaps.gni new file mode 100644 index 00000000000..2238fb6e9e7 --- /dev/null +++ b/chromium/services/preferences/public/cpp/typemaps.gni @@ -0,0 +1,5 @@ +# Copyright 2017 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. + +typemaps = [ "//services/preferences/public/cpp/preferences.typemap" ] diff --git a/chromium/services/preferences/public/interfaces/BUILD.gn b/chromium/services/preferences/public/interfaces/BUILD.gn index c7e9091f046..8bc3749fe60 100644 --- a/chromium/services/preferences/public/interfaces/BUILD.gn +++ b/chromium/services/preferences/public/interfaces/BUILD.gn @@ -7,6 +7,8 @@ import("//mojo/public/tools/bindings/mojom.gni") mojom("interfaces") { sources = [ "preferences.mojom", + "preferences_configuration.mojom", + "tracked_preference_validation_delegate.mojom", ] public_deps = [ "//mojo/common:common_custom_types", diff --git a/chromium/services/preferences/public/interfaces/preferences.mojom b/chromium/services/preferences/public/interfaces/preferences.mojom index 1978e4b671f..47a79dc5386 100644 --- a/chromium/services/preferences/public/interfaces/preferences.mojom +++ b/chromium/services/preferences/public/interfaces/preferences.mojom @@ -5,25 +5,137 @@ module prefs.mojom; import "mojo/common/values.mojom"; +import "services/preferences/public/interfaces/preferences_configuration.mojom"; const string kServiceName = "preferences"; +const string kForwarderServiceName = "preferences_forwarder"; -// Used for the creation of a PreferencesService and to ensure that the -// PreferencesServiceClient is bound at creation time. -interface PreferencesServiceFactory { - // Creates a PreferencesService bound to the provided |observer|. - Create(PreferencesServiceClient observer, PreferencesService& service); +// The know pref store types. +// +// Should be kept in sync with PrefValueStore::PrefStoreType. +enum PrefStoreType { + MANAGED, + SUPERVISED_USER, + EXTENSION, + COMMAND_LINE, + USER, + RECOMMENDED, + DEFAULT, }; -// Used to subscribe to preference changes within PreferenceManager. After -// requesting to observe, the current values for all requested keys are sent. -interface PreferencesServiceClient { - OnPreferencesChanged(mojo.common.mojom.DictionaryValue preferences); +// Allows observing changes to prefs stored in a |PrefStore|. +interface PrefStoreObserver { + // Preferences have been changed. + OnPrefsChanged(array<PrefUpdate> updates); + + // The PrefStore has been initialized (asynchronously). + OnInitializationCompleted(bool succeeded); +}; + +// Captures the connections to a PrefStore by supplying the initial state of the +// store and a handle to receive notifications on. +struct PrefStoreConnection { + // Handle to receive updates on. + PrefStoreObserver& observer; + + // Initial values of the PrefStore. These will not be communicated through + // OnPrefChanged. + mojo.common.mojom.DictionaryValue initial_prefs; + + // Is the PrefStore initialized? If not it should not be used before + // OnInitializationCompleted has been called. + bool is_initialized; +}; + +struct PersistentPrefStoreConnection { + enum ReadError { + NONE = 0, + JSON_PARSE = 1, + JSON_TYPE = 2, + ACCESS_DENIED = 3, + FILE_OTHER = 4, + FILE_LOCKED = 5, + NO_FILE = 6, + JSON_REPEAT = 7, + // OTHER = 8, // Deprecated. + FILE_NOT_SPECIFIED = 9, + ASYNCHRONOUS_TASK_INCOMPLETE = 10, + }; + + PrefStoreConnection? pref_store_connection; + PersistentPrefStore? pref_store; + ReadError read_error; + bool read_only; +}; + +// Manages actual read of preference data. Accepts observers who subscribe to +// preferences, notifying them of changes. +interface PrefStore { + // Add an observer of changes to prefs contained in |prefs_to_observe|. This + // current values of all prefs will not be communicated through a call to + // |observer| but instead be returned in |initial_prefs|. + AddObserver(array<string> prefs_to_observe) => ( + PrefStoreConnection connection); +}; + +// Manages a registry of all pref stores. Registered pref stores can be +// connected to through the |PrefStoreConnector| interface. +interface PrefStoreRegistry { + // Register a pref store. + Register(PrefStoreType type, PrefStore pref_store); +}; + +// Allows connections to pref stores registered with |PrefStoreRegistry|. +interface PrefStoreConnector { + // Connect to all registered pref stores, retrieving the current values of all + // prefs in each store and an |observer| interfaces through which updates can + // be received. The client asserts that it is already connected to the + // |already_connected_types| pref stores through some other means, so the + // Connect call will not connect to those. + [Sync] + Connect(PrefRegistry pref_registry, + array<PrefStoreType> already_connected_types) => + (PersistentPrefStoreConnection connection, + map<PrefStoreType, PrefStoreConnection> connections); +}; + +// An update to a pref. +struct PrefUpdate { + // The key of the pref being updated. + string key; + // The new value; a null |value| indicates a delete. + mojo.common.mojom.Value? value; + //|flags| is a bitmask of WritablePrefStore::PrefWriteFlags. + uint32 flags; +}; + +// An interface providing mutation access to a PersistentPrefStore. +interface PersistentPrefStore { + // Sets the values for prefs. + SetValues(array<PrefUpdate> updates); + + // These mirror the C++ PersistentPrefStore methods. + CommitPendingWrite(); + SchedulePendingLossyWrites(); + ClearMutableValues(); +}; + +// A registry of all prefs registered by a single client. +struct PrefRegistry { + map<string, PrefRegistration> registrations; +}; + +struct PrefRegistration { + mojo.common.mojom.Value default_value; + + // A bitfield of flags. Flag values are defined in + // PrefRegistry::PrefRegistrationFlags and + // PrefRegistrySyncable::PrefRegistrationFlags. + uint32 flags; }; -// Manages actual read/write of preference data. Accepts observers who subscribe -// to preferences, notifying them of changes. -interface PreferencesService { - SetPreferences(mojo.common.mojom.DictionaryValue preferences); - Subscribe(array<string> preferences); +interface PrefServiceControl { + // Initializes the pref service. This must be called before the service can + // be used. + Init(PersistentPrefStoreConfiguration configuration); }; diff --git a/chromium/services/preferences/public/interfaces/preferences_configuration.mojom b/chromium/services/preferences/public/interfaces/preferences_configuration.mojom new file mode 100644 index 00000000000..76f7785ae06 --- /dev/null +++ b/chromium/services/preferences/public/interfaces/preferences_configuration.mojom @@ -0,0 +1,62 @@ +// Copyright 2017 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. + +module prefs.mojom; + +import "mojo/common/file_path.mojom"; +import "mojo/common/string16.mojom"; +import "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom"; + +union PersistentPrefStoreConfiguration { + SimplePersistentPrefStoreConfiguration simple_configuration; + TrackedPersistentPrefStoreConfiguration tracked_configuration; +}; + +struct SimplePersistentPrefStoreConfiguration { + mojo.common.mojom.FilePath pref_filename; +}; + +// These parameters are passed to prefs::CreateTrackedPersistentPrefStore() in +// services/preferences/persistent_pref_store_factory.cc. +struct TrackedPersistentPrefStoreConfiguration { + mojo.common.mojom.FilePath unprotected_pref_filename; + mojo.common.mojom.FilePath protected_pref_filename; + array<TrackedPreferenceMetadata> tracking_configuration; + uint64 reporting_ids_count; + string seed; + string legacy_device_id; + string registry_seed; + mojo.common.mojom.String16 registry_path; + TrackedPreferenceValidationDelegate? validation_delegate; + ResetOnLoadObserver? reset_on_load_observer; +}; + +struct TrackedPreferenceMetadata { + enum EnforcementLevel { NO_ENFORCEMENT, ENFORCE_ON_LOAD }; + + enum PrefTrackingStrategy { + // Atomic preferences are tracked as a whole. + ATOMIC, + // Split preferences are dictionaries for which each top-level entry is + // tracked independently. Note: preferences using this strategy must be kept + // in sync with TrackedSplitPreferences in histograms.xml. + SPLIT, + }; + + enum ValueType { + IMPERSONAL, + // The preference value may contain personal information. + PERSONAL, + }; + + uint64 reporting_id; + string name; + EnforcementLevel enforcement_level; + PrefTrackingStrategy strategy; + ValueType value_type; +}; + +interface ResetOnLoadObserver { + OnResetOnLoad(); +}; diff --git a/chromium/services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom b/chromium/services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom new file mode 100644 index 00000000000..370f95f75d9 --- /dev/null +++ b/chromium/services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom @@ -0,0 +1,56 @@ +// Copyright 2017 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. + +module prefs.mojom; + +import "mojo/common/values.mojom"; + +// A TrackedPreferenceValidationDelegate is notified of the results of each +// tracked preference validation event. +interface TrackedPreferenceValidationDelegate { + enum ValueState { + // The preference value corresponds to its stored hash. + UNCHANGED, + // The preference has been cleared since the last hash. + CLEARED, + // The preference value corresponds to its stored hash, but the hash was + // calculated using a deprecated hash algorithm which is just as safe as + // the current one. + SECURE_LEGACY, + // The preference value has been changed since the last hash. + CHANGED, + // No stored hash exists for the preference value. + UNTRUSTED_UNKNOWN_VALUE, + // No stored hash exists for the preference value, but the current set of + // hashes stored is trusted and thus this value can safely be seeded. This + // happens when all hashes are already properly seeded and a newly + // tracked value needs to be seeded). + TRUSTED_UNKNOWN_VALUE, + // Null values are inherently trusted. + TRUSTED_NULL_VALUE, + // This transaction's store type is not supported. + UNSUPPORTED, + }; + + // Notifies observes of the result (|value_state|) of checking the atomic + // |value| (which may be null) at |pref_path|. |is_personal| indicates whether + // or not the value may contain personal information. + OnAtomicPreferenceValidation( + string pref_path, + mojo.common.mojom.Value? value, + ValueState value_state, + ValueState external_validation_value_state, + bool is_personal); + + // Notifies observes of the result (|value_state|) of checking the split + // value at |pref_path|. |is_personal| indicates whether or not the value may + // contain personal information. + OnSplitPreferenceValidation( + string pref_path, + array<string> invalid_keys, + array<string> external_validation_invalid_keys, + ValueState value_state, + ValueState external_validation_value_state, + bool is_personal); +}; diff --git a/chromium/services/preferences/tracked/BUILD.gn b/chromium/services/preferences/tracked/BUILD.gn new file mode 100644 index 00000000000..16be8a2d696 --- /dev/null +++ b/chromium/services/preferences/tracked/BUILD.gn @@ -0,0 +1,86 @@ +# Copyright 2014 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. + +source_set("tracked") { + sources = [ + "device_id.h", + "device_id_mac.cc", + "device_id_stub.cc", + "device_id_win.cc", + "dictionary_hash_store_contents.cc", + "dictionary_hash_store_contents.h", + "hash_store_contents.h", + "interceptable_pref_filter.cc", + "interceptable_pref_filter.h", + "pref_hash_calculator.cc", + "pref_hash_calculator.h", + "pref_hash_filter.cc", + "pref_hash_filter.h", + "pref_hash_store.h", + "pref_hash_store_impl.cc", + "pref_hash_store_impl.h", + "pref_hash_store_transaction.h", + "registry_hash_store_contents_win.cc", + "registry_hash_store_contents_win.h", + "segregated_pref_store.cc", + "segregated_pref_store.h", + "tracked_atomic_preference.cc", + "tracked_atomic_preference.h", + "tracked_persistent_pref_store_factory.cc", + "tracked_persistent_pref_store_factory.h", + "tracked_preference.h", + "tracked_preference_helper.cc", + "tracked_preference_helper.h", + "tracked_preferences_migration.cc", + "tracked_preferences_migration.h", + "tracked_split_preference.cc", + "tracked_split_preference.h", + ] + + if (is_win || is_mac) { + sources -= [ "device_id_stub.cc" ] + } + + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + configs += [ "//build/config/compiler:no_size_t_to_int_warning" ] + + public_deps = [ + "//services/preferences/public/cpp/tracked", + "//services/preferences/public/interfaces", + ] + + deps = [ + "//base", + "//components/pref_registry", + "//components/prefs", + "//crypto", + ] + + if (is_mac) { + libs = [ "IOKit.framework" ] + } +} + +source_set("unit_tests") { + testonly = true + sources = [ + "device_id_unittest.cc", + "interceptable_pref_filter_unittest.cc", + "pref_hash_calculator_unittest.cc", + "pref_hash_filter_unittest.cc", + "pref_hash_store_impl_unittest.cc", + "registry_hash_store_contents_win_unittest.cc", + "segregated_pref_store_unittest.cc", + "tracked_preferences_migration_unittest.cc", + ] + + deps = [ + ":tracked", + "//base", + "//base/test:test_support", + "//components/prefs:test_support", + "//services/preferences/public/cpp/tracked:test_support", + "//testing/gtest", + ] +} diff --git a/chromium/services/preferences/tracked/DEPS b/chromium/services/preferences/tracked/DEPS new file mode 100644 index 00000000000..6ccb05e9fab --- /dev/null +++ b/chromium/services/preferences/tracked/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+components/pref_registry", + "+crypto/hmac.h", +] diff --git a/chromium/services/preferences/tracked/OWNERS b/chromium/services/preferences/tracked/OWNERS new file mode 100644 index 00000000000..2845b0f366d --- /dev/null +++ b/chromium/services/preferences/tracked/OWNERS @@ -0,0 +1,2 @@ +bauerb@chromium.org +gab@chromium.org diff --git a/chromium/services/preferences/tracked/device_id.h b/chromium/services/preferences/tracked/device_id.h new file mode 100644 index 00000000000..04355628c7d --- /dev/null +++ b/chromium/services/preferences/tracked/device_id.h @@ -0,0 +1,23 @@ +// Copyright 2015 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 SERVICES_PREFERENCES_TRACKED_DEVICE_ID_H_ +#define SERVICES_PREFERENCES_TRACKED_DEVICE_ID_H_ + +#include <string> + +enum class MachineIdStatus { + SUCCESS = 0, + FAILURE, // Returned if attempt to obtain a machine-specific ID fails. + NOT_IMPLEMENTED // Returned if the method for obtaining a machine-specific ID + // is not implemented for the system. +}; + +// Populates |machine_id| with a deterministic ID for this machine. |machine_id| +// must not be null. Returns |FAILURE| if a machine ID cannot be obtained or +// |NOT_IMPLEMENTED| on systems for which this feature is not supported (in both +// cases |machine_id| is left untouched). +MachineIdStatus GetDeterministicMachineSpecificId(std::string* machine_id); + +#endif // SERVICES_PREFERENCES_TRACKED_DEVICE_ID_H_ diff --git a/chromium/services/preferences/tracked/device_id_mac.cc b/chromium/services/preferences/tracked/device_id_mac.cc new file mode 100644 index 00000000000..38dfb340c7e --- /dev/null +++ b/chromium/services/preferences/tracked/device_id_mac.cc @@ -0,0 +1,32 @@ +// Copyright 2015 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 "services/preferences/tracked/device_id.h" + +#include <IOKit/IOKitLib.h> + +#include "base/mac/foundation_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_ioobject.h" +#include "base/strings/sys_string_conversions.h" + +MachineIdStatus GetDeterministicMachineSpecificId(std::string* machine_id) { + base::mac::ScopedIOObject<io_service_t> platform_expert( + IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching("IOPlatformExpertDevice"))); + if (!platform_expert.get()) + return MachineIdStatus::FAILURE; + + base::ScopedCFTypeRef<CFTypeRef> uuid(IORegistryEntryCreateCFProperty( + platform_expert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0)); + if (!uuid.get()) + return MachineIdStatus::FAILURE; + + CFStringRef uuid_string = base::mac::CFCast<CFStringRef>(uuid); + if (!uuid_string) + return MachineIdStatus::FAILURE; + + *machine_id = base::SysCFStringRefToUTF8(uuid_string); + return MachineIdStatus::SUCCESS; +} diff --git a/chromium/services/preferences/tracked/device_id_stub.cc b/chromium/services/preferences/tracked/device_id_stub.cc new file mode 100644 index 00000000000..9db4d503863 --- /dev/null +++ b/chromium/services/preferences/tracked/device_id_stub.cc @@ -0,0 +1,11 @@ +// Copyright 2015 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 "base/logging.h" +#include "services/preferences/tracked/device_id.h" + +MachineIdStatus GetDeterministicMachineSpecificId(std::string* machine_id) { + DCHECK(machine_id); + return MachineIdStatus::NOT_IMPLEMENTED; +} diff --git a/chromium/services/preferences/tracked/device_id_unittest.cc b/chromium/services/preferences/tracked/device_id_unittest.cc new file mode 100644 index 00000000000..297cf42e618 --- /dev/null +++ b/chromium/services/preferences/tracked/device_id_unittest.cc @@ -0,0 +1,35 @@ +// Copyright 2015 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 "services/preferences/tracked/device_id.h" + +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(GetDeterministicMachineSpecificIdTest, IsDeterministic) { + std::string first_machine_id; + std::string second_machine_id; + + const MachineIdStatus kExpectedStatus = +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + MachineIdStatus::SUCCESS; +#else + MachineIdStatus::NOT_IMPLEMENTED; +#endif + + ASSERT_EQ(kExpectedStatus, + GetDeterministicMachineSpecificId(&first_machine_id)); + ASSERT_EQ(kExpectedStatus, + GetDeterministicMachineSpecificId(&second_machine_id)); + + // The reason for using |EXPECT_TRUE| with one argument instead of |EXPECT_EQ| + // with two arguments is a compiler bug in gcc that results in a "converting + // 'false' to pointer type" error when the first argument to |EXPECT_EQ| is a + // compile-time const false value. See also the following bug reports: + // https://code.google.com/p/googletest/issues/detail?id=322 + // https://code.google.com/p/googletest/issues/detail?id=458 + EXPECT_TRUE((kExpectedStatus == MachineIdStatus::SUCCESS) == + !first_machine_id.empty()); + EXPECT_EQ(first_machine_id, second_machine_id); +} diff --git a/chromium/services/preferences/tracked/device_id_win.cc b/chromium/services/preferences/tracked/device_id_win.cc new file mode 100644 index 00000000000..9dbd110674a --- /dev/null +++ b/chromium/services/preferences/tracked/device_id_win.cc @@ -0,0 +1,73 @@ +// Copyright 2015 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 "services/preferences/tracked/device_id.h" + +#include <windows.h> + +#include <sddl.h> // For ConvertSidToStringSidA. + +#include <memory> + +#include "base/logging.h" +#include "base/macros.h" + +MachineIdStatus GetDeterministicMachineSpecificId(std::string* machine_id) { + DCHECK(machine_id); + + wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1] = {}; + DWORD computer_name_size = arraysize(computer_name); + + if (!::GetComputerNameW(computer_name, &computer_name_size)) + return MachineIdStatus::FAILURE; + + DWORD sid_size = SECURITY_MAX_SID_SIZE; + char sid_buffer[SECURITY_MAX_SID_SIZE]; + SID* sid = reinterpret_cast<SID*>(sid_buffer); + DWORD domain_size = 128; // Will expand below if needed. + std::unique_ptr<wchar_t[]> domain_buffer(new wchar_t[domain_size]); + SID_NAME_USE sid_name_use; + + // Although the fifth argument to |LookupAccountNameW()|, + // |ReferencedDomainName|, is annotated as |_Out_opt_|, if a null + // value is passed in, zero is returned and |GetLastError()| will + // return |ERROR_INSUFFICIENT_BUFFER| (assuming that nothing else went + // wrong). In order to ensure that the call to |LookupAccountNameW()| + // has succeeded, it is necessary to include the following logic and + // obtain the domain name. + if (!::LookupAccountNameW(nullptr, computer_name, sid, &sid_size, + domain_buffer.get(), &domain_size, &sid_name_use)) { + // If the initial size of |domain_buffer| was too small, the + // required size is now found in |domain_size|. Resize and try + // again. + if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return MachineIdStatus::FAILURE; + + domain_buffer.reset(new wchar_t[domain_size]); + if (!::LookupAccountNameW(nullptr, computer_name, sid, &sid_size, + domain_buffer.get(), &domain_size, + &sid_name_use)) { + return MachineIdStatus::FAILURE; + } + } + + // Ensure that the correct type of SID was obtained. The + // |LookupAccountNameW()| function seems to always return + // |SidTypeDomain| instead of |SidTypeComputer| when the computer name + // is passed in as its second argument and therefore both enum values + // will be considered acceptable. If the computer name and user name + // coincide, |LookupAccountNameW()| seems to always return the machine + // SID and set the returned enum to |SidTypeDomain|. + DCHECK(sid_name_use == SID_NAME_USE::SidTypeComputer || + sid_name_use == SID_NAME_USE::SidTypeDomain); + + char* sid_string = nullptr; + if (!::ConvertSidToStringSidA(sid, &sid_string)) + return MachineIdStatus::FAILURE; + + *machine_id = sid_string; + ::LocalFree(sid_string); + + return MachineIdStatus::SUCCESS; +} diff --git a/chromium/services/preferences/tracked/dictionary_hash_store_contents.cc b/chromium/services/preferences/tracked/dictionary_hash_store_contents.cc new file mode 100644 index 00000000000..3cef69b05a1 --- /dev/null +++ b/chromium/services/preferences/tracked/dictionary_hash_store_contents.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2014 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 "services/preferences/tracked/dictionary_hash_store_contents.h" + +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/values.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/prefs/persistent_pref_store.h" + +namespace { +const char kPreferenceMACs[] = "protection.macs"; +const char kSuperMACPref[] = "protection.super_mac"; +} + +DictionaryHashStoreContents::DictionaryHashStoreContents( + base::DictionaryValue* storage) + : storage_(storage) {} + +// static +void DictionaryHashStoreContents::RegisterProfilePrefs( + user_prefs::PrefRegistrySyncable* registry) { + registry->RegisterDictionaryPref(kPreferenceMACs); + registry->RegisterStringPref(kSuperMACPref, std::string()); +} + +bool DictionaryHashStoreContents::IsCopyable() const { + return false; +} + +std::unique_ptr<HashStoreContents> DictionaryHashStoreContents::MakeCopy() + const { + NOTREACHED() << "DictionaryHashStoreContents does not support MakeCopy"; + return nullptr; +} + +base::StringPiece DictionaryHashStoreContents::GetUMASuffix() const { + // To stay consistent with existing reported data, do not append a suffix + // when reporting UMA stats for this content. + return base::StringPiece(); +} + +void DictionaryHashStoreContents::Reset() { + storage_->Remove(kPreferenceMACs, NULL); +} + +bool DictionaryHashStoreContents::GetMac(const std::string& path, + std::string* out_value) { + const base::DictionaryValue* macs_dict = GetContents(); + if (macs_dict) + return macs_dict->GetString(path, out_value); + + return false; +} + +bool DictionaryHashStoreContents::GetSplitMacs( + const std::string& path, + std::map<std::string, std::string>* split_macs) { + DCHECK(split_macs); + DCHECK(split_macs->empty()); + + const base::DictionaryValue* macs_dict = GetContents(); + const base::DictionaryValue* split_macs_dict = NULL; + if (!macs_dict || !macs_dict->GetDictionary(path, &split_macs_dict)) + return false; + for (base::DictionaryValue::Iterator it(*split_macs_dict); !it.IsAtEnd(); + it.Advance()) { + std::string mac_string; + if (!it.value().GetAsString(&mac_string)) { + NOTREACHED(); + continue; + } + split_macs->insert(make_pair(it.key(), mac_string)); + } + return true; +} + +void DictionaryHashStoreContents::SetMac(const std::string& path, + const std::string& value) { + base::DictionaryValue* macs_dict = GetMutableContents(true); + macs_dict->SetString(path, value); +} + +void DictionaryHashStoreContents::SetSplitMac(const std::string& path, + const std::string& split_path, + const std::string& value) { + // DictionaryValue handles a '.' delimiter. + SetMac(path + '.' + split_path, value); +} + +void DictionaryHashStoreContents::ImportEntry(const std::string& path, + const base::Value* in_value) { + base::DictionaryValue* macs_dict = GetMutableContents(true); + macs_dict->Set(path, in_value->DeepCopy()); +} + +bool DictionaryHashStoreContents::RemoveEntry(const std::string& path) { + base::DictionaryValue* macs_dict = GetMutableContents(false); + if (macs_dict) + return macs_dict->RemovePath(path, NULL); + + return false; +} + +std::string DictionaryHashStoreContents::GetSuperMac() const { + std::string super_mac_string; + storage_->GetString(kSuperMACPref, &super_mac_string); + return super_mac_string; +} + +void DictionaryHashStoreContents::SetSuperMac(const std::string& super_mac) { + storage_->SetString(kSuperMACPref, super_mac); +} + +const base::DictionaryValue* DictionaryHashStoreContents::GetContents() const { + const base::DictionaryValue* macs_dict = NULL; + storage_->GetDictionary(kPreferenceMACs, &macs_dict); + return macs_dict; +} + +base::DictionaryValue* DictionaryHashStoreContents::GetMutableContents( + bool create_if_null) { + base::DictionaryValue* macs_dict = NULL; + storage_->GetDictionary(kPreferenceMACs, &macs_dict); + if (!macs_dict && create_if_null) { + macs_dict = new base::DictionaryValue; + storage_->Set(kPreferenceMACs, macs_dict); + } + return macs_dict; +} diff --git a/chromium/services/preferences/tracked/dictionary_hash_store_contents.h b/chromium/services/preferences/tracked/dictionary_hash_store_contents.h new file mode 100644 index 00000000000..16fe628ad81 --- /dev/null +++ b/chromium/services/preferences/tracked/dictionary_hash_store_contents.h @@ -0,0 +1,62 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_TRACKED_DICTIONARY_HASH_STORE_CONTENTS_H_ +#define SERVICES_PREFERENCES_TRACKED_DICTIONARY_HASH_STORE_CONTENTS_H_ + +#include "base/macros.h" +#include "services/preferences/tracked/hash_store_contents.h" + +namespace base { +class DictionaryValue; +class Value; +} // namespace base + +namespace user_prefs { +class PrefRegistrySyncable; +} // namespace user_prefs + +// Implements HashStoreContents by storing MACs in a DictionaryValue. The +// DictionaryValue is presumed to be the contents of a PrefStore. +// RegisterProfilePrefs() may be used to register all of the preferences used by +// this object. +class DictionaryHashStoreContents : public HashStoreContents { + public: + // Constructs a DictionaryHashStoreContents that reads from and writes to + // |storage|. + explicit DictionaryHashStoreContents(base::DictionaryValue* storage); + + // Registers required preferences. + static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); + + // HashStoreContents implementation + bool IsCopyable() const override; + std::unique_ptr<HashStoreContents> MakeCopy() const override; + base::StringPiece GetUMASuffix() const override; + void Reset() override; + bool GetMac(const std::string& path, std::string* out_value) override; + bool GetSplitMacs(const std::string& path, + std::map<std::string, std::string>* split_macs) override; + void SetMac(const std::string& path, const std::string& value) override; + void SetSplitMac(const std::string& path, + const std::string& split_path, + const std::string& value) override; + void ImportEntry(const std::string& path, + const base::Value* in_value) override; + bool RemoveEntry(const std::string& path) override; + const base::DictionaryValue* GetContents() const override; + std::string GetSuperMac() const override; + void SetSuperMac(const std::string& super_mac) override; + + private: + base::DictionaryValue* storage_; + + // Helper function to get a mutable version of the macs from |storage_|, + // creating it if needed and |create_if_null| is true. + base::DictionaryValue* GetMutableContents(bool create_if_null); + + DISALLOW_COPY_AND_ASSIGN(DictionaryHashStoreContents); +}; + +#endif // SERVICES_PREFERENCES_TRACKED_DICTIONARY_HASH_STORE_CONTENTS_H_ diff --git a/chromium/services/preferences/tracked/hash_store_contents.h b/chromium/services/preferences/tracked/hash_store_contents.h new file mode 100644 index 00000000000..5486514bf23 --- /dev/null +++ b/chromium/services/preferences/tracked/hash_store_contents.h @@ -0,0 +1,90 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_TRACKED_HASH_STORE_CONTENTS_H_ +#define SERVICES_PREFERENCES_TRACKED_HASH_STORE_CONTENTS_H_ + +#include <map> +#include <memory> +#include <string> + +#include "base/strings/string_piece.h" + +namespace base { +class DictionaryValue; +class Value; +} // namespace base + +// Provides access to the contents of a preference hash store. The store +// contains the following data: +// Contents: a client-defined dictionary that should map preference names to +// MACs. +// Version: a client-defined version number for the format of Contents. +// Super MAC: a MAC that authenticates the entirety of Contents. +class HashStoreContents { + public: + virtual ~HashStoreContents() {} + + // Returns true if this implementation of HashStoreContents can be copied via + // MakeCopy(). + virtual bool IsCopyable() const = 0; + + // Returns a copy of this HashStoreContents. Must only be called on + // lightweight implementations (which return true from IsCopyable()) and only + // in scenarios where a copy cannot be avoided. + virtual std::unique_ptr<HashStoreContents> MakeCopy() const = 0; + + // Returns the suffix to be appended to UMA histograms for this store type. + // The returned value must either be an empty string or one of the values in + // histograms.xml's TrackedPreferencesExternalValidators. + virtual base::StringPiece GetUMASuffix() const = 0; + + // Discards all data related to this hash store. + virtual void Reset() = 0; + + // Outputs the MAC validating the preference at path. Returns true if a MAC + // was successfully read and false otherwise. + virtual bool GetMac(const std::string& path, std::string* out_value) = 0; + + // Outputs the MACS validating the split preference at path. Returns true if + // MACS were successfully read and false otherwise. + virtual bool GetSplitMacs(const std::string& path, + std::map<std::string, std::string>* out_value) = 0; + + // Set the MAC validating the preference at path. + virtual void SetMac(const std::string& path, const std::string& value) = 0; + + // Set the MAC validating the split preference at path and split_path. + // For example, |path| is 'extension' and |split_path| is some extenson id. + virtual void SetSplitMac(const std::string& path, + const std::string& split_path, + const std::string& value) = 0; + + // Sets the MAC for the preference at |path|. + // If |path| is a split preference |in_value| must be a DictionaryValue whose + // keys are keys in the split preference and whose values are MACs of the + // corresponding values in the split preference. + // If |path| is an atomic preference |in_value| must be a StringValue + // containing a MAC of the preference value. + virtual void ImportEntry(const std::string& path, + const base::Value* in_value) = 0; + + // Removes the MAC (for atomic preferences) or MACs (for split preferences) + // at |path|. Returns true if there was an entry at |path| which was + // successfully removed. + virtual bool RemoveEntry(const std::string& path) = 0; + + // Only needed if this store supports super MACs. + virtual const base::DictionaryValue* GetContents() const = 0; + + // Retrieves the super MAC value previously stored by SetSuperMac. May be + // empty if no super MAC has been stored or if this store does not support + // super MACs. + virtual std::string GetSuperMac() const = 0; + + // Stores a super MAC value for this hash store. + virtual void SetSuperMac(const std::string& super_mac) = 0; +}; + +#endif // SERVICES_PREFERENCES_TRACKED_HASH_STORE_CONTENTS_H_ diff --git a/chromium/services/preferences/tracked/interceptable_pref_filter.cc b/chromium/services/preferences/tracked/interceptable_pref_filter.cc new file mode 100644 index 00000000000..81f2783558e --- /dev/null +++ b/chromium/services/preferences/tracked/interceptable_pref_filter.cc @@ -0,0 +1,39 @@ +// Copyright 2014 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 "services/preferences/tracked/interceptable_pref_filter.h" + +#include <utility> + +#include "base/bind.h" +#include "base/callback_helpers.h" + +InterceptablePrefFilter::InterceptablePrefFilter() {} +InterceptablePrefFilter::~InterceptablePrefFilter() {} + +void InterceptablePrefFilter::FilterOnLoad( + const PostFilterOnLoadCallback& post_filter_on_load_callback, + std::unique_ptr<base::DictionaryValue> pref_store_contents) { + if (filter_on_load_interceptor_.is_null()) { + FinalizeFilterOnLoad(post_filter_on_load_callback, + std::move(pref_store_contents), false); + } else { + // Note, in practice (in the implementation as it was in May 2014) it would + // be okay to pass an unretained |this| pointer below, but in order to avoid + // having to augment the API everywhere to explicitly enforce the ownership + // model as it happens to currently be: make the relationship simpler by + // weakly binding the FinalizeFilterOnLoadCallback below to |this|. + const FinalizeFilterOnLoadCallback finalize_filter_on_load( + base::Bind(&InterceptablePrefFilter::FinalizeFilterOnLoad, AsWeakPtr(), + post_filter_on_load_callback)); + base::ResetAndReturn(&filter_on_load_interceptor_) + .Run(finalize_filter_on_load, std::move(pref_store_contents)); + } +} + +void InterceptablePrefFilter::InterceptNextFilterOnLoad( + const FilterOnLoadInterceptor& filter_on_load_interceptor) { + DCHECK(filter_on_load_interceptor_.is_null()); + filter_on_load_interceptor_ = filter_on_load_interceptor; +} diff --git a/chromium/services/preferences/tracked/interceptable_pref_filter.h b/chromium/services/preferences/tracked/interceptable_pref_filter.h new file mode 100644 index 00000000000..7971c8da8f1 --- /dev/null +++ b/chromium/services/preferences/tracked/interceptable_pref_filter.h @@ -0,0 +1,68 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_TRACKED_INTERCEPTABLE_PREF_FILTER_H_ +#define SERVICES_PREFERENCES_TRACKED_INTERCEPTABLE_PREF_FILTER_H_ + +#include <memory> + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/values.h" +#include "components/prefs/pref_filter.h" + +// A partial implementation of a PrefFilter whose FilterOnLoad call may be +// intercepted by a FilterOnLoadInterceptor. Implementations of +// InterceptablePrefFilter are expected to override FinalizeFilterOnLoad rather +// than re-overriding FilterOnLoad. +class InterceptablePrefFilter + : public PrefFilter, + public base::SupportsWeakPtr<InterceptablePrefFilter> { + public: + // A callback to be invoked by a FilterOnLoadInterceptor when its ready to + // hand back the |prefs| it was handed for early filtering. |prefs_altered| + // indicates whether the |prefs| were actually altered by the + // FilterOnLoadInterceptor before being handed back. + typedef base::Callback<void(std::unique_ptr<base::DictionaryValue> prefs, + bool prefs_altered)> + FinalizeFilterOnLoadCallback; + + // A callback to be invoked from FilterOnLoad. It takes ownership of prefs + // and may modify them before handing them back to this + // InterceptablePrefFilter via |finalize_filter_on_load|. + typedef base::Callback<void( + const FinalizeFilterOnLoadCallback& finalize_filter_on_load, + std::unique_ptr<base::DictionaryValue> prefs)> + FilterOnLoadInterceptor; + + InterceptablePrefFilter(); + ~InterceptablePrefFilter() override; + + // PrefFilter partial implementation. + void FilterOnLoad( + const PostFilterOnLoadCallback& post_filter_on_load_callback, + std::unique_ptr<base::DictionaryValue> pref_store_contents) override; + + // Registers |filter_on_load_interceptor| to intercept the next FilterOnLoad + // event. At most one FilterOnLoadInterceptor should be registered per + // PrefFilter. + void InterceptNextFilterOnLoad( + const FilterOnLoadInterceptor& filter_on_load_interceptor); + + private: + // Does any extra filtering required by the implementation of this + // InterceptablePrefFilter and hands back the |pref_store_contents| to the + // initial caller of FilterOnLoad. + virtual void FinalizeFilterOnLoad( + const PostFilterOnLoadCallback& post_filter_on_load_callback, + std::unique_ptr<base::DictionaryValue> pref_store_contents, + bool prefs_altered) = 0; + + // Callback to be invoked only once (and subsequently reset) on the next + // FilterOnLoad event. It will be allowed to modify the |prefs| handed to + // FilterOnLoad before handing them back to this PrefHashFilter. + FilterOnLoadInterceptor filter_on_load_interceptor_; +}; + +#endif // SERVICES_PREFERENCES_TRACKED_INTERCEPTABLE_PREF_FILTER_H_ diff --git a/chromium/services/preferences/tracked/interceptable_pref_filter_unittest.cc b/chromium/services/preferences/tracked/interceptable_pref_filter_unittest.cc new file mode 100644 index 00000000000..cdaaadb37d4 --- /dev/null +++ b/chromium/services/preferences/tracked/interceptable_pref_filter_unittest.cc @@ -0,0 +1,55 @@ +// Copyright 2017 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 "services/preferences/tracked/interceptable_pref_filter.h" + +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class TestInterceptablePrefFilter : public InterceptablePrefFilter { + public: + void FinalizeFilterOnLoad( + const PostFilterOnLoadCallback& post_filter_on_load_callback, + std::unique_ptr<base::DictionaryValue> pref_store_contents, + bool prefs_altered) override { + post_filter_on_load_callback.Run(std::move(pref_store_contents), + prefs_altered); + } + + void FilterUpdate(const std::string& path) override {} + + OnWriteCallbackPair FilterSerializeData( + base::DictionaryValue* pref_store_contents) override { + return {}; + } +}; + +void NoOpIntercept(const InterceptablePrefFilter::FinalizeFilterOnLoadCallback& + finalize_filter_on_load, + std::unique_ptr<base::DictionaryValue> prefs) { + finalize_filter_on_load.Run(std::move(prefs), false); +} + +void DeleteFilter(std::unique_ptr<TestInterceptablePrefFilter>* filter, + std::unique_ptr<base::DictionaryValue> prefs, + bool schedule_write) { + filter->reset(); +} + +TEST(InterceptablePrefFilterTest, CallbackDeletes) { + auto filter = base::MakeUnique<TestInterceptablePrefFilter>(); + filter->InterceptNextFilterOnLoad(base::Bind(&NoOpIntercept)); + filter->FilterOnLoad(base::Bind(&DeleteFilter, &filter), + base::MakeUnique<base::DictionaryValue>()); + EXPECT_FALSE(filter); +} + +} // namespace diff --git a/chromium/services/preferences/tracked/pref_hash_calculator.cc b/chromium/services/preferences/tracked/pref_hash_calculator.cc new file mode 100644 index 00000000000..207221c5688 --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_calculator.cc @@ -0,0 +1,112 @@ +// Copyright 2013 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 "services/preferences/tracked/pref_hash_calculator.h" + +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "base/bind.h" +#include "base/json/json_string_value_serializer.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "crypto/hmac.h" + +namespace { + +// Calculates an HMAC of |message| using |key|, encoded as a hexadecimal string. +std::string GetDigestString(const std::string& key, + const std::string& message) { + crypto::HMAC hmac(crypto::HMAC::SHA256); + std::vector<uint8_t> digest(hmac.DigestLength()); + if (!hmac.Init(key) || !hmac.Sign(message, &digest[0], digest.size())) { + NOTREACHED(); + return std::string(); + } + return base::HexEncode(&digest[0], digest.size()); +} + +// Verifies that |digest_string| is a valid HMAC of |message| using |key|. +// |digest_string| must be encoded as a hexadecimal string. +bool VerifyDigestString(const std::string& key, + const std::string& message, + const std::string& digest_string) { + crypto::HMAC hmac(crypto::HMAC::SHA256); + std::vector<uint8_t> digest; + return base::HexStringToBytes(digest_string, &digest) && hmac.Init(key) && + hmac.Verify(message, + base::StringPiece(reinterpret_cast<char*>(&digest[0]), + digest.size())); +} + +// Renders |value| as a string. |value| may be NULL, in which case the result +// is an empty string. This method can be expensive and its result should be +// re-used rather than recomputed where possible. +std::string ValueAsString(const base::Value* value) { + // Dictionary values may contain empty lists and sub-dictionaries. Make a + // deep copy with those removed to make the hash more stable. + const base::DictionaryValue* dict_value; + std::unique_ptr<base::DictionaryValue> canonical_dict_value; + if (value && value->GetAsDictionary(&dict_value)) { + canonical_dict_value = dict_value->DeepCopyWithoutEmptyChildren(); + value = canonical_dict_value.get(); + } + + std::string value_as_string; + if (value) { + JSONStringValueSerializer serializer(&value_as_string); + serializer.Serialize(*value); + } + + return value_as_string; +} + +// Concatenates |device_id|, |path|, and |value_as_string| to give the hash +// input. +std::string GetMessage(const std::string& device_id, + const std::string& path, + const std::string& value_as_string) { + std::string message; + message.reserve(device_id.size() + path.size() + value_as_string.size()); + message.append(device_id); + message.append(path); + message.append(value_as_string); + return message; +} + +} // namespace + +PrefHashCalculator::PrefHashCalculator(const std::string& seed, + const std::string& device_id, + const std::string& legacy_device_id) + : seed_(seed), device_id_(device_id), legacy_device_id_(legacy_device_id) {} + +PrefHashCalculator::~PrefHashCalculator() {} + +std::string PrefHashCalculator::Calculate(const std::string& path, + const base::Value* value) const { + return GetDigestString(seed_, + GetMessage(device_id_, path, ValueAsString(value))); +} + +PrefHashCalculator::ValidationResult PrefHashCalculator::Validate( + const std::string& path, + const base::Value* value, + const std::string& digest_string) const { + const std::string value_as_string(ValueAsString(value)); + if (VerifyDigestString(seed_, GetMessage(device_id_, path, value_as_string), + digest_string)) { + return VALID; + } + if (VerifyDigestString(seed_, + GetMessage(legacy_device_id_, path, value_as_string), + digest_string)) { + return VALID_SECURE_LEGACY; + } + return INVALID; +} diff --git a/chromium/services/preferences/tracked/pref_hash_calculator.h b/chromium/services/preferences/tracked/pref_hash_calculator.h new file mode 100644 index 00000000000..1fc14a180aa --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_calculator.h @@ -0,0 +1,55 @@ +// Copyright 2013 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 SERVICES_PREFERENCES_TRACKED_PREF_HASH_CALCULATOR_H_ +#define SERVICES_PREFERENCES_TRACKED_PREF_HASH_CALCULATOR_H_ + +#include <string> + +#include "base/macros.h" + +namespace base { +class Value; +} // namespace base + +// Calculates and validates preference value hashes. +class PrefHashCalculator { + public: + enum ValidationResult { + INVALID, + VALID, + // Valid under a deprecated but as secure algorithm. + VALID_SECURE_LEGACY, + }; + + // Constructs a PrefHashCalculator using |seed|, |device_id| and + // |legacy_device_id|. The same parameters must be used in order to + // successfully validate generated hashes. |_device_id| or |legacy_device_id| + // may be empty. + PrefHashCalculator(const std::string& seed, + const std::string& device_id, + const std::string& legacy_device_id); + + ~PrefHashCalculator(); + + // Calculates a hash value for the supplied preference |path| and |value|. + // |value| may be null if the preference has no value. + std::string Calculate(const std::string& path, + const base::Value* value) const; + + // Validates the provided preference hash using current and legacy hashing + // algorithms. + ValidationResult Validate(const std::string& path, + const base::Value* value, + const std::string& hash) const; + + private: + const std::string seed_; + const std::string device_id_; + const std::string legacy_device_id_; + + DISALLOW_COPY_AND_ASSIGN(PrefHashCalculator); +}; + +#endif // SERVICES_PREFERENCES_TRACKED_PREF_HASH_CALCULATOR_H_ diff --git a/chromium/services/preferences/tracked/pref_hash_calculator_unittest.cc b/chromium/services/preferences/tracked/pref_hash_calculator_unittest.cc new file mode 100644 index 00000000000..df3746a09e0 --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_calculator_unittest.cc @@ -0,0 +1,196 @@ +// Copyright 2013 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 "services/preferences/tracked/pref_hash_calculator.h" + +#include <memory> +#include <string> + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PrefHashCalculatorTest, TestCurrentAlgorithm) { + base::Value string_value_1("string value 1"); + base::Value string_value_2("string value 2"); + base::DictionaryValue dictionary_value_1; + dictionary_value_1.SetInteger("int value", 1); + dictionary_value_1.Set("nested empty map", new base::DictionaryValue); + base::DictionaryValue dictionary_value_1_equivalent; + dictionary_value_1_equivalent.SetInteger("int value", 1); + base::DictionaryValue dictionary_value_2; + dictionary_value_2.SetInteger("int value", 2); + + PrefHashCalculator calc1("seed1", "deviceid", "legacydeviceid"); + PrefHashCalculator calc1_dup("seed1", "deviceid", "legacydeviceid"); + PrefHashCalculator calc2("seed2", "deviceid", "legacydeviceid"); + PrefHashCalculator calc3("seed1", "deviceid2", "legacydeviceid"); + + // Two calculators with same seed produce same hash. + ASSERT_EQ(calc1.Calculate("pref_path", &string_value_1), + calc1_dup.Calculate("pref_path", &string_value_1)); + ASSERT_EQ(PrefHashCalculator::VALID, + calc1_dup.Validate("pref_path", &string_value_1, + calc1.Calculate("pref_path", &string_value_1))); + + // Different seeds, different hashes. + ASSERT_NE(calc1.Calculate("pref_path", &string_value_1), + calc2.Calculate("pref_path", &string_value_1)); + ASSERT_EQ(PrefHashCalculator::INVALID, + calc2.Validate("pref_path", &string_value_1, + calc1.Calculate("pref_path", &string_value_1))); + + // Different device IDs, different hashes. + ASSERT_NE(calc1.Calculate("pref_path", &string_value_1), + calc3.Calculate("pref_path", &string_value_1)); + + // Different values, different hashes. + ASSERT_NE(calc1.Calculate("pref_path", &string_value_1), + calc1.Calculate("pref_path", &string_value_2)); + + // Different paths, different hashes. + ASSERT_NE(calc1.Calculate("pref_path", &string_value_1), + calc1.Calculate("pref_path_2", &string_value_1)); + + // Works for dictionaries. + ASSERT_EQ(calc1.Calculate("pref_path", &dictionary_value_1), + calc1.Calculate("pref_path", &dictionary_value_1)); + ASSERT_NE(calc1.Calculate("pref_path", &dictionary_value_1), + calc1.Calculate("pref_path", &dictionary_value_2)); + + // Empty dictionary children are pruned. + ASSERT_EQ(calc1.Calculate("pref_path", &dictionary_value_1), + calc1.Calculate("pref_path", &dictionary_value_1_equivalent)); + + // NULL value is supported. + ASSERT_FALSE(calc1.Calculate("pref_path", NULL).empty()); +} + +// Tests the output against a known value to catch unexpected algorithm changes. +// The test hashes below must NEVER be updated, the serialization algorithm used +// must always be able to generate data that will produce these exact hashes. +TEST(PrefHashCalculatorTest, CatchHashChanges) { + static const char kSeed[] = "0123456789ABCDEF0123456789ABCDEF"; + static const char kDeviceId[] = "test_device_id1"; + + auto null_value = base::MakeUnique<base::Value>(); + std::unique_ptr<base::Value> bool_value(new base::Value(false)); + std::unique_ptr<base::Value> int_value(new base::Value(1234567890)); + std::unique_ptr<base::Value> double_value(new base::Value(123.0987654321)); + std::unique_ptr<base::Value> string_value( + new base::Value("testing with special chars:\n<>{}:^^@#$\\/")); + + // For legacy reasons, we have to support pruning of empty lists/dictionaries + // and nested empty ists/dicts in the hash generation algorithm. + std::unique_ptr<base::DictionaryValue> nested_empty_dict( + new base::DictionaryValue); + nested_empty_dict->Set("a", new base::DictionaryValue); + nested_empty_dict->Set("b", new base::ListValue); + std::unique_ptr<base::ListValue> nested_empty_list(new base::ListValue); + nested_empty_list->Append(base::MakeUnique<base::DictionaryValue>()); + nested_empty_list->Append(base::MakeUnique<base::ListValue>()); + nested_empty_list->Append(nested_empty_dict->CreateDeepCopy()); + + // A dictionary with an empty dictionary, an empty list, and nested empty + // dictionaries/lists in it. + std::unique_ptr<base::DictionaryValue> dict_value(new base::DictionaryValue); + dict_value->Set("a", new base::Value("foo")); + dict_value->Set("d", new base::ListValue); + dict_value->Set("b", new base::DictionaryValue); + dict_value->Set("c", new base::Value("baz")); + dict_value->Set("e", nested_empty_dict.release()); + dict_value->Set("f", nested_empty_list.release()); + + std::unique_ptr<base::ListValue> list_value(new base::ListValue); + list_value->AppendBoolean(true); + list_value->AppendInteger(100); + list_value->AppendDouble(1.0); + + ASSERT_EQ(base::Value::Type::NONE, null_value->GetType()); + ASSERT_EQ(base::Value::Type::BOOLEAN, bool_value->GetType()); + ASSERT_EQ(base::Value::Type::INTEGER, int_value->GetType()); + ASSERT_EQ(base::Value::Type::DOUBLE, double_value->GetType()); + ASSERT_EQ(base::Value::Type::STRING, string_value->GetType()); + ASSERT_EQ(base::Value::Type::DICTIONARY, dict_value->GetType()); + ASSERT_EQ(base::Value::Type::LIST, list_value->GetType()); + + // Test every value type independently. Intentionally omits Type::BINARY which + // isn't even allowed in JSONWriter's input. + static const char kExpectedNullValue[] = + "82A9F3BBC7F9FF84C76B033C854E79EEB162783FA7B3E99FF9372FA8E12C44F7"; + EXPECT_EQ(PrefHashCalculator::VALID, + PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid") + .Validate("pref.path", null_value.get(), kExpectedNullValue)); + + static const char kExpectedBooleanValue[] = + "A520D8F43EA307B0063736DC9358C330539D0A29417580514C8B9862632C4CCC"; + EXPECT_EQ( + PrefHashCalculator::VALID, + PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid") + .Validate("pref.path", bool_value.get(), kExpectedBooleanValue)); + + static const char kExpectedIntegerValue[] = + "8D60DA1F10BF5AA29819D2D66D7CCEF9AABC5DA93C11A0D2BD21078D63D83682"; + EXPECT_EQ(PrefHashCalculator::VALID, + PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid") + .Validate("pref.path", int_value.get(), kExpectedIntegerValue)); + + static const char kExpectedDoubleValue[] = + "C9D94772516125BEEDAE68C109D44BC529E719EE020614E894CC7FB4098C545D"; + EXPECT_EQ( + PrefHashCalculator::VALID, + PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid") + .Validate("pref.path", double_value.get(), kExpectedDoubleValue)); + + static const char kExpectedStringValue[] = + "05ACCBD3B05C45C36CD06190F63EC577112311929D8380E26E5F13182EB68318"; + EXPECT_EQ( + PrefHashCalculator::VALID, + PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid") + .Validate("pref.path", string_value.get(), kExpectedStringValue)); + + static const char kExpectedDictValue[] = + "7A84DCC710D796C771F789A4DA82C952095AA956B6F1667EE42D0A19ECAA3C4A"; + EXPECT_EQ(PrefHashCalculator::VALID, + PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid") + .Validate("pref.path", dict_value.get(), kExpectedDictValue)); + + static const char kExpectedListValue[] = + "8D5A25972DF5AE20D041C780E7CA54E40F614AD53513A0724EE8D62D4F992740"; + EXPECT_EQ(PrefHashCalculator::VALID, + PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid") + .Validate("pref.path", list_value.get(), kExpectedListValue)); + + // Also test every value type together in the same dictionary. + base::DictionaryValue everything; + everything.Set("null", null_value.release()); + everything.Set("bool", bool_value.release()); + everything.Set("int", int_value.release()); + everything.Set("double", double_value.release()); + everything.Set("string", string_value.release()); + everything.Set("list", list_value.release()); + everything.Set("dict", dict_value.release()); + static const char kExpectedEverythingValue[] = + "B97D09BE7005693574DCBDD03D8D9E44FB51F4008B73FB56A49A9FA671A1999B"; + EXPECT_EQ(PrefHashCalculator::VALID, + PrefHashCalculator(kSeed, kDeviceId, "legacydeviceid") + .Validate("pref.path", &everything, kExpectedEverythingValue)); +} + +TEST(PrefHashCalculatorTest, TestCompatibilityWithLegacyDeviceId) { + static const char kSeed[] = "0123456789ABCDEF0123456789ABCDEF"; + static const char kNewDeviceId[] = "new_test_device_id1"; + static const char kLegacyDeviceId[] = "test_device_id1"; + + // As in PrefHashCalculatorTest.CatchHashChanges. + const base::Value string_value("testing with special chars:\n<>{}:^^@#$\\/"); + static const char kExpectedValue[] = + "05ACCBD3B05C45C36CD06190F63EC577112311929D8380E26E5F13182EB68318"; + + EXPECT_EQ(PrefHashCalculator::VALID_SECURE_LEGACY, + PrefHashCalculator(kSeed, kNewDeviceId, kLegacyDeviceId) + .Validate("pref.path", &string_value, kExpectedValue)); +} diff --git a/chromium/services/preferences/tracked/pref_hash_filter.cc b/chromium/services/preferences/tracked/pref_hash_filter.cc new file mode 100644 index 00000000000..29d6b9ab830 --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_filter.cc @@ -0,0 +1,366 @@ +// Copyright 2013 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 "services/preferences/tracked/pref_hash_filter.h" + +#include <stdint.h> +#include <algorithm> +#include <utility> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/string_number_conversions.h" +#include "base/time/time.h" +#include "base/values.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/pref_store.h" +#include "services/preferences/public/cpp/tracked/pref_names.h" +#include "services/preferences/tracked/dictionary_hash_store_contents.h" +#include "services/preferences/tracked/pref_hash_store.h" +#include "services/preferences/tracked/pref_hash_store_transaction.h" +#include "services/preferences/tracked/tracked_atomic_preference.h" +#include "services/preferences/tracked/tracked_split_preference.h" + +namespace { + +void CleanupDeprecatedTrackedPreferences( + base::DictionaryValue* pref_store_contents, + PrefHashStoreTransaction* hash_store_transaction) { + // Add deprecated previously tracked preferences below for them to be cleaned + // up from both the pref files and the hash store. + static const char* const kDeprecatedTrackedPreferences[] = { + // TODO(a-v-y): Remove in M60+, + "default_search_provider.search_url", "default_search_provider.name", + "default_search_provider.keyword"}; + + for (size_t i = 0; i < arraysize(kDeprecatedTrackedPreferences); ++i) { + const char* key = kDeprecatedTrackedPreferences[i]; + pref_store_contents->Remove(key, NULL); + hash_store_transaction->ClearHash(key); + } +} + +} // namespace + +using PrefTrackingStrategy = + prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy; + +PrefHashFilter::PrefHashFilter( + std::unique_ptr<PrefHashStore> pref_hash_store, + StoreContentsPair external_validation_hash_store_pair, + const std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>& + tracked_preferences, + prefs::mojom::ResetOnLoadObserverPtr reset_on_load_observer, + prefs::mojom::TrackedPreferenceValidationDelegate* delegate, + size_t reporting_ids_count, + bool report_super_mac_validity) + : pref_hash_store_(std::move(pref_hash_store)), + external_validation_hash_store_pair_( + external_validation_hash_store_pair.first + ? base::make_optional( + std::move(external_validation_hash_store_pair)) + : base::nullopt), + reset_on_load_observer_(std::move(reset_on_load_observer)), + report_super_mac_validity_(report_super_mac_validity) { + DCHECK(pref_hash_store_); + DCHECK_GE(reporting_ids_count, tracked_preferences.size()); + // Verify that, if |external_validation_hash_store_pair_| is present, both its + // items are non-null. + DCHECK(!external_validation_hash_store_pair_.has_value() || + (external_validation_hash_store_pair_->first && + external_validation_hash_store_pair_->second)); + + for (size_t i = 0; i < tracked_preferences.size(); ++i) { + const prefs::mojom::TrackedPreferenceMetadata& metadata = + *tracked_preferences[i]; + + std::unique_ptr<TrackedPreference> tracked_preference; + switch (metadata.strategy) { + case PrefTrackingStrategy::ATOMIC: + tracked_preference.reset(new TrackedAtomicPreference( + metadata.name, metadata.reporting_id, reporting_ids_count, + metadata.enforcement_level, metadata.value_type, delegate)); + break; + case PrefTrackingStrategy::SPLIT: + tracked_preference.reset(new TrackedSplitPreference( + metadata.name, metadata.reporting_id, reporting_ids_count, + metadata.enforcement_level, metadata.value_type, delegate)); + break; + } + DCHECK(tracked_preference); + + bool is_new = tracked_paths_ + .insert(std::make_pair(metadata.name, + std::move(tracked_preference))) + .second; + DCHECK(is_new); + } +} + +PrefHashFilter::~PrefHashFilter() { + // Ensure new values for all |changed_paths_| have been flushed to + // |pref_hash_store_| already. + DCHECK(changed_paths_.empty()); +} + +// static +void PrefHashFilter::RegisterProfilePrefs( + user_prefs::PrefRegistrySyncable* registry) { + // See GetResetTime for why this is a StringPref and not Int64Pref. + registry->RegisterStringPref( + user_prefs::kPreferenceResetTime, + base::Int64ToString(base::Time().ToInternalValue())); +} + +// static +base::Time PrefHashFilter::GetResetTime(PrefService* user_prefs) { + // Provide our own implementation (identical to the PrefService::GetInt64) in + // order to ensure it remains consistent with the way we store this value + // (which we do via a PrefStore, preventing us from reusing + // PrefService::SetInt64). + int64_t internal_value = base::Time().ToInternalValue(); + if (!base::StringToInt64( + user_prefs->GetString(user_prefs::kPreferenceResetTime), + &internal_value)) { + // Somehow the value stored on disk is not a valid int64_t. + NOTREACHED(); + return base::Time(); + } + return base::Time::FromInternalValue(internal_value); +} + +// static +void PrefHashFilter::ClearResetTime(PrefService* user_prefs) { + user_prefs->ClearPref(user_prefs::kPreferenceResetTime); +} + +void PrefHashFilter::Initialize(base::DictionaryValue* pref_store_contents) { + DictionaryHashStoreContents dictionary_contents(pref_store_contents); + std::unique_ptr<PrefHashStoreTransaction> hash_store_transaction( + pref_hash_store_->BeginTransaction(&dictionary_contents)); + for (auto it = tracked_paths_.begin(); it != tracked_paths_.end(); ++it) { + const std::string& initialized_path = it->first; + const TrackedPreference* initialized_preference = it->second.get(); + const base::Value* value = nullptr; + pref_store_contents->Get(initialized_path, &value); + initialized_preference->OnNewValue(value, hash_store_transaction.get()); + } +} + +// Marks |path| has having changed if it is part of |tracked_paths_|. A new hash +// will be stored for it the next time FilterSerializeData() is invoked. +void PrefHashFilter::FilterUpdate(const std::string& path) { + auto it = tracked_paths_.find(path); + if (it != tracked_paths_.end()) + changed_paths_.insert(std::make_pair(path, it->second.get())); +} + +// Updates the stored hashes for |changed_paths_| before serializing data to +// disk. This is required as storing the hash everytime a pref's value changes +// is too expensive (see perf regression @ http://crbug.com/331273). +PrefFilter::OnWriteCallbackPair PrefHashFilter::FilterSerializeData( + base::DictionaryValue* pref_store_contents) { + // Generate the callback pair before clearing |changed_paths_|. + PrefFilter::OnWriteCallbackPair callback_pair = + GetOnWriteSynchronousCallbacks(pref_store_contents); + + if (!changed_paths_.empty()) { + base::TimeTicks checkpoint = base::TimeTicks::Now(); + { + DictionaryHashStoreContents dictionary_contents(pref_store_contents); + std::unique_ptr<PrefHashStoreTransaction> hash_store_transaction( + pref_hash_store_->BeginTransaction(&dictionary_contents)); + + std::unique_ptr<PrefHashStoreTransaction> + external_validation_hash_store_transaction; + if (external_validation_hash_store_pair_) { + external_validation_hash_store_transaction = + external_validation_hash_store_pair_->first->BeginTransaction( + external_validation_hash_store_pair_->second.get()); + } + + for (ChangedPathsMap::const_iterator it = changed_paths_.begin(); + it != changed_paths_.end(); ++it) { + const std::string& changed_path = it->first; + const TrackedPreference* changed_preference = it->second; + const base::Value* value = nullptr; + pref_store_contents->Get(changed_path, &value); + changed_preference->OnNewValue(value, hash_store_transaction.get()); + } + changed_paths_.clear(); + } + UMA_HISTOGRAM_TIMES("Settings.FilterSerializeDataTime", + base::TimeTicks::Now() - checkpoint); + } + + return callback_pair; +} + +void PrefHashFilter::FinalizeFilterOnLoad( + const PostFilterOnLoadCallback& post_filter_on_load_callback, + std::unique_ptr<base::DictionaryValue> pref_store_contents, + bool prefs_altered) { + DCHECK(pref_store_contents); + base::TimeTicks checkpoint = base::TimeTicks::Now(); + + bool did_reset = false; + { + DictionaryHashStoreContents dictionary_contents(pref_store_contents.get()); + std::unique_ptr<PrefHashStoreTransaction> hash_store_transaction( + pref_hash_store_->BeginTransaction(&dictionary_contents)); + + std::unique_ptr<PrefHashStoreTransaction> + external_validation_hash_store_transaction; + if (external_validation_hash_store_pair_) { + external_validation_hash_store_transaction = + external_validation_hash_store_pair_->first->BeginTransaction( + external_validation_hash_store_pair_->second.get()); + } + + CleanupDeprecatedTrackedPreferences(pref_store_contents.get(), + hash_store_transaction.get()); + + if (report_super_mac_validity_) { + UMA_HISTOGRAM_BOOLEAN("Settings.HashesDictionaryTrusted", + hash_store_transaction->IsSuperMACValid()); + } + + for (auto it = tracked_paths_.begin(); it != tracked_paths_.end(); ++it) { + if (it->second->EnforceAndReport( + pref_store_contents.get(), hash_store_transaction.get(), + external_validation_hash_store_transaction.get())) { + did_reset = true; + prefs_altered = true; + } + } + if (hash_store_transaction->StampSuperMac()) + prefs_altered = true; + } + + if (did_reset) { + pref_store_contents->Set(user_prefs::kPreferenceResetTime, + new base::Value(base::Int64ToString( + base::Time::Now().ToInternalValue()))); + FilterUpdate(user_prefs::kPreferenceResetTime); + + if (reset_on_load_observer_) + reset_on_load_observer_->OnResetOnLoad(); + } + reset_on_load_observer_.reset(); + + UMA_HISTOGRAM_TIMES("Settings.FilterOnLoadTime", + base::TimeTicks::Now() - checkpoint); + + post_filter_on_load_callback.Run(std::move(pref_store_contents), + prefs_altered); +} + +// static +void PrefHashFilter::ClearFromExternalStore( + HashStoreContents* external_validation_hash_store_contents, + const base::DictionaryValue* changed_paths_and_macs) { + DCHECK(!changed_paths_and_macs->empty()); + + for (base::DictionaryValue::Iterator it(*changed_paths_and_macs); + !it.IsAtEnd(); it.Advance()) { + external_validation_hash_store_contents->RemoveEntry(it.key()); + } +} + +// static +void PrefHashFilter::FlushToExternalStore( + std::unique_ptr<HashStoreContents> external_validation_hash_store_contents, + std::unique_ptr<base::DictionaryValue> changed_paths_and_macs, + bool write_success) { + DCHECK(!changed_paths_and_macs->empty()); + DCHECK(external_validation_hash_store_contents); + if (!write_success) + return; + + for (base::DictionaryValue::Iterator it(*changed_paths_and_macs); + !it.IsAtEnd(); it.Advance()) { + const std::string& changed_path = it.key(); + + const base::DictionaryValue* split_values = nullptr; + if (it.value().GetAsDictionary(&split_values)) { + for (base::DictionaryValue::Iterator inner_it(*split_values); + !inner_it.IsAtEnd(); inner_it.Advance()) { + std::string mac; + bool is_string = inner_it.value().GetAsString(&mac); + DCHECK(is_string); + + external_validation_hash_store_contents->SetSplitMac( + changed_path, inner_it.key(), mac); + } + } else { + const base::Value* value_as_string; + bool is_string = it.value().GetAsString(&value_as_string); + DCHECK(is_string); + + external_validation_hash_store_contents->SetMac( + changed_path, value_as_string->GetString()); + } + } +} + +PrefFilter::OnWriteCallbackPair PrefHashFilter::GetOnWriteSynchronousCallbacks( + base::DictionaryValue* pref_store_contents) { + if (changed_paths_.empty() || !external_validation_hash_store_pair_) { + return std::make_pair(base::Closure(), + base::Callback<void(bool success)>()); + } + + std::unique_ptr<base::DictionaryValue> changed_paths_macs = + base::MakeUnique<base::DictionaryValue>(); + + for (ChangedPathsMap::const_iterator it = changed_paths_.begin(); + it != changed_paths_.end(); ++it) { + const std::string& changed_path = it->first; + const TrackedPreference* changed_preference = it->second; + + switch (changed_preference->GetType()) { + case TrackedPreferenceType::ATOMIC: { + const base::Value* new_value = nullptr; + pref_store_contents->Get(changed_path, &new_value); + changed_paths_macs->SetStringWithoutPathExpansion( + changed_path, + external_validation_hash_store_pair_->first->ComputeMac( + changed_path, new_value)); + break; + } + case TrackedPreferenceType::SPLIT: { + const base::DictionaryValue* dict_value = nullptr; + pref_store_contents->GetDictionary(changed_path, &dict_value); + changed_paths_macs->SetWithoutPathExpansion( + changed_path, + external_validation_hash_store_pair_->first->ComputeSplitMacs( + changed_path, dict_value)); + break; + } + } + } + + DCHECK(external_validation_hash_store_pair_->second->IsCopyable()) + << "External HashStoreContents must be copyable as it needs to be used " + "off-thread"; + + std::unique_ptr<HashStoreContents> hash_store_contents_copy = + external_validation_hash_store_pair_->second->MakeCopy(); + + // We can use raw pointers for the first callback instead of making more + // copies as it will be executed in sequence before the second callback, + // which owns the pointers. + HashStoreContents* raw_contents = hash_store_contents_copy.get(); + base::DictionaryValue* raw_changed_paths_macs = changed_paths_macs.get(); + + return std::make_pair( + base::Bind(&ClearFromExternalStore, base::Unretained(raw_contents), + base::Unretained(raw_changed_paths_macs)), + base::Bind(&FlushToExternalStore, base::Passed(&hash_store_contents_copy), + base::Passed(&changed_paths_macs))); +} diff --git a/chromium/services/preferences/tracked/pref_hash_filter.h b/chromium/services/preferences/tracked/pref_hash_filter.h new file mode 100644 index 00000000000..4c3cdaa4996 --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_filter.h @@ -0,0 +1,157 @@ +// Copyright 2013 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 SERVICES_PREFERENCES_TRACKED_PREF_HASH_FILTER_H_ +#define SERVICES_PREFERENCES_TRACKED_PREF_HASH_FILTER_H_ + +#include <stddef.h> + +#include <map> +#include <memory> +#include <set> +#include <unordered_map> +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/optional.h" +#include "services/preferences/public/interfaces/preferences_configuration.mojom.h" +#include "services/preferences/tracked/hash_store_contents.h" +#include "services/preferences/tracked/interceptable_pref_filter.h" +#include "services/preferences/tracked/tracked_preference.h" + +class PrefHashStore; +class PrefService; + +namespace base { +class DictionaryValue; +class Time; +} // namespace base + +namespace prefs { +namespace mojom { +class TrackedPreferenceValidationDelegate; +} +} + +namespace user_prefs { +class PrefRegistrySyncable; +} // namespace user_prefs + +// Intercepts preference values as they are loaded from disk and verifies them +// using a PrefHashStore. Keeps the PrefHashStore contents up to date as values +// are changed. +class PrefHashFilter : public InterceptablePrefFilter { + public: + using StoreContentsPair = std::pair<std::unique_ptr<PrefHashStore>, + std::unique_ptr<HashStoreContents>>; + + // Constructs a PrefHashFilter tracking the specified |tracked_preferences| + // using |pref_hash_store| to check/store hashes. An optional |delegate| is + // notified of the status of each preference as it is checked. + // If |reset_on_load_observer| is provided, it will be notified if a reset + // occurs in FilterOnLoad. + // |reporting_ids_count| is the count of all possible IDs (possibly greater + // than |tracked_preferences.size()|). If |report_super_mac_validity| is true, + // the state of the super MAC will be reported via UMA during + // FinalizeFilterOnLoad. + // |external_validation_hash_store_pair_| will be used (if non-null) to + // perform extra validations without triggering resets. + PrefHashFilter(std::unique_ptr<PrefHashStore> pref_hash_store, + StoreContentsPair external_validation_hash_store_pair_, + const std::vector<prefs::mojom::TrackedPreferenceMetadataPtr>& + tracked_preferences, + prefs::mojom::ResetOnLoadObserverPtr reset_on_load_observer, + prefs::mojom::TrackedPreferenceValidationDelegate* delegate, + size_t reporting_ids_count, + bool report_super_mac_validity); + + ~PrefHashFilter() override; + + // Registers required user preferences. + static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); + + // Retrieves the time of the last reset event, if any, for the provided user + // preferences. If no reset has occurred, Returns a null |Time|. + static base::Time GetResetTime(PrefService* user_prefs); + + // Clears the time of the last reset event, if any, for the provided user + // preferences. + static void ClearResetTime(PrefService* user_prefs); + + // Initializes the PrefHashStore with hashes of the tracked preferences in + // |pref_store_contents|. |pref_store_contents| will be the |storage| passed + // to PrefHashStore::BeginTransaction(). + void Initialize(base::DictionaryValue* pref_store_contents); + + // PrefFilter remaining implementation. + void FilterUpdate(const std::string& path) override; + OnWriteCallbackPair FilterSerializeData( + base::DictionaryValue* pref_store_contents) override; + + private: + // InterceptablePrefFilter implementation. + void FinalizeFilterOnLoad( + const PostFilterOnLoadCallback& post_filter_on_load_callback, + std::unique_ptr<base::DictionaryValue> pref_store_contents, + bool prefs_altered) override; + + // Helper function to generate FilterSerializeData()'s pre-write and + // post-write callbacks. The returned callbacks are thread-safe. + OnWriteCallbackPair GetOnWriteSynchronousCallbacks( + base::DictionaryValue* pref_store_contents); + + // Clears the MACs contained in |external_validation_hash_store_contents| + // which are present in |paths_to_clear|. + static void ClearFromExternalStore( + HashStoreContents* external_validation_hash_store_contents, + const base::DictionaryValue* changed_paths_and_macs); + + // Flushes the MACs contained in |changed_paths_and_mac| to + // external_hash_store_contents if |write_success|, otherwise discards the + // changes. + static void FlushToExternalStore( + std::unique_ptr<HashStoreContents> external_hash_store_contents, + std::unique_ptr<base::DictionaryValue> changed_paths_and_macs, + bool write_success); + + // Callback to be invoked only once (and subsequently reset) on the next + // FilterOnLoad event. It will be allowed to modify the |prefs| handed to + // FilterOnLoad before handing them back to this PrefHashFilter. + FilterOnLoadInterceptor filter_on_load_interceptor_; + + // A map of paths to TrackedPreferences; this map owns this individual + // TrackedPreference objects. + using TrackedPreferencesMap = + std::unordered_map<std::string, std::unique_ptr<TrackedPreference>>; + + // A map from changed paths to their corresponding TrackedPreferences (which + // aren't owned by this map). + using ChangedPathsMap = std::map<std::string, const TrackedPreference*>; + + std::unique_ptr<PrefHashStore> pref_hash_store_; + + // A store and contents on which to perform extra validations without + // triggering resets. + // Will be null if the platform does not support external validation. + const base::Optional<StoreContentsPair> external_validation_hash_store_pair_; + + // Notified if a reset occurs in a call to FilterOnLoad. + prefs::mojom::ResetOnLoadObserverPtr reset_on_load_observer_; + + TrackedPreferencesMap tracked_paths_; + + // The set of all paths whose value has changed since the last call to + // FilterSerializeData. + ChangedPathsMap changed_paths_; + + // Whether to report the validity of the super MAC at load time (via UMA). + bool report_super_mac_validity_; + + DISALLOW_COPY_AND_ASSIGN(PrefHashFilter); +}; + +#endif // SERVICES_PREFERENCES_TRACKED_PREF_HASH_FILTER_H_ diff --git a/chromium/services/preferences/tracked/pref_hash_filter_unittest.cc b/chromium/services/preferences/tracked/pref_hash_filter_unittest.cc new file mode 100644 index 00000000000..5c288dcc77d --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_filter_unittest.cc @@ -0,0 +1,1355 @@ +// Copyright 2013 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 "services/preferences/tracked/pref_hash_filter.h" + +#include <stddef.h> + +#include <map> +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" +#include "base/metrics/statistics_recorder.h" +#include "base/run_loop.h" +#include "base/values.h" +#include "components/prefs/testing_pref_store.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "services/preferences/public/cpp/tracked/configuration.h" +#include "services/preferences/public/cpp/tracked/mock_validation_delegate.h" +#include "services/preferences/public/cpp/tracked/pref_names.h" +#include "services/preferences/tracked/hash_store_contents.h" +#include "services/preferences/tracked/pref_hash_store.h" +#include "services/preferences/tracked/pref_hash_store_transaction.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using EnforcementLevel = + prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel; +using PrefTrackingStrategy = + prefs::mojom::TrackedPreferenceMetadata::PrefTrackingStrategy; +using ValueType = prefs::mojom::TrackedPreferenceMetadata::ValueType; +using ValueState = + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState; + +const char kAtomicPref[] = "atomic_pref"; +const char kAtomicPref2[] = "atomic_pref2"; +const char kAtomicPref3[] = "pref3"; +const char kAtomicPref4[] = "pref4"; +const char kReportOnlyPref[] = "report_only"; +const char kReportOnlySplitPref[] = "report_only_split_pref"; +const char kSplitPref[] = "split_pref"; + +const prefs::TrackedPreferenceMetadata kTestTrackedPrefs[] = { + {0, kAtomicPref, EnforcementLevel::ENFORCE_ON_LOAD, + PrefTrackingStrategy::ATOMIC, ValueType::PERSONAL}, + {1, kReportOnlyPref, EnforcementLevel::NO_ENFORCEMENT, + PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL}, + {2, kSplitPref, EnforcementLevel::ENFORCE_ON_LOAD, + PrefTrackingStrategy::SPLIT, ValueType::IMPERSONAL}, + {3, kReportOnlySplitPref, EnforcementLevel::NO_ENFORCEMENT, + PrefTrackingStrategy::SPLIT, ValueType::IMPERSONAL}, + {4, kAtomicPref2, EnforcementLevel::ENFORCE_ON_LOAD, + PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL}, + {5, kAtomicPref3, EnforcementLevel::ENFORCE_ON_LOAD, + PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL}, + {6, kAtomicPref4, EnforcementLevel::ENFORCE_ON_LOAD, + PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL}, +}; + +} // namespace + +// A PrefHashStore that allows simulation of CheckValue results and captures +// checked and stored values. +class MockPrefHashStore : public PrefHashStore { + public: + typedef std::pair<const void*, PrefTrackingStrategy> ValuePtrStrategyPair; + + MockPrefHashStore() + : stamp_super_mac_result_(false), + is_super_mac_valid_result_(false), + transactions_performed_(0), + transaction_active_(false) {} + + ~MockPrefHashStore() override { EXPECT_FALSE(transaction_active_); } + + // Set the result that will be returned when |path| is passed to + // |CheckValue/CheckSplitValue|. + void SetCheckResult(const std::string& path, ValueState result); + + // Set the invalid_keys that will be returned when |path| is passed to + // |CheckSplitValue|. SetCheckResult should already have been called for + // |path| with |result == CHANGED| for this to make any sense. + void SetInvalidKeysResult( + const std::string& path, + const std::vector<std::string>& invalid_keys_result); + + // Sets the value that will be returned from + // PrefHashStoreTransaction::StampSuperMAC(). + void set_stamp_super_mac_result(bool result) { + stamp_super_mac_result_ = result; + } + + // Sets the value that will be returned from + // PrefHashStoreTransaction::IsSuperMACValid(). + void set_is_super_mac_valid_result(bool result) { + is_super_mac_valid_result_ = result; + } + + // Returns the number of transactions that were performed. + size_t transactions_performed() { return transactions_performed_; } + + // Returns the number of paths checked. + size_t checked_paths_count() const { return checked_values_.size(); } + + // Returns the number of paths stored. + size_t stored_paths_count() const { return stored_values_.size(); } + + // Returns the pointer value and strategy that was passed to + // |CheckHash/CheckSplitHash| for |path|. The returned pointer could since + // have been freed and is thus not safe to dereference. + ValuePtrStrategyPair checked_value(const std::string& path) const { + std::map<std::string, ValuePtrStrategyPair>::const_iterator value = + checked_values_.find(path); + if (value != checked_values_.end()) + return value->second; + return std::make_pair(reinterpret_cast<void*>(0xBAD), + static_cast<PrefTrackingStrategy>(-1)); + } + + // Returns the pointer value that was passed to |StoreHash/StoreSplitHash| for + // |path|. The returned pointer could since have been freed and is thus not + // safe to dereference. + ValuePtrStrategyPair stored_value(const std::string& path) const { + std::map<std::string, ValuePtrStrategyPair>::const_iterator value = + stored_values_.find(path); + if (value != stored_values_.end()) + return value->second; + return std::make_pair(reinterpret_cast<void*>(0xBAD), + static_cast<PrefTrackingStrategy>(-1)); + } + + // PrefHashStore implementation. + std::unique_ptr<PrefHashStoreTransaction> BeginTransaction( + HashStoreContents* storage) override; + std::string ComputeMac(const std::string& path, + const base::Value* new_value) override; + std::unique_ptr<base::DictionaryValue> ComputeSplitMacs( + const std::string& path, + const base::DictionaryValue* split_values) override; + + private: + // A MockPrefHashStoreTransaction is handed to the caller on + // MockPrefHashStore::BeginTransaction(). It then stores state in its + // underlying MockPrefHashStore about calls it receives from that same caller + // which can later be verified in tests. + class MockPrefHashStoreTransaction : public PrefHashStoreTransaction { + public: + explicit MockPrefHashStoreTransaction(MockPrefHashStore* outer) + : outer_(outer) {} + + ~MockPrefHashStoreTransaction() override { + outer_->transaction_active_ = false; + ++outer_->transactions_performed_; + } + + // PrefHashStoreTransaction implementation. + base::StringPiece GetStoreUMASuffix() const override; + ValueState CheckValue(const std::string& path, + const base::Value* value) const override; + void StoreHash(const std::string& path, + const base::Value* new_value) override; + ValueState CheckSplitValue( + const std::string& path, + const base::DictionaryValue* initial_split_value, + std::vector<std::string>* invalid_keys) const override; + void StoreSplitHash(const std::string& path, + const base::DictionaryValue* split_value) override; + bool HasHash(const std::string& path) const override; + void ImportHash(const std::string& path, const base::Value* hash) override; + void ClearHash(const std::string& path) override; + bool IsSuperMACValid() const override; + bool StampSuperMac() override; + + private: + MockPrefHashStore* outer_; + + DISALLOW_COPY_AND_ASSIGN(MockPrefHashStoreTransaction); + }; + + // Records a call to this mock's CheckValue/CheckSplitValue methods. + ValueState RecordCheckValue(const std::string& path, + const base::Value* value, + PrefTrackingStrategy strategy); + + // Records a call to this mock's StoreHash/StoreSplitHash methods. + void RecordStoreHash(const std::string& path, + const base::Value* new_value, + PrefTrackingStrategy strategy); + + std::map<std::string, ValueState> check_results_; + std::map<std::string, std::vector<std::string>> invalid_keys_results_; + + bool stamp_super_mac_result_; + bool is_super_mac_valid_result_; + + std::map<std::string, ValuePtrStrategyPair> checked_values_; + std::map<std::string, ValuePtrStrategyPair> stored_values_; + + // Number of transactions that were performed via this MockPrefHashStore. + size_t transactions_performed_; + + // Whether a transaction is currently active (only one transaction should be + // active at a time). + bool transaction_active_; + + DISALLOW_COPY_AND_ASSIGN(MockPrefHashStore); +}; + +void MockPrefHashStore::SetCheckResult(const std::string& path, + ValueState result) { + check_results_.insert(std::make_pair(path, result)); +} + +void MockPrefHashStore::SetInvalidKeysResult( + const std::string& path, + const std::vector<std::string>& invalid_keys_result) { + // Ensure |check_results_| has a CHANGED entry for |path|. + std::map<std::string, ValueState>::const_iterator result = + check_results_.find(path); + ASSERT_TRUE(result != check_results_.end()); + ASSERT_EQ(ValueState::CHANGED, result->second); + + invalid_keys_results_.insert(std::make_pair(path, invalid_keys_result)); +} + +std::unique_ptr<PrefHashStoreTransaction> MockPrefHashStore::BeginTransaction( + HashStoreContents* storage) { + EXPECT_FALSE(transaction_active_); + return std::unique_ptr<PrefHashStoreTransaction>( + new MockPrefHashStoreTransaction(this)); +} + +std::string MockPrefHashStore::ComputeMac(const std::string& path, + const base::Value* new_value) { + return "atomic mac for: " + path; +}; + +std::unique_ptr<base::DictionaryValue> MockPrefHashStore::ComputeSplitMacs( + const std::string& path, + const base::DictionaryValue* split_values) { + std::unique_ptr<base::DictionaryValue> macs_dict(new base::DictionaryValue); + for (base::DictionaryValue::Iterator it(*split_values); !it.IsAtEnd(); + it.Advance()) { + macs_dict->SetStringWithoutPathExpansion( + it.key(), "split mac for: " + path + "/" + it.key()); + } + return macs_dict; +}; + +ValueState MockPrefHashStore::RecordCheckValue(const std::string& path, + const base::Value* value, + PrefTrackingStrategy strategy) { + // Record that |path| was checked and validate that it wasn't previously + // checked. + EXPECT_TRUE(checked_values_ + .insert(std::make_pair(path, std::make_pair(value, strategy))) + .second); + std::map<std::string, ValueState>::const_iterator result = + check_results_.find(path); + if (result != check_results_.end()) + return result->second; + return ValueState::UNCHANGED; +} + +void MockPrefHashStore::RecordStoreHash(const std::string& path, + const base::Value* new_value, + PrefTrackingStrategy strategy) { + EXPECT_TRUE( + stored_values_ + .insert(std::make_pair(path, std::make_pair(new_value, strategy))) + .second); +} + +base::StringPiece +MockPrefHashStore::MockPrefHashStoreTransaction ::GetStoreUMASuffix() const { + return "unused"; +} + +ValueState MockPrefHashStore::MockPrefHashStoreTransaction::CheckValue( + const std::string& path, + const base::Value* value) const { + return outer_->RecordCheckValue(path, value, PrefTrackingStrategy::ATOMIC); +} + +void MockPrefHashStore::MockPrefHashStoreTransaction::StoreHash( + const std::string& path, + const base::Value* new_value) { + outer_->RecordStoreHash(path, new_value, PrefTrackingStrategy::ATOMIC); +} + +ValueState MockPrefHashStore::MockPrefHashStoreTransaction::CheckSplitValue( + const std::string& path, + const base::DictionaryValue* initial_split_value, + std::vector<std::string>* invalid_keys) const { + EXPECT_TRUE(invalid_keys && invalid_keys->empty()); + + std::map<std::string, std::vector<std::string>>::const_iterator + invalid_keys_result = outer_->invalid_keys_results_.find(path); + if (invalid_keys_result != outer_->invalid_keys_results_.end()) { + invalid_keys->insert(invalid_keys->begin(), + invalid_keys_result->second.begin(), + invalid_keys_result->second.end()); + } + + return outer_->RecordCheckValue(path, initial_split_value, + PrefTrackingStrategy::SPLIT); +} + +void MockPrefHashStore::MockPrefHashStoreTransaction::StoreSplitHash( + const std::string& path, + const base::DictionaryValue* new_value) { + outer_->RecordStoreHash(path, new_value, PrefTrackingStrategy::SPLIT); +} + +bool MockPrefHashStore::MockPrefHashStoreTransaction::HasHash( + const std::string& path) const { + ADD_FAILURE() << "Unexpected call."; + return false; +} + +void MockPrefHashStore::MockPrefHashStoreTransaction::ImportHash( + const std::string& path, + const base::Value* hash) { + ADD_FAILURE() << "Unexpected call."; +} + +void MockPrefHashStore::MockPrefHashStoreTransaction::ClearHash( + const std::string& path) { + // Allow this to be called by PrefHashFilter's deprecated tracked prefs + // cleanup tasks. +} + +bool MockPrefHashStore::MockPrefHashStoreTransaction::IsSuperMACValid() const { + return outer_->is_super_mac_valid_result_; +} + +bool MockPrefHashStore::MockPrefHashStoreTransaction::StampSuperMac() { + return outer_->stamp_super_mac_result_; +} + +std::vector<prefs::mojom::TrackedPreferenceMetadataPtr> GetConfiguration( + EnforcementLevel max_enforcement_level) { + auto configuration = prefs::ConstructTrackedConfiguration(kTestTrackedPrefs); + for (const auto& metadata : configuration) { + if (metadata->enforcement_level > max_enforcement_level) + metadata->enforcement_level = max_enforcement_level; + } + return configuration; +} + +class MockHashStoreContents : public HashStoreContents { + public: + MockHashStoreContents(){}; + + // Returns the number of hashes stored. + size_t stored_hashes_count() const { return dictionary_.size(); } + + // Returns the number of paths cleared. + size_t cleared_paths_count() const { return removed_entries_.size(); } + + // Returns the stored MAC for an Atomic preference. + std::string GetStoredMac(const std::string& path) const; + // Returns the stored MAC for a Split preference. + std::string GetStoredSplitMac(const std::string& path, + const std::string& split_path) const; + + // HashStoreContents implementation. + bool IsCopyable() const override; + std::unique_ptr<HashStoreContents> MakeCopy() const override; + base::StringPiece GetUMASuffix() const override; + void Reset() override; + bool GetMac(const std::string& path, std::string* out_value) override; + bool GetSplitMacs(const std::string& path, + std::map<std::string, std::string>* split_macs) override; + void SetMac(const std::string& path, const std::string& value) override; + void SetSplitMac(const std::string& path, + const std::string& split_path, + const std::string& value) override; + void ImportEntry(const std::string& path, + const base::Value* in_value) override; + bool RemoveEntry(const std::string& path) override; + const base::DictionaryValue* GetContents() const override; + std::string GetSuperMac() const override; + void SetSuperMac(const std::string& super_mac) override; + + private: + MockHashStoreContents(MockHashStoreContents* origin_mock); + + // Records calls to this mock's SetMac/SetSplitMac methods. + void RecordSetMac(const std::string& path, const std::string& mac) { + dictionary_.SetStringWithoutPathExpansion(path, mac); + } + void RecordSetSplitMac(const std::string& path, + const std::string& split_path, + const std::string& mac) { + base::DictionaryValue* mac_dict = nullptr; + dictionary_.GetDictionaryWithoutPathExpansion(path, &mac_dict); + if (!mac_dict) { + mac_dict = new base::DictionaryValue; + dictionary_.SetWithoutPathExpansion(path, mac_dict); + } + mac_dict->SetStringWithoutPathExpansion(split_path, mac); + } + + // Records a call to this mock's RemoveEntry method. + void RecordRemoveEntry(const std::string& path) { + // Don't expect the same pref to be cleared more than once. + EXPECT_EQ(removed_entries_.end(), removed_entries_.find(path)); + removed_entries_.insert(path); + } + + base::DictionaryValue dictionary_; + std::set<std::string> removed_entries_; + + // The code being tested copies its HashStoreContents for use in a callback + // which can be executed during shutdown. To be able to capture the behavior + // of the copy, we make it forward calls to the mock it was created from. + // Once set, |origin_mock_| must outlive this instance. + MockHashStoreContents* origin_mock_; + + DISALLOW_COPY_AND_ASSIGN(MockHashStoreContents); +}; + +std::string MockHashStoreContents::GetStoredMac(const std::string& path) const { + const base::Value* out_value; + if (dictionary_.GetWithoutPathExpansion(path, &out_value)) { + const base::Value* value_as_string; + EXPECT_TRUE(out_value->GetAsString(&value_as_string)); + + return value_as_string->GetString(); + } + + return std::string(); +} + +std::string MockHashStoreContents::GetStoredSplitMac( + const std::string& path, + const std::string& split_path) const { + const base::Value* out_value; + if (dictionary_.GetWithoutPathExpansion(path, &out_value)) { + const base::DictionaryValue* value_as_dict; + EXPECT_TRUE(out_value->GetAsDictionary(&value_as_dict)); + + if (value_as_dict->GetWithoutPathExpansion(split_path, &out_value)) { + const base::Value* value_as_string; + EXPECT_TRUE(out_value->GetAsString(&value_as_string)); + + return value_as_string->GetString(); + } + } + + return std::string(); +} + +MockHashStoreContents::MockHashStoreContents(MockHashStoreContents* origin_mock) + : origin_mock_(origin_mock) {} + +bool MockHashStoreContents::IsCopyable() const { + return true; +} + +std::unique_ptr<HashStoreContents> MockHashStoreContents::MakeCopy() const { + // Return a new MockHashStoreContents which forwards all requests to this + // mock instance. + return std::unique_ptr<HashStoreContents>( + new MockHashStoreContents(const_cast<MockHashStoreContents*>(this))); +} + +base::StringPiece MockHashStoreContents::GetUMASuffix() const { + return "Unused"; +} + +void MockHashStoreContents::Reset() { + ADD_FAILURE() << "Unexpected call."; +} + +bool MockHashStoreContents::GetMac(const std::string& path, + std::string* out_value) { + ADD_FAILURE() << "Unexpected call."; + return false; +} + +bool MockHashStoreContents::GetSplitMacs( + const std::string& path, + std::map<std::string, std::string>* split_macs) { + ADD_FAILURE() << "Unexpected call."; + return false; +} + +void MockHashStoreContents::SetMac(const std::string& path, + const std::string& value) { + if (origin_mock_) + origin_mock_->RecordSetMac(path, value); + else + RecordSetMac(path, value); +} + +void MockHashStoreContents::SetSplitMac(const std::string& path, + const std::string& split_path, + const std::string& value) { + if (origin_mock_) + origin_mock_->RecordSetSplitMac(path, split_path, value); + else + RecordSetSplitMac(path, split_path, value); +} + +void MockHashStoreContents::ImportEntry(const std::string& path, + const base::Value* in_value) { + ADD_FAILURE() << "Unexpected call."; +} + +bool MockHashStoreContents::RemoveEntry(const std::string& path) { + if (origin_mock_) + origin_mock_->RecordRemoveEntry(path); + else + RecordRemoveEntry(path); + return true; +} + +const base::DictionaryValue* MockHashStoreContents::GetContents() const { + ADD_FAILURE() << "Unexpected call."; + return nullptr; +} + +std::string MockHashStoreContents::GetSuperMac() const { + ADD_FAILURE() << "Unexpected call."; + return std::string(); +} + +void MockHashStoreContents::SetSuperMac(const std::string& super_mac) { + ADD_FAILURE() << "Unexpected call."; +} + +class PrefHashFilterTest : public testing::TestWithParam<EnforcementLevel>, + public prefs::mojom::ResetOnLoadObserver { + public: + PrefHashFilterTest() + : mock_pref_hash_store_(NULL), + pref_store_contents_(new base::DictionaryValue), + mock_validation_delegate_record_(new MockValidationDelegateRecord), + mock_validation_delegate_(mock_validation_delegate_record_), + reset_recorded_(false) {} + + void SetUp() override { + base::StatisticsRecorder::Initialize(); + Reset(); + } + + protected: + // Reset the PrefHashFilter instance. + void Reset() { + // Construct a PrefHashFilter and MockPrefHashStore for the test. + InitializePrefHashFilter(GetConfiguration(GetParam())); + } + + // Initializes |pref_hash_filter_| with a PrefHashFilter that uses a + // MockPrefHashStore. The raw pointer to the MockPrefHashStore (owned by the + // PrefHashFilter) is stored in |mock_pref_hash_store_|. + void InitializePrefHashFilter( + std::vector<prefs::mojom::TrackedPreferenceMetadataPtr> configuration) { + std::unique_ptr<MockPrefHashStore> temp_mock_pref_hash_store( + new MockPrefHashStore); + std::unique_ptr<MockPrefHashStore> + temp_mock_external_validation_pref_hash_store(new MockPrefHashStore); + std::unique_ptr<MockHashStoreContents> + temp_mock_external_validation_hash_store_contents( + new MockHashStoreContents); + mock_pref_hash_store_ = temp_mock_pref_hash_store.get(); + mock_external_validation_pref_hash_store_ = + temp_mock_external_validation_pref_hash_store.get(); + mock_external_validation_hash_store_contents_ = + temp_mock_external_validation_hash_store_contents.get(); + pref_hash_filter_.reset(new PrefHashFilter( + std::move(temp_mock_pref_hash_store), + PrefHashFilter::StoreContentsPair( + std::move(temp_mock_external_validation_pref_hash_store), + std::move(temp_mock_external_validation_hash_store_contents)), + std::move(configuration), + reset_on_load_observer_bindings_.CreateInterfacePtrAndBind(this), + &mock_validation_delegate_, arraysize(kTestTrackedPrefs), true)); + } + + // Verifies whether a reset was reported by the PrefHashFiler. Also verifies + // that kPreferenceResetTime was set (or not) accordingly. + void VerifyRecordedReset(bool reset_expected) { + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(reset_expected, reset_recorded_); + EXPECT_EQ(reset_expected, pref_store_contents_->Get( + user_prefs::kPreferenceResetTime, NULL)); + } + + // Calls FilterOnLoad() on |pref_hash_Filter_|. |pref_store_contents_| is + // handed off, but should be given back to us synchronously through + // GetPrefsBack() as there is no FilterOnLoadInterceptor installed on + // |pref_hash_filter_|. + void DoFilterOnLoad(bool expect_prefs_modifications) { + pref_hash_filter_->FilterOnLoad( + base::Bind(&PrefHashFilterTest::GetPrefsBack, base::Unretained(this), + expect_prefs_modifications), + std::move(pref_store_contents_)); + } + + MockPrefHashStore* mock_pref_hash_store_; + MockPrefHashStore* mock_external_validation_pref_hash_store_; + MockHashStoreContents* mock_external_validation_hash_store_contents_; + std::unique_ptr<base::DictionaryValue> pref_store_contents_; + scoped_refptr<MockValidationDelegateRecord> mock_validation_delegate_record_; + std::unique_ptr<PrefHashFilter> pref_hash_filter_; + + private: + // Stores |prefs| back in |pref_store_contents| and ensure + // |expected_schedule_write| matches the reported |schedule_write|. + void GetPrefsBack(bool expected_schedule_write, + std::unique_ptr<base::DictionaryValue> prefs, + bool schedule_write) { + pref_store_contents_ = std::move(prefs); + EXPECT_TRUE(pref_store_contents_); + EXPECT_EQ(expected_schedule_write, schedule_write); + } + + void OnResetOnLoad() override { + // As-is |reset_recorded_| is only designed to remember a single reset, make + // sure none was previously recorded. + EXPECT_FALSE(reset_recorded_); + reset_recorded_ = true; + } + + base::MessageLoop message_loop_; + MockValidationDelegate mock_validation_delegate_; + mojo::BindingSet<prefs::mojom::ResetOnLoadObserver> + reset_on_load_observer_bindings_; + bool reset_recorded_; + + DISALLOW_COPY_AND_ASSIGN(PrefHashFilterTest); +}; + +TEST_P(PrefHashFilterTest, EmptyAndUnchanged) { + DoFilterOnLoad(false); + // All paths checked. + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_pref_hash_store_->checked_paths_count()); + // No paths stored, since they all return |UNCHANGED|. + ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count()); + // Since there was nothing in |pref_store_contents_| the checked value should + // have been NULL for all tracked preferences. + for (size_t i = 0; i < arraysize(kTestTrackedPrefs); ++i) { + ASSERT_EQ( + NULL, + mock_pref_hash_store_->checked_value(kTestTrackedPrefs[i].name).first); + } + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + VerifyRecordedReset(false); + + // Delegate saw all paths, and all unchanged. + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_validation_delegate_record_->recorded_validations_count()); + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_validation_delegate_record_->CountValidationsOfState( + ValueState::UNCHANGED)); +} + +TEST_P(PrefHashFilterTest, StampSuperMACAltersStore) { + mock_pref_hash_store_->set_stamp_super_mac_result(true); + DoFilterOnLoad(true); + // No paths stored, since they all return |UNCHANGED|. The StampSuperMAC + // result is the only reason the prefs were considered altered. + ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count()); +} + +TEST_P(PrefHashFilterTest, FilterTrackedPrefUpdate) { + base::DictionaryValue root_dict; + // Ownership of |string_value| is transfered to |root_dict|. + base::Value* string_value = new base::Value("string value"); + root_dict.Set(kAtomicPref, string_value); + + // No path should be stored on FilterUpdate. + pref_hash_filter_->FilterUpdate(kAtomicPref); + ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count()); + + // One path should be stored on FilterSerializeData. + pref_hash_filter_->FilterSerializeData(&root_dict); + ASSERT_EQ(1u, mock_pref_hash_store_->stored_paths_count()); + MockPrefHashStore::ValuePtrStrategyPair stored_value = + mock_pref_hash_store_->stored_value(kAtomicPref); + ASSERT_EQ(string_value, stored_value.first); + ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_value.second); + + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + VerifyRecordedReset(false); +} + +TEST_P(PrefHashFilterTest, ReportSuperMacValidity) { + // Do this once just to force the histogram to be defined. + DoFilterOnLoad(false); + + base::HistogramBase* histogram = base::StatisticsRecorder::FindHistogram( + "Settings.HashesDictionaryTrusted"); + ASSERT_TRUE(histogram); + + base::HistogramBase::Count initial_untrusted = + histogram->SnapshotSamples()->GetCount(0); + base::HistogramBase::Count initial_trusted = + histogram->SnapshotSamples()->GetCount(1); + + Reset(); + + // Run with an invalid super MAC. + mock_pref_hash_store_->set_is_super_mac_valid_result(false); + + DoFilterOnLoad(false); + + // Verify that the invalidity was reported. + ASSERT_EQ(initial_untrusted + 1, histogram->SnapshotSamples()->GetCount(0)); + ASSERT_EQ(initial_trusted, histogram->SnapshotSamples()->GetCount(1)); + + Reset(); + + // Run with a valid super MAC. + mock_pref_hash_store_->set_is_super_mac_valid_result(true); + + DoFilterOnLoad(false); + + // Verify that the validity was reported. + ASSERT_EQ(initial_untrusted + 1, histogram->SnapshotSamples()->GetCount(0)); + ASSERT_EQ(initial_trusted + 1, histogram->SnapshotSamples()->GetCount(1)); +} + +TEST_P(PrefHashFilterTest, FilterSplitPrefUpdate) { + base::DictionaryValue root_dict; + // Ownership of |dict_value| is transfered to |root_dict|. + base::DictionaryValue* dict_value = new base::DictionaryValue; + dict_value->SetString("a", "foo"); + dict_value->SetInteger("b", 1234); + root_dict.Set(kSplitPref, dict_value); + + // No path should be stored on FilterUpdate. + pref_hash_filter_->FilterUpdate(kSplitPref); + ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count()); + + // One path should be stored on FilterSerializeData. + pref_hash_filter_->FilterSerializeData(&root_dict); + ASSERT_EQ(1u, mock_pref_hash_store_->stored_paths_count()); + MockPrefHashStore::ValuePtrStrategyPair stored_value = + mock_pref_hash_store_->stored_value(kSplitPref); + ASSERT_EQ(dict_value, stored_value.first); + ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_value.second); + + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + VerifyRecordedReset(false); +} + +TEST_P(PrefHashFilterTest, FilterUntrackedPrefUpdate) { + base::DictionaryValue root_dict; + root_dict.Set("untracked", new base::Value("some value")); + pref_hash_filter_->FilterUpdate("untracked"); + + // No paths should be stored on FilterUpdate. + ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count()); + + // Nor on FilterSerializeData. + pref_hash_filter_->FilterSerializeData(&root_dict); + ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count()); + + // No transaction should even be started on FilterSerializeData() if there are + // no updates to perform. + ASSERT_EQ(0u, mock_pref_hash_store_->transactions_performed()); +} + +TEST_P(PrefHashFilterTest, MultiplePrefsFilterSerializeData) { + base::DictionaryValue root_dict; + // Ownership of the following values is transfered to |root_dict|. + base::Value* int_value1 = new base::Value(1); + base::Value* int_value2 = new base::Value(2); + base::Value* int_value3 = new base::Value(3); + base::Value* int_value4 = new base::Value(4); + base::DictionaryValue* dict_value = new base::DictionaryValue; + dict_value->Set("a", new base::Value(true)); + root_dict.Set(kAtomicPref, int_value1); + root_dict.Set(kAtomicPref2, int_value2); + root_dict.Set(kAtomicPref3, int_value3); + root_dict.Set("untracked", int_value4); + root_dict.Set(kSplitPref, dict_value); + + // Only update kAtomicPref, kAtomicPref3, and kSplitPref. + pref_hash_filter_->FilterUpdate(kAtomicPref); + pref_hash_filter_->FilterUpdate(kAtomicPref3); + pref_hash_filter_->FilterUpdate(kSplitPref); + ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count()); + + // Update kAtomicPref3 again, nothing should be stored still. + base::Value* int_value5 = new base::Value(5); + root_dict.Set(kAtomicPref3, int_value5); + ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths_count()); + + // On FilterSerializeData, only kAtomicPref, kAtomicPref3, and kSplitPref + // should get a new hash. + pref_hash_filter_->FilterSerializeData(&root_dict); + ASSERT_EQ(3u, mock_pref_hash_store_->stored_paths_count()); + MockPrefHashStore::ValuePtrStrategyPair stored_value_atomic1 = + mock_pref_hash_store_->stored_value(kAtomicPref); + ASSERT_EQ(int_value1, stored_value_atomic1.first); + ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_value_atomic1.second); + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + + MockPrefHashStore::ValuePtrStrategyPair stored_value_atomic3 = + mock_pref_hash_store_->stored_value(kAtomicPref3); + ASSERT_EQ(int_value5, stored_value_atomic3.first); + ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_value_atomic3.second); + + MockPrefHashStore::ValuePtrStrategyPair stored_value_split = + mock_pref_hash_store_->stored_value(kSplitPref); + ASSERT_EQ(dict_value, stored_value_split.first); + ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_value_split.second); +} + +TEST_P(PrefHashFilterTest, UnknownNullValue) { + ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL)); + ASSERT_FALSE(pref_store_contents_->Get(kSplitPref, NULL)); + // NULL values are always trusted by the PrefHashStore. + mock_pref_hash_store_->SetCheckResult(kAtomicPref, + ValueState::TRUSTED_NULL_VALUE); + mock_pref_hash_store_->SetCheckResult(kSplitPref, + ValueState::TRUSTED_NULL_VALUE); + DoFilterOnLoad(false); + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_pref_hash_store_->checked_paths_count()); + ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count()); + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + + MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value = + mock_pref_hash_store_->stored_value(kAtomicPref); + ASSERT_EQ(NULL, stored_atomic_value.first); + ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second); + + MockPrefHashStore::ValuePtrStrategyPair stored_split_value = + mock_pref_hash_store_->stored_value(kSplitPref); + ASSERT_EQ(NULL, stored_split_value.first); + ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second); + + // Delegate saw all prefs, two of which had the expected value_state. + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_validation_delegate_record_->recorded_validations_count()); + ASSERT_EQ(2u, mock_validation_delegate_record_->CountValidationsOfState( + ValueState::TRUSTED_NULL_VALUE)); + ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u, + mock_validation_delegate_record_->CountValidationsOfState( + ValueState::UNCHANGED)); + + const MockValidationDelegateRecord::ValidationEvent* validated_split_pref = + mock_validation_delegate_record_->GetEventForPath(kSplitPref); + ASSERT_EQ(PrefTrackingStrategy::SPLIT, validated_split_pref->strategy); + ASSERT_FALSE(validated_split_pref->is_personal); + const MockValidationDelegateRecord::ValidationEvent* validated_atomic_pref = + mock_validation_delegate_record_->GetEventForPath(kAtomicPref); + ASSERT_EQ(PrefTrackingStrategy::ATOMIC, validated_atomic_pref->strategy); + ASSERT_TRUE(validated_atomic_pref->is_personal); +} + +TEST_P(PrefHashFilterTest, InitialValueUnknown) { + // Ownership of these values is transfered to |pref_store_contents_|. + base::Value* string_value = new base::Value("string value"); + pref_store_contents_->Set(kAtomicPref, string_value); + + base::DictionaryValue* dict_value = new base::DictionaryValue; + dict_value->SetString("a", "foo"); + dict_value->SetInteger("b", 1234); + pref_store_contents_->Set(kSplitPref, dict_value); + + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, NULL)); + ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, NULL)); + + mock_pref_hash_store_->SetCheckResult(kAtomicPref, + ValueState::UNTRUSTED_UNKNOWN_VALUE); + mock_pref_hash_store_->SetCheckResult(kSplitPref, + ValueState::UNTRUSTED_UNKNOWN_VALUE); + // If we are enforcing, expect this to report changes. + DoFilterOnLoad(GetParam() >= EnforcementLevel::ENFORCE_ON_LOAD); + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_pref_hash_store_->checked_paths_count()); + ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count()); + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + + // Delegate saw all prefs, two of which had the expected value_state. + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_validation_delegate_record_->recorded_validations_count()); + ASSERT_EQ(2u, mock_validation_delegate_record_->CountValidationsOfState( + ValueState::UNTRUSTED_UNKNOWN_VALUE)); + ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u, + mock_validation_delegate_record_->CountValidationsOfState( + ValueState::UNCHANGED)); + + MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value = + mock_pref_hash_store_->stored_value(kAtomicPref); + MockPrefHashStore::ValuePtrStrategyPair stored_split_value = + mock_pref_hash_store_->stored_value(kSplitPref); + ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second); + ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second); + if (GetParam() == EnforcementLevel::ENFORCE_ON_LOAD) { + // Ensure the prefs were cleared and the hashes for NULL were restored if + // the current enforcement level denies seeding. + ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL)); + ASSERT_EQ(NULL, stored_atomic_value.first); + + ASSERT_FALSE(pref_store_contents_->Get(kSplitPref, NULL)); + ASSERT_EQ(NULL, stored_split_value.first); + + VerifyRecordedReset(true); + } else { + // Otherwise the values should have remained intact and the hashes should + // have been updated to match them. + const base::Value* atomic_value_in_store; + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, &atomic_value_in_store)); + ASSERT_EQ(string_value, atomic_value_in_store); + ASSERT_EQ(string_value, stored_atomic_value.first); + + const base::Value* split_value_in_store; + ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, &split_value_in_store)); + ASSERT_EQ(dict_value, split_value_in_store); + ASSERT_EQ(dict_value, stored_split_value.first); + + VerifyRecordedReset(false); + } +} + +TEST_P(PrefHashFilterTest, InitialValueTrustedUnknown) { + // Ownership of this value is transfered to |pref_store_contents_|. + base::Value* string_value = new base::Value("test"); + pref_store_contents_->Set(kAtomicPref, string_value); + + base::DictionaryValue* dict_value = new base::DictionaryValue; + dict_value->SetString("a", "foo"); + dict_value->SetInteger("b", 1234); + pref_store_contents_->Set(kSplitPref, dict_value); + + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, NULL)); + ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, NULL)); + + mock_pref_hash_store_->SetCheckResult(kAtomicPref, + ValueState::TRUSTED_UNKNOWN_VALUE); + mock_pref_hash_store_->SetCheckResult(kSplitPref, + ValueState::TRUSTED_UNKNOWN_VALUE); + DoFilterOnLoad(false); + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_pref_hash_store_->checked_paths_count()); + ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count()); + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + + // Delegate saw all prefs, two of which had the expected value_state. + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_validation_delegate_record_->recorded_validations_count()); + ASSERT_EQ(2u, mock_validation_delegate_record_->CountValidationsOfState( + ValueState::TRUSTED_UNKNOWN_VALUE)); + ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u, + mock_validation_delegate_record_->CountValidationsOfState( + ValueState::UNCHANGED)); + + // Seeding is always allowed for trusted unknown values. + const base::Value* atomic_value_in_store; + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, &atomic_value_in_store)); + ASSERT_EQ(string_value, atomic_value_in_store); + MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value = + mock_pref_hash_store_->stored_value(kAtomicPref); + ASSERT_EQ(string_value, stored_atomic_value.first); + ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second); + + const base::Value* split_value_in_store; + ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, &split_value_in_store)); + ASSERT_EQ(dict_value, split_value_in_store); + MockPrefHashStore::ValuePtrStrategyPair stored_split_value = + mock_pref_hash_store_->stored_value(kSplitPref); + ASSERT_EQ(dict_value, stored_split_value.first); + ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second); +} + +TEST_P(PrefHashFilterTest, InitialValueChanged) { + // Ownership of this value is transfered to |pref_store_contents_|. + base::Value* int_value = new base::Value(1234); + pref_store_contents_->Set(kAtomicPref, int_value); + + base::DictionaryValue* dict_value = new base::DictionaryValue; + dict_value->SetString("a", "foo"); + dict_value->SetInteger("b", 1234); + dict_value->SetInteger("c", 56); + dict_value->SetBoolean("d", false); + pref_store_contents_->Set(kSplitPref, dict_value); + + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, NULL)); + ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, NULL)); + + mock_pref_hash_store_->SetCheckResult(kAtomicPref, ValueState::CHANGED); + mock_pref_hash_store_->SetCheckResult(kSplitPref, ValueState::CHANGED); + + std::vector<std::string> mock_invalid_keys; + mock_invalid_keys.push_back("a"); + mock_invalid_keys.push_back("c"); + mock_pref_hash_store_->SetInvalidKeysResult(kSplitPref, mock_invalid_keys); + + DoFilterOnLoad(GetParam() >= EnforcementLevel::ENFORCE_ON_LOAD); + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_pref_hash_store_->checked_paths_count()); + ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count()); + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + + MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value = + mock_pref_hash_store_->stored_value(kAtomicPref); + MockPrefHashStore::ValuePtrStrategyPair stored_split_value = + mock_pref_hash_store_->stored_value(kSplitPref); + ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second); + ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second); + if (GetParam() == EnforcementLevel::ENFORCE_ON_LOAD) { + // Ensure the atomic pref was cleared and the hash for NULL was restored if + // the current enforcement level prevents changes. + ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL)); + ASSERT_EQ(NULL, stored_atomic_value.first); + + // The split pref on the other hand should only have been stripped of its + // invalid keys. + const base::Value* split_value_in_store; + ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, &split_value_in_store)); + ASSERT_EQ(2U, dict_value->size()); + ASSERT_FALSE(dict_value->HasKey("a")); + ASSERT_TRUE(dict_value->HasKey("b")); + ASSERT_FALSE(dict_value->HasKey("c")); + ASSERT_TRUE(dict_value->HasKey("d")); + ASSERT_EQ(dict_value, stored_split_value.first); + + VerifyRecordedReset(true); + } else { + // Otherwise the value should have remained intact and the hash should have + // been updated to match it. + const base::Value* atomic_value_in_store; + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, &atomic_value_in_store)); + ASSERT_EQ(int_value, atomic_value_in_store); + ASSERT_EQ(int_value, stored_atomic_value.first); + + const base::Value* split_value_in_store; + ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, &split_value_in_store)); + ASSERT_EQ(dict_value, split_value_in_store); + ASSERT_EQ(4U, dict_value->size()); + ASSERT_TRUE(dict_value->HasKey("a")); + ASSERT_TRUE(dict_value->HasKey("b")); + ASSERT_TRUE(dict_value->HasKey("c")); + ASSERT_TRUE(dict_value->HasKey("d")); + ASSERT_EQ(dict_value, stored_split_value.first); + + VerifyRecordedReset(false); + } +} + +TEST_P(PrefHashFilterTest, EmptyCleared) { + ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL)); + ASSERT_FALSE(pref_store_contents_->Get(kSplitPref, NULL)); + mock_pref_hash_store_->SetCheckResult(kAtomicPref, ValueState::CLEARED); + mock_pref_hash_store_->SetCheckResult(kSplitPref, ValueState::CLEARED); + DoFilterOnLoad(false); + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_pref_hash_store_->checked_paths_count()); + ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count()); + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + + // Delegate saw all prefs, two of which had the expected value_state. + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_validation_delegate_record_->recorded_validations_count()); + ASSERT_EQ(2u, mock_validation_delegate_record_->CountValidationsOfState( + ValueState::CLEARED)); + ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u, + mock_validation_delegate_record_->CountValidationsOfState( + ValueState::UNCHANGED)); + + // Regardless of the enforcement level, the only thing that should be done is + // to restore the hash for NULL. The value itself should still be NULL. + ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL)); + MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value = + mock_pref_hash_store_->stored_value(kAtomicPref); + ASSERT_EQ(NULL, stored_atomic_value.first); + ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second); + + ASSERT_FALSE(pref_store_contents_->Get(kSplitPref, NULL)); + MockPrefHashStore::ValuePtrStrategyPair stored_split_value = + mock_pref_hash_store_->stored_value(kSplitPref); + ASSERT_EQ(NULL, stored_split_value.first); + ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second); +} + +TEST_P(PrefHashFilterTest, InitialValueUnchangedLegacyId) { + // Ownership of these values is transfered to |pref_store_contents_|. + base::Value* string_value = new base::Value("string value"); + pref_store_contents_->Set(kAtomicPref, string_value); + + base::DictionaryValue* dict_value = new base::DictionaryValue; + dict_value->SetString("a", "foo"); + dict_value->SetInteger("b", 1234); + pref_store_contents_->Set(kSplitPref, dict_value); + + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, NULL)); + ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, NULL)); + + mock_pref_hash_store_->SetCheckResult(kAtomicPref, ValueState::SECURE_LEGACY); + mock_pref_hash_store_->SetCheckResult(kSplitPref, ValueState::SECURE_LEGACY); + DoFilterOnLoad(false); + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_pref_hash_store_->checked_paths_count()); + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + + // Delegate saw all prefs, two of which had the expected value_state. + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_validation_delegate_record_->recorded_validations_count()); + ASSERT_EQ(2u, mock_validation_delegate_record_->CountValidationsOfState( + ValueState::SECURE_LEGACY)); + ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u, + mock_validation_delegate_record_->CountValidationsOfState( + ValueState::UNCHANGED)); + + // Ensure that both the atomic and split hashes were restored. + ASSERT_EQ(2u, mock_pref_hash_store_->stored_paths_count()); + + // In all cases, the values should have remained intact and the hashes should + // have been updated to match them. + + MockPrefHashStore::ValuePtrStrategyPair stored_atomic_value = + mock_pref_hash_store_->stored_value(kAtomicPref); + ASSERT_EQ(PrefTrackingStrategy::ATOMIC, stored_atomic_value.second); + const base::Value* atomic_value_in_store; + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, &atomic_value_in_store)); + ASSERT_EQ(string_value, atomic_value_in_store); + ASSERT_EQ(string_value, stored_atomic_value.first); + + MockPrefHashStore::ValuePtrStrategyPair stored_split_value = + mock_pref_hash_store_->stored_value(kSplitPref); + ASSERT_EQ(PrefTrackingStrategy::SPLIT, stored_split_value.second); + const base::Value* split_value_in_store; + ASSERT_TRUE(pref_store_contents_->Get(kSplitPref, &split_value_in_store)); + ASSERT_EQ(dict_value, split_value_in_store); + ASSERT_EQ(dict_value, stored_split_value.first); + + VerifyRecordedReset(false); +} + +TEST_P(PrefHashFilterTest, DontResetReportOnly) { + // Ownership of these values is transfered to |pref_store_contents_|. + base::Value* int_value1 = new base::Value(1); + base::Value* int_value2 = new base::Value(2); + base::Value* report_only_val = new base::Value(3); + base::DictionaryValue* report_only_split_val = new base::DictionaryValue; + report_only_split_val->SetInteger("a", 1234); + pref_store_contents_->Set(kAtomicPref, int_value1); + pref_store_contents_->Set(kAtomicPref2, int_value2); + pref_store_contents_->Set(kReportOnlyPref, report_only_val); + pref_store_contents_->Set(kReportOnlySplitPref, report_only_split_val); + + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, NULL)); + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref2, NULL)); + ASSERT_TRUE(pref_store_contents_->Get(kReportOnlyPref, NULL)); + ASSERT_TRUE(pref_store_contents_->Get(kReportOnlySplitPref, NULL)); + + mock_pref_hash_store_->SetCheckResult(kAtomicPref, ValueState::CHANGED); + mock_pref_hash_store_->SetCheckResult(kAtomicPref2, ValueState::CHANGED); + mock_pref_hash_store_->SetCheckResult(kReportOnlyPref, ValueState::CHANGED); + mock_pref_hash_store_->SetCheckResult(kReportOnlySplitPref, + ValueState::CHANGED); + + DoFilterOnLoad(GetParam() >= EnforcementLevel::ENFORCE_ON_LOAD); + // All prefs should be checked and a new hash should be stored for each tested + // pref. + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_pref_hash_store_->checked_paths_count()); + ASSERT_EQ(4u, mock_pref_hash_store_->stored_paths_count()); + ASSERT_EQ(1u, mock_pref_hash_store_->transactions_performed()); + + // Delegate saw all prefs, four of which had the expected value_state. + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_validation_delegate_record_->recorded_validations_count()); + ASSERT_EQ(4u, mock_validation_delegate_record_->CountValidationsOfState( + ValueState::CHANGED)); + ASSERT_EQ(arraysize(kTestTrackedPrefs) - 4u, + mock_validation_delegate_record_->CountValidationsOfState( + ValueState::UNCHANGED)); + + // No matter what the enforcement level is, the report only pref should never + // be reset. + ASSERT_TRUE(pref_store_contents_->Get(kReportOnlyPref, NULL)); + ASSERT_TRUE(pref_store_contents_->Get(kReportOnlySplitPref, NULL)); + ASSERT_EQ(report_only_val, + mock_pref_hash_store_->stored_value(kReportOnlyPref).first); + ASSERT_EQ(report_only_split_val, + mock_pref_hash_store_->stored_value(kReportOnlySplitPref).first); + + // All other prefs should have been reset if the enforcement level allows it. + if (GetParam() == EnforcementLevel::ENFORCE_ON_LOAD) { + ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref, NULL)); + ASSERT_FALSE(pref_store_contents_->Get(kAtomicPref2, NULL)); + ASSERT_EQ(NULL, mock_pref_hash_store_->stored_value(kAtomicPref).first); + ASSERT_EQ(NULL, mock_pref_hash_store_->stored_value(kAtomicPref2).first); + + VerifyRecordedReset(true); + } else { + const base::Value* value_in_store; + const base::Value* value_in_store2; + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref, &value_in_store)); + ASSERT_TRUE(pref_store_contents_->Get(kAtomicPref2, &value_in_store2)); + ASSERT_EQ(int_value1, value_in_store); + ASSERT_EQ(int_value1, + mock_pref_hash_store_->stored_value(kAtomicPref).first); + ASSERT_EQ(int_value2, value_in_store2); + ASSERT_EQ(int_value2, + mock_pref_hash_store_->stored_value(kAtomicPref2).first); + + VerifyRecordedReset(false); + } +} + +TEST_P(PrefHashFilterTest, CallFilterSerializeDataCallbacks) { + base::DictionaryValue root_dict; + // Ownership of the following values is transfered to |root_dict|. + base::Value* int_value1 = new base::Value(1); + base::Value* int_value2 = new base::Value(2); + base::DictionaryValue* dict_value = new base::DictionaryValue; + dict_value->Set("a", new base::Value(true)); + root_dict.Set(kAtomicPref, int_value1); + root_dict.Set(kAtomicPref2, int_value2); + root_dict.Set(kSplitPref, dict_value); + + // Skip updating kAtomicPref2. + pref_hash_filter_->FilterUpdate(kAtomicPref); + pref_hash_filter_->FilterUpdate(kSplitPref); + + PrefHashFilter::OnWriteCallbackPair callbacks = + pref_hash_filter_->FilterSerializeData(&root_dict); + + ASSERT_FALSE(callbacks.first.is_null()); + + // Prefs should be cleared from the external validation store only once the + // before-write callback is run. + ASSERT_EQ( + 0u, mock_external_validation_hash_store_contents_->cleared_paths_count()); + callbacks.first.Run(); + ASSERT_EQ( + 2u, mock_external_validation_hash_store_contents_->cleared_paths_count()); + + // No pref write should occur before the after-write callback is run. + ASSERT_EQ( + 0u, mock_external_validation_hash_store_contents_->stored_hashes_count()); + + callbacks.second.Run(true); + + ASSERT_EQ( + 2u, mock_external_validation_hash_store_contents_->stored_hashes_count()); + ASSERT_EQ( + "atomic mac for: atomic_pref", + mock_external_validation_hash_store_contents_->GetStoredMac(kAtomicPref)); + ASSERT_EQ("split mac for: split_pref/a", + mock_external_validation_hash_store_contents_->GetStoredSplitMac( + kSplitPref, "a")); + + // The callbacks should write directly to the contents without going through + // a pref hash store. + ASSERT_EQ(0u, + mock_external_validation_pref_hash_store_->stored_paths_count()); +} + +TEST_P(PrefHashFilterTest, CallFilterSerializeDataCallbacksWithFailure) { + base::DictionaryValue root_dict; + // Ownership of the following values is transfered to |root_dict|. + base::Value* int_value1 = new base::Value(1); + root_dict.Set(kAtomicPref, int_value1); + + // Only update kAtomicPref. + pref_hash_filter_->FilterUpdate(kAtomicPref); + + PrefHashFilter::OnWriteCallbackPair callbacks = + pref_hash_filter_->FilterSerializeData(&root_dict); + + ASSERT_FALSE(callbacks.first.is_null()); + + callbacks.first.Run(); + + // The pref should have been cleared from the external validation store. + ASSERT_EQ( + 1u, mock_external_validation_hash_store_contents_->cleared_paths_count()); + + callbacks.second.Run(false); + + // Expect no writes to the external validation hash store contents. + ASSERT_EQ(0u, + mock_external_validation_pref_hash_store_->stored_paths_count()); + ASSERT_EQ( + 0u, mock_external_validation_hash_store_contents_->stored_hashes_count()); +} + +TEST_P(PrefHashFilterTest, ExternalValidationValueChanged) { + // Ownership of this value is transfered to |pref_store_contents_|. + base::Value* int_value = new base::Value(1234); + pref_store_contents_->Set(kAtomicPref, int_value); + + base::DictionaryValue* dict_value = new base::DictionaryValue; + dict_value->SetString("a", "foo"); + dict_value->SetInteger("b", 1234); + dict_value->SetInteger("c", 56); + dict_value->SetBoolean("d", false); + pref_store_contents_->Set(kSplitPref, dict_value); + + mock_external_validation_pref_hash_store_->SetCheckResult( + kAtomicPref, ValueState::CHANGED); + mock_external_validation_pref_hash_store_->SetCheckResult( + kSplitPref, ValueState::CHANGED); + + std::vector<std::string> mock_invalid_keys; + mock_invalid_keys.push_back("a"); + mock_invalid_keys.push_back("c"); + mock_external_validation_pref_hash_store_->SetInvalidKeysResult( + kSplitPref, mock_invalid_keys); + + DoFilterOnLoad(false); + + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_external_validation_pref_hash_store_->checked_paths_count()); + ASSERT_EQ(2u, + mock_external_validation_pref_hash_store_->stored_paths_count()); + ASSERT_EQ( + 1u, mock_external_validation_pref_hash_store_->transactions_performed()); + + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_validation_delegate_record_->recorded_validations_count()); + + // Regular validation should not have any CHANGED prefs. + ASSERT_EQ(arraysize(kTestTrackedPrefs), + mock_validation_delegate_record_->CountValidationsOfState( + ValueState::UNCHANGED)); + + // External validation should have two CHANGED prefs (kAtomic and kSplit). + ASSERT_EQ(2u, + mock_validation_delegate_record_->CountExternalValidationsOfState( + ValueState::CHANGED)); + ASSERT_EQ(arraysize(kTestTrackedPrefs) - 2u, + mock_validation_delegate_record_->CountExternalValidationsOfState( + ValueState::UNCHANGED)); +} + +INSTANTIATE_TEST_CASE_P(PrefHashFilterTestInstance, + PrefHashFilterTest, + testing::Values(EnforcementLevel::NO_ENFORCEMENT, + EnforcementLevel::ENFORCE_ON_LOAD)); diff --git a/chromium/services/preferences/tracked/pref_hash_store.h b/chromium/services/preferences/tracked/pref_hash_store.h new file mode 100644 index 00000000000..4716834c946 --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_store.h @@ -0,0 +1,48 @@ +// Copyright 2013 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 SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_H_ +#define SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_H_ + +#include <memory> +#include <string> + +#include "base/values.h" +#include "services/preferences/tracked/pref_hash_store_transaction.h" + +class HashStoreContents; +class PrefHashStoreTransaction; + +// Holds the configuration and implementation used to calculate and verify +// preference MACs. +// TODO(gab): Rename this class as it is no longer a store. +class PrefHashStore { + public: + virtual ~PrefHashStore() {} + + // Returns a PrefHashStoreTransaction which can be used to perform a series + // of operations on the hash store. |storage| MAY be used as the backing store + // depending on the implementation. Therefore the HashStoreContents used for + // related transactions should correspond to the same underlying data store. + // |storage| must outlive the returned transaction. + virtual std::unique_ptr<PrefHashStoreTransaction> BeginTransaction( + HashStoreContents* storage) = 0; + + // Computes the MAC to be associated with |path| and |value| in this store. + // PrefHashStoreTransaction typically uses this internally but it's also + // exposed for users that want to compute MACs ahead of time for asynchronous + // operations. + virtual std::string ComputeMac(const std::string& path, + const base::Value* value) = 0; + + // Computes the MAC to be associated with |path| and |split_values| in this + // store. PrefHashStoreTransaction typically uses this internally but it's + // also exposed for users that want to compute MACs ahead of time for + // asynchronous operations. + virtual std::unique_ptr<base::DictionaryValue> ComputeSplitMacs( + const std::string& path, + const base::DictionaryValue* split_values) = 0; +}; + +#endif // SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_H_ diff --git a/chromium/services/preferences/tracked/pref_hash_store_impl.cc b/chromium/services/preferences/tracked/pref_hash_store_impl.cc new file mode 100644 index 00000000000..522b013c5bc --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_store_impl.cc @@ -0,0 +1,321 @@ +// Copyright 2013 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 "services/preferences/tracked/pref_hash_store_impl.h" + +#include <stddef.h> +#include <utility> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/metrics/histogram_macros.h" +#include "services/preferences/tracked/device_id.h" +#include "services/preferences/tracked/hash_store_contents.h" + +namespace { + +using ValueState = + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState; + +// Returns a deterministic ID for this machine. +std::string GenerateDeviceId() { + static std::string cached_device_id; + if (!cached_device_id.empty()) + return cached_device_id; + + std::string device_id; + MachineIdStatus status = GetDeterministicMachineSpecificId(&device_id); + if (status != MachineIdStatus::NOT_IMPLEMENTED) { + // TODO(proberge): Remove this histogram once we validate that machine id + // generation is not flaky and consider adding a CHECK or DCHECK. + UMA_HISTOGRAM_BOOLEAN("Settings.MachineIdGenerationSuccess", + status == MachineIdStatus::SUCCESS); + } + + if (status == MachineIdStatus::SUCCESS) { + cached_device_id = device_id; + return device_id; + } + + return std::string(); +} + +} // namespace + +class PrefHashStoreImpl::PrefHashStoreTransactionImpl + : public PrefHashStoreTransaction { + public: + // Constructs a PrefHashStoreTransactionImpl which can use the private + // members of its |outer| PrefHashStoreImpl. + PrefHashStoreTransactionImpl(PrefHashStoreImpl* outer, + HashStoreContents* storage); + ~PrefHashStoreTransactionImpl() override; + + // PrefHashStoreTransaction implementation. + base::StringPiece GetStoreUMASuffix() const override; + ValueState CheckValue(const std::string& path, + const base::Value* value) const override; + void StoreHash(const std::string& path, const base::Value* value) override; + ValueState CheckSplitValue( + const std::string& path, + const base::DictionaryValue* initial_split_value, + std::vector<std::string>* invalid_keys) const override; + void StoreSplitHash(const std::string& path, + const base::DictionaryValue* split_value) override; + bool HasHash(const std::string& path) const override; + void ImportHash(const std::string& path, const base::Value* hash) override; + void ClearHash(const std::string& path) override; + bool IsSuperMACValid() const override; + bool StampSuperMac() override; + + private: + PrefHashStoreImpl* outer_; + HashStoreContents* contents_; + + bool super_mac_valid_; + bool super_mac_dirty_; + + DISALLOW_COPY_AND_ASSIGN(PrefHashStoreTransactionImpl); +}; + +PrefHashStoreImpl::PrefHashStoreImpl(const std::string& seed, + const std::string& legacy_device_id, + bool use_super_mac) + : pref_hash_calculator_(seed, GenerateDeviceId(), legacy_device_id), + use_super_mac_(use_super_mac) {} + +PrefHashStoreImpl::~PrefHashStoreImpl() {} + +std::unique_ptr<PrefHashStoreTransaction> PrefHashStoreImpl::BeginTransaction( + HashStoreContents* storage) { + return std::unique_ptr<PrefHashStoreTransaction>( + new PrefHashStoreTransactionImpl(this, std::move(storage))); +} + +std::string PrefHashStoreImpl::ComputeMac(const std::string& path, + const base::Value* value) { + return pref_hash_calculator_.Calculate(path, value); +} + +std::unique_ptr<base::DictionaryValue> PrefHashStoreImpl::ComputeSplitMacs( + const std::string& path, + const base::DictionaryValue* split_values) { + DCHECK(split_values); + + std::string keyed_path(path); + keyed_path.push_back('.'); + const size_t common_part_length = keyed_path.length(); + + std::unique_ptr<base::DictionaryValue> split_macs(new base::DictionaryValue); + + for (base::DictionaryValue::Iterator it(*split_values); !it.IsAtEnd(); + it.Advance()) { + // Keep the common part from the old |keyed_path| and replace the key to + // get the new |keyed_path|. + keyed_path.replace(common_part_length, std::string::npos, it.key()); + + split_macs->SetStringWithoutPathExpansion( + it.key(), ComputeMac(keyed_path, &it.value())); + } + + return split_macs; +} + +PrefHashStoreImpl::PrefHashStoreTransactionImpl::PrefHashStoreTransactionImpl( + PrefHashStoreImpl* outer, + HashStoreContents* storage) + : outer_(outer), + contents_(std::move(storage)), + super_mac_valid_(false), + super_mac_dirty_(false) { + if (!outer_->use_super_mac_) + return; + + // The store must have a valid super MAC to be trusted. + std::string super_mac = contents_->GetSuperMac(); + if (super_mac.empty()) + return; + + super_mac_valid_ = + outer_->pref_hash_calculator_.Validate( + "", contents_->GetContents(), super_mac) == PrefHashCalculator::VALID; +} + +PrefHashStoreImpl::PrefHashStoreTransactionImpl:: + ~PrefHashStoreTransactionImpl() { + if (super_mac_dirty_ && outer_->use_super_mac_) { + // Get the dictionary of hashes (or NULL if it doesn't exist). + const base::DictionaryValue* hashes_dict = contents_->GetContents(); + contents_->SetSuperMac(outer_->ComputeMac("", hashes_dict)); + } +} + +base::StringPiece +PrefHashStoreImpl::PrefHashStoreTransactionImpl::GetStoreUMASuffix() const { + return contents_->GetUMASuffix(); +} + +ValueState PrefHashStoreImpl::PrefHashStoreTransactionImpl::CheckValue( + const std::string& path, + const base::Value* initial_value) const { + std::string last_hash; + contents_->GetMac(path, &last_hash); + + if (last_hash.empty()) { + // In the absence of a hash for this pref, always trust a NULL value, but + // only trust an existing value if the initial hashes dictionary is trusted. + if (!initial_value) + return ValueState::TRUSTED_NULL_VALUE; + else if (super_mac_valid_) + return ValueState::TRUSTED_UNKNOWN_VALUE; + else + return ValueState::UNTRUSTED_UNKNOWN_VALUE; + } + + PrefHashCalculator::ValidationResult validation_result = + outer_->pref_hash_calculator_.Validate(path, initial_value, last_hash); + switch (validation_result) { + case PrefHashCalculator::VALID: + return ValueState::UNCHANGED; + case PrefHashCalculator::VALID_SECURE_LEGACY: + return ValueState::SECURE_LEGACY; + case PrefHashCalculator::INVALID: + return initial_value ? ValueState::CHANGED : ValueState::CLEARED; + } + NOTREACHED() << "Unexpected PrefHashCalculator::ValidationResult: " + << validation_result; + return ValueState::UNTRUSTED_UNKNOWN_VALUE; +} + +void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreHash( + const std::string& path, + const base::Value* new_value) { + const std::string mac = outer_->ComputeMac(path, new_value); + contents_->SetMac(path, mac); + super_mac_dirty_ = true; +} + +ValueState PrefHashStoreImpl::PrefHashStoreTransactionImpl::CheckSplitValue( + const std::string& path, + const base::DictionaryValue* initial_split_value, + std::vector<std::string>* invalid_keys) const { + DCHECK(invalid_keys && invalid_keys->empty()); + + std::map<std::string, std::string> split_macs; + const bool has_hashes = contents_->GetSplitMacs(path, &split_macs); + + // Treat NULL and empty the same; otherwise we would need to store a hash for + // the entire dictionary (or some other special beacon) to differentiate these + // two cases which are really the same for dictionaries. + if (!initial_split_value || initial_split_value->empty()) + return has_hashes ? ValueState::CLEARED : ValueState::UNCHANGED; + + if (!has_hashes) + return super_mac_valid_ ? ValueState::TRUSTED_UNKNOWN_VALUE + : ValueState::UNTRUSTED_UNKNOWN_VALUE; + + bool has_secure_legacy_id_hashes = false; + std::string keyed_path(path); + keyed_path.push_back('.'); + const size_t common_part_length = keyed_path.length(); + for (base::DictionaryValue::Iterator it(*initial_split_value); !it.IsAtEnd(); + it.Advance()) { + std::map<std::string, std::string>::iterator entry = + split_macs.find(it.key()); + if (entry == split_macs.end()) { + invalid_keys->push_back(it.key()); + } else { + // Keep the common part from the old |keyed_path| and replace the key to + // get the new |keyed_path|. + keyed_path.replace(common_part_length, std::string::npos, it.key()); + switch (outer_->pref_hash_calculator_.Validate(keyed_path, &it.value(), + entry->second)) { + case PrefHashCalculator::VALID: + break; + case PrefHashCalculator::VALID_SECURE_LEGACY: + // Secure legacy device IDs based hashes are still accepted, but we + // should make sure to notify the caller for them to update the legacy + // hashes. + has_secure_legacy_id_hashes = true; + break; + case PrefHashCalculator::INVALID: + invalid_keys->push_back(it.key()); + break; + } + // Remove processed MACs, remaining MACs at the end will also be + // considered invalid. + split_macs.erase(entry); + } + } + + // Anything left in the map is missing from the data. + for (std::map<std::string, std::string>::const_iterator it = + split_macs.begin(); + it != split_macs.end(); ++it) { + invalid_keys->push_back(it->first); + } + + return invalid_keys->empty() + ? (has_secure_legacy_id_hashes ? ValueState::SECURE_LEGACY + : ValueState::UNCHANGED) + : ValueState::CHANGED; +} + +void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreSplitHash( + const std::string& path, + const base::DictionaryValue* split_value) { + contents_->RemoveEntry(path); + + if (split_value) { + std::unique_ptr<base::DictionaryValue> split_macs = + outer_->ComputeSplitMacs(path, split_value); + + for (base::DictionaryValue::Iterator it(*split_macs); !it.IsAtEnd(); + it.Advance()) { + const base::Value* value_as_string; + bool is_string = it.value().GetAsString(&value_as_string); + DCHECK(is_string); + + contents_->SetSplitMac(path, it.key(), value_as_string->GetString()); + } + } + super_mac_dirty_ = true; +} + +bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::HasHash( + const std::string& path) const { + std::string out_value; + std::map<std::string, std::string> out_values; + return contents_->GetMac(path, &out_value) || + contents_->GetSplitMacs(path, &out_values); +} + +void PrefHashStoreImpl::PrefHashStoreTransactionImpl::ImportHash( + const std::string& path, + const base::Value* hash) { + DCHECK(hash); + + contents_->ImportEntry(path, hash); + + if (super_mac_valid_) + super_mac_dirty_ = true; +} + +void PrefHashStoreImpl::PrefHashStoreTransactionImpl::ClearHash( + const std::string& path) { + if (contents_->RemoveEntry(path) && super_mac_valid_) { + super_mac_dirty_ = true; + } +} + +bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::IsSuperMACValid() const { + return super_mac_valid_; +} + +bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::StampSuperMac() { + if (!outer_->use_super_mac_ || super_mac_valid_) + return false; + super_mac_dirty_ = true; + return true; +} diff --git a/chromium/services/preferences/tracked/pref_hash_store_impl.h b/chromium/services/preferences/tracked/pref_hash_store_impl.h new file mode 100644 index 00000000000..8e7ae8a796c --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_store_impl.h @@ -0,0 +1,61 @@ +// Copyright 2013 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 SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_IMPL_H_ +#define SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_IMPL_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "services/preferences/tracked/pref_hash_calculator.h" +#include "services/preferences/tracked/pref_hash_store.h" + +// Implements PrefHashStoreImpl by storing preference hashes in a +// HashStoreContents. +class PrefHashStoreImpl : public PrefHashStore { + public: + enum StoreVersion { + // No hashes have been stored in this PrefHashStore yet. + VERSION_UNINITIALIZED = 0, + // The hashes in this PrefHashStore were stored before the introduction + // of a version number and should be re-initialized. + VERSION_PRE_MIGRATION = 1, + // The hashes in this PrefHashStore were stored using the latest algorithm. + VERSION_LATEST = 2, + }; + + // Constructs a PrefHashStoreImpl that calculates hashes using + // |seed| and |legacy_device_id| and stores them in |contents|. + // + // The same |seed| and |legacy_device_id| must be used to load and validate + // previously stored hashes in |contents|. + PrefHashStoreImpl(const std::string& seed, + const std::string& legacy_device_id, + bool use_super_mac); + + ~PrefHashStoreImpl() override; + + // Clears the contents of this PrefHashStore. |IsInitialized()| will return + // false after this call. + void Reset(); + + // PrefHashStore implementation. + std::unique_ptr<PrefHashStoreTransaction> BeginTransaction( + HashStoreContents* storage) override; + + std::string ComputeMac(const std::string& path, + const base::Value* new_value) override; + std::unique_ptr<base::DictionaryValue> ComputeSplitMacs( + const std::string& path, + const base::DictionaryValue* split_values) override; + + private: + class PrefHashStoreTransactionImpl; + + const PrefHashCalculator pref_hash_calculator_; + bool use_super_mac_; + + DISALLOW_COPY_AND_ASSIGN(PrefHashStoreImpl); +}; + +#endif // SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_IMPL_H_ diff --git a/chromium/services/preferences/tracked/pref_hash_store_impl_unittest.cc b/chromium/services/preferences/tracked/pref_hash_store_impl_unittest.cc new file mode 100644 index 00000000000..3c6ca13126f --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_store_impl_unittest.cc @@ -0,0 +1,507 @@ +// Copyright 2013 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 "services/preferences/tracked/pref_hash_store_impl.h" + +#include <string> + +#include "base/macros.h" +#include "base/values.h" +#include "services/preferences/tracked/dictionary_hash_store_contents.h" +#include "services/preferences/tracked/hash_store_contents.h" +#include "services/preferences/tracked/pref_hash_store_transaction.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ValueState = + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState; + +class PrefHashStoreImplTest : public testing::Test { + public: + PrefHashStoreImplTest() : contents_(&pref_store_contents_) {} + + protected: + HashStoreContents* GetHashStoreContents() { return &contents_; } + + private: + base::DictionaryValue pref_store_contents_; + // Must be declared after |pref_store_contents_| as it needs to be outlived + // by it. + DictionaryHashStoreContents contents_; + + DISALLOW_COPY_AND_ASSIGN(PrefHashStoreImplTest); +}; + +TEST_F(PrefHashStoreImplTest, ComputeMac) { + base::Value string_1("string1"); + base::Value string_2("string2"); + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + + std::string computed_mac_1 = pref_hash_store.ComputeMac("path1", &string_1); + std::string computed_mac_2 = pref_hash_store.ComputeMac("path1", &string_2); + std::string computed_mac_3 = pref_hash_store.ComputeMac("path2", &string_1); + + // Quick sanity checks here, see pref_hash_calculator_unittest.cc for more + // complete tests. + EXPECT_EQ(computed_mac_1, pref_hash_store.ComputeMac("path1", &string_1)); + EXPECT_NE(computed_mac_1, computed_mac_2); + EXPECT_NE(computed_mac_1, computed_mac_3); + EXPECT_EQ(64U, computed_mac_1.size()); +} + +TEST_F(PrefHashStoreImplTest, ComputeSplitMacs) { + base::DictionaryValue dict; + dict.Set("a", new base::Value("string1")); + dict.Set("b", new base::Value("string2")); + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + + std::unique_ptr<base::DictionaryValue> computed_macs = + pref_hash_store.ComputeSplitMacs("foo.bar", &dict); + + std::string mac_1; + std::string mac_2; + ASSERT_TRUE(computed_macs->GetString("a", &mac_1)); + ASSERT_TRUE(computed_macs->GetString("b", &mac_2)); + + EXPECT_EQ(2U, computed_macs->size()); + + base::Value string_1("string1"); + base::Value string_2("string2"); + EXPECT_EQ(pref_hash_store.ComputeMac("foo.bar.a", &string_1), mac_1); + EXPECT_EQ(pref_hash_store.ComputeMac("foo.bar.b", &string_2), mac_2); +} + +TEST_F(PrefHashStoreImplTest, AtomicHashStoreAndCheck) { + base::Value string_1("string1"); + base::Value string_2("string2"); + + { + // 32 NULL bytes is the seed that was used to generate the legacy hash. + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + + // Only NULL should be trusted in the absence of a hash. + EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE, + transaction->CheckValue("path1", &string_1)); + EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE, + transaction->CheckValue("path1", NULL)); + + transaction->StoreHash("path1", &string_1); + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckValue("path1", &string_1)); + EXPECT_EQ(ValueState::CLEARED, transaction->CheckValue("path1", NULL)); + transaction->StoreHash("path1", NULL); + EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckValue("path1", NULL)); + EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2)); + + base::DictionaryValue dict; + dict.Set("a", new base::Value("foo")); + dict.Set("d", new base::Value("bad")); + dict.Set("b", new base::Value("bar")); + dict.Set("c", new base::Value("baz")); + + transaction->StoreHash("path1", &dict); + EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckValue("path1", &dict)); + } + + ASSERT_FALSE(GetHashStoreContents()->GetSuperMac().empty()); + + { + // |pref_hash_store2| should trust its initial hashes dictionary and thus + // trust new unknown values. + PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store2.BeginTransaction(GetHashStoreContents())); + EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE, + transaction->CheckValue("new_path", &string_1)); + EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE, + transaction->CheckValue("new_path", &string_2)); + EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE, + transaction->CheckValue("new_path", NULL)); + } + + // Manually corrupt the super MAC. + GetHashStoreContents()->SetSuperMac(std::string(64, 'A')); + + { + // |pref_hash_store3| should no longer trust its initial hashes dictionary + // and thus shouldn't trust non-NULL unknown values. + PrefHashStoreImpl pref_hash_store3(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store3.BeginTransaction(GetHashStoreContents())); + EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE, + transaction->CheckValue("new_path", &string_1)); + EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE, + transaction->CheckValue("new_path", &string_2)); + EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE, + transaction->CheckValue("new_path", NULL)); + } +} + +TEST_F(PrefHashStoreImplTest, ImportExportOperations) { + base::Value string_1("string1"); + base::Value string_2("string2"); + + // Initial state: no super MAC. + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + ASSERT_FALSE(transaction->IsSuperMACValid()); + + ASSERT_FALSE(transaction->HasHash("path1")); + + // Storing a hash will stamp the super MAC. + transaction->StoreHash("path1", &string_1); + + ASSERT_TRUE(transaction->HasHash("path1")); + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckValue("path1", &string_1)); + EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2)); + } + + // Make a copy of the stored hash for future use. + const base::Value* hash = NULL; + ASSERT_TRUE(GetHashStoreContents()->GetContents()->Get("path1", &hash)); + std::unique_ptr<base::Value> path_1_string_1_hash_copy(hash->DeepCopy()); + hash = NULL; + + // Verify that the super MAC was stamped. + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + ASSERT_TRUE(transaction->IsSuperMACValid()); + ASSERT_TRUE(transaction->HasHash("path1")); + + // Clearing the hash should preserve validity. + transaction->ClearHash("path1"); + + // The effects of the clear should be immediately visible. + ASSERT_FALSE(transaction->HasHash("path1")); + EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE, + transaction->CheckValue("path1", NULL)); + EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE, + transaction->CheckValue("path1", &string_1)); + } + + // Verify that validity was preserved and that the clear took effect. + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + ASSERT_TRUE(transaction->IsSuperMACValid()); + ASSERT_FALSE(transaction->HasHash("path1")); + } + + // Invalidate the super MAC. + GetHashStoreContents()->SetSuperMac(std::string()); + + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + ASSERT_FALSE(transaction->IsSuperMACValid()); + ASSERT_FALSE(transaction->HasHash("path1")); + + // An import should preserve invalidity. + transaction->ImportHash("path1", path_1_string_1_hash_copy.get()); + + ASSERT_TRUE(transaction->HasHash("path1")); + + // The imported hash should be usable for validating the original value. + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckValue("path1", &string_1)); + } + + // Verify that invalidity was preserved and that the import took effect. + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + ASSERT_FALSE(transaction->IsSuperMACValid()); + ASSERT_TRUE(transaction->HasHash("path1")); + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckValue("path1", &string_1)); + + // After clearing the hash, non-null values are UNTRUSTED_UNKNOWN. + transaction->ClearHash("path1"); + + EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE, + transaction->CheckValue("path1", NULL)); + EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE, + transaction->CheckValue("path1", &string_1)); + } + + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + ASSERT_FALSE(transaction->IsSuperMACValid()); + + // Test StampSuperMac. + transaction->StampSuperMac(); + } + + // Verify that the store is now valid. + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + ASSERT_TRUE(transaction->IsSuperMACValid()); + + // Store the hash of a different value to test an "over-import". + transaction->StoreHash("path1", &string_2); + EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_1)); + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckValue("path1", &string_2)); + } + + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + ASSERT_TRUE(transaction->IsSuperMACValid()); + + // "Over-import". An import should preserve validity. + transaction->ImportHash("path1", path_1_string_1_hash_copy.get()); + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckValue("path1", &string_1)); + EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2)); + } + + // Verify that validity was preserved and the "over-import" took effect. + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + ASSERT_TRUE(transaction->IsSuperMACValid()); + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckValue("path1", &string_1)); + EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2)); + } +} + +TEST_F(PrefHashStoreImplTest, SuperMACDisabled) { + base::Value string_1("string1"); + base::Value string_2("string2"); + + { + // Pass |use_super_mac| => false. + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", false); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + + transaction->StoreHash("path1", &string_2); + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckValue("path1", &string_2)); + } + + ASSERT_TRUE(GetHashStoreContents()->GetSuperMac().empty()); + + { + PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", false); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store2.BeginTransaction(GetHashStoreContents())); + EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE, + transaction->CheckValue("new_path", &string_1)); + } +} + +TEST_F(PrefHashStoreImplTest, SplitHashStoreAndCheck) { + base::DictionaryValue dict; + dict.Set("a", new base::Value("to be replaced")); + dict.Set("b", new base::Value("same")); + dict.Set("o", new base::Value("old")); + + base::DictionaryValue modified_dict; + modified_dict.Set("a", new base::Value("replaced")); + modified_dict.Set("b", new base::Value("same")); + modified_dict.Set("c", new base::Value("new")); + + base::DictionaryValue empty_dict; + + std::vector<std::string> invalid_keys; + + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + + // No hashes stored yet and hashes dictionary is empty (and thus not + // trusted). + EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE, + transaction->CheckSplitValue("path1", &dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + + transaction->StoreSplitHash("path1", &dict); + + // Verify match post storage. + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckSplitValue("path1", &dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + + // Verify new path is still unknown. + EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE, + transaction->CheckSplitValue("path2", &dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + + // Verify NULL or empty dicts are declared as having been cleared. + EXPECT_EQ(ValueState::CLEARED, + transaction->CheckSplitValue("path1", NULL, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + EXPECT_EQ(ValueState::CLEARED, transaction->CheckSplitValue( + "path1", &empty_dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + + // Verify changes are properly detected. + EXPECT_EQ(ValueState::CHANGED, transaction->CheckSplitValue( + "path1", &modified_dict, &invalid_keys)); + std::vector<std::string> expected_invalid_keys1; + expected_invalid_keys1.push_back("a"); + expected_invalid_keys1.push_back("c"); + expected_invalid_keys1.push_back("o"); + EXPECT_EQ(expected_invalid_keys1, invalid_keys); + invalid_keys.clear(); + + // Verify |dict| still matches post check. + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckSplitValue("path1", &dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + + // Store hash for |modified_dict|. + transaction->StoreSplitHash("path1", &modified_dict); + + // Verify |modified_dict| is now the one that verifies correctly. + EXPECT_EQ( + ValueState::UNCHANGED, + transaction->CheckSplitValue("path1", &modified_dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + + // Verify old dict no longer matches. + EXPECT_EQ(ValueState::CHANGED, + transaction->CheckSplitValue("path1", &dict, &invalid_keys)); + std::vector<std::string> expected_invalid_keys2; + expected_invalid_keys2.push_back("a"); + expected_invalid_keys2.push_back("o"); + expected_invalid_keys2.push_back("c"); + EXPECT_EQ(expected_invalid_keys2, invalid_keys); + invalid_keys.clear(); + } + + { + // |pref_hash_store2| should trust its initial hashes dictionary and thus + // trust new unknown values. + PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store2.BeginTransaction(GetHashStoreContents())); + EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE, + transaction->CheckSplitValue("new_path", &dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + } + + // Manually corrupt the super MAC. + GetHashStoreContents()->SetSuperMac(std::string(64, 'A')); + + { + // |pref_hash_store3| should no longer trust its initial hashes dictionary + // and thus shouldn't trust unknown values. + PrefHashStoreImpl pref_hash_store3(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store3.BeginTransaction(GetHashStoreContents())); + EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE, + transaction->CheckSplitValue("new_path", &dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + } +} + +TEST_F(PrefHashStoreImplTest, EmptyAndNULLSplitDict) { + base::DictionaryValue empty_dict; + + std::vector<std::string> invalid_keys; + + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + + // Store hashes for a random dict to be overwritten below. + base::DictionaryValue initial_dict; + initial_dict.Set("a", new base::Value("foo")); + transaction->StoreSplitHash("path1", &initial_dict); + + // Verify stored empty dictionary matches NULL and empty dictionary back. + transaction->StoreSplitHash("path1", &empty_dict); + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckSplitValue("path1", NULL, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckSplitValue( + "path1", &empty_dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + + // Same when storing NULL directly. + transaction->StoreSplitHash("path1", NULL); + EXPECT_EQ(ValueState::UNCHANGED, + transaction->CheckSplitValue("path1", NULL, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckSplitValue( + "path1", &empty_dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + } + + { + // |pref_hash_store2| should trust its initial hashes dictionary (and thus + // trust new unknown values) even though the last action done was to clear + // the hashes for path1 by setting its value to NULL (this is a regression + // test ensuring that the internal action of clearing some hashes does + // update the stored hash of hashes). + PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store2.BeginTransaction(GetHashStoreContents())); + + base::DictionaryValue tested_dict; + tested_dict.Set("a", new base::Value("foo")); + tested_dict.Set("b", new base::Value("bar")); + EXPECT_EQ( + ValueState::TRUSTED_UNKNOWN_VALUE, + transaction->CheckSplitValue("new_path", &tested_dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + } +} + +// Test that the PrefHashStore returns TRUSTED_UNKNOWN_VALUE when checking for +// a split preference even if there is an existing atomic preference's hash +// stored. There is no point providing a migration path for preferences +// switching strategies after their initial release as split preferences are +// turned into split preferences specifically because the atomic hash isn't +// considered useful. +TEST_F(PrefHashStoreImplTest, TrustedUnknownSplitValueFromExistingAtomic) { + base::Value string("string1"); + + base::DictionaryValue dict; + dict.Set("a", new base::Value("foo")); + dict.Set("d", new base::Value("bad")); + dict.Set("b", new base::Value("bar")); + dict.Set("c", new base::Value("baz")); + + { + PrefHashStoreImpl pref_hash_store(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store.BeginTransaction(GetHashStoreContents())); + + transaction->StoreHash("path1", &string); + EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckValue("path1", &string)); + } + + { + // Load a new |pref_hash_store2| in which the hashes dictionary is trusted. + PrefHashStoreImpl pref_hash_store2(std::string(32, 0), "device_id", true); + std::unique_ptr<PrefHashStoreTransaction> transaction( + pref_hash_store2.BeginTransaction(GetHashStoreContents())); + std::vector<std::string> invalid_keys; + EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE, + transaction->CheckSplitValue("path1", &dict, &invalid_keys)); + EXPECT_TRUE(invalid_keys.empty()); + } +} diff --git a/chromium/services/preferences/tracked/pref_hash_store_transaction.h b/chromium/services/preferences/tracked/pref_hash_store_transaction.h new file mode 100644 index 00000000000..2e888859ee8 --- /dev/null +++ b/chromium/services/preferences/tracked/pref_hash_store_transaction.h @@ -0,0 +1,80 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_TRANSACTION_H_ +#define SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_TRANSACTION_H_ + +#include <string> +#include <vector> + +#include "base/strings/string_piece.h" +#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h" + +namespace base { +class DictionaryValue; +class Value; +} // namespace base + +// Used to perform a series of checks/transformations on a PrefHashStore. +class PrefHashStoreTransaction { + public: + // Finalizes any remaining work after the transaction has been performed. + virtual ~PrefHashStoreTransaction() {} + + // Returns the suffix to be appended to UMA histograms for the store contained + // in this transaction. + virtual base::StringPiece GetStoreUMASuffix() const = 0; + + // Checks |initial_value| against the existing stored value hash. + virtual prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + CheckValue(const std::string& path, + const base::Value* initial_value) const = 0; + + // Stores a hash of the current |value| of the preference at |path|. + virtual void StoreHash(const std::string& path, const base::Value* value) = 0; + + // Checks |initial_value| against the existing stored hashes for the split + // preference at |path|. |initial_split_value| being an empty dictionary or + // NULL is equivalent. |invalid_keys| must initially be empty. |invalid_keys| + // will not be modified unless the return value is CHANGED, in which case it + // will be filled with the keys that are considered invalid (unknown or + // changed). + virtual prefs::mojom::TrackedPreferenceValidationDelegate::ValueState + CheckSplitValue(const std::string& path, + const base::DictionaryValue* initial_split_value, + std::vector<std::string>* invalid_keys) const = 0; + + // Stores hashes for the |value| of the split preference at |path|. + // |split_value| being an empty dictionary or NULL is equivalent. + virtual void StoreSplitHash(const std::string& path, + const base::DictionaryValue* split_value) = 0; + + // Indicates whether the store contains a hash for the preference at |path|. + virtual bool HasHash(const std::string& path) const = 0; + + // Sets the hash for the preference at |path|. + // If |path| is a split preference |hash| must be a DictionaryValue whose + // keys are keys in the split preference and whose values are MACs of the + // corresponding values in the split preference. + // If |path| is an atomic preference |hash| must be a StringValue + // containing a MAC of the preference value. + // |hash| should originate from a PrefHashStore sharing the same MAC + // parameters as this transaction's store. + // The (in)validity of the super MAC will be maintained by this call. + virtual void ImportHash(const std::string& path, const base::Value* hash) = 0; + + // Removes the hash stored at |path|. The (in)validity of the super MAC will + // be maintained by this call. + virtual void ClearHash(const std::string& path) = 0; + + // Indicates whether the super MAC was successfully verified at the beginning + // of this transaction. + virtual bool IsSuperMACValid() const = 0; + + // Forces a valid super MAC to be stored when this transaction terminates. + // Returns true if this results in a change to the store contents. + virtual bool StampSuperMac() = 0; +}; + +#endif // SERVICES_PREFERENCES_TRACKED_PREF_HASH_STORE_TRANSACTION_H_ diff --git a/chromium/services/preferences/tracked/registry_hash_store_contents_win.cc b/chromium/services/preferences/tracked/registry_hash_store_contents_win.cc new file mode 100644 index 00000000000..ab1c2c56478 --- /dev/null +++ b/chromium/services/preferences/tracked/registry_hash_store_contents_win.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2016 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 "services/preferences/tracked/registry_hash_store_contents_win.h" + +#include <windows.h> + +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "base/win/registry.h" +#include "services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h" + +using base::win::RegistryValueIterator; + +namespace { + +constexpr size_t kMacSize = 64; + +base::string16 GetSplitPrefKeyName(const base::string16& reg_key_name, + const std::string& split_key_name) { + return reg_key_name + L"\\" + base::UTF8ToUTF16(split_key_name); +} + +bool ReadMacFromRegistry(const base::win::RegKey& key, + const std::string& value_name, + std::string* out_mac) { + base::string16 string_value; + if (key.ReadValue(base::UTF8ToUTF16(value_name).c_str(), &string_value) == + ERROR_SUCCESS && + string_value.size() == kMacSize) { + out_mac->assign(base::UTF16ToUTF8(string_value)); + return true; + } + return false; +} + +// Removes |value_name| under |reg_key_name|. Returns true if found and +// successfully removed. +bool ClearAtomicMac(const base::string16& reg_key_name, + const std::string& value_name) { + base::win::RegKey key; + if (key.Open(HKEY_CURRENT_USER, reg_key_name.c_str(), + KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) { + return key.DeleteValue(base::UTF8ToUTF16(value_name).c_str()) == + ERROR_SUCCESS; + } + return false; +} + +// Deletes |split_key_name| under |reg_key_name|. Returns true if found and +// successfully removed. +bool ClearSplitMac(const base::string16& reg_key_name, + const std::string& split_key_name) { + base::win::RegKey key; + if (key.Open(HKEY_CURRENT_USER, + GetSplitPrefKeyName(reg_key_name, split_key_name).c_str(), + KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) { + return key.DeleteKey(L"") == ERROR_SUCCESS; + } + return false; +} + +} // namespace + +RegistryHashStoreContentsWin::RegistryHashStoreContentsWin( + const base::string16& registry_path, + const base::string16& store_key) + : preference_key_name_(registry_path + L"\\PreferenceMACs\\" + store_key) {} + +RegistryHashStoreContentsWin::RegistryHashStoreContentsWin( + const RegistryHashStoreContentsWin& other) = default; + +bool RegistryHashStoreContentsWin::IsCopyable() const { + return true; +} + +std::unique_ptr<HashStoreContents> RegistryHashStoreContentsWin::MakeCopy() + const { + return base::WrapUnique(new RegistryHashStoreContentsWin(*this)); +} + +base::StringPiece RegistryHashStoreContentsWin::GetUMASuffix() const { + return user_prefs::tracked::kTrackedPrefRegistryValidationSuffix; +} + +void RegistryHashStoreContentsWin::Reset() { + base::win::RegKey key; + if (key.Open(HKEY_CURRENT_USER, preference_key_name_.c_str(), + KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) { + LONG result = key.DeleteKey(L""); + DCHECK(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND) << result; + } +} + +bool RegistryHashStoreContentsWin::GetMac(const std::string& path, + std::string* out_value) { + base::win::RegKey key; + if (key.Open(HKEY_CURRENT_USER, preference_key_name_.c_str(), + KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) { + return ReadMacFromRegistry(key, path, out_value); + } + + return false; +} + +bool RegistryHashStoreContentsWin::GetSplitMacs( + const std::string& path, + std::map<std::string, std::string>* split_macs) { + DCHECK(split_macs); + DCHECK(split_macs->empty()); + + RegistryValueIterator iter_key( + HKEY_CURRENT_USER, + GetSplitPrefKeyName(preference_key_name_, path).c_str()); + + for (; iter_key.Valid(); ++iter_key) { + split_macs->insert(make_pair(base::UTF16ToUTF8(iter_key.Name()), + base::UTF16ToUTF8(iter_key.Value()))); + } + + return !split_macs->empty(); +} + +void RegistryHashStoreContentsWin::SetMac(const std::string& path, + const std::string& value) { + base::win::RegKey key; + DCHECK_EQ(kMacSize, value.size()); + + if (key.Create(HKEY_CURRENT_USER, preference_key_name_.c_str(), + KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) { + key.WriteValue(base::UTF8ToUTF16(path).c_str(), + base::UTF8ToUTF16(value).c_str()); + } +} + +void RegistryHashStoreContentsWin::SetSplitMac(const std::string& path, + const std::string& split_path, + const std::string& value) { + base::win::RegKey key; + DCHECK_EQ(kMacSize, value.size()); + + if (key.Create(HKEY_CURRENT_USER, + GetSplitPrefKeyName(preference_key_name_, path).c_str(), + KEY_SET_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) { + key.WriteValue(base::UTF8ToUTF16(split_path).c_str(), + base::UTF8ToUTF16(value).c_str()); + } +} + +bool RegistryHashStoreContentsWin::RemoveEntry(const std::string& path) { + return ClearAtomicMac(preference_key_name_, path) || + ClearSplitMac(preference_key_name_, path); +} + +void RegistryHashStoreContentsWin::ImportEntry(const std::string& path, + const base::Value* in_value) { + NOTREACHED() + << "RegistryHashStoreContents does not support the ImportEntry operation"; +} + +const base::DictionaryValue* RegistryHashStoreContentsWin::GetContents() const { + NOTREACHED() + << "RegistryHashStoreContents does not support the GetContents operation"; + return NULL; +} + +std::string RegistryHashStoreContentsWin::GetSuperMac() const { + NOTREACHED() + << "RegistryHashStoreContents does not support the GetSuperMac operation"; + return NULL; +} + +void RegistryHashStoreContentsWin::SetSuperMac(const std::string& super_mac) { + NOTREACHED() + << "RegistryHashStoreContents does not support the SetSuperMac operation"; +} diff --git a/chromium/services/preferences/tracked/registry_hash_store_contents_win.h b/chromium/services/preferences/tracked/registry_hash_store_contents_win.h new file mode 100644 index 00000000000..34496f97cec --- /dev/null +++ b/chromium/services/preferences/tracked/registry_hash_store_contents_win.h @@ -0,0 +1,49 @@ +// Copyright 2016 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 SERVICES_PREFERENCES_TRACKED_REGISTRY_HASH_STORE_CONTENTS_WIN_H_ +#define SERVICES_PREFERENCES_TRACKED_REGISTRY_HASH_STORE_CONTENTS_WIN_H_ + +#include "base/macros.h" +#include "base/strings/string16.h" +#include "services/preferences/tracked/hash_store_contents.h" + +// Implements HashStoreContents by storing MACs in the Windows registry. +class RegistryHashStoreContentsWin : public HashStoreContents { + public: + // Constructs a RegistryHashStoreContents which acts on a registry entry + // defined by |registry_path| and |store_key|. + explicit RegistryHashStoreContentsWin(const base::string16& registry_path, + const base::string16& store_key); + + // HashStoreContents overrides: + bool IsCopyable() const override; + std::unique_ptr<HashStoreContents> MakeCopy() const override; + base::StringPiece GetUMASuffix() const override; + void Reset() override; + bool GetMac(const std::string& path, std::string* out_value) override; + bool GetSplitMacs(const std::string& path, + std::map<std::string, std::string>* split_macs) override; + void SetMac(const std::string& path, const std::string& value) override; + void SetSplitMac(const std::string& path, + const std::string& split_path, + const std::string& value) override; + bool RemoveEntry(const std::string& path) override; + + // Unsupported HashStoreContents overrides: + void ImportEntry(const std::string& path, + const base::Value* in_value) override; + const base::DictionaryValue* GetContents() const override; + std::string GetSuperMac() const override; + void SetSuperMac(const std::string& super_mac) override; + + private: + // Helper constructor for |MakeCopy|. + explicit RegistryHashStoreContentsWin( + const RegistryHashStoreContentsWin& other); + + const base::string16 preference_key_name_; +}; + +#endif // SERVICES_PREFERENCES_TRACKED_REGISTRY_HASH_STORE_CONTENTS_WIN_H_ diff --git a/chromium/services/preferences/tracked/registry_hash_store_contents_win_unittest.cc b/chromium/services/preferences/tracked/registry_hash_store_contents_win_unittest.cc new file mode 100644 index 00000000000..468843437fb --- /dev/null +++ b/chromium/services/preferences/tracked/registry_hash_store_contents_win_unittest.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2016 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 "services/preferences/tracked/registry_hash_store_contents_win.h" + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/test_reg_util_win.h" +#include "base/values.h" +#include "base/win/registry.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +constexpr base::char16 kRegistryPath[] = L"Foo\\TestStore"; +constexpr base::char16 kStoreKey[] = L"test_store_key"; + +// Hex-encoded MACs are 64 characters long. +constexpr char kTestStringA[] = + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +constexpr char kTestStringB[] = + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + +constexpr char kAtomicPrefPath[] = "path1"; +constexpr char kSplitPrefPath[] = "extension"; + +class RegistryHashStoreContentsWinTest : public testing::Test { + protected: + RegistryHashStoreContentsWinTest() {} + + void SetUp() override { + ASSERT_NO_FATAL_FAILURE( + registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER)); + + contents.reset(new RegistryHashStoreContentsWin(kRegistryPath, kStoreKey)); + } + + std::unique_ptr<HashStoreContents> contents; + + private: + registry_util::RegistryOverrideManager registry_override_manager_; + + DISALLOW_COPY_AND_ASSIGN(RegistryHashStoreContentsWinTest); +}; + +} // namespace + +TEST_F(RegistryHashStoreContentsWinTest, TestSetAndGetMac) { + std::string stored_mac; + EXPECT_FALSE(contents->GetMac(kAtomicPrefPath, &stored_mac)); + + contents->SetMac(kAtomicPrefPath, kTestStringA); + + EXPECT_TRUE(contents->GetMac(kAtomicPrefPath, &stored_mac)); + EXPECT_EQ(kTestStringA, stored_mac); +} + +TEST_F(RegistryHashStoreContentsWinTest, TestSetAndGetSplitMacs) { + std::map<std::string, std::string> split_macs; + EXPECT_FALSE(contents->GetSplitMacs(kSplitPrefPath, &split_macs)); + + contents->SetSplitMac(kSplitPrefPath, "a", kTestStringA); + contents->SetSplitMac(kSplitPrefPath, "b", kTestStringB); + + EXPECT_TRUE(contents->GetSplitMacs(kSplitPrefPath, &split_macs)); + EXPECT_EQ(2U, split_macs.size()); + EXPECT_EQ(kTestStringA, split_macs.at("a")); + EXPECT_EQ(kTestStringB, split_macs.at("b")); +} + +TEST_F(RegistryHashStoreContentsWinTest, TestRemoveAtomicMac) { + contents->SetMac(kAtomicPrefPath, kTestStringA); + + std::string stored_mac; + EXPECT_TRUE(contents->GetMac(kAtomicPrefPath, &stored_mac)); + EXPECT_EQ(kTestStringA, stored_mac); + + contents->RemoveEntry(kAtomicPrefPath); + + EXPECT_FALSE(contents->GetMac(kAtomicPrefPath, &stored_mac)); +} + +TEST_F(RegistryHashStoreContentsWinTest, TestRemoveSplitMacs) { + contents->SetSplitMac(kSplitPrefPath, "a", kTestStringA); + contents->SetSplitMac(kSplitPrefPath, "b", kTestStringB); + + std::map<std::string, std::string> split_macs; + EXPECT_TRUE(contents->GetSplitMacs(kSplitPrefPath, &split_macs)); + EXPECT_EQ(2U, split_macs.size()); + + contents->RemoveEntry(kSplitPrefPath); + + split_macs.clear(); + EXPECT_FALSE(contents->GetSplitMacs(kSplitPrefPath, &split_macs)); + EXPECT_EQ(0U, split_macs.size()); +} + +TEST_F(RegistryHashStoreContentsWinTest, TestReset) { + contents->SetMac(kAtomicPrefPath, kTestStringA); + contents->SetSplitMac(kSplitPrefPath, "a", kTestStringA); + + std::string stored_mac; + EXPECT_TRUE(contents->GetMac(kAtomicPrefPath, &stored_mac)); + EXPECT_EQ(kTestStringA, stored_mac); + + std::map<std::string, std::string> split_macs; + EXPECT_TRUE(contents->GetSplitMacs(kSplitPrefPath, &split_macs)); + EXPECT_EQ(1U, split_macs.size()); + + contents->Reset(); + + stored_mac.clear(); + EXPECT_FALSE(contents->GetMac(kAtomicPrefPath, &stored_mac)); + EXPECT_TRUE(stored_mac.empty()); + + split_macs.clear(); + EXPECT_FALSE(contents->GetSplitMacs(kSplitPrefPath, &split_macs)); + EXPECT_EQ(0U, split_macs.size()); +} diff --git a/chromium/services/preferences/tracked/segregated_pref_store.cc b/chromium/services/preferences/tracked/segregated_pref_store.cc new file mode 100644 index 00000000000..af7349e0382 --- /dev/null +++ b/chromium/services/preferences/tracked/segregated_pref_store.cc @@ -0,0 +1,195 @@ +// Copyright 2014 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 "services/preferences/tracked/segregated_pref_store.h" + +#include <utility> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/values.h" + +SegregatedPrefStore::AggregatingObserver::AggregatingObserver( + SegregatedPrefStore* outer) + : outer_(outer), + failed_sub_initializations_(0), + successful_sub_initializations_(0) {} + +void SegregatedPrefStore::AggregatingObserver::OnPrefValueChanged( + const std::string& key) { + // There is no need to tell clients about changes if they have not yet been + // told about initialization. + if (failed_sub_initializations_ + successful_sub_initializations_ < 2) + return; + + for (auto& observer : outer_->observers_) + observer.OnPrefValueChanged(key); +} + +void SegregatedPrefStore::AggregatingObserver::OnInitializationCompleted( + bool succeeded) { + if (succeeded) + ++successful_sub_initializations_; + else + ++failed_sub_initializations_; + + DCHECK_LE(failed_sub_initializations_ + successful_sub_initializations_, 2); + + if (failed_sub_initializations_ + successful_sub_initializations_ == 2) { + if (successful_sub_initializations_ == 2 && outer_->read_error_delegate_) { + PersistentPrefStore::PrefReadError read_error = outer_->GetReadError(); + if (read_error != PersistentPrefStore::PREF_READ_ERROR_NONE) + outer_->read_error_delegate_->OnError(read_error); + } + + for (auto& observer : outer_->observers_) + observer.OnInitializationCompleted(successful_sub_initializations_ == 2); + } +} + +SegregatedPrefStore::SegregatedPrefStore( + const scoped_refptr<PersistentPrefStore>& default_pref_store, + const scoped_refptr<PersistentPrefStore>& selected_pref_store, + const std::set<std::string>& selected_pref_names, + prefs::mojom::TrackedPreferenceValidationDelegatePtr validation_delegate) + : validation_delegate_(std::move(validation_delegate)), + default_pref_store_(default_pref_store), + selected_pref_store_(selected_pref_store), + selected_preference_names_(selected_pref_names), + aggregating_observer_(this) { + default_pref_store_->AddObserver(&aggregating_observer_); + selected_pref_store_->AddObserver(&aggregating_observer_); +} + +void SegregatedPrefStore::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void SegregatedPrefStore::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +bool SegregatedPrefStore::HasObservers() const { + return observers_.might_have_observers(); +} + +bool SegregatedPrefStore::IsInitializationComplete() const { + return default_pref_store_->IsInitializationComplete() && + selected_pref_store_->IsInitializationComplete(); +} + +bool SegregatedPrefStore::GetValue(const std::string& key, + const base::Value** result) const { + return StoreForKey(key)->GetValue(key, result); +} + +std::unique_ptr<base::DictionaryValue> SegregatedPrefStore::GetValues() const { + auto values = default_pref_store_->GetValues(); + auto selected_pref_store_values = selected_pref_store_->GetValues(); + for (const auto& key : selected_preference_names_) { + const base::Value* value = nullptr; + if (selected_pref_store_values->Get(key, &value)) { + values->Set(key, value->CreateDeepCopy()); + } else { + values->Remove(key, nullptr); + } + } + return values; +} + +void SegregatedPrefStore::SetValue(const std::string& key, + std::unique_ptr<base::Value> value, + uint32_t flags) { + StoreForKey(key)->SetValue(key, std::move(value), flags); +} + +void SegregatedPrefStore::RemoveValue(const std::string& key, uint32_t flags) { + StoreForKey(key)->RemoveValue(key, flags); +} + +bool SegregatedPrefStore::GetMutableValue(const std::string& key, + base::Value** result) { + return StoreForKey(key)->GetMutableValue(key, result); +} + +void SegregatedPrefStore::ReportValueChanged(const std::string& key, + uint32_t flags) { + StoreForKey(key)->ReportValueChanged(key, flags); +} + +void SegregatedPrefStore::SetValueSilently(const std::string& key, + std::unique_ptr<base::Value> value, + uint32_t flags) { + StoreForKey(key)->SetValueSilently(key, std::move(value), flags); +} + +bool SegregatedPrefStore::ReadOnly() const { + return selected_pref_store_->ReadOnly() || default_pref_store_->ReadOnly(); +} + +PersistentPrefStore::PrefReadError SegregatedPrefStore::GetReadError() const { + PersistentPrefStore::PrefReadError read_error = + default_pref_store_->GetReadError(); + if (read_error == PersistentPrefStore::PREF_READ_ERROR_NONE) { + read_error = selected_pref_store_->GetReadError(); + // Ignore NO_FILE from selected_pref_store_. + if (read_error == PersistentPrefStore::PREF_READ_ERROR_NO_FILE) + read_error = PersistentPrefStore::PREF_READ_ERROR_NONE; + } + return read_error; +} + +PersistentPrefStore::PrefReadError SegregatedPrefStore::ReadPrefs() { + // Note: Both of these stores own PrefFilters which makes ReadPrefs + // asynchronous. This is okay in this case as only the first call will be + // truly asynchronous, the second call will then unblock the migration in + // TrackedPreferencesMigrator and complete synchronously. + default_pref_store_->ReadPrefs(); + PersistentPrefStore::PrefReadError selected_store_read_error = + selected_pref_store_->ReadPrefs(); + DCHECK_NE(PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE, + selected_store_read_error); + + return GetReadError(); +} + +void SegregatedPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) { + read_error_delegate_.reset(error_delegate); + default_pref_store_->ReadPrefsAsync(NULL); + selected_pref_store_->ReadPrefsAsync(NULL); +} + +void SegregatedPrefStore::CommitPendingWrite() { + default_pref_store_->CommitPendingWrite(); + selected_pref_store_->CommitPendingWrite(); +} + +void SegregatedPrefStore::SchedulePendingLossyWrites() { + default_pref_store_->SchedulePendingLossyWrites(); + selected_pref_store_->SchedulePendingLossyWrites(); +} + +void SegregatedPrefStore::ClearMutableValues() { + NOTIMPLEMENTED(); +} + +SegregatedPrefStore::~SegregatedPrefStore() { + default_pref_store_->RemoveObserver(&aggregating_observer_); + selected_pref_store_->RemoveObserver(&aggregating_observer_); +} + +PersistentPrefStore* SegregatedPrefStore::StoreForKey(const std::string& key) { + return (base::ContainsKey(selected_preference_names_, key) + ? selected_pref_store_ + : default_pref_store_) + .get(); +} + +const PersistentPrefStore* SegregatedPrefStore::StoreForKey( + const std::string& key) const { + return (base::ContainsKey(selected_preference_names_, key) + ? selected_pref_store_ + : default_pref_store_) + .get(); +} diff --git a/chromium/services/preferences/tracked/segregated_pref_store.h b/chromium/services/preferences/tracked/segregated_pref_store.h new file mode 100644 index 00000000000..3621e49aa07 --- /dev/null +++ b/chromium/services/preferences/tracked/segregated_pref_store.h @@ -0,0 +1,122 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_TRACKED_SEGREGATED_PREF_STORE_H_ +#define SERVICES_PREFERENCES_TRACKED_SEGREGATED_PREF_STORE_H_ + +#include <stdint.h> + +#include <memory> +#include <set> +#include <string> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "components/prefs/persistent_pref_store.h" +#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h" + +// Provides a unified PersistentPrefStore implementation that splits its storage +// and retrieval between two underlying PersistentPrefStore instances: a set of +// preference names is used to partition the preferences. +// +// Combines properties of the two stores as follows: +// * The unified read error will be: +// Selected Store Error +// Default Store Error | NO_ERROR | NO_FILE | other selected | +// NO_ERROR | NO_ERROR | NO_ERROR | other selected | +// NO_FILE | NO_FILE | NO_FILE | NO_FILE | +// other default | other default | other default | other default | +// * The unified initialization success, initialization completion, and +// read-only state are the boolean OR of the underlying stores' properties. +class SegregatedPrefStore : public PersistentPrefStore { + public: + // Creates an instance that delegates to |selected_pref_store| for the + // preferences named in |selected_pref_names| and to |default_pref_store| + // for all others. If an unselected preference is present in + // |selected_pref_store| (i.e. because it was previously selected) it will + // be migrated back to |default_pref_store| upon access via a non-const + // method. + // |on_initialization| will be invoked when both stores have been initialized, + // before observers of the SegregatedPrefStore store are notified. + SegregatedPrefStore( + const scoped_refptr<PersistentPrefStore>& default_pref_store, + const scoped_refptr<PersistentPrefStore>& selected_pref_store, + const std::set<std::string>& selected_pref_names, + prefs::mojom::TrackedPreferenceValidationDelegatePtr validation_delegate); + + // PrefStore implementation + void AddObserver(Observer* observer) override; + void RemoveObserver(Observer* observer) override; + bool HasObservers() const override; + bool IsInitializationComplete() const override; + bool GetValue(const std::string& key, + const base::Value** result) const override; + std::unique_ptr<base::DictionaryValue> GetValues() const override; + + // WriteablePrefStore implementation + void SetValue(const std::string& key, + std::unique_ptr<base::Value> value, + uint32_t flags) override; + void RemoveValue(const std::string& key, uint32_t flags) override; + + // PersistentPrefStore implementation + bool GetMutableValue(const std::string& key, base::Value** result) override; + void ReportValueChanged(const std::string& key, uint32_t flags) override; + void SetValueSilently(const std::string& key, + std::unique_ptr<base::Value> value, + uint32_t flags) override; + bool ReadOnly() const override; + PrefReadError GetReadError() const override; + PrefReadError ReadPrefs() override; + void ReadPrefsAsync(ReadErrorDelegate* error_delegate) override; + void CommitPendingWrite() override; + void SchedulePendingLossyWrites() override; + + void ClearMutableValues() override; + + private: + // Aggregates events from the underlying stores and synthesizes external + // events via |on_initialization|, |read_error_delegate_|, and |observers_|. + class AggregatingObserver : public PrefStore::Observer { + public: + explicit AggregatingObserver(SegregatedPrefStore* outer); + + // PrefStore::Observer implementation + void OnPrefValueChanged(const std::string& key) override; + void OnInitializationCompleted(bool succeeded) override; + + private: + SegregatedPrefStore* outer_; + int failed_sub_initializations_; + int successful_sub_initializations_; + + DISALLOW_COPY_AND_ASSIGN(AggregatingObserver); + }; + + ~SegregatedPrefStore() override; + + // Returns |selected_pref_store| if |key| is selected and |default_pref_store| + // otherwise. + PersistentPrefStore* StoreForKey(const std::string& key); + const PersistentPrefStore* StoreForKey(const std::string& key) const; + + // |validation_delegate_| is used by |default_pref_store_| and + // |selected_pref_store_| PrefHashFilters. Its lifetime is managed here since + // a single owner is required. + prefs::mojom::TrackedPreferenceValidationDelegatePtr validation_delegate_; + + scoped_refptr<PersistentPrefStore> default_pref_store_; + scoped_refptr<PersistentPrefStore> selected_pref_store_; + std::set<std::string> selected_preference_names_; + + std::unique_ptr<PersistentPrefStore::ReadErrorDelegate> read_error_delegate_; + base::ObserverList<PrefStore::Observer, true> observers_; + AggregatingObserver aggregating_observer_; + + DISALLOW_COPY_AND_ASSIGN(SegregatedPrefStore); +}; + +#endif // SERVICES_PREFERENCES_TRACKED_SEGREGATED_PREF_STORE_H_ diff --git a/chromium/services/preferences/tracked/segregated_pref_store_unittest.cc b/chromium/services/preferences/tracked/segregated_pref_store_unittest.cc new file mode 100644 index 00000000000..a237f38285e --- /dev/null +++ b/chromium/services/preferences/tracked/segregated_pref_store_unittest.cc @@ -0,0 +1,304 @@ +// Copyright 2014 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 "services/preferences/tracked/segregated_pref_store.h" + +#include <memory> +#include <set> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/values.h" +#include "components/prefs/persistent_pref_store.h" +#include "components/prefs/pref_store_observer_mock.h" +#include "components/prefs/testing_pref_store.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kSelectedPref[] = "selected_pref"; +const char kUnselectedPref[] = "unselected_pref"; +const char kSharedPref[] = "shared_pref"; + +const char kValue1[] = "value1"; +const char kValue2[] = "value2"; + +class MockReadErrorDelegate : public PersistentPrefStore::ReadErrorDelegate { + public: + struct Data { + Data(bool invoked_in, PersistentPrefStore::PrefReadError read_error_in) + : invoked(invoked_in), read_error(read_error_in) {} + + bool invoked; + PersistentPrefStore::PrefReadError read_error; + }; + + explicit MockReadErrorDelegate(Data* data) : data_(data) { + DCHECK(data_); + EXPECT_FALSE(data_->invoked); + } + + // PersistentPrefStore::ReadErrorDelegate implementation + void OnError(PersistentPrefStore::PrefReadError read_error) override { + EXPECT_FALSE(data_->invoked); + data_->invoked = true; + data_->read_error = read_error; + } + + private: + Data* data_; +}; + +} // namespace + +class SegregatedPrefStoreTest : public testing::Test { + public: + SegregatedPrefStoreTest() + : read_error_delegate_data_(false, + PersistentPrefStore::PREF_READ_ERROR_NONE), + read_error_delegate_( + new MockReadErrorDelegate(&read_error_delegate_data_)) {} + + void SetUp() override { + selected_store_ = new TestingPrefStore; + default_store_ = new TestingPrefStore; + + std::set<std::string> selected_pref_names; + selected_pref_names.insert(kSelectedPref); + selected_pref_names.insert(kSharedPref); + + segregated_store_ = new SegregatedPrefStore(default_store_, selected_store_, + selected_pref_names, nullptr); + + segregated_store_->AddObserver(&observer_); + } + + void TearDown() override { segregated_store_->RemoveObserver(&observer_); } + + protected: + std::unique_ptr<PersistentPrefStore::ReadErrorDelegate> + GetReadErrorDelegate() { + EXPECT_TRUE(read_error_delegate_); + return std::move(read_error_delegate_); + } + + PrefStoreObserverMock observer_; + + scoped_refptr<TestingPrefStore> default_store_; + scoped_refptr<TestingPrefStore> selected_store_; + scoped_refptr<SegregatedPrefStore> segregated_store_; + + MockReadErrorDelegate::Data read_error_delegate_data_; + + private: + std::unique_ptr<MockReadErrorDelegate> read_error_delegate_; +}; + +TEST_F(SegregatedPrefStoreTest, StoreValues) { + ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, + segregated_store_->ReadPrefs()); + + // Properly stores new values. + segregated_store_->SetValue(kSelectedPref, + base::MakeUnique<base::Value>(kValue1), + WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + segregated_store_->SetValue(kUnselectedPref, + base::MakeUnique<base::Value>(kValue2), + WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + + ASSERT_TRUE(selected_store_->GetValue(kSelectedPref, NULL)); + ASSERT_FALSE(selected_store_->GetValue(kUnselectedPref, NULL)); + ASSERT_FALSE(default_store_->GetValue(kSelectedPref, NULL)); + ASSERT_TRUE(default_store_->GetValue(kUnselectedPref, NULL)); + + ASSERT_TRUE(segregated_store_->GetValue(kSelectedPref, NULL)); + ASSERT_TRUE(segregated_store_->GetValue(kUnselectedPref, NULL)); + + ASSERT_FALSE(selected_store_->committed()); + ASSERT_FALSE(default_store_->committed()); + + segregated_store_->CommitPendingWrite(); + + ASSERT_TRUE(selected_store_->committed()); + ASSERT_TRUE(default_store_->committed()); +} + +TEST_F(SegregatedPrefStoreTest, ReadValues) { + selected_store_->SetValue(kSelectedPref, + base::MakeUnique<base::Value>(kValue1), + WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + default_store_->SetValue(kUnselectedPref, + base::MakeUnique<base::Value>(kValue2), + WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + + // Works properly with values that are already there. + ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, + segregated_store_->ReadPrefs()); + ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, + segregated_store_->GetReadError()); + + ASSERT_TRUE(selected_store_->GetValue(kSelectedPref, NULL)); + ASSERT_FALSE(selected_store_->GetValue(kUnselectedPref, NULL)); + ASSERT_FALSE(default_store_->GetValue(kSelectedPref, NULL)); + ASSERT_TRUE(default_store_->GetValue(kUnselectedPref, NULL)); + + ASSERT_TRUE(segregated_store_->GetValue(kSelectedPref, NULL)); + ASSERT_TRUE(segregated_store_->GetValue(kUnselectedPref, NULL)); +} + +TEST_F(SegregatedPrefStoreTest, Observer) { + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, + segregated_store_->ReadPrefs()); + EXPECT_TRUE(observer_.initialized); + EXPECT_TRUE(observer_.initialization_success); + EXPECT_TRUE(observer_.changed_keys.empty()); + segregated_store_->SetValue(kSelectedPref, + base::MakeUnique<base::Value>(kValue1), + WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + observer_.VerifyAndResetChangedKey(kSelectedPref); + segregated_store_->SetValue(kUnselectedPref, + base::MakeUnique<base::Value>(kValue2), + WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + observer_.VerifyAndResetChangedKey(kUnselectedPref); +} + +TEST_F(SegregatedPrefStoreTest, SelectedPrefReadNoFileError) { + // PREF_READ_ERROR_NO_FILE for the selected prefs file is silently converted + // to PREF_READ_ERROR_NONE. + selected_store_->set_read_error(PersistentPrefStore::PREF_READ_ERROR_NO_FILE); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, + segregated_store_->ReadPrefs()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, + segregated_store_->GetReadError()); +} + +TEST_F(SegregatedPrefStoreTest, SelectedPrefReadError) { + selected_store_->set_read_error( + PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED, + segregated_store_->ReadPrefs()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED, + segregated_store_->GetReadError()); +} + +TEST_F(SegregatedPrefStoreTest, SelectedPrefReadNoFileErrorAsync) { + // PREF_READ_ERROR_NO_FILE for the selected prefs file is silently converted + // to PREF_READ_ERROR_NONE. + selected_store_->set_read_error(PersistentPrefStore::PREF_READ_ERROR_NO_FILE); + + default_store_->SetBlockAsyncRead(true); + + EXPECT_FALSE(read_error_delegate_data_.invoked); + + segregated_store_->ReadPrefsAsync(GetReadErrorDelegate().release()); + + EXPECT_FALSE(read_error_delegate_data_.invoked); + + default_store_->SetBlockAsyncRead(false); + + // ReadErrorDelegate is not invoked for ERROR_NONE. + EXPECT_FALSE(read_error_delegate_data_.invoked); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, + segregated_store_->GetReadError()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, + segregated_store_->GetReadError()); +} + +TEST_F(SegregatedPrefStoreTest, UnselectedPrefReadNoFileError) { + default_store_->set_read_error(PersistentPrefStore::PREF_READ_ERROR_NO_FILE); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE, + segregated_store_->ReadPrefs()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE, + segregated_store_->GetReadError()); +} + +TEST_F(SegregatedPrefStoreTest, UnselectedPrefReadError) { + default_store_->set_read_error( + PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED, + segregated_store_->ReadPrefs()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED, + segregated_store_->GetReadError()); +} + +TEST_F(SegregatedPrefStoreTest, BothPrefReadError) { + default_store_->set_read_error(PersistentPrefStore::PREF_READ_ERROR_NO_FILE); + selected_store_->set_read_error( + PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE, + segregated_store_->ReadPrefs()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE, + segregated_store_->GetReadError()); +} + +TEST_F(SegregatedPrefStoreTest, BothPrefReadErrorAsync) { + default_store_->set_read_error(PersistentPrefStore::PREF_READ_ERROR_NO_FILE); + selected_store_->set_read_error( + PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED); + + selected_store_->SetBlockAsyncRead(true); + + EXPECT_FALSE(read_error_delegate_data_.invoked); + + segregated_store_->ReadPrefsAsync(GetReadErrorDelegate().release()); + + EXPECT_FALSE(read_error_delegate_data_.invoked); + + selected_store_->SetBlockAsyncRead(false); + + EXPECT_TRUE(read_error_delegate_data_.invoked); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE, + segregated_store_->GetReadError()); + EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE, + segregated_store_->GetReadError()); +} + +TEST_F(SegregatedPrefStoreTest, IsInitializationComplete) { + EXPECT_FALSE(segregated_store_->IsInitializationComplete()); + segregated_store_->ReadPrefs(); + EXPECT_TRUE(segregated_store_->IsInitializationComplete()); +} + +TEST_F(SegregatedPrefStoreTest, IsInitializationCompleteAsync) { + selected_store_->SetBlockAsyncRead(true); + default_store_->SetBlockAsyncRead(true); + EXPECT_FALSE(segregated_store_->IsInitializationComplete()); + segregated_store_->ReadPrefsAsync(NULL); + EXPECT_FALSE(segregated_store_->IsInitializationComplete()); + selected_store_->SetBlockAsyncRead(false); + EXPECT_FALSE(segregated_store_->IsInitializationComplete()); + default_store_->SetBlockAsyncRead(false); + EXPECT_TRUE(segregated_store_->IsInitializationComplete()); +} + +TEST_F(SegregatedPrefStoreTest, GetValues) { + // To check merge behavior, create selected and default stores so each has a + // key the other doesn't have and they have one key in common. + selected_store_->SetValue(kSelectedPref, + base::MakeUnique<base::Value>(kValue1), + WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + default_store_->SetValue(kUnselectedPref, + base::MakeUnique<base::Value>(kValue2), + WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + selected_store_->SetValue(kSharedPref, base::MakeUnique<base::Value>(kValue1), + WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + + auto values = segregated_store_->GetValues(); + const base::Value* value = nullptr; + // Check that a selected preference is returned. + ASSERT_TRUE(values->Get(kSelectedPref, &value)); + EXPECT_TRUE(base::Value(kValue1).Equals(value)); + + // Check that a a default preference is returned. + ASSERT_TRUE(values->Get(kUnselectedPref, &value)); + EXPECT_TRUE(base::Value(kValue2).Equals(value)); + + // Check that the selected preference is preferred. + ASSERT_TRUE(values->Get(kSharedPref, &value)); + EXPECT_TRUE(base::Value(kValue1).Equals(value)); +} diff --git a/chromium/services/preferences/tracked/tracked_atomic_preference.cc b/chromium/services/preferences/tracked/tracked_atomic_preference.cc new file mode 100644 index 00000000000..8df3a3609fa --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_atomic_preference.cc @@ -0,0 +1,89 @@ +// Copyright 2014 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 "services/preferences/tracked/tracked_atomic_preference.h" + +#include "base/values.h" +#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h" +#include "services/preferences/tracked/pref_hash_store_transaction.h" + +using ValueState = + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState; + +TrackedAtomicPreference::TrackedAtomicPreference( + const std::string& pref_path, + size_t reporting_id, + size_t reporting_ids_count, + prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel enforcement_level, + prefs::mojom::TrackedPreferenceMetadata::ValueType value_type, + prefs::mojom::TrackedPreferenceValidationDelegate* delegate) + : pref_path_(pref_path), + helper_(pref_path, + reporting_id, + reporting_ids_count, + enforcement_level, + value_type), + delegate_(delegate) {} + +TrackedPreferenceType TrackedAtomicPreference::GetType() const { + return TrackedPreferenceType::ATOMIC; +} + +void TrackedAtomicPreference::OnNewValue( + const base::Value* value, + PrefHashStoreTransaction* transaction) const { + transaction->StoreHash(pref_path_, value); +} + +bool TrackedAtomicPreference::EnforceAndReport( + base::DictionaryValue* pref_store_contents, + PrefHashStoreTransaction* transaction, + PrefHashStoreTransaction* external_validation_transaction) const { + const base::Value* value = NULL; + pref_store_contents->Get(pref_path_, &value); + ValueState value_state = transaction->CheckValue(pref_path_, value); + helper_.ReportValidationResult(value_state, transaction->GetStoreUMASuffix()); + + ValueState external_validation_value_state = ValueState::UNSUPPORTED; + if (external_validation_transaction) { + external_validation_value_state = + external_validation_transaction->CheckValue(pref_path_, value); + helper_.ReportValidationResult( + external_validation_value_state, + external_validation_transaction->GetStoreUMASuffix()); + } + + if (delegate_) { + delegate_->OnAtomicPreferenceValidation( + pref_path_, value ? value->CreateDeepCopy() : nullptr, value_state, + external_validation_value_state, helper_.IsPersonal()); + } + TrackedPreferenceHelper::ResetAction reset_action = + helper_.GetAction(value_state); + helper_.ReportAction(reset_action); + + bool was_reset = false; + if (reset_action == TrackedPreferenceHelper::DO_RESET) { + pref_store_contents->RemovePath(pref_path_, NULL); + was_reset = true; + } + + if (value_state != ValueState::UNCHANGED) { + // Store the hash for the new value (whether it was reset or not). + const base::Value* new_value = NULL; + pref_store_contents->Get(pref_path_, &new_value); + transaction->StoreHash(pref_path_, new_value); + } + + // Update MACs in the external store if there is one and there either was a + // reset or external validation failed. + if (external_validation_transaction && + (was_reset || external_validation_value_state != ValueState::UNCHANGED)) { + const base::Value* new_value = nullptr; + pref_store_contents->Get(pref_path_, &new_value); + external_validation_transaction->StoreHash(pref_path_, new_value); + } + + return was_reset; +} diff --git a/chromium/services/preferences/tracked/tracked_atomic_preference.h b/chromium/services/preferences/tracked/tracked_atomic_preference.h new file mode 100644 index 00000000000..3f3477e9dd9 --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_atomic_preference.h @@ -0,0 +1,55 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_TRACKED_TRACKED_ATOMIC_PREFERENCE_H_ +#define SERVICES_PREFERENCES_TRACKED_TRACKED_ATOMIC_PREFERENCE_H_ + +#include <stddef.h> + +#include <string> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "services/preferences/tracked/pref_hash_filter.h" +#include "services/preferences/tracked/tracked_preference.h" +#include "services/preferences/tracked/tracked_preference_helper.h" + +namespace prefs { +namespace mojom { +class TrackedPreferenceValidationDelegate; +} +} + +// A TrackedAtomicPreference is tracked as a whole. A hash is stored for its +// entire value and it is entirely reset on mismatch. An optional delegate is +// notified of the status of the preference during enforcement. +class TrackedAtomicPreference : public TrackedPreference { + public: + TrackedAtomicPreference( + const std::string& pref_path, + size_t reporting_id, + size_t reporting_ids_count, + prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel + enforcement_level, + prefs::mojom::TrackedPreferenceMetadata::ValueType value_type, + prefs::mojom::TrackedPreferenceValidationDelegate* delegate); + + // TrackedPreference implementation. + TrackedPreferenceType GetType() const override; + void OnNewValue(const base::Value* value, + PrefHashStoreTransaction* transaction) const override; + bool EnforceAndReport( + base::DictionaryValue* pref_store_contents, + PrefHashStoreTransaction* transaction, + PrefHashStoreTransaction* external_validation_transaction) const override; + + private: + const std::string pref_path_; + const TrackedPreferenceHelper helper_; + prefs::mojom::TrackedPreferenceValidationDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(TrackedAtomicPreference); +}; + +#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_ATOMIC_PREFERENCE_H_ diff --git a/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.cc b/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.cc new file mode 100644 index 00000000000..ffb5ab2e9ac --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.cc @@ -0,0 +1,133 @@ +// Copyright 2017 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 "services/preferences/tracked/tracked_persistent_pref_store_factory.h" + +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "components/prefs/json_pref_store.h" +#include "components/prefs/pref_filter.h" +#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h" +#include "services/preferences/tracked/pref_hash_filter.h" +#include "services/preferences/tracked/pref_hash_store_impl.h" +#include "services/preferences/tracked/segregated_pref_store.h" +#include "services/preferences/tracked/tracked_preferences_migration.h" + +#if defined(OS_WIN) +#include "services/preferences/tracked/registry_hash_store_contents_win.h" +#endif + +namespace { + +void RemoveValueSilently(const base::WeakPtr<JsonPrefStore> pref_store, + const std::string& key) { + if (pref_store) { + pref_store->RemoveValueSilently( + key, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); + } +} + +std::unique_ptr<PrefHashStore> CreatePrefHashStore( + const prefs::mojom::TrackedPersistentPrefStoreConfiguration& config, + bool use_super_mac) { + return base::MakeUnique<PrefHashStoreImpl>( + config.seed, config.legacy_device_id, use_super_mac); +} + +std::pair<std::unique_ptr<PrefHashStore>, std::unique_ptr<HashStoreContents>> +GetExternalVerificationPrefHashStorePair( + const prefs::mojom::TrackedPersistentPrefStoreConfiguration& config) { +#if defined(OS_WIN) + return std::make_pair( + base::MakeUnique<PrefHashStoreImpl>(config.registry_seed, + config.legacy_device_id, + false /* use_super_mac */), + base::MakeUnique<RegistryHashStoreContentsWin>( + config.registry_path, config.unprotected_pref_filename.DirName() + .BaseName() + .LossyDisplayName())); +#else + return std::make_pair(nullptr, nullptr); +#endif +} + +} // namespace + +PersistentPrefStore* CreateTrackedPersistentPrefStore( + prefs::mojom::TrackedPersistentPrefStoreConfigurationPtr config, + base::SequencedWorkerPool* worker_pool) { + auto io_task_runner = JsonPrefStore::GetTaskRunnerForFile( + config->unprotected_pref_filename.DirName(), worker_pool); + + std::vector<prefs::mojom::TrackedPreferenceMetadataPtr> + unprotected_configuration; + std::vector<prefs::mojom::TrackedPreferenceMetadataPtr> + protected_configuration; + std::set<std::string> protected_pref_names; + std::set<std::string> unprotected_pref_names; + for (auto& metadata : config->tracking_configuration) { + if (metadata->enforcement_level > prefs::mojom::TrackedPreferenceMetadata:: + EnforcementLevel::NO_ENFORCEMENT) { + protected_pref_names.insert(metadata->name); + protected_configuration.push_back(std::move(metadata)); + } else { + unprotected_pref_names.insert(metadata->name); + unprotected_configuration.push_back(std::move(metadata)); + } + } + config->tracking_configuration.clear(); + + std::unique_ptr<PrefHashFilter> unprotected_pref_hash_filter( + new PrefHashFilter(CreatePrefHashStore(*config, false), + GetExternalVerificationPrefHashStorePair(*config), + unprotected_configuration, nullptr, + config->validation_delegate.get(), + config->reporting_ids_count, false)); + std::unique_ptr<PrefHashFilter> protected_pref_hash_filter(new PrefHashFilter( + CreatePrefHashStore(*config, true), + GetExternalVerificationPrefHashStorePair(*config), + protected_configuration, std::move(config->reset_on_load_observer), + config->validation_delegate.get(), config->reporting_ids_count, true)); + + PrefHashFilter* raw_unprotected_pref_hash_filter = + unprotected_pref_hash_filter.get(); + PrefHashFilter* raw_protected_pref_hash_filter = + protected_pref_hash_filter.get(); + + scoped_refptr<JsonPrefStore> unprotected_pref_store( + new JsonPrefStore(config->unprotected_pref_filename, io_task_runner.get(), + std::move(unprotected_pref_hash_filter))); + scoped_refptr<JsonPrefStore> protected_pref_store( + new JsonPrefStore(config->protected_pref_filename, io_task_runner.get(), + std::move(protected_pref_hash_filter))); + + SetupTrackedPreferencesMigration( + unprotected_pref_names, protected_pref_names, + base::Bind(&RemoveValueSilently, unprotected_pref_store->AsWeakPtr()), + base::Bind(&RemoveValueSilently, protected_pref_store->AsWeakPtr()), + base::Bind(&JsonPrefStore::RegisterOnNextSuccessfulWriteReply, + unprotected_pref_store->AsWeakPtr()), + base::Bind(&JsonPrefStore::RegisterOnNextSuccessfulWriteReply, + protected_pref_store->AsWeakPtr()), + CreatePrefHashStore(*config, false), CreatePrefHashStore(*config, true), + raw_unprotected_pref_hash_filter, raw_protected_pref_hash_filter); + + return new SegregatedPrefStore(unprotected_pref_store, protected_pref_store, + protected_pref_names, + std::move(config->validation_delegate)); +} + +void InitializeMasterPrefsTracking( + prefs::mojom::TrackedPersistentPrefStoreConfigurationPtr configuration, + base::DictionaryValue* master_prefs) { + PrefHashFilter(CreatePrefHashStore(*configuration, false), + GetExternalVerificationPrefHashStorePair(*configuration), + configuration->tracking_configuration, nullptr, nullptr, + configuration->reporting_ids_count, false) + .Initialize(master_prefs); +} diff --git a/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.h b/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.h new file mode 100644 index 00000000000..fb426bab47d --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_persistent_pref_store_factory.h @@ -0,0 +1,27 @@ +// Copyright 2017 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 SERVICES_PREFERENCES_TRACKED_TRACKED_PERSISTENT_PREF_STORE_FACTORY_H_ +#define SERVICES_PREFERENCES_TRACKED_TRACKED_PERSISTENT_PREF_STORE_FACTORY_H_ + +#include <utility> +#include "services/preferences/public/interfaces/preferences_configuration.mojom.h" + +namespace base { +class DictionaryValue; +class SequencedWorkerPool; +} + +class PersistentPrefStore; + +PersistentPrefStore* CreateTrackedPersistentPrefStore( + prefs::mojom::TrackedPersistentPrefStoreConfigurationPtr config, + base::SequencedWorkerPool* worker_pool); + +// TODO(sammc): This should move somewhere more appropriate in the longer term. +void InitializeMasterPrefsTracking( + prefs::mojom::TrackedPersistentPrefStoreConfigurationPtr configuration, + base::DictionaryValue* master_prefs); + +#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_PERSISTENT_PREF_STORE_FACTORY_H_ diff --git a/chromium/services/preferences/tracked/tracked_preference.h b/chromium/services/preferences/tracked/tracked_preference.h new file mode 100644 index 00000000000..eeb333c52a1 --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_preference.h @@ -0,0 +1,44 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_H_ +#define SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_H_ + +class PrefHashStoreTransaction; + +namespace base { +class DictionaryValue; +class Value; +} + +enum class TrackedPreferenceType { ATOMIC, SPLIT }; + +// A TrackedPreference tracks changes to an individual preference, reporting and +// reacting to them according to preference-specific and browser-wide policies. +class TrackedPreference { + public: + virtual ~TrackedPreference() {} + + virtual TrackedPreferenceType GetType() const = 0; + + // Notifies the underlying TrackedPreference about its new |value| which + // can update hashes in the corresponding hash store via |transaction|. + virtual void OnNewValue(const base::Value* value, + PrefHashStoreTransaction* transaction) const = 0; + + // Verifies that the value of this TrackedPreference in |pref_store_contents| + // is valid. Responds to verification failures according to + // preference-specific and browser-wide policy and reports results to via UMA. + // May use |transaction| to check/modify hashes in the corresponding hash + // store. Performs validation and reports results without enforcing for + // |external_validation_transaction|. This call assumes exclusive access to + // |external_validation_transaction| and its associated state and as such + // should only be called before any other subsystem is made aware of it. + virtual bool EnforceAndReport( + base::DictionaryValue* pref_store_contents, + PrefHashStoreTransaction* transaction, + PrefHashStoreTransaction* external_validation_transaction) const = 0; +}; + +#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_H_ diff --git a/chromium/services/preferences/tracked/tracked_preference_helper.cc b/chromium/services/preferences/tracked/tracked_preference_helper.cc new file mode 100644 index 00000000000..d368ddb2f00 --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_preference_helper.cc @@ -0,0 +1,139 @@ +// Copyright 2014 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 "services/preferences/tracked/tracked_preference_helper.h" + +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/metrics/histogram_macros.h" +#include "services/preferences/public/cpp/tracked/tracked_preference_histogram_names.h" + +using ValueState = + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState; + +TrackedPreferenceHelper::TrackedPreferenceHelper( + const std::string& pref_path, + size_t reporting_id, + size_t reporting_ids_count, + prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel enforcement_level, + prefs::mojom::TrackedPreferenceMetadata::ValueType value_type) + : pref_path_(pref_path), + reporting_id_(reporting_id), + reporting_ids_count_(reporting_ids_count), + enforce_(enforcement_level == prefs::mojom::TrackedPreferenceMetadata:: + EnforcementLevel::ENFORCE_ON_LOAD), + personal_(value_type == + prefs::mojom::TrackedPreferenceMetadata::ValueType::PERSONAL) {} + +TrackedPreferenceHelper::ResetAction TrackedPreferenceHelper::GetAction( + ValueState value_state) const { + switch (value_state) { + case ValueState::UNCHANGED: + // Desired case, nothing to do. + return DONT_RESET; + case ValueState::CLEARED: + // Unfortunate case, but there is nothing we can do. + return DONT_RESET; + case ValueState::TRUSTED_NULL_VALUE: // Falls through. + case ValueState::TRUSTED_UNKNOWN_VALUE: + // It is okay to seed the hash in this case. + return DONT_RESET; + case ValueState::SECURE_LEGACY: + // Accept secure legacy device ID based hashes. + return DONT_RESET; + case ValueState::UNSUPPORTED: + NOTREACHED() + << "GetAction should not be called with an UNSUPPORTED value state"; + return DONT_RESET; + case ValueState::UNTRUSTED_UNKNOWN_VALUE: // Falls through. + case ValueState::CHANGED: + return enforce_ ? DO_RESET : WANTED_RESET; + } + NOTREACHED() << "Unexpected ValueState: " << value_state; + return DONT_RESET; +} + +bool TrackedPreferenceHelper::IsPersonal() const { + return personal_; +} + +void TrackedPreferenceHelper::ReportValidationResult( + ValueState value_state, + base::StringPiece validation_type_suffix) const { + const char* histogram_name = nullptr; + switch (value_state) { + case ValueState::UNCHANGED: + histogram_name = user_prefs::tracked::kTrackedPrefHistogramUnchanged; + break; + case ValueState::CLEARED: + histogram_name = user_prefs::tracked::kTrackedPrefHistogramCleared; + break; + case ValueState::SECURE_LEGACY: + histogram_name = + user_prefs::tracked::kTrackedPrefHistogramMigratedLegacyDeviceId; + break; + case ValueState::CHANGED: + histogram_name = user_prefs::tracked::kTrackedPrefHistogramChanged; + break; + case ValueState::UNTRUSTED_UNKNOWN_VALUE: + histogram_name = user_prefs::tracked::kTrackedPrefHistogramInitialized; + break; + case ValueState::TRUSTED_UNKNOWN_VALUE: + histogram_name = + user_prefs::tracked::kTrackedPrefHistogramTrustedInitialized; + break; + case ValueState::TRUSTED_NULL_VALUE: + histogram_name = + user_prefs::tracked::kTrackedPrefHistogramNullInitialized; + break; + case ValueState::UNSUPPORTED: + NOTREACHED() << "ReportValidationResult should not be called with an " + "UNSUPPORTED value state"; + return; + } + DCHECK(histogram_name); + + std::string full_histogram_name(histogram_name); + if (!validation_type_suffix.empty()) { + full_histogram_name.push_back('.'); + validation_type_suffix.AppendToString(&full_histogram_name); + } + + // Using FactoryGet to allow dynamic histogram names. This is equivalent to + // UMA_HISTOGRAM_ENUMERATION(name, reporting_id_, reporting_ids_count_); + base::HistogramBase* histogram = base::LinearHistogram::FactoryGet( + full_histogram_name, 1, reporting_ids_count_, reporting_ids_count_ + 1, + base::HistogramBase::kUmaTargetedHistogramFlag); + histogram->Add(reporting_id_); +} + +void TrackedPreferenceHelper::ReportAction(ResetAction reset_action) const { + switch (reset_action) { + case DONT_RESET: + // No report for DONT_RESET. + break; + case WANTED_RESET: + UMA_HISTOGRAM_EXACT_LINEAR( + user_prefs::tracked::kTrackedPrefHistogramWantedReset, reporting_id_, + reporting_ids_count_); + break; + case DO_RESET: + UMA_HISTOGRAM_EXACT_LINEAR( + user_prefs::tracked::kTrackedPrefHistogramReset, reporting_id_, + reporting_ids_count_); + break; + } +} + +void TrackedPreferenceHelper::ReportSplitPreferenceChangedCount( + size_t count) const { + // The histogram below is an expansion of the UMA_HISTOGRAM_COUNTS_100 macro + // adapted to allow for a dynamically suffixed histogram name. + // Note: The factory creates and owns the histogram. + base::HistogramBase* histogram = base::LinearHistogram::FactoryGet( + user_prefs::tracked::kTrackedSplitPrefHistogramChanged + pref_path_, 1, + 100, // Allow counts up to 100. + 101, base::HistogramBase::kUmaTargetedHistogramFlag); + histogram->Add(count); +} diff --git a/chromium/services/preferences/tracked/tracked_preference_helper.h b/chromium/services/preferences/tracked/tracked_preference_helper.h new file mode 100644 index 00000000000..182520dd106 --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_preference_helper.h @@ -0,0 +1,75 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_HELPER_H_ +#define SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_HELPER_H_ + +#include <stddef.h> + +#include <string> + +#include "base/macros.h" +#include "services/preferences/tracked/pref_hash_filter.h" +#include "services/preferences/tracked/pref_hash_store_transaction.h" + +// A TrackedPreferenceHelper is a helper class for TrackedPreference which +// handles decision making and reporting for TrackedPreference's +// implementations. +class TrackedPreferenceHelper { + public: + enum ResetAction { + DONT_RESET, + // WANTED_RESET is reported when DO_RESET would have been reported but the + // current |enforcement_level| doesn't allow a reset for the detected state. + WANTED_RESET, + DO_RESET, + }; + + TrackedPreferenceHelper( + const std::string& pref_path, + size_t reporting_id, + size_t reporting_ids_count, + prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel + enforcement_level, + prefs::mojom::TrackedPreferenceMetadata::ValueType value_type); + + // Returns a ResetAction stating whether a reset is desired (DO_RESET) or not + // (DONT_RESET) based on observing |value_state|. Can also return WANTED_RESET + // if a reset would have been desired but the current |enforcement_level| + // doesn't allow it. + ResetAction GetAction( + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state) + const; + + // Returns true if the preference value may contain personal information. + bool IsPersonal() const; + + // Reports |value_state| via UMA under |reporting_id_|. + // |validation_type_suffix| is appended to the reported histogram's name. + void ReportValidationResult( + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState value_state, + base::StringPiece validation_type_suffix) const; + + // Reports |reset_action| via UMA under |reporting_id_|. + void ReportAction(ResetAction reset_action) const; + + // Reports, via UMA, the |count| of split preference entries that were + // considered invalid in a CHANGED event. + void ReportSplitPreferenceChangedCount(size_t count) const; + + private: + const std::string pref_path_; + + const size_t reporting_id_; + const size_t reporting_ids_count_; + + // Deny setting changes and hash seeding/migration. + const bool enforce_; + + const bool personal_; + + DISALLOW_COPY_AND_ASSIGN(TrackedPreferenceHelper); +}; + +#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCE_HELPER_H_ diff --git a/chromium/services/preferences/tracked/tracked_preferences_migration.cc b/chromium/services/preferences/tracked/tracked_preferences_migration.cc new file mode 100644 index 00000000000..f2af336e350 --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_preferences_migration.cc @@ -0,0 +1,333 @@ +// Copyright 2014 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 "services/preferences/tracked/tracked_preferences_migration.h" + +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/metrics/histogram.h" +#include "base/values.h" +#include "services/preferences/tracked/dictionary_hash_store_contents.h" +#include "services/preferences/tracked/hash_store_contents.h" +#include "services/preferences/tracked/interceptable_pref_filter.h" +#include "services/preferences/tracked/pref_hash_store.h" +#include "services/preferences/tracked/pref_hash_store_transaction.h" + +namespace { + +class TrackedPreferencesMigrator + : public base::RefCounted<TrackedPreferencesMigrator> { + public: + TrackedPreferencesMigrator( + const std::set<std::string>& unprotected_pref_names, + const std::set<std::string>& protected_pref_names, + const base::Callback<void(const std::string& key)>& + unprotected_store_cleaner, + const base::Callback<void(const std::string& key)>& + protected_store_cleaner, + const base::Callback<void(const base::Closure&)>& + register_on_successful_unprotected_store_write_callback, + const base::Callback<void(const base::Closure&)>& + register_on_successful_protected_store_write_callback, + std::unique_ptr<PrefHashStore> unprotected_pref_hash_store, + std::unique_ptr<PrefHashStore> protected_pref_hash_store, + InterceptablePrefFilter* unprotected_pref_filter, + InterceptablePrefFilter* protected_pref_filter); + + private: + friend class base::RefCounted<TrackedPreferencesMigrator>; + + enum PrefFilterID { UNPROTECTED_PREF_FILTER, PROTECTED_PREF_FILTER }; + + ~TrackedPreferencesMigrator(); + + // Stores the data coming in from the filter identified by |id| into this + // class and then calls MigrateIfReady(); + void InterceptFilterOnLoad( + PrefFilterID id, + const InterceptablePrefFilter::FinalizeFilterOnLoadCallback& + finalize_filter_on_load, + std::unique_ptr<base::DictionaryValue> prefs); + + // Proceeds with migration if both |unprotected_prefs_| and |protected_prefs_| + // have been set. + void MigrateIfReady(); + + const std::set<std::string> unprotected_pref_names_; + const std::set<std::string> protected_pref_names_; + + const base::Callback<void(const std::string& key)> unprotected_store_cleaner_; + const base::Callback<void(const std::string& key)> protected_store_cleaner_; + const base::Callback<void(const base::Closure&)> + register_on_successful_unprotected_store_write_callback_; + const base::Callback<void(const base::Closure&)> + register_on_successful_protected_store_write_callback_; + + InterceptablePrefFilter::FinalizeFilterOnLoadCallback + finalize_unprotected_filter_on_load_; + InterceptablePrefFilter::FinalizeFilterOnLoadCallback + finalize_protected_filter_on_load_; + + std::unique_ptr<PrefHashStore> unprotected_pref_hash_store_; + std::unique_ptr<PrefHashStore> protected_pref_hash_store_; + + std::unique_ptr<base::DictionaryValue> unprotected_prefs_; + std::unique_ptr<base::DictionaryValue> protected_prefs_; + + DISALLOW_COPY_AND_ASSIGN(TrackedPreferencesMigrator); +}; + +// Invokes |store_cleaner| for every |keys_to_clean|. +void CleanupPrefStore( + const base::Callback<void(const std::string& key)>& store_cleaner, + const std::set<std::string>& keys_to_clean) { + for (std::set<std::string>::const_iterator it = keys_to_clean.begin(); + it != keys_to_clean.end(); ++it) { + store_cleaner.Run(*it); + } +} + +// If |wait_for_commit_to_destination_store|: schedules (via +// |register_on_successful_destination_store_write_callback|) a cleanup of the +// |keys_to_clean| from the source pref store (through |source_store_cleaner|) +// once the destination pref store they were migrated to was successfully +// written to disk. Otherwise, executes the cleanup right away. +void ScheduleSourcePrefStoreCleanup( + const base::Callback<void(const base::Closure&)>& + register_on_successful_destination_store_write_callback, + const base::Callback<void(const std::string& key)>& source_store_cleaner, + const std::set<std::string>& keys_to_clean, + bool wait_for_commit_to_destination_store) { + DCHECK(!keys_to_clean.empty()); + if (wait_for_commit_to_destination_store) { + register_on_successful_destination_store_write_callback.Run( + base::Bind(&CleanupPrefStore, source_store_cleaner, keys_to_clean)); + } else { + CleanupPrefStore(source_store_cleaner, keys_to_clean); + } +} + +// Removes hashes for |migrated_pref_names| from |origin_pref_store| using +// the configuration/implementation in |origin_pref_hash_store|. +void CleanupMigratedHashes(const std::set<std::string>& migrated_pref_names, + PrefHashStore* origin_pref_hash_store, + base::DictionaryValue* origin_pref_store) { + DictionaryHashStoreContents dictionary_contents(origin_pref_store); + std::unique_ptr<PrefHashStoreTransaction> transaction( + origin_pref_hash_store->BeginTransaction(&dictionary_contents)); + for (std::set<std::string>::const_iterator it = migrated_pref_names.begin(); + it != migrated_pref_names.end(); ++it) { + transaction->ClearHash(*it); + } +} + +// Copies the value of each pref in |pref_names| which is set in |old_store|, +// but not in |new_store| into |new_store|. Sets |old_store_needs_cleanup| to +// true if any old duplicates remain in |old_store| and sets |new_store_altered| +// to true if any value was copied to |new_store|. +void MigratePrefsFromOldToNewStore(const std::set<std::string>& pref_names, + base::DictionaryValue* old_store, + base::DictionaryValue* new_store, + PrefHashStore* new_hash_store, + bool* old_store_needs_cleanup, + bool* new_store_altered) { + const base::DictionaryValue* old_hash_store_contents = + DictionaryHashStoreContents(old_store).GetContents(); + DictionaryHashStoreContents dictionary_contents(new_store); + std::unique_ptr<PrefHashStoreTransaction> new_hash_store_transaction( + new_hash_store->BeginTransaction(&dictionary_contents)); + + for (std::set<std::string>::const_iterator it = pref_names.begin(); + it != pref_names.end(); ++it) { + const std::string& pref_name = *it; + const base::Value* value_in_old_store = NULL; + + // If the destination does not have a hash for this pref we will + // unconditionally attempt to move it. + bool destination_hash_missing = + !new_hash_store_transaction->HasHash(pref_name); + // If we migrate the value we will also attempt to migrate the hash. + bool migrated_value = false; + if (old_store->Get(pref_name, &value_in_old_store)) { + // Whether this value ends up being copied below or was left behind by a + // previous incomplete migration, it should be cleaned up. + *old_store_needs_cleanup = true; + + if (!new_store->Get(pref_name, NULL)) { + // Copy the value from |old_store| to |new_store| rather than moving it + // to avoid data loss should |old_store| be flushed to disk without + // |new_store| having equivalently been successfully flushed to disk + // (e.g., on crash or in cases where |new_store| is read-only following + // a read error on startup). + new_store->Set(pref_name, value_in_old_store->DeepCopy()); + migrated_value = true; + *new_store_altered = true; + } + } + + if (destination_hash_missing || migrated_value) { + const base::Value* old_hash = NULL; + if (old_hash_store_contents) + old_hash_store_contents->Get(pref_name, &old_hash); + if (old_hash) { + new_hash_store_transaction->ImportHash(pref_name, old_hash); + *new_store_altered = true; + } else if (!destination_hash_missing) { + // Do not allow values to be migrated without MACs if the destination + // already has a MAC (http://crbug.com/414554). Remove the migrated + // value in order to provide the same no-op behaviour as if the pref was + // added to the wrong file when there was already a value for + // |pref_name| in |new_store|. + new_store->Remove(pref_name, NULL); + *new_store_altered = true; + } + } + } +} + +TrackedPreferencesMigrator::TrackedPreferencesMigrator( + const std::set<std::string>& unprotected_pref_names, + const std::set<std::string>& protected_pref_names, + const base::Callback<void(const std::string& key)>& + unprotected_store_cleaner, + const base::Callback<void(const std::string& key)>& protected_store_cleaner, + const base::Callback<void(const base::Closure&)>& + register_on_successful_unprotected_store_write_callback, + const base::Callback<void(const base::Closure&)>& + register_on_successful_protected_store_write_callback, + std::unique_ptr<PrefHashStore> unprotected_pref_hash_store, + std::unique_ptr<PrefHashStore> protected_pref_hash_store, + InterceptablePrefFilter* unprotected_pref_filter, + InterceptablePrefFilter* protected_pref_filter) + : unprotected_pref_names_(unprotected_pref_names), + protected_pref_names_(protected_pref_names), + unprotected_store_cleaner_(unprotected_store_cleaner), + protected_store_cleaner_(protected_store_cleaner), + register_on_successful_unprotected_store_write_callback_( + register_on_successful_unprotected_store_write_callback), + register_on_successful_protected_store_write_callback_( + register_on_successful_protected_store_write_callback), + unprotected_pref_hash_store_(std::move(unprotected_pref_hash_store)), + protected_pref_hash_store_(std::move(protected_pref_hash_store)) { + // The callbacks bound below will own this TrackedPreferencesMigrator by + // reference. + unprotected_pref_filter->InterceptNextFilterOnLoad( + base::Bind(&TrackedPreferencesMigrator::InterceptFilterOnLoad, this, + UNPROTECTED_PREF_FILTER)); + protected_pref_filter->InterceptNextFilterOnLoad( + base::Bind(&TrackedPreferencesMigrator::InterceptFilterOnLoad, this, + PROTECTED_PREF_FILTER)); +} + +TrackedPreferencesMigrator::~TrackedPreferencesMigrator() {} + +void TrackedPreferencesMigrator::InterceptFilterOnLoad( + PrefFilterID id, + const InterceptablePrefFilter::FinalizeFilterOnLoadCallback& + finalize_filter_on_load, + std::unique_ptr<base::DictionaryValue> prefs) { + switch (id) { + case UNPROTECTED_PREF_FILTER: + finalize_unprotected_filter_on_load_ = finalize_filter_on_load; + unprotected_prefs_ = std::move(prefs); + break; + case PROTECTED_PREF_FILTER: + finalize_protected_filter_on_load_ = finalize_filter_on_load; + protected_prefs_ = std::move(prefs); + break; + } + + MigrateIfReady(); +} + +void TrackedPreferencesMigrator::MigrateIfReady() { + // Wait for both stores to have been read before proceeding. + if (!protected_prefs_ || !unprotected_prefs_) + return; + + bool protected_prefs_need_cleanup = false; + bool unprotected_prefs_altered = false; + MigratePrefsFromOldToNewStore( + unprotected_pref_names_, protected_prefs_.get(), unprotected_prefs_.get(), + unprotected_pref_hash_store_.get(), &protected_prefs_need_cleanup, + &unprotected_prefs_altered); + bool unprotected_prefs_need_cleanup = false; + bool protected_prefs_altered = false; + MigratePrefsFromOldToNewStore( + protected_pref_names_, unprotected_prefs_.get(), protected_prefs_.get(), + protected_pref_hash_store_.get(), &unprotected_prefs_need_cleanup, + &protected_prefs_altered); + + if (!unprotected_prefs_altered && !protected_prefs_altered) { + // Clean up any MACs that might have been previously migrated from the + // various stores. It's safe to leave them behind for a little while as they + // will be ignored unless the corresponding value is _also_ present. The + // cleanup must be deferred until the MACs have been written to their target + // stores, and doing so in a subsequent launch is easier than within the + // same process. + CleanupMigratedHashes(unprotected_pref_names_, + protected_pref_hash_store_.get(), + protected_prefs_.get()); + CleanupMigratedHashes(protected_pref_names_, + unprotected_pref_hash_store_.get(), + unprotected_prefs_.get()); + } + + // Hand the processed prefs back to their respective filters. + finalize_unprotected_filter_on_load_.Run(std::move(unprotected_prefs_), + unprotected_prefs_altered); + finalize_protected_filter_on_load_.Run(std::move(protected_prefs_), + protected_prefs_altered); + + if (unprotected_prefs_need_cleanup) { + // Schedule a cleanup of the |protected_pref_names_| from the unprotected + // prefs once the protected prefs were successfully written to disk (or + // do it immediately if |!protected_prefs_altered|). + ScheduleSourcePrefStoreCleanup( + register_on_successful_protected_store_write_callback_, + unprotected_store_cleaner_, protected_pref_names_, + protected_prefs_altered); + } + + if (protected_prefs_need_cleanup) { + // Schedule a cleanup of the |unprotected_pref_names_| from the protected + // prefs once the unprotected prefs were successfully written to disk (or + // do it immediately if |!unprotected_prefs_altered|). + ScheduleSourcePrefStoreCleanup( + register_on_successful_unprotected_store_write_callback_, + protected_store_cleaner_, unprotected_pref_names_, + unprotected_prefs_altered); + } +} + +} // namespace + +void SetupTrackedPreferencesMigration( + const std::set<std::string>& unprotected_pref_names, + const std::set<std::string>& protected_pref_names, + const base::Callback<void(const std::string& key)>& + unprotected_store_cleaner, + const base::Callback<void(const std::string& key)>& protected_store_cleaner, + const base::Callback<void(const base::Closure&)>& + register_on_successful_unprotected_store_write_callback, + const base::Callback<void(const base::Closure&)>& + register_on_successful_protected_store_write_callback, + std::unique_ptr<PrefHashStore> unprotected_pref_hash_store, + std::unique_ptr<PrefHashStore> protected_pref_hash_store, + InterceptablePrefFilter* unprotected_pref_filter, + InterceptablePrefFilter* protected_pref_filter) { + scoped_refptr<TrackedPreferencesMigrator> prefs_migrator( + new TrackedPreferencesMigrator( + unprotected_pref_names, protected_pref_names, + unprotected_store_cleaner, protected_store_cleaner, + register_on_successful_unprotected_store_write_callback, + register_on_successful_protected_store_write_callback, + std::move(unprotected_pref_hash_store), + std::move(protected_pref_hash_store), unprotected_pref_filter, + protected_pref_filter)); +} diff --git a/chromium/services/preferences/tracked/tracked_preferences_migration.h b/chromium/services/preferences/tracked/tracked_preferences_migration.h new file mode 100644 index 00000000000..381434aada7 --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_preferences_migration.h @@ -0,0 +1,45 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCES_MIGRATION_H_ +#define SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCES_MIGRATION_H_ + +#include <memory> +#include <set> +#include <string> + +#include "base/callback_forward.h" +#include "services/preferences/tracked/pref_hash_store.h" + +class InterceptablePrefFilter; +class PrefHashStore; + +// Sets up InterceptablePrefFilter::FilterOnLoadInterceptors on +// |unprotected_pref_filter| and |protected_pref_filter| which prevents each +// filter from running their on load operations until the interceptors decide to +// hand the prefs back to them (after migration is complete). | +// (un)protected_store_cleaner| and +// |register_on_successful_(un)protected_store_write_callback| are used to do +// post-migration cleanup tasks. Those should be bound to weak pointers to avoid +// blocking shutdown. |(un)protected_pref_hash_store| is used to migrate MACs +// along with their protected preferences. Migrated MACs will only be cleared +// from their old location in a subsequent run. The migration framework is +// resilient to a failed cleanup (it will simply try again in the next Chrome +// run). +void SetupTrackedPreferencesMigration( + const std::set<std::string>& unprotected_pref_names, + const std::set<std::string>& protected_pref_names, + const base::Callback<void(const std::string& key)>& + unprotected_store_cleaner, + const base::Callback<void(const std::string& key)>& protected_store_cleaner, + const base::Callback<void(const base::Closure&)>& + register_on_successful_unprotected_store_write_callback, + const base::Callback<void(const base::Closure&)>& + register_on_successful_protected_store_write_callback, + std::unique_ptr<PrefHashStore> unprotected_pref_hash_store, + std::unique_ptr<PrefHashStore> protected_pref_hash_store, + InterceptablePrefFilter* unprotected_pref_filter, + InterceptablePrefFilter* protected_pref_filter); + +#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_PREFERENCES_MIGRATION_H_ diff --git a/chromium/services/preferences/tracked/tracked_preferences_migration_unittest.cc b/chromium/services/preferences/tracked/tracked_preferences_migration_unittest.cc new file mode 100644 index 00000000000..fdbdd099d3d --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_preferences_migration_unittest.cc @@ -0,0 +1,645 @@ +// Copyright 2014 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 "services/preferences/tracked/tracked_preferences_migration.h" + +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/strings/string_split.h" +#include "base/values.h" +#include "components/prefs/testing_pref_service.h" +#include "services/preferences/tracked/dictionary_hash_store_contents.h" +#include "services/preferences/tracked/hash_store_contents.h" +#include "services/preferences/tracked/interceptable_pref_filter.h" +#include "services/preferences/tracked/pref_hash_store.h" +#include "services/preferences/tracked/pref_hash_store_impl.h" +#include "services/preferences/tracked/pref_hash_store_transaction.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// An unprotected pref. +const char kUnprotectedPref[] = "unprotected"; +// A protected pref. +const char kProtectedPref[] = "protected"; +// A protected pref which is initially stored in the unprotected store. +const char kPreviouslyUnprotectedPref[] = "previously.unprotected"; +// An unprotected pref which is initially stored in the protected store. +const char kPreviouslyProtectedPref[] = "previously.protected"; + +const char kUnprotectedPrefValue[] = "unprotected_value"; +const char kProtectedPrefValue[] = "protected_value"; +const char kPreviouslyUnprotectedPrefValue[] = "previously_unprotected_value"; +const char kPreviouslyProtectedPrefValue[] = "previously_protected_value"; + +// A simple InterceptablePrefFilter which doesn't do anything but hand the prefs +// back downstream in FinalizeFilterOnLoad. +class SimpleInterceptablePrefFilter : public InterceptablePrefFilter { + public: + // PrefFilter remaining implementation. + void FilterUpdate(const std::string& path) override { ADD_FAILURE(); } + OnWriteCallbackPair FilterSerializeData( + base::DictionaryValue* pref_store_contents) override { + ADD_FAILURE(); + return std::make_pair(base::Closure(), + base::Callback<void(bool success)>()); + } + + private: + // InterceptablePrefFilter implementation. + void FinalizeFilterOnLoad( + const PostFilterOnLoadCallback& post_filter_on_load_callback, + std::unique_ptr<base::DictionaryValue> pref_store_contents, + bool prefs_altered) override { + post_filter_on_load_callback.Run(std::move(pref_store_contents), + prefs_altered); + } +}; + +// A test fixture designed to be used like this: +// 1) Set up initial store prefs with PresetStoreValue(). +// 2) Hand both sets of prefs to the migrator via HandPrefsToMigrator(). +// 3) Migration completes synchronously when the second store hands its prefs +// over. +// 4) Verifications can be made via various methods of this fixture. +// Call Reset() to perform a second migration. +class TrackedPreferencesMigrationTest : public testing::Test { + public: + enum MockPrefStoreID { + MOCK_UNPROTECTED_PREF_STORE, + MOCK_PROTECTED_PREF_STORE, + }; + + TrackedPreferencesMigrationTest() + : unprotected_prefs_(new base::DictionaryValue), + protected_prefs_(new base::DictionaryValue), + migration_modified_unprotected_store_(false), + migration_modified_protected_store_(false), + unprotected_store_migration_complete_(false), + protected_store_migration_complete_(false) {} + + void SetUp() override { Reset(); } + + void Reset() { + std::set<std::string> unprotected_pref_names; + std::set<std::string> protected_pref_names; + unprotected_pref_names.insert(kUnprotectedPref); + unprotected_pref_names.insert(kPreviouslyProtectedPref); + protected_pref_names.insert(kProtectedPref); + protected_pref_names.insert(kPreviouslyUnprotectedPref); + + migration_modified_unprotected_store_ = false; + migration_modified_protected_store_ = false; + unprotected_store_migration_complete_ = false; + protected_store_migration_complete_ = false; + + unprotected_store_successful_write_callback_.Reset(); + protected_store_successful_write_callback_.Reset(); + + SetupTrackedPreferencesMigration( + unprotected_pref_names, protected_pref_names, + base::Bind(&TrackedPreferencesMigrationTest::RemovePathFromStore, + base::Unretained(this), MOCK_UNPROTECTED_PREF_STORE), + base::Bind(&TrackedPreferencesMigrationTest::RemovePathFromStore, + base::Unretained(this), MOCK_PROTECTED_PREF_STORE), + base::Bind( + &TrackedPreferencesMigrationTest::RegisterSuccessfulWriteClosure, + base::Unretained(this), MOCK_UNPROTECTED_PREF_STORE), + base::Bind( + &TrackedPreferencesMigrationTest::RegisterSuccessfulWriteClosure, + base::Unretained(this), MOCK_PROTECTED_PREF_STORE), + std::unique_ptr<PrefHashStore>( + new PrefHashStoreImpl(kSeed, kDeviceId, false)), + std::unique_ptr<PrefHashStore>( + new PrefHashStoreImpl(kSeed, kDeviceId, true)), + &mock_unprotected_pref_filter_, &mock_protected_pref_filter_); + + // Verify initial expectations are met. + EXPECT_TRUE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE)); + EXPECT_FALSE( + WasOnSuccessfulWriteCallbackRegistered(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_FALSE( + WasOnSuccessfulWriteCallbackRegistered(MOCK_PROTECTED_PREF_STORE)); + } + + protected: + // Sets |key| to |value| in the test store identified by |store_id| before + // migration begins. Also sets the corresponding hash in the same store. + void PresetStoreValue(MockPrefStoreID store_id, + const std::string& key, + const std::string value) { + PresetStoreValueOnly(store_id, key, value); + PresetStoreValueHash(store_id, key, value); + } + + // Stores a hash for |key| and |value| in the hash store identified by + // |store_id| before migration begins. + void PresetStoreValueHash(MockPrefStoreID store_id, + const std::string& key, + const std::string value) { + base::DictionaryValue* store = NULL; + std::unique_ptr<PrefHashStore> pref_hash_store; + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + store = unprotected_prefs_.get(); + pref_hash_store.reset(new PrefHashStoreImpl(kSeed, kDeviceId, false)); + break; + case MOCK_PROTECTED_PREF_STORE: + store = protected_prefs_.get(); + pref_hash_store.reset(new PrefHashStoreImpl(kSeed, kDeviceId, true)); + break; + } + DCHECK(store); + + base::Value string_value(value); + DictionaryHashStoreContents contents(store); + pref_hash_store->BeginTransaction(&contents)->StoreHash(key, &string_value); + } + + // Returns true if the store opposite to |store_id| is observed for its next + // successful write. + bool WasOnSuccessfulWriteCallbackRegistered(MockPrefStoreID store_id) { + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + return !protected_store_successful_write_callback_.is_null(); + case MOCK_PROTECTED_PREF_STORE: + return !unprotected_store_successful_write_callback_.is_null(); + } + NOTREACHED(); + return false; + } + + // Verifies that the (key, value) pairs in |expected_prefs_in_store| are found + // in the store identified by |store_id|. + void VerifyValuesStored(MockPrefStoreID store_id, + const base::StringPairs& expected_prefs_in_store) { + base::DictionaryValue* store = NULL; + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + store = unprotected_prefs_.get(); + break; + case MOCK_PROTECTED_PREF_STORE: + store = protected_prefs_.get(); + break; + } + DCHECK(store); + + for (base::StringPairs::const_iterator it = expected_prefs_in_store.begin(); + it != expected_prefs_in_store.end(); ++it) { + std::string val; + EXPECT_TRUE(store->GetString(it->first, &val)); + EXPECT_EQ(it->second, val); + } + } + + // Determines whether |expected_pref_in_hash_store| has a hash in the hash + // store identified by |store_id|. + bool ContainsHash(MockPrefStoreID store_id, + std::string expected_pref_in_hash_store) { + base::DictionaryValue* store = NULL; + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + store = unprotected_prefs_.get(); + break; + case MOCK_PROTECTED_PREF_STORE: + store = protected_prefs_.get(); + break; + } + DCHECK(store); + const base::DictionaryValue* hash_store_contents = + DictionaryHashStoreContents(store).GetContents(); + return hash_store_contents && + hash_store_contents->GetString(expected_pref_in_hash_store, + static_cast<std::string*>(NULL)); + } + + // Both stores need to hand their prefs over in order for migration to kick + // in. + void HandPrefsToMigrator(MockPrefStoreID store_id) { + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + mock_unprotected_pref_filter_.FilterOnLoad( + base::Bind(&TrackedPreferencesMigrationTest::GetPrefsBack, + base::Unretained(this), MOCK_UNPROTECTED_PREF_STORE), + std::move(unprotected_prefs_)); + break; + case MOCK_PROTECTED_PREF_STORE: + mock_protected_pref_filter_.FilterOnLoad( + base::Bind(&TrackedPreferencesMigrationTest::GetPrefsBack, + base::Unretained(this), MOCK_PROTECTED_PREF_STORE), + std::move(protected_prefs_)); + break; + } + } + + bool HasPrefs(MockPrefStoreID store_id) { + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + return !!unprotected_prefs_; + case MOCK_PROTECTED_PREF_STORE: + return !!protected_prefs_; + } + NOTREACHED(); + return false; + } + + bool StoreModifiedByMigration(MockPrefStoreID store_id) { + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + return migration_modified_unprotected_store_; + case MOCK_PROTECTED_PREF_STORE: + return migration_modified_protected_store_; + } + NOTREACHED(); + return false; + } + + bool MigrationCompleted() { + return unprotected_store_migration_complete_ && + protected_store_migration_complete_; + } + + void SimulateSuccessfulWrite(MockPrefStoreID store_id) { + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + EXPECT_FALSE(unprotected_store_successful_write_callback_.is_null()); + unprotected_store_successful_write_callback_.Run(); + unprotected_store_successful_write_callback_.Reset(); + break; + case MOCK_PROTECTED_PREF_STORE: + EXPECT_FALSE(protected_store_successful_write_callback_.is_null()); + protected_store_successful_write_callback_.Run(); + protected_store_successful_write_callback_.Reset(); + break; + } + } + + private: + void RegisterSuccessfulWriteClosure( + MockPrefStoreID store_id, + const base::Closure& successful_write_closure) { + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + EXPECT_TRUE(unprotected_store_successful_write_callback_.is_null()); + unprotected_store_successful_write_callback_ = successful_write_closure; + break; + case MOCK_PROTECTED_PREF_STORE: + EXPECT_TRUE(protected_store_successful_write_callback_.is_null()); + protected_store_successful_write_callback_ = successful_write_closure; + break; + } + } + + // Helper given as an InterceptablePrefFilter::FinalizeFilterOnLoadCallback + // to the migrator to be invoked when it's done. + void GetPrefsBack(MockPrefStoreID store_id, + std::unique_ptr<base::DictionaryValue> prefs, + bool prefs_altered) { + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + EXPECT_FALSE(unprotected_prefs_); + unprotected_prefs_ = std::move(prefs); + migration_modified_unprotected_store_ = prefs_altered; + unprotected_store_migration_complete_ = true; + break; + case MOCK_PROTECTED_PREF_STORE: + EXPECT_FALSE(protected_prefs_); + protected_prefs_ = std::move(prefs); + migration_modified_protected_store_ = prefs_altered; + protected_store_migration_complete_ = true; + break; + } + } + + // Helper given as a cleaning callback to the migrator. + void RemovePathFromStore(MockPrefStoreID store_id, const std::string& key) { + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + ASSERT_TRUE(unprotected_prefs_); + unprotected_prefs_->RemovePath(key, NULL); + break; + case MOCK_PROTECTED_PREF_STORE: + ASSERT_TRUE(protected_prefs_); + protected_prefs_->RemovePath(key, NULL); + break; + } + } + + // Sets |key| to |value| in the test store identified by |store_id| before + // migration begins. Does not store a preference hash. + void PresetStoreValueOnly(MockPrefStoreID store_id, + const std::string& key, + const std::string value) { + base::DictionaryValue* store = NULL; + switch (store_id) { + case MOCK_UNPROTECTED_PREF_STORE: + store = unprotected_prefs_.get(); + break; + case MOCK_PROTECTED_PREF_STORE: + store = protected_prefs_.get(); + break; + } + DCHECK(store); + + store->SetString(key, value); + } + + static const char kSeed[]; + static const char kDeviceId[]; + + std::unique_ptr<base::DictionaryValue> unprotected_prefs_; + std::unique_ptr<base::DictionaryValue> protected_prefs_; + + SimpleInterceptablePrefFilter mock_unprotected_pref_filter_; + SimpleInterceptablePrefFilter mock_protected_pref_filter_; + + base::Closure unprotected_store_successful_write_callback_; + base::Closure protected_store_successful_write_callback_; + + bool migration_modified_unprotected_store_; + bool migration_modified_protected_store_; + + bool unprotected_store_migration_complete_; + bool protected_store_migration_complete_; + + TestingPrefServiceSimple local_state_; + + DISALLOW_COPY_AND_ASSIGN(TrackedPreferencesMigrationTest); +}; + +// static +const char TrackedPreferencesMigrationTest::kSeed[] = "seed"; + +// static +const char TrackedPreferencesMigrationTest::kDeviceId[] = "device-id"; + +} // namespace + +TEST_F(TrackedPreferencesMigrationTest, NoMigrationRequired) { + PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref, + kUnprotectedPrefValue); + PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kProtectedPref, + kProtectedPrefValue); + + EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref)); + EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref)); + + EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref)); + EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref)); + + // Hand unprotected prefs to the migrator which should wait for the protected + // prefs. + HandPrefsToMigrator(MOCK_UNPROTECTED_PREF_STORE); + EXPECT_FALSE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE)); + EXPECT_FALSE(MigrationCompleted()); + + // Hand protected prefs to the migrator which should proceed with the + // migration synchronously. + HandPrefsToMigrator(MOCK_PROTECTED_PREF_STORE); + EXPECT_TRUE(MigrationCompleted()); + + // Prefs should have been handed back over. + EXPECT_TRUE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE)); + EXPECT_FALSE( + WasOnSuccessfulWriteCallbackRegistered(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_FALSE( + WasOnSuccessfulWriteCallbackRegistered(MOCK_PROTECTED_PREF_STORE)); + EXPECT_FALSE(StoreModifiedByMigration(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_FALSE(StoreModifiedByMigration(MOCK_PROTECTED_PREF_STORE)); + + base::StringPairs expected_unprotected_values; + expected_unprotected_values.push_back( + std::make_pair(kUnprotectedPref, kUnprotectedPrefValue)); + VerifyValuesStored(MOCK_UNPROTECTED_PREF_STORE, expected_unprotected_values); + + base::StringPairs expected_protected_values; + expected_protected_values.push_back( + std::make_pair(kProtectedPref, kProtectedPrefValue)); + VerifyValuesStored(MOCK_PROTECTED_PREF_STORE, expected_protected_values); + + EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref)); + EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref)); + + EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref)); + EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref)); +} + +TEST_F(TrackedPreferencesMigrationTest, FullMigration) { + PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref, + kUnprotectedPrefValue); + PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref, + kPreviouslyUnprotectedPrefValue); + PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kProtectedPref, + kProtectedPrefValue); + PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref, + kPreviouslyProtectedPrefValue); + + EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref)); + EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref)); + EXPECT_FALSE( + ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyProtectedPref)); + + EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref)); + EXPECT_FALSE( + ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyUnprotectedPref)); + EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref)); + + HandPrefsToMigrator(MOCK_UNPROTECTED_PREF_STORE); + EXPECT_FALSE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE)); + EXPECT_FALSE(MigrationCompleted()); + + HandPrefsToMigrator(MOCK_PROTECTED_PREF_STORE); + EXPECT_TRUE(MigrationCompleted()); + + EXPECT_TRUE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE)); + EXPECT_TRUE( + WasOnSuccessfulWriteCallbackRegistered(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_TRUE( + WasOnSuccessfulWriteCallbackRegistered(MOCK_PROTECTED_PREF_STORE)); + EXPECT_TRUE(StoreModifiedByMigration(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_TRUE(StoreModifiedByMigration(MOCK_PROTECTED_PREF_STORE)); + + // Values should have been migrated to their store, but migrated values should + // still remain in the source store until cleanup tasks are later invoked. + { + base::StringPairs expected_unprotected_values; + expected_unprotected_values.push_back( + std::make_pair(kUnprotectedPref, kUnprotectedPrefValue)); + expected_unprotected_values.push_back(std::make_pair( + kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue)); + expected_unprotected_values.push_back(std::make_pair( + kPreviouslyUnprotectedPref, kPreviouslyUnprotectedPrefValue)); + VerifyValuesStored(MOCK_UNPROTECTED_PREF_STORE, + expected_unprotected_values); + + base::StringPairs expected_protected_values; + expected_protected_values.push_back( + std::make_pair(kProtectedPref, kProtectedPrefValue)); + expected_protected_values.push_back(std::make_pair( + kPreviouslyUnprotectedPref, kPreviouslyUnprotectedPrefValue)); + expected_unprotected_values.push_back(std::make_pair( + kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue)); + VerifyValuesStored(MOCK_PROTECTED_PREF_STORE, expected_protected_values); + + EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref)); + EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyProtectedPref)); + + EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyUnprotectedPref)); + EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref)); + } + + // A successful write of the protected pref store should result in a clean up + // of the unprotected store. + SimulateSuccessfulWrite(MOCK_PROTECTED_PREF_STORE); + + { + base::StringPairs expected_unprotected_values; + expected_unprotected_values.push_back( + std::make_pair(kUnprotectedPref, kUnprotectedPrefValue)); + expected_unprotected_values.push_back(std::make_pair( + kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue)); + VerifyValuesStored(MOCK_UNPROTECTED_PREF_STORE, + expected_unprotected_values); + + base::StringPairs expected_protected_values; + expected_protected_values.push_back( + std::make_pair(kProtectedPref, kProtectedPrefValue)); + expected_protected_values.push_back(std::make_pair( + kPreviouslyUnprotectedPref, kPreviouslyUnprotectedPrefValue)); + expected_unprotected_values.push_back(std::make_pair( + kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue)); + VerifyValuesStored(MOCK_PROTECTED_PREF_STORE, expected_protected_values); + } + + SimulateSuccessfulWrite(MOCK_UNPROTECTED_PREF_STORE); + + { + base::StringPairs expected_unprotected_values; + expected_unprotected_values.push_back( + std::make_pair(kUnprotectedPref, kUnprotectedPrefValue)); + expected_unprotected_values.push_back(std::make_pair( + kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue)); + VerifyValuesStored(MOCK_UNPROTECTED_PREF_STORE, + expected_unprotected_values); + + base::StringPairs expected_protected_values; + expected_protected_values.push_back( + std::make_pair(kProtectedPref, kProtectedPrefValue)); + expected_protected_values.push_back(std::make_pair( + kPreviouslyUnprotectedPref, kPreviouslyUnprotectedPrefValue)); + VerifyValuesStored(MOCK_PROTECTED_PREF_STORE, expected_protected_values); + } + + // Hashes are not cleaned up yet. + EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref)); + EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyProtectedPref)); + + EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyUnprotectedPref)); + EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref)); + + Reset(); + + HandPrefsToMigrator(MOCK_UNPROTECTED_PREF_STORE); + HandPrefsToMigrator(MOCK_PROTECTED_PREF_STORE); + EXPECT_TRUE(MigrationCompleted()); + + // Hashes are cleaned up. + EXPECT_TRUE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref)); + EXPECT_FALSE( + ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref)); + EXPECT_FALSE(ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kProtectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyProtectedPref)); + + EXPECT_FALSE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kUnprotectedPref)); + EXPECT_TRUE( + ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyUnprotectedPref)); + EXPECT_TRUE(ContainsHash(MOCK_PROTECTED_PREF_STORE, kProtectedPref)); + EXPECT_FALSE( + ContainsHash(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref)); +} + +TEST_F(TrackedPreferencesMigrationTest, CleanupOnly) { + // Already migrated; only cleanup needed. + PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kUnprotectedPref, + kUnprotectedPrefValue); + PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyProtectedPref, + kPreviouslyProtectedPrefValue); + PresetStoreValue(MOCK_UNPROTECTED_PREF_STORE, kPreviouslyUnprotectedPref, + kPreviouslyUnprotectedPrefValue); + PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kProtectedPref, + kProtectedPrefValue); + PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kPreviouslyProtectedPref, + kPreviouslyProtectedPrefValue); + PresetStoreValue(MOCK_PROTECTED_PREF_STORE, kPreviouslyUnprotectedPref, + kPreviouslyUnprotectedPrefValue); + + HandPrefsToMigrator(MOCK_UNPROTECTED_PREF_STORE); + EXPECT_FALSE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE)); + EXPECT_FALSE(MigrationCompleted()); + + HandPrefsToMigrator(MOCK_PROTECTED_PREF_STORE); + EXPECT_TRUE(MigrationCompleted()); + + EXPECT_TRUE(HasPrefs(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_TRUE(HasPrefs(MOCK_PROTECTED_PREF_STORE)); + EXPECT_FALSE( + WasOnSuccessfulWriteCallbackRegistered(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_FALSE( + WasOnSuccessfulWriteCallbackRegistered(MOCK_PROTECTED_PREF_STORE)); + EXPECT_FALSE(StoreModifiedByMigration(MOCK_UNPROTECTED_PREF_STORE)); + EXPECT_FALSE(StoreModifiedByMigration(MOCK_PROTECTED_PREF_STORE)); + + // Cleanup should happen synchronously if the values were already present in + // their destination stores. + { + base::StringPairs expected_unprotected_values; + expected_unprotected_values.push_back( + std::make_pair(kUnprotectedPref, kUnprotectedPrefValue)); + expected_unprotected_values.push_back(std::make_pair( + kPreviouslyProtectedPref, kPreviouslyProtectedPrefValue)); + VerifyValuesStored(MOCK_UNPROTECTED_PREF_STORE, + expected_unprotected_values); + + base::StringPairs expected_protected_values; + expected_protected_values.push_back( + std::make_pair(kProtectedPref, kProtectedPrefValue)); + expected_protected_values.push_back(std::make_pair( + kPreviouslyUnprotectedPref, kPreviouslyUnprotectedPrefValue)); + VerifyValuesStored(MOCK_PROTECTED_PREF_STORE, expected_protected_values); + } +} diff --git a/chromium/services/preferences/tracked/tracked_split_preference.cc b/chromium/services/preferences/tracked/tracked_split_preference.cc new file mode 100644 index 00000000000..da6ceba8fbf --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_split_preference.cc @@ -0,0 +1,120 @@ +// Copyright 2014 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 "services/preferences/tracked/tracked_split_preference.h" + +#include <vector> + +#include "base/logging.h" +#include "base/values.h" +#include "services/preferences/public/interfaces/tracked_preference_validation_delegate.mojom.h" +#include "services/preferences/tracked/pref_hash_store_transaction.h" + +using ValueState = + prefs::mojom::TrackedPreferenceValidationDelegate::ValueState; + +TrackedSplitPreference::TrackedSplitPreference( + const std::string& pref_path, + size_t reporting_id, + size_t reporting_ids_count, + prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel enforcement_level, + prefs::mojom::TrackedPreferenceMetadata::ValueType value_type, + prefs::mojom::TrackedPreferenceValidationDelegate* delegate) + : pref_path_(pref_path), + helper_(pref_path, + reporting_id, + reporting_ids_count, + enforcement_level, + value_type), + delegate_(delegate) {} + +TrackedPreferenceType TrackedSplitPreference::GetType() const { + return TrackedPreferenceType::SPLIT; +} + +void TrackedSplitPreference::OnNewValue( + const base::Value* value, + PrefHashStoreTransaction* transaction) const { + const base::DictionaryValue* dict_value = NULL; + if (value && !value->GetAsDictionary(&dict_value)) { + NOTREACHED(); + return; + } + transaction->StoreSplitHash(pref_path_, dict_value); +} + +bool TrackedSplitPreference::EnforceAndReport( + base::DictionaryValue* pref_store_contents, + PrefHashStoreTransaction* transaction, + PrefHashStoreTransaction* external_validation_transaction) const { + base::DictionaryValue* dict_value = NULL; + if (!pref_store_contents->GetDictionary(pref_path_, &dict_value) && + pref_store_contents->Get(pref_path_, NULL)) { + // There should be a dictionary or nothing at |pref_path_|. + NOTREACHED(); + return false; + } + + std::vector<std::string> invalid_keys; + ValueState value_state = + transaction->CheckSplitValue(pref_path_, dict_value, &invalid_keys); + + if (value_state == ValueState::CHANGED) + helper_.ReportSplitPreferenceChangedCount(invalid_keys.size()); + + helper_.ReportValidationResult(value_state, transaction->GetStoreUMASuffix()); + + ValueState external_validation_value_state = ValueState::UNSUPPORTED; + std::vector<std::string> external_validation_invalid_keys; + if (external_validation_transaction) { + external_validation_value_state = + external_validation_transaction->CheckSplitValue( + pref_path_, dict_value, &external_validation_invalid_keys); + helper_.ReportValidationResult( + external_validation_value_state, + external_validation_transaction->GetStoreUMASuffix()); + } + + if (delegate_) { + delegate_->OnSplitPreferenceValidation( + pref_path_, invalid_keys, external_validation_invalid_keys, value_state, + external_validation_value_state, helper_.IsPersonal()); + } + TrackedPreferenceHelper::ResetAction reset_action = + helper_.GetAction(value_state); + helper_.ReportAction(reset_action); + + bool was_reset = false; + if (reset_action == TrackedPreferenceHelper::DO_RESET) { + if (value_state == ValueState::CHANGED) { + DCHECK(!invalid_keys.empty()); + + for (std::vector<std::string>::const_iterator it = invalid_keys.begin(); + it != invalid_keys.end(); ++it) { + dict_value->Remove(*it, NULL); + } + } else { + pref_store_contents->RemovePath(pref_path_, NULL); + } + was_reset = true; + } + + if (value_state != ValueState::UNCHANGED) { + // Store the hash for the new value (whether it was reset or not). + const base::DictionaryValue* new_dict_value = NULL; + pref_store_contents->GetDictionary(pref_path_, &new_dict_value); + transaction->StoreSplitHash(pref_path_, new_dict_value); + } + + // Update MACs in the external store if there is one and there either was a + // reset or external validation failed. + if (external_validation_transaction && + (was_reset || external_validation_value_state != ValueState::UNCHANGED)) { + const base::DictionaryValue* new_dict_value = nullptr; + pref_store_contents->GetDictionary(pref_path_, &new_dict_value); + external_validation_transaction->StoreSplitHash(pref_path_, new_dict_value); + } + + return was_reset; +} diff --git a/chromium/services/preferences/tracked/tracked_split_preference.h b/chromium/services/preferences/tracked/tracked_split_preference.h new file mode 100644 index 00000000000..f416b3695fa --- /dev/null +++ b/chromium/services/preferences/tracked/tracked_split_preference.h @@ -0,0 +1,58 @@ +// Copyright 2014 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 SERVICES_PREFERENCES_TRACKED_TRACKED_SPLIT_PREFERENCE_H_ +#define SERVICES_PREFERENCES_TRACKED_TRACKED_SPLIT_PREFERENCE_H_ + +#include <stddef.h> + +#include <string> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "services/preferences/tracked/pref_hash_filter.h" +#include "services/preferences/tracked/tracked_preference.h" +#include "services/preferences/tracked/tracked_preference_helper.h" + +namespace prefs { +namespace mojom { +class TrackedPreferenceValidationDelegate; +} +} + +// A TrackedSplitPreference must be tracking a dictionary pref. Each top-level +// entry in its dictionary is tracked and enforced independently. An optional +// delegate is notified of the status of the preference during enforcement. +// Note: preferences using this strategy must be kept in sync with +// TrackedSplitPreferences in histograms.xml. +class TrackedSplitPreference : public TrackedPreference { + public: + // Constructs a TrackedSplitPreference. |pref_path| must be a dictionary pref. + TrackedSplitPreference( + const std::string& pref_path, + size_t reporting_id, + size_t reporting_ids_count, + prefs::mojom::TrackedPreferenceMetadata::EnforcementLevel + enforcement_level, + prefs::mojom::TrackedPreferenceMetadata::ValueType value_type, + prefs::mojom::TrackedPreferenceValidationDelegate* delegate); + + // TrackedPreference implementation. + TrackedPreferenceType GetType() const override; + void OnNewValue(const base::Value* value, + PrefHashStoreTransaction* transaction) const override; + bool EnforceAndReport( + base::DictionaryValue* pref_store_contents, + PrefHashStoreTransaction* transaction, + PrefHashStoreTransaction* external_validation_transaction) const override; + + private: + const std::string pref_path_; + const TrackedPreferenceHelper helper_; + prefs::mojom::TrackedPreferenceValidationDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(TrackedSplitPreference); +}; + +#endif // SERVICES_PREFERENCES_TRACKED_TRACKED_SPLIT_PREFERENCE_H_ diff --git a/chromium/services/preferences/unittest_manifest.json b/chromium/services/preferences/unittest_manifest.json new file mode 100644 index 00000000000..df6d7441066 --- /dev/null +++ b/chromium/services/preferences/unittest_manifest.json @@ -0,0 +1,16 @@ +{ + "name": "prefs_unittests", + "display_name": "Prefs Unittests", + "interface_provider_specs": { + "service_manager:connector": { + "provides": { + "service_manager:service_factory": [ + "service_manager::mojom::ServiceFactory" + ] + }, + "requires": { + "preferences": [ "pref_client", "pref_control" ] + } + } + } +} |