1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/prefs/profile_pref_store_manager.h"
9 #include "base/compiler_specific.h"
10 #include "base/files/file_enumerator.h"
11 #include "base/files/file_util.h"
12 #include "base/files/scoped_temp_dir.h"
13 #include "base/memory/ref_counted.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/prefs/json_pref_store.h"
16 #include "base/prefs/persistent_pref_store.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/prefs/pref_service_factory.h"
19 #include "base/prefs/pref_store.h"
20 #include "base/prefs/testing_pref_service.h"
21 #include "base/run_loop.h"
22 #include "base/strings/string_util.h"
23 #include "base/values.h"
24 #include "components/pref_registry/pref_registry_syncable.h"
25 #include "components/user_prefs/tracked/mock_validation_delegate.h"
26 #include "components/user_prefs/tracked/pref_hash_filter.h"
27 #include "components/user_prefs/tracked/pref_names.h"
28 #include "components/user_prefs/tracked/pref_service_hash_store_contents.h"
29 #include "testing/gtest/include/gtest/gtest.h"
33 class FirstEqualsPredicate
{
35 explicit FirstEqualsPredicate(const std::string
& expected
)
36 : expected_(expected
) {}
37 bool operator()(const std::pair
<std::string
, base::Value
*>& pair
) {
38 return pair
.first
== expected_
;
42 const std::string expected_
;
45 // Observes changes to the PrefStore and verifies that only registered prefs are
47 class RegistryVerifier
: public PrefStore::Observer
{
49 explicit RegistryVerifier(PrefRegistry
* pref_registry
)
50 : pref_registry_(pref_registry
) {}
52 // PrefStore::Observer implementation
53 void OnPrefValueChanged(const std::string
& key
) override
{
54 EXPECT_TRUE(pref_registry_
->end() !=
55 std::find_if(pref_registry_
->begin(),
56 pref_registry_
->end(),
57 FirstEqualsPredicate(key
)))
58 << "Unregistered key " << key
<< " was changed.";
61 void OnInitializationCompleted(bool succeeded
) override
{}
64 scoped_refptr
<PrefRegistry
> pref_registry_
;
67 const char kUnprotectedPref
[] = "unprotected_pref";
68 const char kTrackedAtomic
[] = "tracked_atomic";
69 const char kProtectedAtomic
[] = "protected_atomic";
71 const char kFoobar
[] = "FOOBAR";
72 const char kBarfoo
[] = "BARFOO";
73 const char kHelloWorld
[] = "HELLOWORLD";
74 const char kGoodbyeWorld
[] = "GOODBYEWORLD";
76 const PrefHashFilter::TrackedPreferenceMetadata kConfiguration
[] = {
77 {0u, kTrackedAtomic
, PrefHashFilter::NO_ENFORCEMENT
,
78 PrefHashFilter::TRACKING_STRATEGY_ATOMIC
},
79 {1u, kProtectedAtomic
, PrefHashFilter::ENFORCE_ON_LOAD
,
80 PrefHashFilter::TRACKING_STRATEGY_ATOMIC
}};
82 const size_t kExtraReportingId
= 2u;
83 const size_t kReportingIdCount
= 3u;
87 class ProfilePrefStoreManagerTest
: public testing::Test
{
89 ProfilePrefStoreManagerTest()
90 : configuration_(kConfiguration
,
91 kConfiguration
+ arraysize(kConfiguration
)),
92 profile_pref_registry_(new user_prefs::PrefRegistrySyncable
),
93 registry_verifier_(profile_pref_registry_
.get()),
95 reset_recorded_(false) {}
97 void SetUp() override
{
98 ProfilePrefStoreManager::RegisterPrefs(local_state_
.registry());
99 ProfilePrefStoreManager::RegisterProfilePrefs(profile_pref_registry_
.get());
100 for (const PrefHashFilter::TrackedPreferenceMetadata
* it
= kConfiguration
;
101 it
!= kConfiguration
+ arraysize(kConfiguration
);
103 if (it
->strategy
== PrefHashFilter::TRACKING_STRATEGY_ATOMIC
) {
104 profile_pref_registry_
->RegisterStringPref(it
->name
, std::string());
106 profile_pref_registry_
->RegisterDictionaryPref(it
->name
);
109 profile_pref_registry_
->RegisterStringPref(kUnprotectedPref
, std::string());
111 // As in chrome_pref_service_factory.cc, kPreferencesResetTime needs to be
112 // declared as protected in order to be read from the proper store by the
113 // SegregatedPrefStore. Only declare it after configured prefs have been
114 // registered above for this test as kPreferenceResetTime is already
115 // registered in ProfilePrefStoreManager::RegisterProfilePrefs.
116 PrefHashFilter::TrackedPreferenceMetadata pref_reset_time_config
=
117 {configuration_
.rbegin()->reporting_id
+ 1,
118 user_prefs::kPreferenceResetTime
,
119 PrefHashFilter::ENFORCE_ON_LOAD
,
120 PrefHashFilter::TRACKING_STRATEGY_ATOMIC
};
121 configuration_
.push_back(pref_reset_time_config
);
123 ASSERT_TRUE(profile_dir_
.CreateUniqueTempDir());
124 ReloadConfiguration();
127 void ReloadConfiguration() {
128 manager_
.reset(new ProfilePrefStoreManager(profile_dir_
.path(),
136 void TearDown() override
{ DestroyPrefStore(); }
139 // Verifies whether a reset was reported via the RecordReset() hook. Also
140 // verifies that GetResetTime() was set (or not) accordingly.
141 void VerifyResetRecorded(bool reset_expected
) {
142 EXPECT_EQ(reset_expected
, reset_recorded_
);
144 base::PrefServiceFactory pref_service_factory
;
145 pref_service_factory
.set_user_prefs(pref_store_
);
147 scoped_ptr
<PrefService
> pref_service(
148 pref_service_factory
.Create(profile_pref_registry_
.get()));
152 !ProfilePrefStoreManager::GetResetTime(pref_service
.get()).is_null());
155 void ClearResetRecorded() {
156 reset_recorded_
= false;
158 base::PrefServiceFactory pref_service_factory
;
159 pref_service_factory
.set_user_prefs(pref_store_
);
161 scoped_ptr
<PrefService
> pref_service(
162 pref_service_factory
.Create(profile_pref_registry_
.get()));
164 ProfilePrefStoreManager::ClearResetTime(pref_service
.get());
167 void InitializePrefs() {
168 // According to the implementation of ProfilePrefStoreManager, this is
169 // actually a SegregatedPrefStore backed by two underlying pref stores.
170 scoped_refptr
<PersistentPrefStore
> pref_store
=
171 manager_
->CreateProfilePrefStore(
172 main_message_loop_
.task_runner(),
173 base::Bind(&ProfilePrefStoreManagerTest::RecordReset
,
174 base::Unretained(this)),
175 &mock_validation_delegate_
);
176 InitializePrefStore(pref_store
.get());
178 base::RunLoop().RunUntilIdle();
181 void DestroyPrefStore() {
182 if (pref_store_
.get()) {
183 ClearResetRecorded();
184 // Force everything to be written to disk, triggering the PrefHashFilter
185 // while our RegistryVerifier is watching.
186 pref_store_
->CommitPendingWrite();
187 base::RunLoop().RunUntilIdle();
189 pref_store_
->RemoveObserver(®istry_verifier_
);
191 // Nothing should have to happen on the background threads, but just in
193 base::RunLoop().RunUntilIdle();
197 void InitializeDeprecatedCombinedProfilePrefStore() {
198 scoped_refptr
<PersistentPrefStore
> pref_store
=
199 manager_
->CreateDeprecatedCombinedProfilePrefStore(
200 main_message_loop_
.task_runner());
201 InitializePrefStore(pref_store
.get());
203 base::RunLoop().RunUntilIdle();
206 void InitializePrefStore(PersistentPrefStore
* pref_store
) {
207 pref_store
->AddObserver(®istry_verifier_
);
208 PersistentPrefStore::PrefReadError error
= pref_store
->ReadPrefs();
209 EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE
, error
);
210 pref_store
->SetValue(kTrackedAtomic
,
211 make_scoped_ptr(new base::StringValue(kFoobar
)),
212 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS
);
213 pref_store
->SetValue(kProtectedAtomic
,
214 make_scoped_ptr(new base::StringValue(kHelloWorld
)),
215 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS
);
216 pref_store
->SetValue(kUnprotectedPref
,
217 make_scoped_ptr(new base::StringValue(kFoobar
)),
218 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS
);
219 pref_store
->RemoveObserver(®istry_verifier_
);
220 pref_store
->CommitPendingWrite();
221 base::RunLoop().RunUntilIdle();
224 void LoadExistingPrefs() {
226 pref_store_
= manager_
->CreateProfilePrefStore(
227 main_message_loop_
.task_runner(),
228 base::Bind(&ProfilePrefStoreManagerTest::RecordReset
,
229 base::Unretained(this)),
231 pref_store_
->AddObserver(®istry_verifier_
);
232 pref_store_
->ReadPrefs();
235 void ReplaceStringInPrefs(const std::string
& find
,
236 const std::string
& replace
) {
237 base::FileEnumerator
file_enum(
238 profile_dir_
.path(), true, base::FileEnumerator::FILES
);
240 for (base::FilePath path
= file_enum
.Next(); !path
.empty();
241 path
= file_enum
.Next()) {
242 // Tamper with the file's contents
243 std::string contents
;
244 EXPECT_TRUE(base::ReadFileToString(path
, &contents
));
245 base::ReplaceSubstringsAfterOffset(&contents
, 0u, find
, replace
);
246 EXPECT_EQ(static_cast<int>(contents
.length()),
247 base::WriteFile(path
, contents
.c_str(), contents
.length()));
251 void ExpectStringValueEquals(const std::string
& name
,
252 const std::string
& expected
) {
253 const base::Value
* value
= NULL
;
254 std::string as_string
;
255 if (!pref_store_
->GetValue(name
, &value
)) {
256 ADD_FAILURE() << name
<< " is not a defined value.";
257 } else if (!value
->GetAsString(&as_string
)) {
258 ADD_FAILURE() << name
<< " could not be coerced to a string.";
260 EXPECT_EQ(expected
, as_string
);
264 void ExpectValidationObserved(const std::string
& pref_path
) {
265 // No validations are expected for platforms that do not support tracking.
266 if (!ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
)
268 if (!mock_validation_delegate_
.GetEventForPath(pref_path
))
269 ADD_FAILURE() << "No validation observed for preference: " << pref_path
;
272 base::MessageLoop main_message_loop_
;
273 std::vector
<PrefHashFilter::TrackedPreferenceMetadata
> configuration_
;
274 base::ScopedTempDir profile_dir_
;
275 TestingPrefServiceSimple local_state_
;
276 scoped_refptr
<user_prefs::PrefRegistrySyncable
> profile_pref_registry_
;
277 RegistryVerifier registry_verifier_
;
278 MockValidationDelegate mock_validation_delegate_
;
279 scoped_ptr
<ProfilePrefStoreManager
> manager_
;
280 scoped_refptr
<PersistentPrefStore
> pref_store_
;
286 // As-is |reset_recorded_| is only designed to remember a single reset, make
287 // sure none was previously recorded (or that ClearResetRecorded() was
289 EXPECT_FALSE(reset_recorded_
);
290 reset_recorded_
= true;
293 bool reset_recorded_
;
296 TEST_F(ProfilePrefStoreManagerTest
, StoreValues
) {
301 ExpectStringValueEquals(kTrackedAtomic
, kFoobar
);
302 ExpectStringValueEquals(kProtectedAtomic
, kHelloWorld
);
303 VerifyResetRecorded(false);
304 ExpectValidationObserved(kTrackedAtomic
);
305 ExpectValidationObserved(kProtectedAtomic
);
308 TEST_F(ProfilePrefStoreManagerTest
, GetPrefFilePathFromProfilePath
) {
309 base::FilePath pref_file_path
=
310 ProfilePrefStoreManager::GetPrefFilePathFromProfilePath(
311 profile_dir_
.path());
313 EXPECT_FALSE(base::PathExists(pref_file_path
));
317 EXPECT_TRUE(base::PathExists(pref_file_path
));
320 TEST_F(ProfilePrefStoreManagerTest
, ProtectValues
) {
323 ReplaceStringInPrefs(kFoobar
, kBarfoo
);
324 ReplaceStringInPrefs(kHelloWorld
, kGoodbyeWorld
);
328 // kTrackedAtomic is unprotected and thus will be loaded as it appears on
330 ExpectStringValueEquals(kTrackedAtomic
, kBarfoo
);
332 // If preference tracking is supported, the tampered value of kProtectedAtomic
333 // will be discarded at load time, leaving this preference undefined.
334 EXPECT_NE(ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
,
335 pref_store_
->GetValue(kProtectedAtomic
, NULL
));
337 ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
);
339 ExpectValidationObserved(kTrackedAtomic
);
340 ExpectValidationObserved(kProtectedAtomic
);
343 TEST_F(ProfilePrefStoreManagerTest
, MigrateFromOneFile
) {
344 InitializeDeprecatedCombinedProfilePrefStore();
346 // The deprecated model stores hashes in local state (on supported
349 ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
,
350 local_state_
.GetUserPrefValue(
351 PrefServiceHashStoreContents::kProfilePreferenceHashes
) != NULL
);
355 // After a first migration, the hashes were copied to the two user preference
356 // files but were not cleaned.
358 ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
,
359 local_state_
.GetUserPrefValue(
360 PrefServiceHashStoreContents::kProfilePreferenceHashes
) != NULL
);
362 ExpectStringValueEquals(kTrackedAtomic
, kFoobar
);
363 ExpectStringValueEquals(kProtectedAtomic
, kHelloWorld
);
364 VerifyResetRecorded(false);
368 // In a subsequent launch, the local state hash store should be reset.
369 ASSERT_FALSE(local_state_
.GetUserPrefValue(
370 PrefServiceHashStoreContents::kProfilePreferenceHashes
));
372 ExpectStringValueEquals(kTrackedAtomic
, kFoobar
);
373 ExpectStringValueEquals(kProtectedAtomic
, kHelloWorld
);
374 VerifyResetRecorded(false);
377 TEST_F(ProfilePrefStoreManagerTest
, MigrateWithTampering
) {
378 InitializeDeprecatedCombinedProfilePrefStore();
380 ReplaceStringInPrefs(kFoobar
, kBarfoo
);
381 ReplaceStringInPrefs(kHelloWorld
, kGoodbyeWorld
);
383 // The deprecated model stores hashes in local state (on supported
386 ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
,
387 local_state_
.GetUserPrefValue(
388 PrefServiceHashStoreContents::kProfilePreferenceHashes
) != NULL
);
392 // After a first migration, the hashes were copied to the two user preference
393 // files but were not cleaned.
395 ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
,
396 local_state_
.GetUserPrefValue(
397 PrefServiceHashStoreContents::kProfilePreferenceHashes
) != NULL
);
399 // kTrackedAtomic is unprotected and thus will be loaded as it appears on
401 ExpectStringValueEquals(kTrackedAtomic
, kBarfoo
);
403 // If preference tracking is supported, the tampered value of kProtectedAtomic
404 // will be discarded at load time, leaving this preference undefined.
405 EXPECT_NE(ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
,
406 pref_store_
->GetValue(kProtectedAtomic
, NULL
));
408 ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
);
412 // In a subsequent launch, the local state hash store would be reset.
413 ASSERT_FALSE(local_state_
.GetUserPrefValue(
414 PrefServiceHashStoreContents::kProfilePreferenceHashes
));
416 ExpectStringValueEquals(kTrackedAtomic
, kBarfoo
);
417 VerifyResetRecorded(false);
420 TEST_F(ProfilePrefStoreManagerTest
, InitializePrefsFromMasterPrefs
) {
421 base::DictionaryValue master_prefs
;
422 master_prefs
.Set(kTrackedAtomic
, new base::StringValue(kFoobar
));
423 master_prefs
.Set(kProtectedAtomic
, new base::StringValue(kHelloWorld
));
424 EXPECT_TRUE(manager_
->InitializePrefsFromMasterPrefs(master_prefs
));
428 // Verify that InitializePrefsFromMasterPrefs correctly applied the MACs
429 // necessary to authenticate these values.
430 ExpectStringValueEquals(kTrackedAtomic
, kFoobar
);
431 ExpectStringValueEquals(kProtectedAtomic
, kHelloWorld
);
432 VerifyResetRecorded(false);
435 TEST_F(ProfilePrefStoreManagerTest
, UnprotectedToProtected
) {
438 ExpectValidationObserved(kTrackedAtomic
);
439 ExpectValidationObserved(kProtectedAtomic
);
442 ExpectStringValueEquals(kUnprotectedPref
, kFoobar
);
444 // Ensure everything is written out to disk.
447 ReplaceStringInPrefs(kFoobar
, kBarfoo
);
449 // It's unprotected, so we can load the modified value.
451 ExpectStringValueEquals(kUnprotectedPref
, kBarfoo
);
453 // Now update the configuration to protect it.
454 PrefHashFilter::TrackedPreferenceMetadata new_protected
= {
455 kExtraReportingId
, kUnprotectedPref
, PrefHashFilter::ENFORCE_ON_LOAD
,
456 PrefHashFilter::TRACKING_STRATEGY_ATOMIC
};
457 configuration_
.push_back(new_protected
);
458 ReloadConfiguration();
460 // And try loading with the new configuration.
463 // Since there was a valid super MAC we were able to extend the existing trust
464 // to the newly protected preference.
465 ExpectStringValueEquals(kUnprotectedPref
, kBarfoo
);
466 VerifyResetRecorded(false);
468 // Ensure everything is written out to disk.
471 // It's protected now, so (if the platform supports it) any tampering should
473 ReplaceStringInPrefs(kBarfoo
, kFoobar
);
475 EXPECT_NE(ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
,
476 pref_store_
->GetValue(kUnprotectedPref
, NULL
));
478 ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
);
481 TEST_F(ProfilePrefStoreManagerTest
, NewPrefWhenFirstProtecting
) {
482 std::vector
<PrefHashFilter::TrackedPreferenceMetadata
>
483 original_configuration
= configuration_
;
484 for (std::vector
<PrefHashFilter::TrackedPreferenceMetadata
>::iterator it
=
485 configuration_
.begin();
486 it
!= configuration_
.end();
488 it
->enforcement_level
= PrefHashFilter::NO_ENFORCEMENT
;
490 ReloadConfiguration();
494 ExpectValidationObserved(kTrackedAtomic
);
495 ExpectValidationObserved(kProtectedAtomic
);
498 ExpectStringValueEquals(kUnprotectedPref
, kFoobar
);
500 // Ensure everything is written out to disk.
503 // Now introduce protection, including the never-before tracked "new_pref".
504 configuration_
= original_configuration
;
505 PrefHashFilter::TrackedPreferenceMetadata new_protected
= {
506 kExtraReportingId
, kUnprotectedPref
, PrefHashFilter::ENFORCE_ON_LOAD
,
507 PrefHashFilter::TRACKING_STRATEGY_ATOMIC
};
508 configuration_
.push_back(new_protected
);
509 ReloadConfiguration();
511 // And try loading with the new configuration.
514 // Since there was a valid super MAC we were able to extend the existing trust
515 // to the newly tracked & protected preference.
516 ExpectStringValueEquals(kUnprotectedPref
, kFoobar
);
517 VerifyResetRecorded(false);
520 TEST_F(ProfilePrefStoreManagerTest
, UnprotectedToProtectedWithoutTrust
) {
523 ExpectValidationObserved(kTrackedAtomic
);
524 ExpectValidationObserved(kProtectedAtomic
);
526 // Now update the configuration to protect it.
527 PrefHashFilter::TrackedPreferenceMetadata new_protected
= {
528 kExtraReportingId
, kUnprotectedPref
, PrefHashFilter::ENFORCE_ON_LOAD
,
529 PrefHashFilter::TRACKING_STRATEGY_ATOMIC
};
530 configuration_
.push_back(new_protected
);
531 seed_
= "new-seed-to-break-trust";
532 ReloadConfiguration();
534 // And try loading with the new configuration.
537 // If preference tracking is supported, kUnprotectedPref will have been
538 // discarded because new values are not accepted without a valid super MAC.
539 EXPECT_NE(ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
,
540 pref_store_
->GetValue(kUnprotectedPref
, NULL
));
542 ProfilePrefStoreManager::kPlatformSupportsPreferenceTracking
);
545 // This test verifies that preference values are correctly maintained when a
546 // preference's protection state changes from protected to unprotected.
547 TEST_F(ProfilePrefStoreManagerTest
, ProtectedToUnprotected
) {
550 ExpectValidationObserved(kTrackedAtomic
);
551 ExpectValidationObserved(kProtectedAtomic
);
555 // Unconfigure protection for kProtectedAtomic
556 for (std::vector
<PrefHashFilter::TrackedPreferenceMetadata
>::iterator it
=
557 configuration_
.begin();
558 it
!= configuration_
.end();
560 if (it
->name
== kProtectedAtomic
) {
561 it
->enforcement_level
= PrefHashFilter::NO_ENFORCEMENT
;
566 seed_
= "new-seed-to-break-trust";
567 ReloadConfiguration();
570 // Verify that the value was not reset.
571 ExpectStringValueEquals(kProtectedAtomic
, kHelloWorld
);
572 VerifyResetRecorded(false);
574 // Accessing the value of the previously protected pref didn't trigger its
575 // move to the unprotected preferences file, though the loading of the pref
576 // store should still have caused the MAC store to be recalculated.
578 ExpectStringValueEquals(kProtectedAtomic
, kHelloWorld
);
580 // Trigger the logic that migrates it back to the unprotected preferences
582 pref_store_
->SetValue(kProtectedAtomic
,
583 make_scoped_ptr(new base::StringValue(kGoodbyeWorld
)),
584 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS
);
586 ExpectStringValueEquals(kProtectedAtomic
, kGoodbyeWorld
);
587 VerifyResetRecorded(false);