diff options
28 files changed, 1036 insertions, 794 deletions
diff --git a/base/base.gyp b/base/base.gyp index d378d14..bb1085d 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -352,6 +352,7 @@ 'prefs/persistent_pref_store.h', 'prefs/pref_change_registrar.cc', 'prefs/pref_change_registrar.h', + 'prefs/pref_filter.h', 'prefs/pref_member.cc', 'prefs/pref_member.h', 'prefs/pref_notifier.h', diff --git a/base/prefs/json_pref_store.cc b/base/prefs/json_pref_store.cc index ad97b84..f417b8b 100644 --- a/base/prefs/json_pref_store.cc +++ b/base/prefs/json_pref_store.cc @@ -13,6 +13,7 @@ #include "base/json/json_string_value_serializer.h" #include "base/memory/ref_counted.h" #include "base/message_loop/message_loop_proxy.h" +#include "base/prefs/pref_filter.h" #include "base/sequenced_task_runner.h" #include "base/threading/sequenced_worker_pool.h" #include "base/values.h" @@ -151,12 +152,14 @@ scoped_refptr<base::SequencedTaskRunner> JsonPrefStore::GetTaskRunnerForFile( } JsonPrefStore::JsonPrefStore(const base::FilePath& filename, - base::SequencedTaskRunner* sequenced_task_runner) + base::SequencedTaskRunner* sequenced_task_runner, + scoped_ptr<PrefFilter> pref_filter) : path_(filename), sequenced_task_runner_(sequenced_task_runner), prefs_(new base::DictionaryValue()), read_only_(false), writer_(filename, sequenced_task_runner), + pref_filter_(pref_filter.Pass()), initialized_(false), read_error_(PREF_READ_ERROR_OTHER) {} @@ -264,7 +267,14 @@ void JsonPrefStore::CommitPendingWrite() { } void JsonPrefStore::ReportValueChanged(const std::string& key) { + if (pref_filter_) { + const base::Value* tmp = NULL; + prefs_->Get(key, &tmp); + pref_filter_->FilterUpdate(key, tmp); + } + FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key)); + if (!read_only_) writer_.ScheduleWrite(this); } @@ -307,6 +317,9 @@ void JsonPrefStore::OnFileRead(base::Value* value_owned, NOTREACHED() << "Unknown error: " << error; } + if (pref_filter_) + pref_filter_->FilterOnLoad(prefs_.get()); + if (error_delegate_.get() && error != PREF_READ_ERROR_NONE) error_delegate_->OnError(error); diff --git a/base/prefs/json_pref_store.h b/base/prefs/json_pref_store.h index 21fc8f9..ad13feb 100644 --- a/base/prefs/json_pref_store.h +++ b/base/prefs/json_pref_store.h @@ -18,6 +18,8 @@ #include "base/prefs/base_prefs_export.h" #include "base/prefs/persistent_pref_store.h" +class PrefFilter; + namespace base { class DictionaryValue; class FilePath; @@ -41,7 +43,8 @@ class BASE_PREFS_EXPORT JsonPrefStore // |sequenced_task_runner| is must be a shutdown-blocking task runner, ideally // created by GetTaskRunnerForFile() method above. JsonPrefStore(const base::FilePath& pref_filename, - base::SequencedTaskRunner* sequenced_task_runner); + base::SequencedTaskRunner* sequenced_task_runner, + scoped_ptr<PrefFilter> pref_filter); // PrefStore overrides: virtual bool GetValue(const std::string& key, @@ -87,6 +90,7 @@ class BASE_PREFS_EXPORT JsonPrefStore // Helper for safely writing pref data. base::ImportantFileWriter writer_; + scoped_ptr<PrefFilter> pref_filter_; ObserverList<PrefStore::Observer, true> observers_; scoped_ptr<ReadErrorDelegate> error_delegate_; diff --git a/base/prefs/json_pref_store_unittest.cc b/base/prefs/json_pref_store_unittest.cc index a26afd7..119a29c 100644 --- a/base/prefs/json_pref_store_unittest.cc +++ b/base/prefs/json_pref_store_unittest.cc @@ -9,6 +9,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/path_service.h" +#include "base/prefs/pref_filter.h" #include "base/run_loop.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" @@ -60,7 +61,9 @@ TEST_F(JsonPrefStoreTest, NonExistentFile) { base::FilePath bogus_input_file = data_dir_.AppendASCII("read.txt"); ASSERT_FALSE(PathExists(bogus_input_file)); scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore( - bogus_input_file, message_loop_.message_loop_proxy().get()); + bogus_input_file, + message_loop_.message_loop_proxy().get(), + scoped_ptr<PrefFilter>()); EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE, pref_store->ReadPrefs()); EXPECT_FALSE(pref_store->ReadOnly()); @@ -72,7 +75,9 @@ TEST_F(JsonPrefStoreTest, InvalidFile) { base::FilePath invalid_file = temp_dir_.path().AppendASCII("invalid.json"); ASSERT_TRUE(base::CopyFile(invalid_file_original, invalid_file)); scoped_refptr<JsonPrefStore> pref_store = - new JsonPrefStore(invalid_file, message_loop_.message_loop_proxy().get()); + new JsonPrefStore(invalid_file, + message_loop_.message_loop_proxy().get(), + scoped_ptr<PrefFilter>()); EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE, pref_store->ReadPrefs()); EXPECT_FALSE(pref_store->ReadOnly()); @@ -157,8 +162,10 @@ TEST_F(JsonPrefStoreTest, Basic) { // Test that the persistent value can be loaded. base::FilePath input_file = temp_dir_.path().AppendASCII("write.json"); ASSERT_TRUE(PathExists(input_file)); - scoped_refptr<JsonPrefStore> pref_store = - new JsonPrefStore(input_file, message_loop_.message_loop_proxy().get()); + scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore( + input_file, + message_loop_.message_loop_proxy().get(), + scoped_ptr<PrefFilter>()); ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs()); ASSERT_FALSE(pref_store->ReadOnly()); @@ -183,8 +190,10 @@ TEST_F(JsonPrefStoreTest, BasicAsync) { // Test that the persistent value can be loaded. base::FilePath input_file = temp_dir_.path().AppendASCII("write.json"); ASSERT_TRUE(PathExists(input_file)); - scoped_refptr<JsonPrefStore> pref_store = - new JsonPrefStore(input_file, message_loop_.message_loop_proxy().get()); + scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore( + input_file, + message_loop_.message_loop_proxy().get(), + scoped_ptr<PrefFilter>()); { MockPrefStoreObserver mock_observer; @@ -219,8 +228,10 @@ TEST_F(JsonPrefStoreTest, BasicAsync) { TEST_F(JsonPrefStoreTest, PreserveEmptyValues) { FilePath pref_file = temp_dir_.path().AppendASCII("empty_values.json"); - scoped_refptr<JsonPrefStore> pref_store = - new JsonPrefStore(pref_file, message_loop_.message_loop_proxy()); + scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore( + pref_file, + message_loop_.message_loop_proxy(), + scoped_ptr<PrefFilter>()); // Set some keys with empty values. pref_store->SetValue("list", new base::ListValue); @@ -231,7 +242,10 @@ TEST_F(JsonPrefStoreTest, PreserveEmptyValues) { MessageLoop::current()->RunUntilIdle(); // Reload. - pref_store = new JsonPrefStore(pref_file, message_loop_.message_loop_proxy()); + pref_store = new JsonPrefStore( + pref_file, + message_loop_.message_loop_proxy(), + scoped_ptr<PrefFilter>()); ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs()); ASSERT_FALSE(pref_store->ReadOnly()); @@ -248,7 +262,9 @@ TEST_F(JsonPrefStoreTest, AsyncNonExistingFile) { base::FilePath bogus_input_file = data_dir_.AppendASCII("read.txt"); ASSERT_FALSE(PathExists(bogus_input_file)); scoped_refptr<JsonPrefStore> pref_store = new JsonPrefStore( - bogus_input_file, message_loop_.message_loop_proxy().get()); + bogus_input_file, + message_loop_.message_loop_proxy().get(), + scoped_ptr<PrefFilter>()); MockPrefStoreObserver mock_observer; pref_store->AddObserver(&mock_observer); diff --git a/base/prefs/pref_filter.h b/base/prefs/pref_filter.h new file mode 100644 index 0000000..3d136f7 --- /dev/null +++ b/base/prefs/pref_filter.h @@ -0,0 +1,35 @@ +// 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 BASE_PREFS_PREF_FILTER_H_ +#define BASE_PREFS_PREF_FILTER_H_ + +#include <string> + +#include "base/prefs/base_prefs_export.h" + +namespace base { +class DictionaryValue; +class Value; +} // namespace base + +// Filters preferences as they are loaded from disk or updated at runtime. +// Currently supported only by JsonPrefStore. +class BASE_PREFS_EXPORT PrefFilter { + public: + virtual ~PrefFilter() {} + + // Receives notification when the pref store data has been loaded but before + // Observers are notified. + // Changes made by a PrefFilter during FilterOnLoad do not result in + // notifications to |PrefStore::Observer|s. + virtual void FilterOnLoad(base::DictionaryValue* pref_store_contents) = 0; + + // Receives notification when a pref store value is changed, before Observers + // are notified. + virtual void FilterUpdate(const std::string& path, + const base::Value* value) = 0; +}; + +#endif // BASE_PREFS_PREF_FILTER_H_ diff --git a/base/prefs/pref_service_factory.cc b/base/prefs/pref_service_factory.cc index 9c59853..d644cb1 100644 --- a/base/prefs/pref_service_factory.cc +++ b/base/prefs/pref_service_factory.cc @@ -7,6 +7,7 @@ #include "base/bind.h" #include "base/prefs/default_pref_store.h" #include "base/prefs/json_pref_store.h" +#include "base/prefs/pref_filter.h" #include "base/prefs/pref_notifier_impl.h" #include "base/prefs/pref_service.h" @@ -37,7 +38,8 @@ PrefServiceFactory::~PrefServiceFactory() {} void PrefServiceFactory::SetUserPrefsFile( const base::FilePath& prefs_file, base::SequencedTaskRunner* task_runner) { - user_prefs_ = new JsonPrefStore(prefs_file, task_runner); + user_prefs_ = new JsonPrefStore( + prefs_file, task_runner, scoped_ptr<PrefFilter>()); } scoped_ptr<PrefService> PrefServiceFactory::Create( diff --git a/chrome/browser/managed_mode/managed_user_settings_service.cc b/chrome/browser/managed_mode/managed_user_settings_service.cc index 9f229b6..9b24e97 100644 --- a/chrome/browser/managed_mode/managed_user_settings_service.cc +++ b/chrome/browser/managed_mode/managed_user_settings_service.cc @@ -8,6 +8,7 @@ #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/prefs/json_pref_store.h" +#include "base/prefs/pref_filter.h" #include "base/strings/string_util.h" #include "base/threading/sequenced_worker_pool.h" #include "chrome/browser/managed_mode/managed_mode_url_filter.h" @@ -59,7 +60,8 @@ void ManagedUserSettingsService::Init( bool load_synchronously) { base::FilePath path = profile_path.Append(chrome::kManagedUserSettingsFilename); - PersistentPrefStore* store = new JsonPrefStore(path, sequenced_task_runner); + PersistentPrefStore* store = new JsonPrefStore( + path, sequenced_task_runner, scoped_ptr<PrefFilter>()); Init(store); if (load_synchronously) store_->ReadPrefs(); diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc index d6b9f73..1f370ae 100644 --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc @@ -56,7 +56,7 @@ #include "chrome/browser/pepper_flash_settings_manager.h" #include "chrome/browser/plugins/plugin_finder.h" #include "chrome/browser/prefs/incognito_mode_prefs.h" -#include "chrome/browser/prefs/pref_metrics_service.h" +#include "chrome/browser/prefs/pref_hash_store_impl.h" #include "chrome/browser/prefs/pref_service_syncable.h" #include "chrome/browser/prefs/session_startup_pref.h" #include "chrome/browser/printing/cloud_print/cloud_print_url.h" @@ -236,7 +236,7 @@ void RegisterLocalState(PrefRegistrySimple* registry) { MetricsLog::RegisterPrefs(registry); MetricsService::RegisterPrefs(registry); metrics::CachingPermutedEntropyProvider::RegisterPrefs(registry); - PrefMetricsService::RegisterPrefs(registry); + PrefHashStoreImpl::RegisterPrefs(registry); PrefProxyConfigTrackerImpl::RegisterPrefs(registry); ProfileInfoCache::RegisterPrefs(registry); profiles::RegisterPrefs(registry); diff --git a/chrome/browser/prefs/chrome_pref_service_factory.cc b/chrome/browser/prefs/chrome_pref_service_factory.cc index 7fe3d3c..a459ea8 100644 --- a/chrome/browser/prefs/chrome_pref_service_factory.cc +++ b/chrome/browser/prefs/chrome_pref_service_factory.cc @@ -17,6 +17,8 @@ #include "base/prefs/pref_value_store.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/prefs/command_line_pref_store.h" +#include "chrome/browser/prefs/pref_hash_filter.h" +#include "chrome/browser/prefs/pref_hash_store.h" #include "chrome/browser/prefs/pref_model_associator.h" #include "chrome/browser/prefs/pref_service_syncable.h" #include "chrome/browser/prefs/pref_service_syncable_factory.h" @@ -80,6 +82,7 @@ void PrepareBuilder( base::SequencedTaskRunner* pref_io_task_runner, policy::PolicyService* policy_service, ManagedUserSettingsService* managed_user_settings, + scoped_ptr<PrefHashStore> pref_hash_store, const scoped_refptr<PrefStore>& extension_prefs, bool async) { #if defined(OS_LINUX) @@ -122,8 +125,14 @@ void PrepareBuilder( make_scoped_refptr( new CommandLinePrefStore(CommandLine::ForCurrentProcess()))); factory->set_read_error_callback(base::Bind(&HandleReadError)); + scoped_ptr<PrefFilter> pref_filter; + if (pref_hash_store) + pref_filter.reset(new PrefHashFilter(pref_hash_store.Pass())); factory->set_user_prefs( - new JsonPrefStore(pref_filename, pref_io_task_runner)); + new JsonPrefStore( + pref_filename, + pref_io_task_runner, + pref_filter.Pass())); } } // namespace @@ -142,6 +151,7 @@ scoped_ptr<PrefService> CreateLocalState( pref_io_task_runner, policy_service, NULL, + scoped_ptr<PrefHashStore>(), NULL, async); return factory.Create(pref_registry.get()); @@ -152,6 +162,7 @@ scoped_ptr<PrefServiceSyncable> CreateProfilePrefs( base::SequencedTaskRunner* pref_io_task_runner, policy::PolicyService* policy_service, ManagedUserSettingsService* managed_user_settings, + scoped_ptr<PrefHashStore> pref_hash_store, const scoped_refptr<PrefStore>& extension_prefs, const scoped_refptr<user_prefs::PrefRegistrySyncable>& pref_registry, bool async) { @@ -162,6 +173,7 @@ scoped_ptr<PrefServiceSyncable> CreateProfilePrefs( pref_io_task_runner, policy_service, managed_user_settings, + pref_hash_store.Pass(), extension_prefs, async); return factory.CreateSyncable(pref_registry.get()); diff --git a/chrome/browser/prefs/chrome_pref_service_factory.h b/chrome/browser/prefs/chrome_pref_service_factory.h index b1322b7..3456463 100644 --- a/chrome/browser/prefs/chrome_pref_service_factory.h +++ b/chrome/browser/prefs/chrome_pref_service_factory.h @@ -22,6 +22,7 @@ class PrefRegistrySyncable; } class ManagedUserSettingsService; +class PrefHashStore; class PrefRegistry; class PrefService; class PrefServiceSyncable; @@ -56,6 +57,7 @@ scoped_ptr<PrefServiceSyncable> CreateProfilePrefs( base::SequencedTaskRunner* pref_io_task_runner, policy::PolicyService* policy_service, ManagedUserSettingsService* managed_user_settings, + scoped_ptr<PrefHashStore> pref_hash_store, const scoped_refptr<PrefStore>& extension_prefs, const scoped_refptr<user_prefs::PrefRegistrySyncable>& pref_registry, bool async); diff --git a/chrome/browser/prefs/pref_hash_calculator.cc b/chrome/browser/prefs/pref_hash_calculator.cc new file mode 100644 index 0000000..4a787a8 --- /dev/null +++ b/chrome/browser/prefs/pref_hash_calculator.cc @@ -0,0 +1,90 @@ +// 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 "chrome/browser/prefs/pref_hash_calculator.h" + +#include <vector> + +#include "base/json/json_string_value_serializer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "crypto/hmac.h" + +namespace { + +// Renders |value| as a string. |value| may be NULL, in which case the result +// is an empty string. +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; + scoped_ptr<DictionaryValue> canonical_dict_value; + if (value && value->GetAsDictionary(&dict_value)) { + canonical_dict_value.reset(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; +} + +// Common helper for all hash algorithms. +std::string CalculateFromValueAndComponents( + const std::string& seed, + const base::Value* value, + const std::vector<std::string>& extra_components) { + static const size_t kSHA256DigestSize = 32; + + std::string message = JoinString(extra_components, "") + ValueAsString(value); + + crypto::HMAC hmac(crypto::HMAC::SHA256); + unsigned char digest[kSHA256DigestSize]; + if (!hmac.Init(seed) || !hmac.Sign(message, digest, arraysize(digest))) { + NOTREACHED(); + return std::string(); + } + + return base::HexEncode(digest, arraysize(digest)); +} + +} // namespace + +PrefHashCalculator::PrefHashCalculator(const std::string& seed, + const std::string& device_id) + : seed_(seed), device_id_(device_id) {} + +std::string PrefHashCalculator::Calculate(const std::string& path, + const base::Value* value) const { + std::vector<std::string> components; + if (!device_id_.empty()) + components.push_back(device_id_); + components.push_back(path); + return CalculateFromValueAndComponents(seed_, value, components); +} + +PrefHashCalculator::ValidationResult PrefHashCalculator::Validate( + const std::string& path, + const base::Value* value, + const std::string& hash) const { + if (hash == Calculate(path, value)) + return VALID; + if (hash == CalculateLegacyHash(path, value)) + return VALID_LEGACY; + return INVALID; +} + +std::string PrefHashCalculator::CalculateLegacyHash( + const std::string& path, const base::Value* value) const { + return CalculateFromValueAndComponents(seed_, + value, + std::vector<std::string>()); +} diff --git a/chrome/browser/prefs/pref_hash_calculator.h b/chrome/browser/prefs/pref_hash_calculator.h new file mode 100644 index 0000000..6f0fd85 --- /dev/null +++ b/chrome/browser/prefs/pref_hash_calculator.h @@ -0,0 +1,53 @@ +// 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 CHROME_BROWSER_PREFS_PREF_HASH_CALCULATOR_H_ +#define CHROME_BROWSER_PREFS_PREF_HASH_CALCULATOR_H_ + +#include <string> + +#include "base/basictypes.h" + +namespace base { +class Value; +} // namespace base + +// Calculates and validates preference value hashes. +class PrefHashCalculator { + public: + enum ValidationResult { + INVALID, + VALID, + VALID_LEGACY, + }; + + // Constructs a PrefHashCalculator using |seed| and |device_id|. The same + // parameters must be used in order to successfully validate generated hashes. + // |device_id| may be empty. + PrefHashCalculator(const std::string& seed, const std::string& device_id); + + // 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: + // Calculate a hash using a deprecated hash algorithm. For validating old + // hashes during migration. + std::string CalculateLegacyHash(const std::string& path, + const base::Value* value) const; + + std::string seed_; + std::string device_id_; + + DISALLOW_COPY_AND_ASSIGN(PrefHashCalculator); +}; + +#endif // CHROME_BROWSER_PREFS_PREF_HASH_CALCULATOR_H_ diff --git a/chrome/browser/prefs/pref_hash_calculator_unittest.cc b/chrome/browser/prefs/pref_hash_calculator_unittest.cc new file mode 100644 index 0000000..1f5ad7c --- /dev/null +++ b/chrome/browser/prefs/pref_hash_calculator_unittest.cc @@ -0,0 +1,124 @@ +// 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 "chrome/browser/prefs/pref_hash_calculator.h" + +#include <string> + +#include "base/values.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PrefHashCalculatorTest, TestCurrentAlgorithm) { + base::StringValue string_value_1("string value 1"); + base::StringValue 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 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"); + PrefHashCalculator calc1_dup("seed1", "deviceid"); + PrefHashCalculator calc2("seed2", "deviceid"); + PrefHashCalculator calc3("seed1", "deviceid2"); + + // 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. +TEST(PrefHashCalculatorTest, CatchHashChanges) { + const char* kDeviceId = "test_device_id1"; + { + static const char kExpectedValue[] = + "5CE37D7EBCBC9BE510F0F5E7C326CA92C1673713C3717839610AEA1A217D8BB8"; + + base::ListValue list; + list.Set(0, new base::FundamentalValue(true)); + list.Set(1, new base::FundamentalValue(100)); + list.Set(2, new base::FundamentalValue(1.0)); + + // 32 NULL bytes is the seed that was used to generate the hash in old + // tests. Use it again to ensure that we haven't altered the algorithm. + EXPECT_EQ(PrefHashCalculator::VALID, + PrefHashCalculator(std::string(32u, 0), kDeviceId).Validate( + "pref.path2", &list, kExpectedValue)); + } + { + static const char kExpectedValue[] = + "A50FE7EB31BFBC32B8A27E71730AF15421178A9B5815644ACE174B18966735B9"; + + DictionaryValue dict; + dict.Set("a", new StringValue("foo")); + dict.Set("d", new StringValue("bad")); + dict.Set("b", new StringValue("bar")); + dict.Set("c", new StringValue("baz")); + + // 32 NULL bytes is the seed that was used to generate the hash in old + // tests. Use it again to ensure that we haven't altered the algorithm. + EXPECT_EQ(PrefHashCalculator::VALID, + PrefHashCalculator(std::string(32u, 0), kDeviceId).Validate( + "pref.path1", &dict, kExpectedValue)); + } +} + +TEST(PrefHashCalculatorTest, TestLegacyAlgorithm) { + const char* kExpectedValue = + "C503FB7C65EEFD5C07185F616A0AA67923C069909933F362022B1F187E73E9A2"; + const char* kDeviceId = "deviceid"; + + DictionaryValue dict; + dict.Set("a", new StringValue("foo")); + dict.Set("d", new StringValue("bad")); + dict.Set("b", new StringValue("bar")); + dict.Set("c", new StringValue("baz")); + + // 32 NULL bytes is the seed that was used to generate the legacy hash. + EXPECT_EQ(PrefHashCalculator::VALID_LEGACY, + PrefHashCalculator(std::string(32u, 0), kDeviceId).Validate( + "pref.path1", &dict, kExpectedValue)); + +} diff --git a/chrome/browser/prefs/pref_hash_filter.cc b/chrome/browser/prefs/pref_hash_filter.cc new file mode 100644 index 0000000..803ff13 --- /dev/null +++ b/chrome/browser/prefs/pref_hash_filter.cc @@ -0,0 +1,96 @@ +// 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 "chrome/browser/prefs/pref_hash_filter.h" + +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/values.h" +#include "chrome/browser/prefs/pref_hash_store.h" +#include "chrome/common/pref_names.h" + +namespace { + +// These preferences must be kept in sync with the TrackedPreference enum in +// tools/metrics/histograms/histograms.xml. To add a new preference, append it +// to the array and add a corresponding value to the histogram enum. Replace +// removed preferences with "". +const char* kTrackedPrefs[] = { + prefs::kShowHomeButton, + prefs::kHomePageIsNewTabPage, + prefs::kHomePage, + prefs::kRestoreOnStartup, + prefs::kURLsToRestoreOnStartup, + prefs::kExtensionsPref, + prefs::kGoogleServicesLastUsername, + prefs::kSearchProviderOverrides, + prefs::kDefaultSearchProviderSearchURL, + prefs::kDefaultSearchProviderKeyword, + prefs::kDefaultSearchProviderName, +#if !defined(OS_ANDROID) + prefs::kPinnedTabs, +#else + "", +#endif + prefs::kExtensionKnownDisabled, + prefs::kProfileResetPromptMemento, +}; + +void ReportValidationResult(PrefHashStore::ValueState value_state, + size_t value_index) { + switch (value_state) { + case PrefHashStore::UNCHANGED: + UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceUnchanged", + value_index, arraysize(kTrackedPrefs)); + return; + case PrefHashStore::CLEARED: + UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceCleared", + value_index, arraysize(kTrackedPrefs)); + return; + case PrefHashStore::MIGRATED: + UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceMigrated", + value_index, arraysize(kTrackedPrefs)); + return; + case PrefHashStore::CHANGED: + UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceChanged", + value_index, arraysize(kTrackedPrefs)); + return; + case PrefHashStore::UNKNOWN_VALUE: + UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceInitialized", + value_index, arraysize(kTrackedPrefs)); + return; + } + NOTREACHED() << "Unexpected PrefHashStore::ValueState: " << value_state; +} + +} // namespace + +PrefHashFilter::PrefHashFilter(scoped_ptr<PrefHashStore> pref_hash_store) + : pref_hash_store_(pref_hash_store.Pass()), + tracked_paths_(kTrackedPrefs, kTrackedPrefs + arraysize(kTrackedPrefs)) {} + +PrefHashFilter::~PrefHashFilter() {} + +// Updates the stored hash to correspond to the updated preference value. +void PrefHashFilter::FilterUpdate(const std::string& path, + const base::Value* value) { + if (tracked_paths_.find(path) != tracked_paths_.end()) + pref_hash_store_->StoreHash(path, value); +} + +// Validates loaded preference values according to stored hashes, reports +// validation results via UMA, and updates hashes in case of mismatch. +void PrefHashFilter::FilterOnLoad(base::DictionaryValue* pref_store_contents) { + for (size_t i = 0; i < arraysize(kTrackedPrefs); ++i) { + if (kTrackedPrefs[i][0] == '\0') + continue; + const base::Value* value = NULL; + pref_store_contents->Get(kTrackedPrefs[i], &value); + PrefHashStore::ValueState value_state = + pref_hash_store_->CheckValue(kTrackedPrefs[i], value); + ReportValidationResult(value_state, i); + if (value_state != PrefHashStore::UNCHANGED) + pref_hash_store_->StoreHash(kTrackedPrefs[i], value); + } +} diff --git a/chrome/browser/prefs/pref_hash_filter.h b/chrome/browser/prefs/pref_hash_filter.h new file mode 100644 index 0000000..2c74694 --- /dev/null +++ b/chrome/browser/prefs/pref_hash_filter.h @@ -0,0 +1,44 @@ +// 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 CHROME_BROWSER_PREFS_PREF_HASH_FILTER_H_ +#define CHROME_BROWSER_PREFS_PREF_HASH_FILTER_H_ + +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/prefs/pref_filter.h" + +class PrefHashStore; + +namespace base { +class DictionaryValue; +class Value; +} // namespace base + +// 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 PrefFilter { + public: + explicit PrefHashFilter(scoped_ptr<PrefHashStore> pref_hash_store); + virtual ~PrefHashFilter(); + + // PrefFilter implementation. + virtual void FilterOnLoad(base::DictionaryValue* pref_store_contents) + OVERRIDE; + virtual void FilterUpdate(const std::string& path, + const base::Value* value) OVERRIDE; + + private: + scoped_ptr<PrefHashStore> pref_hash_store_; + std::set<std::string> tracked_paths_; + + DISALLOW_COPY_AND_ASSIGN(PrefHashFilter); +}; + +#endif // CHROME_BROWSER_PREFS_PREF_HASH_FILTER_H_ diff --git a/chrome/browser/prefs/pref_hash_filter_unittest.cc b/chrome/browser/prefs/pref_hash_filter_unittest.cc new file mode 100644 index 0000000..d26fe70 --- /dev/null +++ b/chrome/browser/prefs/pref_hash_filter_unittest.cc @@ -0,0 +1,226 @@ +// 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 "chrome/browser/prefs/pref_hash_filter.h" + +#include <map> +#include <set> +#include <string> +#include <utility> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "chrome/browser/prefs/pref_hash_store.h" +#include "testing/gtest/include/gtest/gtest.h" + +// A PrefHashStore that allows simulation of CheckValue results and captures +// checked and stored values. +class MockPrefHashStore : public PrefHashStore { + public: + static const char kNullPlaceholder[]; + + MockPrefHashStore() {} + + // Set the result that will be returned when |path| is passed to |CheckValue|. + void SetCheckResult(const std::string& path, + PrefHashStore::ValueState result); + + // Returns paths that have been passed to |CheckValue|. + const std::set<std::string>& checked_paths() const { + return checked_paths_; + } + + // Returns paths that have been passed to |StoreHash|. + const std::set<std::string>& stored_paths() const { + return stored_paths_; + } + + // Returns the value that was passed to |CheckValue| for |path|, rendered to a + // string. If the checked value was NULL, the string is kNullPlaceholder. + std::string checked_value(const std::string& path) const { + std::map<std::string, std::string>::iterator value = + checked_values_.find(path); + if (value != checked_values_.end()) + return value->second; + return std::string("No checked value."); + } + + // Returns the value that was passed to |StoreHash| for |path|, rendered to a + // string. If the stored value was NULL, the string is kNullPlaceholder. + std::string stored_value(const std::string& path) const { + std::map<std::string, std::string>::const_iterator value = + stored_values_.find(path); + if (value != stored_values_.end()) + return value->second; + return std::string("No stored value."); + } + + // PrefHashStore implementation. + virtual PrefHashStore::ValueState CheckValue( + const std::string& path, const base::Value* value) const OVERRIDE; + virtual void StoreHash(const std::string& path, + const base::Value* new_value) OVERRIDE; + + private: + std::map<std::string, PrefHashStore::ValueState> check_results_; + mutable std::set<std::string> checked_paths_; + std::set<std::string> stored_paths_; + mutable std::map<std::string, std::string> checked_values_; + std::map<std::string, std::string> stored_values_; + + DISALLOW_COPY_AND_ASSIGN(MockPrefHashStore); +}; + +const char MockPrefHashStore::kNullPlaceholder[] = "NULL"; + +void MockPrefHashStore::SetCheckResult( + const std::string& path, PrefHashStore::ValueState result) { + check_results_.insert(std::make_pair(path, result)); +} + +PrefHashStore::ValueState MockPrefHashStore::CheckValue( + const std::string& path, const base::Value* value) const { + EXPECT_TRUE(checked_paths_.insert(path).second); + std::string as_string = kNullPlaceholder; + if (value) + EXPECT_TRUE(value->GetAsString(&as_string)); + + checked_values_.insert(std::make_pair(path, as_string)); + std::map<std::string, PrefHashStore::ValueState>::const_iterator result = + check_results_.find(path); + if (result != check_results_.end()) + return result->second; + return PrefHashStore::UNCHANGED; +} + +void MockPrefHashStore::StoreHash(const std::string& path, + const base::Value* new_value) { + std::string as_string = kNullPlaceholder; + if (new_value) + EXPECT_TRUE(new_value->GetAsString(&as_string)); + stored_paths_.insert(path); + stored_values_.insert(std::make_pair(path, as_string)); +} + +// Creates a PrefHashFilter that uses a MockPrefHashStore. The +// MockPrefHashStore (owned by the PrefHashFilter) is returned in +// |mock_pref_hash_store|. +scoped_ptr<PrefHashFilter> CreatePrefHashFilter( + MockPrefHashStore** mock_pref_hash_store) { + scoped_ptr<MockPrefHashStore> temp_mock_pref_hash_store( + new MockPrefHashStore); + if (mock_pref_hash_store) + *mock_pref_hash_store = temp_mock_pref_hash_store.get(); + return scoped_ptr<PrefHashFilter>( + new PrefHashFilter(temp_mock_pref_hash_store.PassAs<PrefHashStore>())); +} + +class PrefHashFilterTest : public testing::Test { + public: + PrefHashFilterTest() : mock_pref_hash_store_(NULL) {} + + virtual void SetUp() OVERRIDE { + // Capture the name of one tracked pref. + MockPrefHashStore* temp_hash_store = NULL; + scoped_ptr<PrefHashFilter> temp_filter = + CreatePrefHashFilter(&temp_hash_store); + temp_filter->FilterOnLoad(&pref_store_contents_); + ASSERT_FALSE(temp_hash_store->checked_paths().empty()); + tracked_path_ = *temp_hash_store->checked_paths().begin(); + + // Construct a PrefHashFilter and MockPrefHashStore for the test. + pref_hash_filter_ = CreatePrefHashFilter(&mock_pref_hash_store_); + } + + protected: + std::string tracked_path_; + MockPrefHashStore* mock_pref_hash_store_; + base::DictionaryValue pref_store_contents_; + scoped_ptr<PrefHashFilter> pref_hash_filter_; + + DISALLOW_COPY_AND_ASSIGN(PrefHashFilterTest); +}; + +TEST_F(PrefHashFilterTest, EmptyAndUnchanged) { + pref_hash_filter_->FilterOnLoad(&pref_store_contents_); + // More than 0 paths checked. + ASSERT_LT(0u, mock_pref_hash_store_->checked_paths().size()); + // No paths stored, since they all return |UNCHANGED|. + ASSERT_EQ(0u, mock_pref_hash_store_->stored_paths().size()); + // Since there was nothing in |pref_store_contents_| the checked value should + // have been NULL. + ASSERT_EQ(MockPrefHashStore::kNullPlaceholder, + mock_pref_hash_store_->checked_value(tracked_path_)); +} + +TEST_F(PrefHashFilterTest, FilterUpdate) { + base::StringValue string_value("string value"); + pref_hash_filter_->FilterUpdate(tracked_path_, &string_value); + // One path should be stored. + ASSERT_EQ(1u, mock_pref_hash_store_->stored_paths().size()); + ASSERT_EQ(tracked_path_, *mock_pref_hash_store_->stored_paths().begin()); + ASSERT_EQ("string value", mock_pref_hash_store_->stored_value(tracked_path_)); +} + +TEST_F(PrefHashFilterTest, EmptyAndUnknown){ + mock_pref_hash_store_->SetCheckResult(tracked_path_, + PrefHashStore::UNKNOWN_VALUE); + pref_hash_filter_->FilterOnLoad(&pref_store_contents_); + ASSERT_LT(0u, mock_pref_hash_store_->checked_paths().size()); + ASSERT_EQ(1u, mock_pref_hash_store_->stored_paths().size()); + ASSERT_EQ(tracked_path_, *mock_pref_hash_store_->stored_paths().begin()); + ASSERT_EQ(MockPrefHashStore::kNullPlaceholder, + mock_pref_hash_store_->stored_value(tracked_path_)); +} + +TEST_F(PrefHashFilterTest, InitialValueUnknown) { + pref_store_contents_.Set(tracked_path_, + new base::StringValue("string value")); + + mock_pref_hash_store_->SetCheckResult(tracked_path_, + PrefHashStore::UNKNOWN_VALUE); + pref_hash_filter_->FilterOnLoad(&pref_store_contents_); + ASSERT_LT(0u, mock_pref_hash_store_->checked_paths().size()); + ASSERT_EQ(1u, mock_pref_hash_store_->stored_paths().size()); + ASSERT_EQ(tracked_path_, *mock_pref_hash_store_->stored_paths().begin()); + ASSERT_EQ("string value", mock_pref_hash_store_->stored_value(tracked_path_)); +} + +TEST_F(PrefHashFilterTest, InitialValueChanged) { + pref_store_contents_.Set(tracked_path_, + new base::StringValue("string value")); + + mock_pref_hash_store_->SetCheckResult(tracked_path_, + PrefHashStore::CHANGED); + pref_hash_filter_->FilterOnLoad(&pref_store_contents_); + ASSERT_LT(0u, mock_pref_hash_store_->checked_paths().size()); + ASSERT_EQ(1u, mock_pref_hash_store_->stored_paths().size()); + ASSERT_EQ(tracked_path_, *mock_pref_hash_store_->stored_paths().begin()); + ASSERT_EQ("string value", mock_pref_hash_store_->stored_value(tracked_path_)); +} + +TEST_F(PrefHashFilterTest, EmptyCleared) { + mock_pref_hash_store_->SetCheckResult(tracked_path_, + PrefHashStore::CLEARED); + pref_hash_filter_->FilterOnLoad(&pref_store_contents_); + ASSERT_LT(0u, mock_pref_hash_store_->checked_paths().size()); + ASSERT_EQ(1u, mock_pref_hash_store_->stored_paths().size()); + ASSERT_EQ(tracked_path_, *mock_pref_hash_store_->stored_paths().begin()); + ASSERT_EQ(MockPrefHashStore::kNullPlaceholder, + mock_pref_hash_store_->stored_value(tracked_path_)); +} + +TEST_F(PrefHashFilterTest, EmptyMigrated) { + mock_pref_hash_store_->SetCheckResult(tracked_path_, + PrefHashStore::MIGRATED); + pref_hash_filter_->FilterOnLoad(&pref_store_contents_); + ASSERT_LT(0u, mock_pref_hash_store_->checked_paths().size()); + ASSERT_EQ(1u, mock_pref_hash_store_->stored_paths().size()); + ASSERT_EQ(tracked_path_, *mock_pref_hash_store_->stored_paths().begin()); + ASSERT_EQ(MockPrefHashStore::kNullPlaceholder, + mock_pref_hash_store_->stored_value(tracked_path_)); +} diff --git a/chrome/browser/prefs/pref_hash_store.h b/chrome/browser/prefs/pref_hash_store.h new file mode 100644 index 0000000..ebef8c0 --- /dev/null +++ b/chrome/browser/prefs/pref_hash_store.h @@ -0,0 +1,49 @@ +// 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 CHROME_BROWSER_PREFS_PREF_HASH_STORE_H_ +#define CHROME_BROWSER_PREFS_PREF_HASH_STORE_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" + +class PrefHashTracker; + +namespace base { +class Value; +} // namespace base + +// Stores hashes of and verifies preference values. To use, first call +// |InitializeTrackedValue| with each preference that should be tracked. Then +// call |OnPrefValueChanged| to update the hash store when preference values +// change. +class PrefHashStore { + public: + virtual ~PrefHashStore() {} + + 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, which was calculated + // using a legacy hash algorithm. + MIGRATED, + // The preference value has been changed since the last hash. + CHANGED, + // No stored hash exists for the preference value. + UNKNOWN_VALUE, + }; + + // Checks |initial_value| against the existing stored value hash. + virtual 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; +}; + +#endif // CHROME_BROWSER_PREFS_PREF_HASH_STORE_H_ diff --git a/chrome/browser/prefs/pref_hash_store_impl.cc b/chrome/browser/prefs/pref_hash_store_impl.cc new file mode 100644 index 0000000..0d08c55 --- /dev/null +++ b/chrome/browser/prefs/pref_hash_store_impl.cc @@ -0,0 +1,76 @@ +// 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 "chrome/browser/prefs/pref_hash_store_impl.h" + +#include "base/logging.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/pref_service.h" +#include "base/prefs/scoped_user_pref_update.h" +#include "base/values.h" +#include "chrome/common/pref_names.h" + +PrefHashStoreImpl::PrefHashStoreImpl(const std::string& hash_store_id, + const std::string& seed, + const std::string& device_id, + PrefService* local_state) + : hash_store_id_(hash_store_id), + pref_hash_calculator_(seed, device_id), + local_state_(local_state) {} + +// static +void PrefHashStoreImpl::RegisterPrefs(PrefRegistrySimple* registry) { + // Register the top level dictionary to map profile names to dictionaries of + // tracked preferences. + registry->RegisterDictionaryPref(prefs::kProfilePreferenceHashes); +} + +PrefHashStore::ValueState PrefHashStoreImpl::CheckValue( + const std::string& path, const base::Value* initial_value) const { + const base::DictionaryValue* pref_hash_dicts = + local_state_->GetDictionary(prefs::kProfilePreferenceHashes); + const base::DictionaryValue* hashed_prefs = NULL; + pref_hash_dicts->GetDictionaryWithoutPathExpansion(hash_store_id_, + &hashed_prefs); + + std::string last_hash; + if (!hashed_prefs || !hashed_prefs->GetString(path, &last_hash)) + return PrefHashStore::UNKNOWN_VALUE; + + PrefHashCalculator::ValidationResult validation_result = + pref_hash_calculator_.Validate(path, initial_value, last_hash); + switch (validation_result) { + case PrefHashCalculator::VALID: + return PrefHashStore::UNCHANGED; + case PrefHashCalculator::VALID_LEGACY: + return PrefHashStore::MIGRATED; + case PrefHashCalculator::INVALID: + return initial_value ? PrefHashStore::CHANGED : PrefHashStore::CLEARED; + } + NOTREACHED() << "Unexpected PrefHashCalculator::ValidationResult: " + << validation_result; + return PrefHashStore::UNKNOWN_VALUE; +} + +void PrefHashStoreImpl::StoreHash( + const std::string& path, const base::Value* new_value) { + { + DictionaryPrefUpdate update(local_state_, prefs::kProfilePreferenceHashes); + DictionaryValue* child_dictionary = NULL; + + // Get the dictionary corresponding to the profile name, which may have a + // '.' + if (!update->GetDictionaryWithoutPathExpansion(hash_store_id_, + &child_dictionary)) { + child_dictionary = new DictionaryValue; + update->SetWithoutPathExpansion(hash_store_id_, child_dictionary); + } + + child_dictionary->SetString( + path, pref_hash_calculator_.Calculate(path, new_value)); + } + // TODO(erikwright): During tests, pending writes were still waiting when the + // IO thread is already gone. Consider other solutions. + local_state_->CommitPendingWrite(); +} diff --git a/chrome/browser/prefs/pref_hash_store_impl.h b/chrome/browser/prefs/pref_hash_store_impl.h new file mode 100644 index 0000000..1d3f581 --- /dev/null +++ b/chrome/browser/prefs/pref_hash_store_impl.h @@ -0,0 +1,56 @@ +// 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 CHROME_BROWSER_PREFS_PREF_HASH_STORE_IMPL_H_ +#define CHROME_BROWSER_PREFS_PREF_HASH_STORE_IMPL_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/prefs/pref_hash_calculator.h" +#include "chrome/browser/prefs/pref_hash_store.h" + +class PrefRegistrySimple; +class PrefService; + +namespace base { +class Value; +} // namespace base + +// Implements PrefHashStoreImpl by storing preference hashes in a PrefService. +class PrefHashStoreImpl : public PrefHashStore { + public: + // Constructs a PrefHashStoreImpl that calculates hashes using + // |seed| and |device_id| and stores them in |local_state|. Multiple hash + // stores can use the same |local_state| with distinct |hash_store_id|s. + // + // The same |seed|, |device_id|, and |hash_store_id| must be used to load and + // validate previously stored hashes in |local_state|. + // + // |local_state| must have previously been passed to |RegisterPrefs|. + PrefHashStoreImpl(const std::string& hash_store_id, + const std::string& seed, + const std::string& device_id, + PrefService* local_state); + + // Registers required local state preferences. + static void RegisterPrefs(PrefRegistrySimple* registry); + + // PrefHashStore implementation. + virtual ValueState CheckValue(const std::string& path, + const base::Value* value) const OVERRIDE; + virtual void StoreHash(const std::string& path, + const base::Value* value) OVERRIDE; + + private: + std::string hash_store_id_; + PrefHashCalculator pref_hash_calculator_; + PrefService* local_state_; + + DISALLOW_COPY_AND_ASSIGN(PrefHashStoreImpl); +}; + +#endif // CHROME_BROWSER_PREFS_PREF_HASH_STORE_IMPL_H_ diff --git a/chrome/browser/prefs/pref_hash_store_impl_unittest.cc b/chrome/browser/prefs/pref_hash_store_impl_unittest.cc new file mode 100644 index 0000000..330879a --- /dev/null +++ b/chrome/browser/prefs/pref_hash_store_impl_unittest.cc @@ -0,0 +1,59 @@ +// 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 "chrome/browser/prefs/pref_hash_store_impl.h" + +#include <string> + +#include "base/prefs/pref_service.h" +#include "base/prefs/scoped_user_pref_update.h" +#include "base/prefs/testing_pref_service.h" +#include "base/values.h" +#include "chrome/browser/prefs/pref_hash_store.h" +#include "chrome/common/pref_names.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PrefHashStoreImplTest, TestCase) { + base::StringValue string_1("string1"); + base::StringValue string_2("string2"); + + TestingPrefServiceSimple local_state; + PrefHashStoreImpl::RegisterPrefs(local_state.registry()); + + // 32 NULL bytes is the seed that was used to generate the legacy hash. + PrefHashStoreImpl pref_hash_store( + "store_id", std::string(32,0), "device_id", &local_state); + + ASSERT_EQ(PrefHashStore::UNKNOWN_VALUE, + pref_hash_store.CheckValue("path1", &string_1)); + pref_hash_store.StoreHash("path1", &string_1); + ASSERT_EQ(PrefHashStore::UNCHANGED, + pref_hash_store.CheckValue("path1", &string_1)); + ASSERT_EQ(PrefHashStore::CLEARED, pref_hash_store.CheckValue("path1", NULL)); + pref_hash_store.StoreHash("path1", NULL); + ASSERT_EQ(PrefHashStore::UNCHANGED, + pref_hash_store.CheckValue("path1", NULL)); + ASSERT_EQ(PrefHashStore::CHANGED, + pref_hash_store.CheckValue("path1", &string_2)); + + DictionaryValue dict; + dict.Set("a", new StringValue("foo")); + dict.Set("d", new StringValue("bad")); + dict.Set("b", new StringValue("bar")); + dict.Set("c", new StringValue("baz")); + + // Manually shove in a legacy hash. + DictionaryPrefUpdate update(&local_state, prefs::kProfilePreferenceHashes); + DictionaryValue* child_dictionary = NULL; + ASSERT_TRUE(update->GetDictionary("store_id", &child_dictionary)); + child_dictionary->SetString( + "path1", + "C503FB7C65EEFD5C07185F616A0AA67923C069909933F362022B1F187E73E9A2"); + + ASSERT_EQ(PrefHashStore::MIGRATED, + pref_hash_store.CheckValue("path1", &dict)); + pref_hash_store.StoreHash("path1", &dict); + ASSERT_EQ(PrefHashStore::UNCHANGED, + pref_hash_store.CheckValue("path1", &dict)); +} diff --git a/chrome/browser/prefs/pref_metrics_service.cc b/chrome/browser/prefs/pref_metrics_service.cc index dfce44b..8b6f389 100644 --- a/chrome/browser/prefs/pref_metrics_service.cc +++ b/chrome/browser/prefs/pref_metrics_service.cc @@ -6,18 +6,12 @@ #include "base/bind.h" #include "base/command_line.h" -#include "base/json/json_string_value_serializer.h" #include "base/metrics/histogram.h" #include "base/prefs/pref_registry_simple.h" #include "base/prefs/pref_service.h" -#include "base/prefs/scoped_user_pref_update.h" #include "base/strings/string_number_conversions.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/browser_shutdown.h" -// Accessing the Device ID API here is a layering violation. -// TODO(bbudge) Move the API so it's usable here. -// http://crbug.com/276485 -#include "chrome/browser/extensions/api/music_manager_private/device_id.h" #include "chrome/browser/prefs/pref_service_syncable.h" #include "chrome/browser/prefs/session_startup_pref.h" #include "chrome/browser/prefs/synced_pref_change_registrar.h" @@ -29,97 +23,34 @@ #include "chrome/common/pref_names.h" #include "components/browser_context_keyed_service/browser_context_dependency_manager.h" #include "crypto/hmac.h" -#include "grit/browser_resources.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" -#include "ui/base/resource/resource_bundle.h" namespace { const int kSessionStartupPrefValueMax = SessionStartupPref::kPrefValueMax; -// An unregistered preference to fill in indices in kTrackedPrefs below for -// preferences that aren't defined on every platform. This is fine as the code -// below (e.g. CheckTrackedPreferences()) skips unregistered preferences and -// should thus never report any data about that index on the platforms where -// that preference is unimplemented. -const char kUnregisteredPreference[] = "_"; - -// These preferences must be kept in sync with the TrackedPreference enum in -// tools/metrics/histograms/histograms.xml. To add a new preference, append it -// to the array and add a corresponding value to the histogram enum. -const char* kTrackedPrefs[] = { - prefs::kShowHomeButton, - prefs::kHomePageIsNewTabPage, - prefs::kHomePage, - prefs::kRestoreOnStartup, - prefs::kURLsToRestoreOnStartup, - prefs::kExtensionsPref, - prefs::kGoogleServicesLastUsername, - prefs::kSearchProviderOverrides, - prefs::kDefaultSearchProviderSearchURL, - prefs::kDefaultSearchProviderKeyword, - prefs::kDefaultSearchProviderName, -#if !defined(OS_ANDROID) - prefs::kPinnedTabs, -#else - kUnregisteredPreference, -#endif - prefs::kExtensionKnownDisabled, - prefs::kProfileResetPromptMemento, -}; - -const size_t kSHA256DigestSize = 32; - } // namespace PrefMetricsService::PrefMetricsService(Profile* profile) : profile_(profile), prefs_(profile_->GetPrefs()), local_state_(g_browser_process->local_state()), - profile_name_(profile_->GetPath().AsUTF8Unsafe()), - tracked_pref_paths_(kTrackedPrefs), - tracked_pref_path_count_(arraysize(kTrackedPrefs)), - checked_tracked_prefs_(false), weak_factory_(this) { - pref_hash_seed_ = ResourceBundle::GetSharedInstance().GetRawDataResource( - IDR_PREF_HASH_SEED_BIN).as_string(); - RecordLaunchPrefs(); PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); synced_pref_change_registrar_.reset(new SyncedPrefChangeRegistrar(prefs)); RegisterSyncedPrefObservers(); - - // The following code might cause callbacks into this instance before we exit - // the constructor. This instance should be initialized at this point. -#if defined(OS_WIN) || defined(OS_MACOSX) - // We need the machine id to compute pref value hashes. Fetch that, and then - // call CheckTrackedPreferences in the callback. - extensions::api::DeviceId::GetDeviceId( - "PrefMetricsService", // non-empty string to obfuscate the device id. - Bind(&PrefMetricsService::GetDeviceIdCallback, - weak_factory_.GetWeakPtr())); -#endif // defined(OS_WIN) || defined(OS_MACOSX) } // For unit testing only. PrefMetricsService::PrefMetricsService(Profile* profile, - PrefService* local_state, - const std::string& device_id, - const char** tracked_pref_paths, - int tracked_pref_path_count) + PrefService* local_state) : profile_(profile), prefs_(profile->GetPrefs()), local_state_(local_state), - profile_name_(profile_->GetPath().AsUTF8Unsafe()), - pref_hash_seed_(kSHA256DigestSize, 0), - device_id_(device_id), - tracked_pref_paths_(tracked_pref_paths), - tracked_pref_path_count_(tracked_pref_path_count), - checked_tracked_prefs_(false), weak_factory_(this) { - CheckTrackedPreferences(); } PrefMetricsService::~PrefMetricsService() { @@ -189,13 +120,6 @@ void PrefMetricsService::RecordLaunchPrefs() { #endif } -// static -void PrefMetricsService::RegisterPrefs(PrefRegistrySimple* registry) { - // Register the top level dictionary to map profile names to dictionaries of - // tracked preferences. - registry->RegisterDictionaryPref(prefs::kProfilePreferenceHashes); -} - void PrefMetricsService::RegisterSyncedPrefObservers() { LogHistogramValueCallback booleanHandler = base::Bind( &PrefMetricsService::LogBooleanPrefChange, base::Unretained(this)); @@ -259,173 +183,6 @@ void PrefMetricsService::LogIntegerPrefChange(int boundary_value, histogram->Add(integer_value); } -void PrefMetricsService::GetDeviceIdCallback(const std::string& device_id) { -#if !defined(OS_WIN) || defined(ENABLE_RLZ) - // A device_id is expected in all scenarios except when RLZ is disabled on - // Windows. - DCHECK(!device_id.empty()); -#endif - - device_id_ = device_id; - CheckTrackedPreferences(); -} - -// To detect changes to Preferences that happen outside of Chrome, we hash -// selected pref values and save them in local state. CheckTrackedPreferences -// compares the saved values to the values observed in the profile's prefs. A -// dictionary of dictionaries in local state holds the hashed values, grouped by -// profile. To make the system more resistant to spoofing, pref values are -// hashed with the pref path and the device id. -void PrefMetricsService::CheckTrackedPreferences() { - // Make sure this is only called once per instance of this service. - DCHECK(!checked_tracked_prefs_); - checked_tracked_prefs_ = true; - - const base::DictionaryValue* pref_hash_dicts = - local_state_->GetDictionary(prefs::kProfilePreferenceHashes); - // Get the hashed prefs dictionary if it exists. If it doesn't, it will be - // created if we set preference values below. - const base::DictionaryValue* hashed_prefs = NULL; - pref_hash_dicts->GetDictionaryWithoutPathExpansion(profile_name_, - &hashed_prefs); - for (int i = 0; i < tracked_pref_path_count_; ++i) { - if (!prefs_->FindPreference(tracked_pref_paths_[i])) { - // All tracked preferences need to have been registered already. - DCHECK_EQ(kUnregisteredPreference, tracked_pref_paths_[i]); - continue; - } - - const base::Value* value = prefs_->GetUserPrefValue(tracked_pref_paths_[i]); - std::string last_hash; - // First try to get the stored expected hash... - if (hashed_prefs && - hashed_prefs->GetString(tracked_pref_paths_[i], &last_hash)) { - // ... if we have one get the hash of the current value... - const std::string value_hash = - GetHashedPrefValue(tracked_pref_paths_[i], value, - HASHED_PREF_STYLE_NEW); - // ... and check that it matches... - if (value_hash == last_hash) { - UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceUnchanged", - i, tracked_pref_path_count_); - } else { - // ... if it doesn't: was the value simply cleared? - if (!value) { - UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceCleared", - i, tracked_pref_path_count_); - } else { - // ... or does it match the old style hash? - std::string old_style_hash = - GetHashedPrefValue(tracked_pref_paths_[i], value, - HASHED_PREF_STYLE_DEPRECATED); - if (old_style_hash == last_hash) { - UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceMigrated", - i, tracked_pref_path_count_); - } else { - // ... or was it simply changed to something else? - UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceChanged", - i, tracked_pref_path_count_); - } - } - - // ... either way update the expected hash to match the new value. - UpdateTrackedPreference(tracked_pref_paths_[i]); - } - } else { - // Record that we haven't tracked this preference yet, or the hash in - // local state was removed. - UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceInitialized", - i, tracked_pref_path_count_); - UpdateTrackedPreference(tracked_pref_paths_[i]); - } - } - - // Now that we've checked the incoming preferences, register for change - // notifications, unless this is test code. - // TODO(bbudge) Fix failing browser_tests and so we can remove this test. - // Several tests fail when they shutdown before they can write local state. - if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType) && - !CommandLine::ForCurrentProcess()->HasSwitch(switches::kChromeFrame)) { - InitializePrefObservers(); - } -} - -void PrefMetricsService::UpdateTrackedPreference(const char* path) { - const base::Value* value = prefs_->GetUserPrefValue(path); - DictionaryPrefUpdate update(local_state_, prefs::kProfilePreferenceHashes); - DictionaryValue* child_dictionary = NULL; - - // Get the dictionary corresponding to the profile name, - // which may have a '.' - if (!update->GetDictionaryWithoutPathExpansion(profile_name_, - &child_dictionary)) { - child_dictionary = new DictionaryValue; - update->SetWithoutPathExpansion(profile_name_, child_dictionary); - } - - child_dictionary->SetString(path, - GetHashedPrefValue(path, value, - HASHED_PREF_STYLE_NEW)); -} - -std::string PrefMetricsService::GetHashedPrefValue( - const char* path, - const base::Value* value, - HashedPrefStyle desired_style) { - // Dictionary values may contain empty lists and sub-dictionaries. Make a - // deep copy with those removed to make the hash more stable. - const DictionaryValue* dict_value; - scoped_ptr<DictionaryValue> canonical_dict_value; - if (value && - value->GetAsDictionary(&dict_value)) { - canonical_dict_value.reset(dict_value->DeepCopyWithoutEmptyChildren()); - value = canonical_dict_value.get(); - } - - std::string value_as_string; - // If |value| is NULL, we will still build a unique hash based on |device_id_| - // and |path| below. - if (value) { - JSONStringValueSerializer serializer(&value_as_string); - serializer.Serialize(*value); - } - - std::string string_to_hash; - // TODO(gab): Remove this as the old style is phased out. - if (desired_style == HASHED_PREF_STYLE_NEW) { - string_to_hash.append(device_id_); - string_to_hash.append(path); - } - string_to_hash.append(value_as_string); - - crypto::HMAC hmac(crypto::HMAC::SHA256); - unsigned char digest[kSHA256DigestSize]; - if (!hmac.Init(pref_hash_seed_) || - !hmac.Sign(string_to_hash, digest, kSHA256DigestSize)) { - NOTREACHED(); - return std::string(); - } - - return base::HexEncode(digest, kSHA256DigestSize); -} - -void PrefMetricsService::InitializePrefObservers() { - pref_registrar_.Init(prefs_); - for (int i = 0; i < tracked_pref_path_count_; ++i) { - if (!prefs_->FindPreference(tracked_pref_paths_[i])) { - // All tracked preferences need to have been registered already. - DCHECK_EQ(kUnregisteredPreference, tracked_pref_paths_[i]); - continue; - } - - pref_registrar_.Add( - tracked_pref_paths_[i], - base::Bind(&PrefMetricsService::UpdateTrackedPreference, - weak_factory_.GetWeakPtr(), - tracked_pref_paths_[i])); - } -} - // static PrefMetricsService::Factory* PrefMetricsService::Factory::GetInstance() { return Singleton<PrefMetricsService::Factory>::get(); diff --git a/chrome/browser/prefs/pref_metrics_service.h b/chrome/browser/prefs/pref_metrics_service.h index d38a006..c975a43 100644 --- a/chrome/browser/prefs/pref_metrics_service.h +++ b/chrome/browser/prefs/pref_metrics_service.h @@ -22,11 +22,6 @@ class PrefRegistrySimple; // PrefMetricsService is responsible for recording prefs-related UMA stats. class PrefMetricsService : public BrowserContextKeyedService { public: - enum HashedPrefStyle { - HASHED_PREF_STYLE_NEW, - HASHED_PREF_STYLE_DEPRECATED, - }; - explicit PrefMetricsService(Profile* profile); virtual ~PrefMetricsService(); @@ -49,22 +44,15 @@ class PrefMetricsService : public BrowserContextKeyedService { content::BrowserContext* context) const OVERRIDE; }; - // Registers preferences in local state. - static void RegisterPrefs(PrefRegistrySimple* registry); - private: friend class PrefMetricsServiceTest; // Function to log a Value to a histogram - typedef base::Callback<void(const std::string&, const Value*)> + typedef base::Callback<void(const std::string&, const base::Value*)> LogHistogramValueCallback; // For unit testing only. - PrefMetricsService(Profile* profile, - PrefService* local_settings, - const std::string& device_id, - const char** tracked_pref_paths, - int tracked_pref_path_count); + PrefMetricsService(Profile* profile, PrefService* local_settings); // Record prefs state on browser context creation. void RecordLaunchPrefs(); @@ -85,46 +73,16 @@ class PrefMetricsService : public BrowserContextKeyedService { // Callback for a boolean pref change histogram. void LogBooleanPrefChange(const std::string& histogram_name, - const Value* value); + const base::Value* value); // Callback for an integer pref change histogram. void LogIntegerPrefChange(int boundary_value, const std::string& histogram_name, - const Value* value); - - // Callback to receive a unique device_id. - void GetDeviceIdCallback(const std::string& device_id); - - // Checks the tracked preferences against their last known values and reports - // any discrepancies. This must be called after |device_id| has been set. - void CheckTrackedPreferences(); - - // Updates the hash of the tracked preference in local state. This must be - // called after |device_id| has been set. - void UpdateTrackedPreference(const char* path); - - // Computes an MD5 hash for the given preference value. |value| can be - // NULL which will result in the unique hash representing NULL for the pref - // at |path|. - std::string GetHashedPrefValue( - const char* path, - const base::Value* value, - HashedPrefStyle desired_style); - - void InitializePrefObservers(); + const base::Value* value); Profile* profile_; PrefService* prefs_; PrefService* local_state_; - std::string profile_name_; - std::string pref_hash_seed_; - std::string device_id_; - const char** tracked_pref_paths_; - const int tracked_pref_path_count_; - - // TODO(gab): preprocessor define this member out on builds that don't use - // DCHECKs (http://crbug.com/322713). - bool checked_tracked_prefs_; PrefChangeRegistrar pref_registrar_; scoped_ptr<SyncedPrefChangeRegistrar> synced_pref_change_registrar_; diff --git a/chrome/browser/prefs/pref_metrics_service_unittest.cc b/chrome/browser/prefs/pref_metrics_service_unittest.cc deleted file mode 100644 index e80d408..0000000 --- a/chrome/browser/prefs/pref_metrics_service_unittest.cc +++ /dev/null @@ -1,478 +0,0 @@ -// 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 "base/memory/scoped_ptr.h" -#include "base/metrics/histogram.h" -#include "base/metrics/statistics_recorder.h" -#include "base/prefs/scoped_user_pref_update.h" -#include "base/prefs/testing_pref_service.h" -#include "base/values.h" -#include "chrome/browser/prefs/pref_metrics_service.h" -#include "chrome/common/pref_names.h" -#include "chrome/test/base/testing_browser_process.h" -#include "chrome/test/base/testing_pref_service_syncable.h" -#include "chrome/test/base/testing_profile.h" -#include "chrome/test/base/testing_profile_manager.h" -#include "components/user_prefs/pref_registry_syncable.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -// TestingProfile may register some real preferences; to avoid interference, -// define fake preferences for testing. -const char* kTrackedPrefs[] = { - "pref_metrics_service_test.pref1", - "pref_metrics_service_test.pref2", -}; - -const int kTrackedPrefCount = arraysize(kTrackedPrefs); - -const char kTestDeviceId[] = "test_device_id1"; -const char kOtherTestDeviceId[] = "test_device_id2"; - -} // namespace - -class PrefMetricsServiceTest : public testing::Test { - protected: - virtual void SetUp() { - pref1_changed_ = 0; - pref2_changed_ = 0; - pref1_cleared_ = 0; - pref2_cleared_ = 0; - pref1_initialized_ = 0; - pref2_initialized_ = 0; - pref1_migrated_ = 0; - pref2_migrated_ = 0; - pref1_unchanged_ = 0; - pref2_unchanged_ = 0; - - base::StatisticsRecorder::Initialize(); - - // Reset and set up the profile manager. - profile_manager_.reset(new TestingProfileManager( - TestingBrowserProcess::GetGlobal())); - ASSERT_TRUE(profile_manager_->SetUp()); - - // Check that PrefMetricsService behaves with a '.' in the profile name. - profile_ = profile_manager_->CreateTestingProfile("test@example.com"); - - profile_name_ = profile_->GetPath().AsUTF8Unsafe(); - - prefs_ = profile_->GetTestingPrefService(); - - // Register our test-only tracked prefs as string values. - for (int i = 0; i < kTrackedPrefCount; ++i) { - prefs_->registry()->RegisterStringPref( - kTrackedPrefs[i], - "test_default_value", - user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); - } - - // Initialize pref in local state that holds hashed values. - PrefMetricsService::RegisterPrefs(local_state_.registry()); - - // Update global counts in case another test left stray samples. - UpdateHistogramSamples(); - } - - scoped_ptr<PrefMetricsService> CreatePrefMetricsService( - const std::string& device_id) { - return scoped_ptr<PrefMetricsService>( - new PrefMetricsService(profile_, - &local_state_, - device_id, - kTrackedPrefs, - kTrackedPrefCount)); - } - - std::string GetHashedPrefValue(PrefMetricsService* service, - const char* path, - const base::Value* value) { - return service->GetHashedPrefValue( - path, value, PrefMetricsService::HASHED_PREF_STYLE_NEW); - } - - std::string GetOldStyleHashedPrefValue(PrefMetricsService* service, - const char* path, - const base::Value* value) { - return service->GetHashedPrefValue( - path, value, PrefMetricsService::HASHED_PREF_STYLE_DEPRECATED); - } - - void GetSamples(const char* histogram_name, int* bucket1, int* bucket2) { - base::HistogramBase* histogram = - base::StatisticsRecorder::FindHistogram(histogram_name); - if (!histogram) { - *bucket1 = 0; - *bucket2 = 0; - } else { - scoped_ptr<base::HistogramSamples> samples(histogram->SnapshotSamples()); - *bucket1 = samples->GetCount(0); - *bucket2 = samples->GetCount(1); - } - } - - void UpdateHistogramSamples() { - int changed1, changed2; - GetSamples("Settings.TrackedPreferenceChanged", &changed1, &changed2); - pref1_changed_ = changed1 - pref1_changed_total; - pref2_changed_ = changed2 - pref2_changed_total; - pref1_changed_total = changed1; - pref2_changed_total = changed2; - - int cleared1, cleared2; - GetSamples("Settings.TrackedPreferenceCleared", &cleared1, &cleared2); - pref1_cleared_ = cleared1 - pref1_cleared_total; - pref2_cleared_ = cleared2 - pref2_cleared_total; - pref1_cleared_total = cleared1; - pref2_cleared_total = cleared2; - - int inited1, inited2; - GetSamples("Settings.TrackedPreferenceInitialized", &inited1, &inited2); - pref1_initialized_ = inited1 - pref1_initialized_total; - pref2_initialized_ = inited2 - pref2_initialized_total; - pref1_initialized_total = inited1; - pref2_initialized_total = inited2; - - int migrated1, migrated2; - GetSamples("Settings.TrackedPreferenceMigrated", &migrated1, &migrated2); - pref1_migrated_ = migrated1 - pref1_migrated_total; - pref2_migrated_ = migrated2 - pref2_migrated_total; - pref1_migrated_total = migrated1; - pref2_migrated_total = migrated2; - - int unchanged1, unchanged2; - GetSamples("Settings.TrackedPreferenceUnchanged", &unchanged1, &unchanged2); - pref1_unchanged_ = unchanged1 - pref1_unchanged_total; - pref2_unchanged_ = unchanged2 - pref2_unchanged_total; - pref1_unchanged_total = unchanged1; - pref2_unchanged_total = unchanged2; - } - - TestingProfile* profile_; - std::string profile_name_; - scoped_ptr<TestingProfileManager> profile_manager_; - TestingPrefServiceSyncable* prefs_; - TestingPrefServiceSimple local_state_; - - // Since histogram samples are recorded by a global StatisticsRecorder, we - // need to maintain total counts so we can compute deltas for individual - // tests. - static int pref1_changed_total; - static int pref2_changed_total; - static int pref1_cleared_total; - static int pref2_cleared_total; - static int pref1_initialized_total; - static int pref2_initialized_total; - static int pref1_migrated_total; - static int pref2_migrated_total; - static int pref1_unchanged_total; - static int pref2_unchanged_total; - - // Counts of samples recorded since UpdateHistogramSamples was last called. - int pref1_changed_; - int pref2_changed_; - int pref1_cleared_; - int pref2_cleared_; - int pref1_initialized_; - int pref2_initialized_; - int pref1_migrated_; - int pref2_migrated_; - int pref1_unchanged_; - int pref2_unchanged_; -}; - -int PrefMetricsServiceTest::pref1_changed_total; -int PrefMetricsServiceTest::pref2_changed_total; -int PrefMetricsServiceTest::pref1_cleared_total; -int PrefMetricsServiceTest::pref2_cleared_total; -int PrefMetricsServiceTest::pref1_initialized_total; -int PrefMetricsServiceTest::pref2_initialized_total; -int PrefMetricsServiceTest::pref1_migrated_total; -int PrefMetricsServiceTest::pref2_migrated_total; -int PrefMetricsServiceTest::pref1_unchanged_total; -int PrefMetricsServiceTest::pref2_unchanged_total; - -TEST_F(PrefMetricsServiceTest, StartupNoUserPref) { - // Local state is empty and no user prefs are set. We should still have - // initialized all preferences once. - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - UpdateHistogramSamples(); - EXPECT_EQ(0, pref1_changed_); - EXPECT_EQ(0, pref2_changed_); - EXPECT_EQ(0, pref1_cleared_); - EXPECT_EQ(0, pref2_cleared_); - EXPECT_EQ(1, pref1_initialized_); - EXPECT_EQ(1, pref2_initialized_); - EXPECT_EQ(0, pref1_migrated_); - EXPECT_EQ(0, pref2_migrated_); - EXPECT_EQ(0, pref1_unchanged_); - EXPECT_EQ(0, pref2_unchanged_); - - // Ensure that each pref got a hash even though their value is NULL (i.e., - // empty). - const DictionaryValue* root_dictionary = - local_state_.GetDictionary(prefs::kProfilePreferenceHashes); - ASSERT_TRUE(root_dictionary != NULL); - - const DictionaryValue* child_dictionary = NULL; - ASSERT_TRUE(root_dictionary->GetDictionaryWithoutPathExpansion( - profile_name_, &child_dictionary)); - - std::string pref1_hash; - std::string pref2_hash; - ASSERT_TRUE(child_dictionary->GetString(kTrackedPrefs[0], &pref1_hash)); - ASSERT_TRUE(child_dictionary->GetString(kTrackedPrefs[1], &pref2_hash)); - - // These two hashes are expected to be different as the paths on which they're - // based differ. - EXPECT_EQ("2A38C5000E1EDC2D5FA3B6A8E1D3B54068E32D329324D0D8C1AADA65BBDB20B3", - pref1_hash); - EXPECT_EQ("C4FEB38BDADD16CC642815B9798FEA70BF92C6CA9250BACD6993701196D72067", - pref2_hash); -} - -TEST_F(PrefMetricsServiceTest, StartupUserPref) { - // Local state is empty. Set a value for one tracked pref. We should record - // that we checked preferences once and initialized a hash for the pref. - prefs_->SetString(kTrackedPrefs[0], "foo"); - { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - UpdateHistogramSamples(); - EXPECT_EQ(0, pref1_changed_); - EXPECT_EQ(0, pref2_changed_); - EXPECT_EQ(0, pref1_cleared_); - EXPECT_EQ(0, pref2_cleared_); - EXPECT_EQ(1, pref1_initialized_); - EXPECT_EQ(1, pref2_initialized_); - EXPECT_EQ(0, pref1_migrated_); - EXPECT_EQ(0, pref2_migrated_); - EXPECT_EQ(0, pref1_unchanged_); - EXPECT_EQ(0, pref2_unchanged_); - - // Change the pref. This should be observed by the PrefMetricsService, which - // will update the hash in local_state_ to stay in sync. - prefs_->SetString(kTrackedPrefs[0], "bar"); - } - // The next startup should record no changes. - { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - UpdateHistogramSamples(); - EXPECT_EQ(0, pref1_changed_); - EXPECT_EQ(0, pref2_changed_); - EXPECT_EQ(0, pref1_cleared_); - EXPECT_EQ(0, pref2_cleared_); - EXPECT_EQ(0, pref1_initialized_); - EXPECT_EQ(0, pref2_initialized_); - EXPECT_EQ(0, pref1_migrated_); - EXPECT_EQ(0, pref2_migrated_); - EXPECT_EQ(1, pref1_unchanged_); - EXPECT_EQ(1, pref2_unchanged_); - } -} - -TEST_F(PrefMetricsServiceTest, ChangedUserPref) { - // Local state is empty. Set a value for the tracked pref. We should record - // that we checked preferences once and initialized a hash for the pref. - prefs_->SetString(kTrackedPrefs[0], "foo"); - { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - UpdateHistogramSamples(); - EXPECT_EQ(0, pref1_changed_); - EXPECT_EQ(0, pref2_changed_); - EXPECT_EQ(0, pref1_cleared_); - EXPECT_EQ(0, pref2_cleared_); - EXPECT_EQ(1, pref1_initialized_); - EXPECT_EQ(1, pref2_initialized_); - EXPECT_EQ(0, pref1_migrated_); - EXPECT_EQ(0, pref2_migrated_); - EXPECT_EQ(0, pref1_unchanged_); - EXPECT_EQ(0, pref2_unchanged_); - // Hashed prefs should now be stored in local state. - } - // Change the value of the tracked pref while there is no PrefMetricsService - // to update the hash. We should observe a pref value change. - prefs_->SetString(kTrackedPrefs[0], "bar"); - { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - UpdateHistogramSamples(); - EXPECT_EQ(1, pref1_changed_); - EXPECT_EQ(0, pref2_changed_); - EXPECT_EQ(0, pref1_cleared_); - EXPECT_EQ(0, pref2_cleared_); - EXPECT_EQ(0, pref1_initialized_); - EXPECT_EQ(0, pref2_initialized_); - EXPECT_EQ(0, pref1_migrated_); - EXPECT_EQ(0, pref2_migrated_); - EXPECT_EQ(0, pref1_unchanged_); - EXPECT_EQ(1, pref2_unchanged_); - } - // Clear the value of the tracked pref while there is no PrefMetricsService - // to update the hash. We should observe a pref value removal. - prefs_->ClearPref(kTrackedPrefs[0]); - { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - UpdateHistogramSamples(); - EXPECT_EQ(0, pref1_changed_); - EXPECT_EQ(0, pref2_changed_); - EXPECT_EQ(1, pref1_cleared_); - EXPECT_EQ(0, pref2_cleared_); - EXPECT_EQ(0, pref1_initialized_); - EXPECT_EQ(0, pref2_initialized_); - EXPECT_EQ(0, pref1_migrated_); - EXPECT_EQ(0, pref2_migrated_); - EXPECT_EQ(0, pref1_unchanged_); - EXPECT_EQ(1, pref2_unchanged_); - } -} - -TEST_F(PrefMetricsServiceTest, MigratedUserPref) { - // Initialize both preferences and get the old style hash for the first pref - // from the PrefMetricsService before shutting it down. - prefs_->SetString(kTrackedPrefs[0], "foo"); - prefs_->SetString(kTrackedPrefs[1], "bar"); - std::string old_style_hash; - { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - UpdateHistogramSamples(); - EXPECT_EQ(0, pref1_changed_); - EXPECT_EQ(0, pref2_changed_); - EXPECT_EQ(0, pref1_cleared_); - EXPECT_EQ(0, pref2_cleared_); - EXPECT_EQ(1, pref1_initialized_); - EXPECT_EQ(1, pref2_initialized_); - EXPECT_EQ(0, pref1_migrated_); - EXPECT_EQ(0, pref2_migrated_); - EXPECT_EQ(0, pref1_unchanged_); - EXPECT_EQ(0, pref2_unchanged_); - - old_style_hash = - GetOldStyleHashedPrefValue(service.get(), kTrackedPrefs[0], - prefs_->GetUserPrefValue(kTrackedPrefs[0])); - } - - // Update the pref's hash to use the old style while the PrefMetricsService - // isn't running. - { - DictionaryPrefUpdate update(&local_state_, prefs::kProfilePreferenceHashes); - DictionaryValue* child_dictionary = NULL; - // Get the dictionary corresponding to the profile name, - // which may have a '.' - ASSERT_TRUE(update->GetDictionaryWithoutPathExpansion(profile_name_, - &child_dictionary)); - child_dictionary->SetString(kTrackedPrefs[0], old_style_hash); - } - - // Relaunch the service and make sure the first preference got migrated. - { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - UpdateHistogramSamples(); - EXPECT_EQ(0, pref1_changed_); - EXPECT_EQ(0, pref2_changed_); - EXPECT_EQ(0, pref1_cleared_); - EXPECT_EQ(0, pref2_cleared_); - EXPECT_EQ(0, pref1_initialized_); - EXPECT_EQ(0, pref2_initialized_); - EXPECT_EQ(1, pref1_migrated_); - EXPECT_EQ(0, pref2_migrated_); - EXPECT_EQ(0, pref1_unchanged_); - EXPECT_EQ(1, pref2_unchanged_); - } - // Make sure the migration happens only once. - { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - UpdateHistogramSamples(); - EXPECT_EQ(0, pref1_changed_); - EXPECT_EQ(0, pref2_changed_); - EXPECT_EQ(0, pref1_cleared_); - EXPECT_EQ(0, pref2_cleared_); - EXPECT_EQ(0, pref1_initialized_); - EXPECT_EQ(0, pref2_initialized_); - EXPECT_EQ(0, pref1_migrated_); - EXPECT_EQ(0, pref2_migrated_); - EXPECT_EQ(1, pref1_unchanged_); - EXPECT_EQ(1, pref2_unchanged_); - } -} - -// Make sure that the new algorithm is still able to generate old style hashes -// as they were before this change. -TEST_F(PrefMetricsServiceTest, OldStyleHashAsExpected) { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - - // Verify the hashes match the values previously used in the - // "PrefHashStability" test below. - DictionaryValue dict; - dict.Set("a", new StringValue("foo")); - dict.Set("d", new StringValue("bad")); - dict.Set("b", new StringValue("bar")); - dict.Set("c", new StringValue("baz")); - EXPECT_EQ("C503FB7C65EEFD5C07185F616A0AA67923C069909933F362022B1F187E73E9A2", - GetOldStyleHashedPrefValue(service.get(), "pref.path1", &dict)); - ListValue list; - list.Set(0, new base::FundamentalValue(true)); - list.Set(1, new base::FundamentalValue(100)); - list.Set(2, new base::FundamentalValue(1.0)); - EXPECT_EQ("3163EC3C96263143AF83EA5C9860DFB960EE2263413C7D7D8A9973FCC00E7692", - GetOldStyleHashedPrefValue(service.get(), "pref.path2", &list)); -} - -// Tests that serialization of dictionary values is stable. If the order of -// the entries or any whitespace changes, it would cause a spike in pref change -// UMA events as every hash would change. -TEST_F(PrefMetricsServiceTest, PrefHashStability) { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - - DictionaryValue dict; - dict.Set("a", new StringValue("foo")); - dict.Set("d", new StringValue("bad")); - dict.Set("b", new StringValue("bar")); - dict.Set("c", new StringValue("baz")); - EXPECT_EQ("A50FE7EB31BFBC32B8A27E71730AF15421178A9B5815644ACE174B18966735B9", - GetHashedPrefValue(service.get(), "pref.path1", &dict)); - - ListValue list; - list.Set(0, new base::FundamentalValue(true)); - list.Set(1, new base::FundamentalValue(100)); - list.Set(2, new base::FundamentalValue(1.0)); - EXPECT_EQ("5CE37D7EBCBC9BE510F0F5E7C326CA92C1673713C3717839610AEA1A217D8BB8", - GetHashedPrefValue(service.get(), "pref.path2", &list)); -} - -// Tests that different hashes are generated for different device IDs. -TEST_F(PrefMetricsServiceTest, HashIsBasedOnDeviceId) { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - scoped_ptr<PrefMetricsService> other_service = - CreatePrefMetricsService(kOtherTestDeviceId); - - StringValue test_value("test value"); - EXPECT_EQ("49CA276F9F2AEDCF6BFA1CD9FC4747476E1315BBBBC27DD33548B23CD36E2EEE", - GetHashedPrefValue(service.get(), "pref.path", &test_value)); - EXPECT_EQ("13EEDA99C38777ADA8B87C23A3C5CD1FD31ADE1491823E255D3520E5B56C4BC7", - GetHashedPrefValue(other_service.get(), "pref.path", &test_value)); -} - -// Tests that different hashes are generated for different paths. -TEST_F(PrefMetricsServiceTest, HashIsBasedOnPath) { - scoped_ptr<PrefMetricsService> service = - CreatePrefMetricsService(kTestDeviceId); - - StringValue test_value("test value"); - EXPECT_EQ("2A5DCB1294F212DB26DF9C08C46F11C272D80136AAD3B4AAE5B7D008DF5F3F22", - GetHashedPrefValue(service.get(), "pref.path1", &test_value)); - EXPECT_EQ("455EC2A7E192E9F1C06294BBB3B66BBD81B8D1A8550D518EA5D5C8F70FCF6EF3", - GetHashedPrefValue(service.get(), "pref.path2", &test_value)); -} diff --git a/chrome/browser/prefs/pref_service_browsertest.cc b/chrome/browser/prefs/pref_service_browsertest.cc index f524981..26f8820 100644 --- a/chrome/browser/prefs/pref_service_browsertest.cc +++ b/chrome/browser/prefs/pref_service_browsertest.cc @@ -65,9 +65,8 @@ class PreferenceServiceTest : public InProcessBrowserTest { base::FilePath user_data_directory; PathService::Get(chrome::DIR_USER_DATA, &user_data_directory); - base::FilePath reference_pref_file; if (new_profile_) { - reference_pref_file = ui_test_utils::GetTestFilePath( + original_pref_file_ = ui_test_utils::GetTestFilePath( base::FilePath().AppendASCII("profiles"). AppendASCII("window_placement"). AppendASCII("Default"), @@ -77,17 +76,17 @@ class PreferenceServiceTest : public InProcessBrowserTest { CHECK(base::CreateDirectory(tmp_pref_file_)); tmp_pref_file_ = tmp_pref_file_.Append(chrome::kPreferencesFilename); } else { - reference_pref_file = ui_test_utils::GetTestFilePath( + original_pref_file_ = ui_test_utils::GetTestFilePath( base::FilePath().AppendASCII("profiles"). AppendASCII("window_placement"), base::FilePath().Append(chrome::kLocalStateFilename)); tmp_pref_file_ = user_data_directory.Append(chrome::kLocalStateFilename); } - CHECK(base::PathExists(reference_pref_file)); + CHECK(base::PathExists(original_pref_file_)); // Copy only the Preferences file if |new_profile_|, or Local State if not, // and the rest will be automatically created. - CHECK(base::CopyFile(reference_pref_file, tmp_pref_file_)); + CHECK(base::CopyFile(original_pref_file_, tmp_pref_file_)); #if defined(OS_WIN) // Make the copy writable. On POSIX we assume the umask allows files @@ -99,6 +98,7 @@ class PreferenceServiceTest : public InProcessBrowserTest { } protected: + base::FilePath original_pref_file_; base::FilePath tmp_pref_file_; private: @@ -127,7 +127,7 @@ IN_PROC_BROWSER_TEST_F(PreservedWindowPlacementIsLoaded, Test) { // The window should open with the new reference profile, with window // placement values stored in the user data directory. - JSONFileValueSerializer deserializer(tmp_pref_file_); + JSONFileValueSerializer deserializer(original_pref_file_); scoped_ptr<base::Value> root(deserializer.Deserialize(NULL, NULL)); ASSERT_TRUE(root.get()); @@ -188,7 +188,7 @@ IN_PROC_BROWSER_TEST_F(PreservedWindowPlacementIsMigrated, Test) { // The window should open with the old reference profile, with window // placement values stored in Local State. - JSONFileValueSerializer deserializer(tmp_pref_file_); + JSONFileValueSerializer deserializer(original_pref_file_); scoped_ptr<base::Value> root(deserializer.Deserialize(NULL, NULL)); ASSERT_TRUE(root.get()); diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc index d8c3ce3..41dea64 100644 --- a/chrome/browser/profiles/profile_impl.cc +++ b/chrome/browser/profiles/profile_impl.cc @@ -60,6 +60,7 @@ #include "chrome/browser/policy/profile_policy_connector_factory.h" #include "chrome/browser/prefs/browser_prefs.h" #include "chrome/browser/prefs/chrome_pref_service_factory.h" +#include "chrome/browser/prefs/pref_hash_store_impl.h" #include "chrome/browser/prefs/pref_service_syncable.h" #include "chrome/browser/prerender/prerender_manager_factory.h" #include "chrome/browser/profiles/bookmark_model_loaded_observer.h" @@ -93,6 +94,7 @@ #include "content/public/browser/storage_partition.h" #include "content/public/browser/user_metrics.h" #include "content/public/common/content_constants.h" +#include "grit/browser_resources.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.h" @@ -118,6 +120,9 @@ #if defined(OS_WIN) #include "chrome/browser/profiles/file_path_verifier_win.h" #include "chrome/installer/util/install_util.h" +#if defined(ENABLE_RLZ) +#include "rlz/lib/machine_id.h" +#endif #endif #if defined(OS_CHROMEOS) @@ -257,6 +262,33 @@ void SchedulePrefsFileVerification(const base::FilePath& prefs_file) { #endif } +scoped_ptr<PrefHashStore> GetPrefHashStore(Profile* profile) { + // TODO(erikwright): Enable this on Android when race condition is sorted out. +#if defined(OS_ANDROID) + return scoped_ptr<PrefHashStore>(); +#else + std::string seed = ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_PREF_HASH_SEED_BIN).as_string(); + std::string device_id; + +#if defined(OS_WIN) && defined(ENABLE_RLZ) + // This is used by + // chrome/browser/extensions/api/music_manager_private/device_id_win.cc + // but that API is private (http://crbug.com/276485) and other platforms are + // not available synchronously. + // As part of improving pref metrics on other platforms we may want to find + // ways to defer preference loading until the device ID can be used. + rlz_lib::GetMachineId(&device_id); +#endif + + return scoped_ptr<PrefHashStore>(new PrefHashStoreImpl( + profile->GetPath().AsUTF8Unsafe(), + seed, + device_id, + g_browser_process->local_state())); +#endif +} + } // namespace // static @@ -460,6 +492,7 @@ ProfileImpl::ProfileImpl( sequenced_task_runner, profile_policy_connector_->policy_service(), managed_user_settings, + GetPrefHashStore(this), new ExtensionPrefStore( ExtensionPrefValueMapFactory::GetForBrowserContext(this), false), pref_registry_, diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 0f0ac46..ae609f1 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1552,6 +1552,13 @@ 'browser/prefs/command_line_pref_store.h', 'browser/prefs/incognito_mode_prefs.cc', 'browser/prefs/incognito_mode_prefs.h', + 'browser/prefs/pref_hash_calculator.cc', + 'browser/prefs/pref_hash_calculator.h', + 'browser/prefs/pref_hash_filter.cc', + 'browser/prefs/pref_hash_filter.h', + 'browser/prefs/pref_hash_store.h', + 'browser/prefs/pref_hash_store_impl.cc', + 'browser/prefs/pref_hash_store_impl.h', 'browser/prefs/pref_model_associator.cc', 'browser/prefs/pref_model_associator.h', 'browser/prefs/pref_metrics_service.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 7f3393b..a182db3 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1121,7 +1121,9 @@ 'browser/prefs/chrome_pref_service_unittest.cc', 'browser/prefs/command_line_pref_store_unittest.cc', 'browser/prefs/incognito_mode_prefs_unittest.cc', - 'browser/prefs/pref_metrics_service_unittest.cc', + 'browser/prefs/pref_hash_calculator_unittest.cc', + 'browser/prefs/pref_hash_filter_unittest.cc', + 'browser/prefs/pref_hash_store_impl_unittest.cc', 'browser/prefs/pref_model_associator_unittest.cc', 'browser/prefs/proxy_config_dictionary_unittest.cc', 'browser/prefs/proxy_policy_unittest.cc', diff --git a/chrome/service/service_process_prefs.cc b/chrome/service/service_process_prefs.cc index c628a0b..d923185 100644 --- a/chrome/service/service_process_prefs.cc +++ b/chrome/service/service_process_prefs.cc @@ -5,12 +5,15 @@ #include "chrome/service/service_process_prefs.h" #include "base/message_loop/message_loop_proxy.h" +#include "base/prefs/pref_filter.h" #include "base/values.h" ServiceProcessPrefs::ServiceProcessPrefs( const base::FilePath& pref_filename, base::SequencedTaskRunner* task_runner) - : prefs_(new JsonPrefStore(pref_filename, task_runner)) { + : prefs_(new JsonPrefStore(pref_filename, + task_runner, + scoped_ptr<PrefFilter>())) { } ServiceProcessPrefs::~ServiceProcessPrefs() {} |