diff options
Diffstat (limited to 'chromium/components/unified_consent')
19 files changed, 2280 insertions, 0 deletions
diff --git a/chromium/components/unified_consent/BUILD.gn b/chromium/components/unified_consent/BUILD.gn new file mode 100644 index 00000000000..419332e8d31 --- /dev/null +++ b/chromium/components/unified_consent/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright 2018 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +static_library("unified_consent") { + sources = [ + "feature.cc", + "feature.h", + "pref_names.cc", + "pref_names.h", + "unified_consent_service.cc", + "unified_consent_service.h", + "unified_consent_service_client.cc", + "unified_consent_service_client.h", + "url_keyed_data_collection_consent_helper.cc", + "url_keyed_data_collection_consent_helper.h", + ] + deps = [ + "//base", + "//components/autofill/core/common", + "//components/browser_sync", + "//components/pref_registry", + "//components/signin/core/browser", + "//components/sync", + "//services/identity/public/cpp", + ] +} + +static_library("test_support") { + testonly = true + sources = [ + "scoped_unified_consent.cc", + "scoped_unified_consent.h", + ] + + deps = [ + "//base/test:test_support", + ] + + public_deps = [ + ":unified_consent", + "//base", + "//components/sync", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ + "feature_unittest.cc", + "unified_consent_service_unittest.cc", + "url_keyed_data_collection_consent_helper_unittest.cc", + ] + deps = [ + ":test_support", + ":unified_consent", + "//base/test:test_support", + "//components/autofill/core/common", + "//components/sync", + "//components/sync:test_support_driver", + "//components/sync_preferences:test_support", + "//services/identity/public/cpp:test_support", + "//testing/gtest", + ] +} diff --git a/chromium/components/unified_consent/DEPS b/chromium/components/unified_consent/DEPS new file mode 100644 index 00000000000..38bd56db868 --- /dev/null +++ b/chromium/components/unified_consent/DEPS @@ -0,0 +1,9 @@ +include_rules = [ + "+components/autofill/core/common", + "+components/keyed_service/core", + "+components/pref_registry", + "+components/prefs", + "+components/sync", + "+components/sync_preferences", + "+services/identity/public/cpp", +] diff --git a/chromium/components/unified_consent/OWNERS b/chromium/components/unified_consent/OWNERS new file mode 100644 index 00000000000..8a23481825c --- /dev/null +++ b/chromium/components/unified_consent/OWNERS @@ -0,0 +1,5 @@ +msarda@chromium.org +tangltom@chromium.org + +# TEAM: chrome-signin@chromium.org +# COMPONENT: Services>SignIn diff --git a/chromium/components/unified_consent/README.md b/chromium/components/unified_consent/README.md new file mode 100644 index 00000000000..e37be17082a --- /dev/null +++ b/chromium/components/unified_consent/README.md @@ -0,0 +1,8 @@ +The Unified Consent component contains the browser keyed service that +manages user consent when the Unified Consent feature is enabled. It also +holds the prefs and the APIs allowing the various Chromium features to verify if +the user has given consent for a given feature. + +This component is currently in development. + +The component is used on all platforms (desktop, ChromeOS, Android and iOS). diff --git a/chromium/components/unified_consent/feature.cc b/chromium/components/unified_consent/feature.cc new file mode 100644 index 00000000000..b9c1feb616c --- /dev/null +++ b/chromium/components/unified_consent/feature.cc @@ -0,0 +1,38 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/unified_consent/feature.h" + +#include "base/feature_list.h" +#include "base/metrics/field_trial_params.h" +#include "components/sync/driver/sync_driver_switches.h" + +namespace unified_consent { + +// base::Feature definitions. +const base::Feature kUnifiedConsent{"UnifiedConsent", + base::FEATURE_DISABLED_BY_DEFAULT}; +const char kUnifiedConsentShowBumpParameter[] = "show_consent_bump"; + +namespace internal { +UnifiedConsentFeatureState GetUnifiedConsentFeatureState() { + // Unified consent requires user consent to be recorded via its own + // sync model type.The reason is that when unified consent is enabled, + // |USER_EVENTS| sync model type is configurable and the user may disable it. + // Chromium needs to continue to record user consent even if the user + // manually disables |USER_EVENTS|. + if (!base::FeatureList::IsEnabled(switches::kSyncUserConsentSeparateType)) + return UnifiedConsentFeatureState::kDisabled; + + if (!base::FeatureList::IsEnabled(kUnifiedConsent)) + return UnifiedConsentFeatureState::kDisabled; + + std::string show_bump = base::GetFieldTrialParamValueByFeature( + kUnifiedConsent, kUnifiedConsentShowBumpParameter); + return show_bump.empty() ? UnifiedConsentFeatureState::kEnabledNoBump + : UnifiedConsentFeatureState::kEnabledWithBump; +} +} // namespace internal + +} // namespace unified_consent diff --git a/chromium/components/unified_consent/feature.h b/chromium/components/unified_consent/feature.h new file mode 100644 index 00000000000..e602df3665c --- /dev/null +++ b/chromium/components/unified_consent/feature.h @@ -0,0 +1,38 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNIFIED_CONSENT_FEATURE_H_ +#define COMPONENTS_UNIFIED_CONSENT_FEATURE_H_ + +#include "base/feature_list.h" + +namespace unified_consent { + +// State of the "Unified Consent" feature. +enum class UnifiedConsentFeatureState { + // Unified consent is disabled. + kDisabled, + // Unified consent is enabled, but the bump is not shown. + kEnabledNoBump, + // Unified consent is enabled and the bump is shown. + kEnabledWithBump +}; + +// Improved and unified consent for privacy-related features. +extern const base::Feature kUnifiedConsent; +extern const char kUnifiedConsentShowBumpParameter[]; + +namespace internal { +// Returns the state of the "Unified Consent" feature. +// +// WARNING: Do not call this method directly to check whether unfied consent +// is enabled. Please use the per-platfome functions defined in +// * chrome/browser/signin/unified_consent_helper.h +// * ios/chrome/browser/unified_consent/feature.h +unified_consent::UnifiedConsentFeatureState GetUnifiedConsentFeatureState(); +} // namespace internal + +} // namespace unified_consent + +#endif // COMPONENTS_UNIFIED_CONSENT_FEATURE_H_ diff --git a/chromium/components/unified_consent/feature_unittest.cc b/chromium/components/unified_consent/feature_unittest.cc new file mode 100644 index 00000000000..e66297f99c5 --- /dev/null +++ b/chromium/components/unified_consent/feature_unittest.cc @@ -0,0 +1,53 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/unified_consent/feature.h" + +#include <memory> + +#include "components/sync/driver/sync_driver_switches.h" +#include "components/unified_consent/scoped_unified_consent.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace unified_consent { + +TEST(UnifiedConsentFeatureTest, UnifiedConsent) { + // Unified consent is disabled by default. + EXPECT_EQ(UnifiedConsentFeatureState::kDisabled, + internal::GetUnifiedConsentFeatureState()); + + for (UnifiedConsentFeatureState state : + {UnifiedConsentFeatureState::kDisabled, + UnifiedConsentFeatureState::kEnabledNoBump, + UnifiedConsentFeatureState::kEnabledWithBump}) { + ScopedUnifiedConsent scoped_state(state); + EXPECT_EQ(state, internal::GetUnifiedConsentFeatureState()); + } +} + +TEST(UnifiedConsentFeatureTest, SyncUserConsentSeparateTypeDisabled) { + // Enable kSyncUserConsentSeparateType + base::test::ScopedFeatureList scoped_sync_user_consent_separate_type_feature; + scoped_sync_user_consent_separate_type_feature.InitAndDisableFeature( + switches::kSyncUserConsentSeparateType); + + { + base::test::ScopedFeatureList unified_consent_feature_list_; + unified_consent_feature_list_.InitAndEnableFeature(kUnifiedConsent); + EXPECT_EQ(UnifiedConsentFeatureState::kDisabled, + internal::GetUnifiedConsentFeatureState()); + } + + { + std::map<std::string, std::string> feature_params; + feature_params[kUnifiedConsentShowBumpParameter] = "true"; + base::test::ScopedFeatureList unified_consent_feature_list_; + unified_consent_feature_list_.InitAndEnableFeatureWithParameters( + kUnifiedConsent, feature_params); + EXPECT_EQ(UnifiedConsentFeatureState::kDisabled, + internal::GetUnifiedConsentFeatureState()); + } +} + +} // namespace unified_consent diff --git a/chromium/components/unified_consent/pref_names.cc b/chromium/components/unified_consent/pref_names.cc new file mode 100644 index 00000000000..19cdb2bc1fc --- /dev/null +++ b/chromium/components/unified_consent/pref_names.cc @@ -0,0 +1,26 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/unified_consent/pref_names.h" + +namespace unified_consent { +namespace prefs { + +// Boolean indicating whether all criteria is met for the consent bump to be +// shown. +const char kShouldShowUnifiedConsentBump[] = + "unified_consent.consent_bump.should_show"; + +// Boolean that is true when the user opted into unified consent. +const char kUnifiedConsentGiven[] = "unified_consent_given"; + +// Integer indicating the migration state of unified consent, defined in +// unified_consent::MigrationState. +const char kUnifiedConsentMigrationState[] = "unified_consent.migration_state"; + +const char kUrlKeyedAnonymizedDataCollectionEnabled[] = + "url_keyed_anonymized_data_collection.enabled"; + +} // namespace prefs +} // namespace unified_consent diff --git a/chromium/components/unified_consent/pref_names.h b/chromium/components/unified_consent/pref_names.h new file mode 100644 index 00000000000..91ebf362a3a --- /dev/null +++ b/chromium/components/unified_consent/pref_names.h @@ -0,0 +1,19 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNIFIED_CONSENT_PREF_NAMES_H_ +#define COMPONENTS_UNIFIED_CONSENT_PREF_NAMES_H_ + +namespace unified_consent { +namespace prefs { + +extern const char kShouldShowUnifiedConsentBump[]; +extern const char kUnifiedConsentGiven[]; +extern const char kUnifiedConsentMigrationState[]; +extern const char kUrlKeyedAnonymizedDataCollectionEnabled[]; + +} // namespace prefs +} // namespace unified_consent + +#endif // COMPONENTS_UNIFIED_CONSENT_PREF_NAMES_H_ diff --git a/chromium/components/unified_consent/scoped_unified_consent.cc b/chromium/components/unified_consent/scoped_unified_consent.cc new file mode 100644 index 00000000000..dc27b9f0e21 --- /dev/null +++ b/chromium/components/unified_consent/scoped_unified_consent.cc @@ -0,0 +1,43 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/unified_consent/scoped_unified_consent.h" + +#include <map> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/feature_list.h" +#include "base/logging.h" +#include "base/test/scoped_feature_list.h" +#include "components/sync/driver/sync_driver_switches.h" + +namespace unified_consent { + +ScopedUnifiedConsent::ScopedUnifiedConsent(UnifiedConsentFeatureState state) { + sync_user_consent_separate_type_feature_list_.InitAndEnableFeature( + switches::kSyncUserConsentSeparateType); + switch (state) { + case UnifiedConsentFeatureState::kDisabled: + unified_consent_feature_list_.InitAndDisableFeature(kUnifiedConsent); + break; + case UnifiedConsentFeatureState::kEnabledNoBump: + unified_consent_feature_list_.InitAndEnableFeature(kUnifiedConsent); + break; + case UnifiedConsentFeatureState::kEnabledWithBump: { + std::map<std::string, std::string> feature_params; + feature_params[kUnifiedConsentShowBumpParameter] = "true"; + unified_consent_feature_list_.InitAndEnableFeatureWithParameters( + kUnifiedConsent, feature_params); + break; + } + } + + DCHECK_EQ(state, internal::GetUnifiedConsentFeatureState()); +} + +ScopedUnifiedConsent::~ScopedUnifiedConsent() {} + +} // namespace unified_consent diff --git a/chromium/components/unified_consent/scoped_unified_consent.h b/chromium/components/unified_consent/scoped_unified_consent.h new file mode 100644 index 00000000000..a280cdc2244 --- /dev/null +++ b/chromium/components/unified_consent/scoped_unified_consent.h @@ -0,0 +1,34 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNIFIED_CONSENT_SCOPED_UNIFIED_CONSENT_H_ +#define COMPONENTS_UNIFIED_CONSENT_SCOPED_UNIFIED_CONSENT_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/test/scoped_feature_list.h" +#include "components/unified_consent/feature.h" + +namespace unified_consent { + +// Changes the unified consent feature state while it is in scope. Useful for +// tests. +// Also enables the feature |switches::kSyncUserConsentSeparateType| as +// unified consent depends on its. +class ScopedUnifiedConsent { + public: + explicit ScopedUnifiedConsent(UnifiedConsentFeatureState state); + ~ScopedUnifiedConsent(); + + private: + base::test::ScopedFeatureList sync_user_consent_separate_type_feature_list_; + base::test::ScopedFeatureList unified_consent_feature_list_; + + DISALLOW_COPY_AND_ASSIGN(ScopedUnifiedConsent); +}; + +} // namespace unified_consent + +#endif // COMPONENTS_UNIFIED_CONSENT_SCOPED_UNIFIED_CONSENT_H_ diff --git a/chromium/components/unified_consent/unified_consent_service.cc b/chromium/components/unified_consent/unified_consent_service.cc new file mode 100644 index 00000000000..9b9dbc133f6 --- /dev/null +++ b/chromium/components/unified_consent/unified_consent_service.cc @@ -0,0 +1,486 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/unified_consent/unified_consent_service.h" + +#include "base/logging.h" +#include "base/metrics/histogram_macros.h" +#include "base/scoped_observer.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "components/autofill/core/common/autofill_pref_names.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/prefs/pref_change_registrar.h" +#include "components/prefs/pref_service.h" +#include "components/sync/base/model_type.h" +#include "components/sync/base/sync_prefs.h" +#include "components/sync/driver/sync_service.h" +#include "components/unified_consent/pref_names.h" +#include "components/unified_consent/unified_consent_service_client.h" + +namespace unified_consent { + +namespace { + +// Histogram recorded at startup to log which Google services are enabled. +const char kSyncAndGoogleServicesSettingsHistogram[] = + "UnifiedConsent.SyncAndGoogleServicesSettings"; + +// Records a sample in the kSyncAndGoogleServicesSettingsHistogram. Wrapped in a +// function to avoid code size issues caused by histogram macros. +void RecordSettingsHistogramSample(SettingsHistogramValue value) { + UMA_HISTOGRAM_ENUMERATION(kSyncAndGoogleServicesSettingsHistogram, value); +} + +// Checks if a pref is enabled and if so, records a sample in the +// kSyncAndGoogleServicesSettingsHistogram. Returns true if a sample was +// recorded. +bool RecordSettingsHistogramFromPref(const char* pref_name, + PrefService* pref_service, + SettingsHistogramValue value) { + if (!pref_service->GetBoolean(pref_name)) + return false; + RecordSettingsHistogramSample(value); + return true; +} + +// Checks if a service is enabled and if so, records a sample in the +// kSyncAndGoogleServicesSettingsHistogram. Returns true if a sample was +// recorded. +bool RecordSettingsHistogramFromService( + UnifiedConsentServiceClient* client, + UnifiedConsentServiceClient::Service service, + SettingsHistogramValue value) { + if (client->GetServiceState(service) != + UnifiedConsentServiceClient::ServiceState::kEnabled) { + return false; + } + + RecordSettingsHistogramSample(value); + return true; +} + +// Used for observing the sync service and finishing the rollback once the sync +// engine is initialized. +// Note: This object is suicidal - it will kill itself after it finishes the +// rollback. +class RollbackHelper : public syncer::SyncServiceObserver { + public: + explicit RollbackHelper(syncer::SyncService* sync_service); + ~RollbackHelper() override = default; + + private: + // syncer::SyncServiceObserver: + void OnStateChanged(syncer::SyncService* sync_service) override; + + void DoRollbackIfPossibleAndDie(syncer::SyncService* sync_service); + + ScopedObserver<syncer::SyncService, RollbackHelper> scoped_sync_observer_; +}; + +RollbackHelper::RollbackHelper(syncer::SyncService* sync_service) + : scoped_sync_observer_(this) { + if (sync_service->IsEngineInitialized()) + DoRollbackIfPossibleAndDie(sync_service); + else + scoped_sync_observer_.Add(sync_service); +} + +void RollbackHelper::OnStateChanged(syncer::SyncService* sync_service) { + if (!sync_service->IsEngineInitialized()) + return; + + scoped_sync_observer_.RemoveAll(); + DoRollbackIfPossibleAndDie(sync_service); +} + +void RollbackHelper::DoRollbackIfPossibleAndDie( + syncer::SyncService* sync_service) { + DCHECK(!scoped_sync_observer_.IsObservingSources()); + + syncer::ModelTypeSet user_types_without_user_events = + syncer::UserSelectableTypes(); + user_types_without_user_events.Remove(syncer::USER_EVENTS); + + if (sync_service->GetPreferredDataTypes().HasAll( + user_types_without_user_events)) { + // As part of the migration of a profile to Unified Consent, sync everything + // is disabled but sync continues to be enabled for all data types except + // USER_EVENTS. Therefore it is desired to restore sync everything when + // rolling back unified consent to leave sync in the same state as the one + // before migration. + sync_service->OnUserChoseDatatypes(true, syncer::UserSelectableTypes()); + } + + base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this); +} + +} // namespace + +UnifiedConsentService::UnifiedConsentService( + std::unique_ptr<UnifiedConsentServiceClient> service_client, + PrefService* pref_service, + identity::IdentityManager* identity_manager, + syncer::SyncService* sync_service) + : service_client_(std::move(service_client)), + pref_service_(pref_service), + identity_manager_(identity_manager), + sync_service_(sync_service) { + DCHECK(service_client_); + DCHECK(pref_service_); + DCHECK(identity_manager_); + DCHECK(sync_service_); + + if (GetMigrationState() == MigrationState::kNotInitialized) + MigrateProfileToUnifiedConsent(); + + service_client_->AddObserver(this); + identity_manager_->AddObserver(this); + sync_service_->AddObserver(this); + + pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>(); + pref_change_registrar_->Init(pref_service); + pref_change_registrar_->Add( + prefs::kUnifiedConsentGiven, + base::BindRepeating( + &UnifiedConsentService::OnUnifiedConsentGivenPrefChanged, + base::Unretained(this))); + + // If somebody disabled any of the non-personalized services while Chrome + // wasn't running, disable unified consent. + if (!AreAllNonPersonalizedServicesEnabled() && IsUnifiedConsentGiven()) { + SetUnifiedConsentGiven(false); + } + + RecordSettingsHistogram(); +} + +UnifiedConsentService::~UnifiedConsentService() {} + +// static +void UnifiedConsentService::RegisterPrefs( + user_prefs::PrefRegistrySyncable* registry) { + registry->RegisterBooleanPref(prefs::kUrlKeyedAnonymizedDataCollectionEnabled, + false); + registry->RegisterBooleanPref(prefs::kUnifiedConsentGiven, false); + registry->RegisterIntegerPref( + prefs::kUnifiedConsentMigrationState, + static_cast<int>(MigrationState::kNotInitialized)); + registry->RegisterBooleanPref(prefs::kShouldShowUnifiedConsentBump, false); +} + +// static +void UnifiedConsentService::RollbackIfNeeded( + PrefService* user_pref_service, + syncer::SyncService* sync_service) { + DCHECK(user_pref_service); + + if (user_pref_service->GetInteger(prefs::kUnifiedConsentMigrationState) == + static_cast<int>(MigrationState::kNotInitialized)) { + // If there was no migration yet, nothing has to be rolled back. + return; + } + + if (user_pref_service->GetBoolean(prefs::kShouldShowUnifiedConsentBump) && + sync_service && + sync_service->GetDisableReasons() == + syncer::SyncService::DISABLE_REASON_NONE) { + // This will wait until the sync engine is initialized and then enables the + // sync-everything pref in case the user is syncing all data types. + new RollbackHelper(sync_service); + } + + // Clear all unified consent prefs. + user_pref_service->ClearPref(prefs::kUrlKeyedAnonymizedDataCollectionEnabled); + user_pref_service->ClearPref(prefs::kUnifiedConsentGiven); + user_pref_service->ClearPref(prefs::kUnifiedConsentMigrationState); + user_pref_service->ClearPref(prefs::kShouldShowUnifiedConsentBump); +} + +void UnifiedConsentService::SetUnifiedConsentGiven(bool unified_consent_given) { + // Unified consent cannot be enabled if the user is not signed in. + DCHECK(!unified_consent_given || identity_manager_->HasPrimaryAccount()); + pref_service_->SetBoolean(prefs::kUnifiedConsentGiven, unified_consent_given); +} + +bool UnifiedConsentService::IsUnifiedConsentGiven() { + return pref_service_->GetBoolean(prefs::kUnifiedConsentGiven); +} + +bool UnifiedConsentService::ShouldShowConsentBump() { + return pref_service_->GetBoolean(prefs::kShouldShowUnifiedConsentBump); +} + +void UnifiedConsentService::MarkConsentBumpShown() { + // Record suppress reason kNone, which means that it was shown. This also sets + // the |kShouldShowConsentBump| pref to false. + RecordConsentBumpSuppressReason(ConsentBumpSuppressReason::kNone); +} + +void UnifiedConsentService::RecordConsentBumpSuppressReason( + ConsentBumpSuppressReason suppress_reason) { + UMA_HISTOGRAM_ENUMERATION("UnifiedConsent.ConsentBump.SuppressReason", + suppress_reason); + + switch (suppress_reason) { + case ConsentBumpSuppressReason::kNone: + case ConsentBumpSuppressReason::kNotSignedIn: + case ConsentBumpSuppressReason::kSyncEverythingOff: + case ConsentBumpSuppressReason::kPrivacySettingOff: + case ConsentBumpSuppressReason::kSettingsOptIn: + case ConsentBumpSuppressReason::kUserSignedOut: + pref_service_->SetBoolean(prefs::kShouldShowUnifiedConsentBump, false); + break; + case ConsentBumpSuppressReason::kSyncPaused: + // Consent bump should be shown when sync is active again. + DCHECK(ShouldShowConsentBump()); + break; + } +} + +void UnifiedConsentService::Shutdown() { + service_client_->RemoveObserver(this); + identity_manager_->RemoveObserver(this); + sync_service_->RemoveObserver(this); +} + +void UnifiedConsentService::OnServiceStateChanged(Service service) { + // Unified consent is disabled when any of its dependent services gets + // disabled. + if (service_client_->GetServiceState(service) == ServiceState::kDisabled) + SetUnifiedConsentGiven(false); +} + +void UnifiedConsentService::OnPrimaryAccountCleared( + const AccountInfo& account_info) { + // When signing out, the unfied consent is revoked. + pref_service_->SetBoolean(prefs::kUnifiedConsentGiven, false); + + // By design, signing out of Chrome automatically disables off-by-default + // services. + pref_service_->SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled, + false); + service_client_->SetServiceEnabled(Service::kSafeBrowsingExtendedReporting, + false); + service_client_->SetServiceEnabled(Service::kSpellCheck, false); + + if (GetMigrationState() != MigrationState::kCompleted) { + // When the user signs out, the migration is complete. + SetMigrationState(MigrationState::kCompleted); + } + + if (ShouldShowConsentBump()) + RecordConsentBumpSuppressReason(ConsentBumpSuppressReason::kUserSignedOut); +} + +void UnifiedConsentService::OnStateChanged(syncer::SyncService* sync) { + if (sync_service_->GetDisableReasons() != + syncer::SyncService::DISABLE_REASON_NONE || + !sync_service_->IsEngineInitialized()) { + return; + } + + if (GetMigrationState() == MigrationState::kInProgressWaitForSyncInit) + UpdateSettingsForMigration(); + + if (sync_service_->IsUsingSecondaryPassphrase() && IsUnifiedConsentGiven()) { + // Force off unified consent given when the user sets a custom passphrase. + SetUnifiedConsentGiven(false); + } + + syncer::SyncPrefs sync_prefs(pref_service_); + if (IsUnifiedConsentGiven() != sync_prefs.HasKeepEverythingSynced()) { + // Make sync-everything consistent with the |kUnifiedConsentGiven| pref. + SetSyncEverythingIfPossible(IsUnifiedConsentGiven()); + } +} + +void UnifiedConsentService::OnUnifiedConsentGivenPrefChanged() { + bool enabled = pref_service_->GetBoolean(prefs::kUnifiedConsentGiven); + + if (!enabled) { + if (identity_manager_->HasPrimaryAccount()) { + // Sync-everything is set to false, so the user can select individual + // sync data types. + SetSyncEverythingIfPossible(false); + } + return; + } + + DCHECK(sync_service_->IsSyncAllowed()); + DCHECK(identity_manager_->HasPrimaryAccount()); + DCHECK_LT(MigrationState::kNotInitialized, GetMigrationState()); + + if (GetMigrationState() != MigrationState::kCompleted) { + // If the user opted into unified consent, the migration is completed. + SetMigrationState(MigrationState::kCompleted); + } + + if (ShouldShowConsentBump()) + RecordConsentBumpSuppressReason(ConsentBumpSuppressReason::kSettingsOptIn); + + // Enable all sync data types if possible, otherwise they will be enabled with + // |OnStateChanged| once sync is active; + SetSyncEverythingIfPossible(true); + + // Enable all non-personalized services. + pref_service_->SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled, + true); + // Inform client to enable non-personalized services. + for (int i = 0; i <= static_cast<int>(Service::kLast); ++i) { + Service service = static_cast<Service>(i); + if (service_client_->GetServiceState(service) != + ServiceState::kNotSupported) { + service_client_->SetServiceEnabled(service, true); + } + } +} + +void UnifiedConsentService::SetSyncEverythingIfPossible(bool sync_everything) { + syncer::SyncPrefs sync_prefs(pref_service_); + if (sync_everything == sync_prefs.HasKeepEverythingSynced()) + return; + + if (!IsSyncConfigurable()) + return; + + if (sync_everything) { + pref_service_->SetBoolean(autofill::prefs::kAutofillWalletImportEnabled, + true); + sync_service_->OnUserChoseDatatypes(sync_everything, + syncer::UserSelectableTypes()); + } else { + syncer::ModelTypeSet preferred = sync_service_->GetPreferredDataTypes(); + preferred.RetainAll(syncer::UserSelectableTypes()); + sync_service_->OnUserChoseDatatypes(false, preferred); + } +} + +MigrationState UnifiedConsentService::GetMigrationState() { + int migration_state_int = + pref_service_->GetInteger(prefs::kUnifiedConsentMigrationState); + DCHECK_LE(static_cast<int>(MigrationState::kNotInitialized), + migration_state_int); + DCHECK_GE(static_cast<int>(MigrationState::kCompleted), migration_state_int); + return static_cast<MigrationState>(migration_state_int); +} + +void UnifiedConsentService::SetMigrationState(MigrationState migration_state) { + pref_service_->SetInteger(prefs::kUnifiedConsentMigrationState, + static_cast<int>(migration_state)); +} + +void UnifiedConsentService::MigrateProfileToUnifiedConsent() { + DCHECK_EQ(GetMigrationState(), MigrationState::kNotInitialized); + DCHECK(!IsUnifiedConsentGiven()); + + if (!identity_manager_->HasPrimaryAccount()) { + RecordConsentBumpSuppressReason(ConsentBumpSuppressReason::kNotSignedIn); + SetMigrationState(MigrationState::kCompleted); + return; + } + + if (!syncer::SyncPrefs(pref_service_).HasKeepEverythingSynced()) { + RecordConsentBumpSuppressReason( + ConsentBumpSuppressReason::kSyncEverythingOff); + } else if (!AreAllOnByDefaultPrivacySettingsOn()) { + RecordConsentBumpSuppressReason( + ConsentBumpSuppressReason::kPrivacySettingOff); + } else { + // When the user was syncing everything, and all on-by-default privacy + // settings were on, the consent bump should be shown. + pref_service_->SetBoolean(prefs::kShouldShowUnifiedConsentBump, true); + } + + UpdateSettingsForMigration(); +} + +void UnifiedConsentService::UpdateSettingsForMigration() { + if (!IsSyncConfigurable()) { + SetMigrationState(MigrationState::kInProgressWaitForSyncInit); + return; + } + + if (IsUnifiedConsentGiven()) { + // When the user opted into unified consent through the consent bump or the + // settings page while waiting for sync initialization, the migration is + // completed. + SetMigrationState(MigrationState::kCompleted); + return; + } + + // Set URL-keyed anonymized metrics to the state it had before unified + // consent. + bool url_keyed_metrics_enabled = sync_service_->GetPreferredDataTypes().Has( + syncer::HISTORY_DELETE_DIRECTIVES) && + !sync_service_->IsUsingSecondaryPassphrase(); + pref_service_->SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled, + url_keyed_metrics_enabled); + + // Disable the datatype user events for newly migrated users. Also set + // sync-everything to false, so it matches unified consent given. + syncer::ModelTypeSet preferred_types_without_user_events = + sync_service_->GetPreferredDataTypes(); + preferred_types_without_user_events.RetainAll(syncer::UserSelectableTypes()); + preferred_types_without_user_events.Remove(syncer::USER_EVENTS); + sync_service_->OnUserChoseDatatypes(false /*sync everything */, + preferred_types_without_user_events); + + SetMigrationState(MigrationState::kCompleted); +} + +bool UnifiedConsentService::AreAllNonPersonalizedServicesEnabled() { + for (int i = 0; i <= static_cast<int>(Service::kLast); ++i) { + Service service = static_cast<Service>(i); + if (service_client_->GetServiceState(service) == ServiceState::kDisabled) + return false; + } + if (!pref_service_->GetBoolean( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled)) + return false; + + return true; +} + +bool UnifiedConsentService::AreAllOnByDefaultPrivacySettingsOn() { + for (auto service : {Service::kAlternateErrorPages, + Service::kMetricsReporting, Service::kNetworkPrediction, + Service::kSafeBrowsing, Service::kSearchSuggest}) { + if (service_client_->GetServiceState(service) == ServiceState::kDisabled) + return false; + } + return true; +} + +bool UnifiedConsentService::IsSyncConfigurable() { + return sync_service_->GetState() == syncer::SyncService::State::ACTIVE; +} + +void UnifiedConsentService::RecordSettingsHistogram() { + bool metric_recorded = false; + + if (IsUnifiedConsentGiven()) { + RecordSettingsHistogramSample(SettingsHistogramValue::kUnifiedConsentGiven); + metric_recorded = true; + } + if (identity_manager_->HasPrimaryAccount() && + sync_service_->GetPreferredDataTypes().Has(syncer::USER_EVENTS)) { + RecordSettingsHistogramSample(SettingsHistogramValue::kUserEvents); + metric_recorded = true; + } + metric_recorded |= RecordSettingsHistogramFromPref( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled, pref_service_, + SettingsHistogramValue::kUrlKeyedAnonymizedDataCollection); + metric_recorded |= RecordSettingsHistogramFromService( + service_client_.get(), + UnifiedConsentServiceClient::Service::kSafeBrowsingExtendedReporting, + SettingsHistogramValue::kSafeBrowsingExtendedReporting); + metric_recorded |= RecordSettingsHistogramFromService( + service_client_.get(), UnifiedConsentServiceClient::Service::kSpellCheck, + SettingsHistogramValue::kSpellCheck); + + if (!metric_recorded) + RecordSettingsHistogramSample(SettingsHistogramValue::kNone); +} + +} // namespace unified_consent diff --git a/chromium/components/unified_consent/unified_consent_service.h b/chromium/components/unified_consent/unified_consent_service.h new file mode 100644 index 00000000000..f1a85b8bf4a --- /dev/null +++ b/chromium/components/unified_consent/unified_consent_service.h @@ -0,0 +1,168 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNIFIED_CONSENT_UNIFIED_CONSENT_SERVICE_H_ +#define COMPONENTS_UNIFIED_CONSENT_UNIFIED_CONSENT_SERVICE_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/observer_list.h" +#include "components/keyed_service/core/keyed_service.h" +#include "components/sync/driver/sync_service_observer.h" +#include "components/unified_consent/unified_consent_service_client.h" +#include "services/identity/public/cpp/identity_manager.h" + +namespace user_prefs { +class PrefRegistrySyncable; +} + +class PrefChangeRegistrar; +class PrefService; + +namespace syncer { +class SyncService; +} + +namespace unified_consent { + +using Service = UnifiedConsentServiceClient::Service; +using ServiceState = UnifiedConsentServiceClient::ServiceState; + +enum class MigrationState : int { + kNotInitialized = 0, + kInProgressWaitForSyncInit = 1, + // Reserve space for other kInProgress* entries to be added here. + kCompleted = 10, +}; + +// Used in histograms. Do not change existing values, append new values at the +// end. +enum class ConsentBumpSuppressReason { + kNone, + kNotSignedIn, + kSyncEverythingOff, + kPrivacySettingOff, + kSettingsOptIn, + kUserSignedOut, + kSyncPaused, + + kMaxValue = kSyncPaused +}; + +// Google services that can be toggled in user settings. +// Used in histograms. Do not change existing values, append new values at the +// end. +enum class SettingsHistogramValue { + kNone = 0, + kUnifiedConsentGiven = 1, + kUserEvents = 2, + kUrlKeyedAnonymizedDataCollection = 3, + kSafeBrowsingExtendedReporting = 4, + kSpellCheck = 5, + + kMaxValue = kSpellCheck +}; + +// A browser-context keyed service that is used to manage the user consent +// when UnifiedConsent feature is enabled. +class UnifiedConsentService : public KeyedService, + public UnifiedConsentServiceClient::Observer, + public identity::IdentityManager::Observer, + public syncer::SyncServiceObserver { + public: + UnifiedConsentService( + std::unique_ptr<UnifiedConsentServiceClient> service_client, + PrefService* pref_service, + identity::IdentityManager* identity_manager, + syncer::SyncService* sync_service); + ~UnifiedConsentService() override; + + // Register the prefs used by this UnifiedConsentService. + static void RegisterPrefs(user_prefs::PrefRegistrySyncable* registry); + + // Rolls back changes made during migration. This method does nothing if the + // user hasn't migrated to unified consent yet. + static void RollbackIfNeeded(PrefService* user_pref_service, + syncer::SyncService* sync_service); + + // This updates the consent pref and if |unified_consent_given| is true, all + // unified consent services are enabled. + void SetUnifiedConsentGiven(bool unified_consent_given); + bool IsUnifiedConsentGiven(); + + // Returns true if all criteria is met to show the consent bump. + bool ShouldShowConsentBump(); + // Marks the consent bump as shown. Any future calls to + // |ShouldShowConsentBump| are guaranteed to return false. + void MarkConsentBumpShown(); + // Records the consent bump suppress reason and updates the state whether the + // consent bump should be shown. Note: In some cases, e.g. sync paused, + // |ShouldShowConsentBump| will still return true. + void RecordConsentBumpSuppressReason( + ConsentBumpSuppressReason suppress_reason); + + // KeyedService: + void Shutdown() override; + + // UnifiedConsentServiceClient::Observer: + void OnServiceStateChanged(Service service) override; + + // IdentityManager::Observer: + void OnPrimaryAccountCleared( + const AccountInfo& previous_primary_account_info) override; + + private: + friend class UnifiedConsentServiceTest; + + // syncer::SyncServiceObserver: + void OnStateChanged(syncer::SyncService* sync) override; + + // Called when |prefs::kUnifiedConsentGiven| pref value changes. + // When set to true, it enables syncing of all data types and it enables all + // non-personalized services. Otherwise it does nothing. + void OnUnifiedConsentGivenPrefChanged(); + + // Enables/disables syncing everything if the sync engine is initialized. + void SetSyncEverythingIfPossible(bool sync_everything); + + // Migration helpers. + MigrationState GetMigrationState(); + void SetMigrationState(MigrationState migration_state); + // Called when the unified consent service is created. This sets the + // |kShouldShowUnifiedConsentBump| pref to true if the user is eligible and + // calls |UpdateSettingsForMigration| at the end. + void MigrateProfileToUnifiedConsent(); + // Updates the settings preferences for the migration when the sync engine is + // initialized. When it is not, this function will be called again from + // |OnStateChanged| when the sync engine is initialized. + void UpdateSettingsForMigration(); + + // Returns true if all non-personalized services are enabled. + bool AreAllNonPersonalizedServicesEnabled(); + + // Checks if all on-by-default non-personalized services are on. + bool AreAllOnByDefaultPrivacySettingsOn(); + + // Helper that checks whether it's okay to call + // |SyncService::OnUserChoseDatatypes|. + bool IsSyncConfigurable(); + + // Records a sample for each bucket enabled by the user (except kNone). + // kNone is recorded when none of the other buckets are recorded. + void RecordSettingsHistogram(); + + std::unique_ptr<UnifiedConsentServiceClient> service_client_; + PrefService* pref_service_; + identity::IdentityManager* identity_manager_; + syncer::SyncService* sync_service_; + + std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_; + + DISALLOW_COPY_AND_ASSIGN(UnifiedConsentService); +}; + +} // namespace unified_consent + +#endif // COMPONENTS_UNIFIED_CONSENT_UNIFIED_CONSENT_SERVICE_H_ diff --git a/chromium/components/unified_consent/unified_consent_service_client.cc b/chromium/components/unified_consent/unified_consent_service_client.cc new file mode 100644 index 00000000000..1bd56d205eb --- /dev/null +++ b/chromium/components/unified_consent/unified_consent_service_client.cc @@ -0,0 +1,50 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/unified_consent/unified_consent_service_client.h" + +#include "base/bind.h" + +namespace unified_consent { + +UnifiedConsentServiceClient::UnifiedConsentServiceClient() {} +UnifiedConsentServiceClient::~UnifiedConsentServiceClient() {} + +void UnifiedConsentServiceClient::AddObserver(Observer* observer) { + observer_list_.AddObserver(observer); +} + +void UnifiedConsentServiceClient::RemoveObserver(Observer* observer) { + observer_list_.RemoveObserver(observer); +} + +void UnifiedConsentServiceClient::ObserveServicePrefChange( + Service service, + const std::string& pref_name, + PrefService* pref_service) { + service_prefs_[pref_name] = service; + + // First access to the pref registrar of |pref_service| in the map + // automatically creates an entry for it. + PrefChangeRegistrar* pref_change_registrar = + &(pref_change_registrars_[pref_service]); + if (!pref_change_registrar->prefs()) + pref_change_registrar->Init(pref_service); + pref_change_registrar->Add( + pref_name, + base::BindRepeating(&UnifiedConsentServiceClient::OnPrefChanged, + base::Unretained(this))); +} + +void UnifiedConsentServiceClient::FireOnServiceStateChanged(Service service) { + for (auto& observer : observer_list_) + observer.OnServiceStateChanged(service); +} + +void UnifiedConsentServiceClient::OnPrefChanged(const std::string& pref_name) { + DCHECK(service_prefs_.count(pref_name)); + FireOnServiceStateChanged(service_prefs_[pref_name]); +} + +} // namespace unified_consent diff --git a/chromium/components/unified_consent/unified_consent_service_client.h b/chromium/components/unified_consent/unified_consent_service_client.h new file mode 100644 index 00000000000..cb31490d0db --- /dev/null +++ b/chromium/components/unified_consent/unified_consent_service_client.h @@ -0,0 +1,94 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNIFIED_CONSENT_UNIFIED_CONSENT_SERVICE_CLIENT_H_ +#define COMPONENTS_UNIFIED_CONSENT_UNIFIED_CONSENT_SERVICE_CLIENT_H_ + +#include <map> +#include <string> + +#include "base/observer_list.h" +#include "components/prefs/pref_change_registrar.h" + +class PrefService; + +namespace unified_consent { + +class UnifiedConsentServiceClient { + public: + enum class Service { + // Link Doctor error pages. + kAlternateErrorPages, + // Metrics reporting. + kMetricsReporting, + // Prediction of network actions. + kNetworkPrediction, + // Safe browsing. + kSafeBrowsing, + // Extended safe browsing. + kSafeBrowsingExtendedReporting, + // Search suggestions. + kSearchSuggest, + // Spell checking. + kSpellCheck, + + // Last element of the enum, used for iteration. + kLast = kSpellCheck, + }; + + enum class ServiceState { + // The service is not supported on this platform. + kNotSupported, + // The service is supported, but disabled. + kDisabled, + // The service is enabled. + kEnabled + }; + + class Observer { + public: + // Called when the service state of |service| changes. + virtual void OnServiceStateChanged(Service service) = 0; + }; + + UnifiedConsentServiceClient(); + virtual ~UnifiedConsentServiceClient(); + + // Returns the ServiceState for |service|. + virtual ServiceState GetServiceState(Service service) = 0; + // Sets |service| enabled if it is supported on this platform. + virtual void SetServiceEnabled(Service service, bool enabled) = 0; + + // Methods to register or remove observers. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + protected: + // This method adds |pref_name| to the list of prefs that will be observed for + // changes. Whenever there's a change in the pref, all + // |UnifiedConsentServiceClient::Observer|s are fired. + void ObserveServicePrefChange(Service service, + const std::string& pref_name, + PrefService* pref_service); + + // Fires |OnServiceStateChanged| on all observers. + void FireOnServiceStateChanged(Service service); + + private: + // Callback for the pref change registrars. + void OnPrefChanged(const std::string& pref_name); + + base::ObserverList<Observer, true> observer_list_; + + // Matches the pref name to it's service. + std::map<std::string, Service> service_prefs_; + // Matches pref service to it's change registrar. + std::map<PrefService*, PrefChangeRegistrar> pref_change_registrars_; + + DISALLOW_COPY_AND_ASSIGN(UnifiedConsentServiceClient); +}; + +} // namespace unified_consent + +#endif // COMPONENTS_UNIFIED_CONSENT_UNIFIED_CONSENT_SERVICE_CLIENT_H_ diff --git a/chromium/components/unified_consent/unified_consent_service_unittest.cc b/chromium/components/unified_consent/unified_consent_service_unittest.cc new file mode 100644 index 00000000000..332351a1e53 --- /dev/null +++ b/chromium/components/unified_consent/unified_consent_service_unittest.cc @@ -0,0 +1,645 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/unified_consent/unified_consent_service.h" + +#include <map> +#include <memory> + +#include "base/message_loop/message_loop.h" +#include "base/test/metrics/histogram_tester.h" +#include "components/autofill/core/common/autofill_pref_names.h" +#include "components/sync/base/sync_prefs.h" +#include "components/sync/driver/fake_sync_service.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" +#include "components/unified_consent/pref_names.h" +#include "components/unified_consent/scoped_unified_consent.h" +#include "components/unified_consent/unified_consent_service_client.h" +#include "services/identity/public/cpp/identity_test_environment.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace unified_consent { +namespace { + +class TestSyncService : public syncer::FakeSyncService { + public: + explicit TestSyncService(PrefService* pref_service) + : pref_service_(pref_service) {} + + int GetDisableReasons() const override { return DISABLE_REASON_NONE; } + bool IsFirstSetupComplete() const override { return true; } + bool IsEngineInitialized() const override { return engine_initialized_; } + void AddObserver(syncer::SyncServiceObserver* observer) override { + observer_ = observer; + } + void OnUserChoseDatatypes(bool sync_everything, + syncer::ModelTypeSet chosen_types) override { + syncer::SyncPrefs(pref_service_).SetKeepEverythingSynced(sync_everything); + chosen_types_ = chosen_types; + } + syncer::ModelTypeSet GetPreferredDataTypes() const override { + syncer::ModelTypeSet preferred = chosen_types_; + // Add this for the Migration_UpdateSettings test. + preferred.Put(syncer::HISTORY_DELETE_DIRECTIVES); + return preferred; + } + bool IsUsingSecondaryPassphrase() const override { + return is_using_passphrase_; + } + SyncService::State GetState() const override { return state_; } + + void SetEngineInitialized(bool engine_initialized) { + engine_initialized_ = engine_initialized; + } + void SetIsUsingPassphrase(bool using_passphrase) { + is_using_passphrase_ = using_passphrase; + } + void SetState(SyncService::State state) { state_ = state; } + + void FireStateChanged() { + if (observer_) + observer_->OnStateChanged(this); + } + + private: + syncer::SyncServiceObserver* observer_ = nullptr; + bool engine_initialized_ = true; + syncer::ModelTypeSet chosen_types_ = syncer::UserSelectableTypes(); + SyncService::State state_ = SyncService::State::ACTIVE; + bool is_using_passphrase_ = false; + PrefService* pref_service_; +}; + +const char kSpellCheckDummyEnabled[] = "spell_check_dummy.enabled"; + +class FakeUnifiedConsentServiceClient : public UnifiedConsentServiceClient { + public: + FakeUnifiedConsentServiceClient(PrefService* pref_service) + : pref_service_(pref_service) { + // When the |kSpellCheckDummyEnabled| pref is changed, all observers should + // be fired. + ObserveServicePrefChange(Service::kSpellCheck, kSpellCheckDummyEnabled, + pref_service); + } + ~FakeUnifiedConsentServiceClient() override = default; + + // UnifiedConsentServiceClient: + ServiceState GetServiceState(Service service) override { + if (is_not_supported_[service]) + return ServiceState::kNotSupported; + bool enabled; + // Special treatment for spell check. + if (service == Service::kSpellCheck) { + enabled = pref_service_->GetBoolean(kSpellCheckDummyEnabled); + } else { + enabled = service_enabled_[service]; + } + return enabled ? ServiceState::kEnabled : ServiceState::kDisabled; + } + void SetServiceEnabled(Service service, bool enabled) override { + if (is_not_supported_[service]) + return; + // Special treatment for spell check. + if (service == Service::kSpellCheck) { + pref_service_->SetBoolean(kSpellCheckDummyEnabled, enabled); + return; + } + bool should_notify_observers = service_enabled_[service] != enabled; + service_enabled_[service] = enabled; + if (should_notify_observers) + FireOnServiceStateChanged(service); + } + + void SetServiceNotSupported(Service service) { + is_not_supported_[service] = true; + } + + private: + std::map<Service, bool> service_enabled_; + std::map<Service, bool> is_not_supported_; + + PrefService* pref_service_; +}; + +} // namespace + +class UnifiedConsentServiceTest : public testing::Test { + public: + UnifiedConsentServiceTest() : sync_service_(&pref_service_) {} + + // testing::Test: + void SetUp() override { + pref_service_.registry()->RegisterBooleanPref( + autofill::prefs::kAutofillWalletImportEnabled, false); + UnifiedConsentService::RegisterPrefs(pref_service_.registry()); + syncer::SyncPrefs::RegisterProfilePrefs(pref_service_.registry()); + pref_service_.registry()->RegisterBooleanPref(kSpellCheckDummyEnabled, + false); + } + + void TearDown() override { + if (consent_service_) + consent_service_->Shutdown(); + } + + void CreateConsentService(bool client_services_on_by_default = false) { + if (!scoped_unified_consent_) { + SetUnifiedConsentFeatureState( + unified_consent::UnifiedConsentFeatureState::kEnabledWithBump); + } + + auto client = + std::make_unique<FakeUnifiedConsentServiceClient>(&pref_service_); + if (client_services_on_by_default) { + for (int i = 0; i <= static_cast<int>(Service::kLast); ++i) { + Service service = static_cast<Service>(i); + client->SetServiceEnabled(service, true); + } + pref_service_.SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled, + true); + } + consent_service_ = std::make_unique<UnifiedConsentService>( + std::move(client), &pref_service_, + identity_test_environment_.identity_manager(), &sync_service_); + service_client_ = (FakeUnifiedConsentServiceClient*) + consent_service_->service_client_.get(); + } + + void SetUnifiedConsentFeatureState( + unified_consent::UnifiedConsentFeatureState feature_state) { + // First reset |scoped_unified_consent_| to nullptr in case it was set + // before and then initialize it with the new value. This makes sure that + // the old scoped object is deleted before the new one is created. + scoped_unified_consent_.reset(); + scoped_unified_consent_.reset( + new unified_consent::ScopedUnifiedConsent(feature_state)); + } + + bool AreAllNonPersonalizedServicesEnabled() { + return consent_service_->AreAllNonPersonalizedServicesEnabled(); + } + + bool AreAllOnByDefaultPrivacySettingsOn() { + return consent_service_->AreAllOnByDefaultPrivacySettingsOn(); + } + + unified_consent::MigrationState GetMigrationState() { + int migration_state_int = + pref_service_.GetInteger(prefs::kUnifiedConsentMigrationState); + return static_cast<unified_consent::MigrationState>(migration_state_int); + } + + protected: + base::MessageLoop message_loop_; + sync_preferences::TestingPrefServiceSyncable pref_service_; + identity::IdentityTestEnvironment identity_test_environment_; + TestSyncService sync_service_; + std::unique_ptr<UnifiedConsentService> consent_service_; + FakeUnifiedConsentServiceClient* service_client_ = nullptr; + + std::unique_ptr<ScopedUnifiedConsent> scoped_unified_consent_; +}; + +TEST_F(UnifiedConsentServiceTest, DefaultValuesWhenSignedOut) { + CreateConsentService(); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_FALSE(pref_service_.GetBoolean( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled)); +} + +TEST_F(UnifiedConsentServiceTest, EnableUnfiedConsent) { + CreateConsentService(); + identity_test_environment_.SetPrimaryAccount("testaccount"); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_FALSE(pref_service_.GetBoolean( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled)); + EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled()); + + // Enable Unified Consent enables all non-personaized features + pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, true); + EXPECT_TRUE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled()); + + // Disable unified consent does not disable any of the non-personalized + // features. + pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, false); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled()); +} + +TEST_F(UnifiedConsentServiceTest, EnableUnfiedConsent_WithUnsupportedService) { + CreateConsentService(); + identity_test_environment_.SetPrimaryAccount("testaccount"); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_FALSE(pref_service_.GetBoolean( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled)); + EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled()); + service_client_->SetServiceNotSupported(Service::kSpellCheck); + EXPECT_EQ(service_client_->GetServiceState(Service::kSpellCheck), + ServiceState::kNotSupported); + EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled()); + + // Enable Unified Consent enables all supported non-personalized features + pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, true); + EXPECT_TRUE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled()); + + // Disable unified consent does not disable any of the supported + // non-personalized features. + pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, false); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled()); +} + +TEST_F(UnifiedConsentServiceTest, EnableUnfiedConsent_SyncNotActive) { + CreateConsentService(); + identity_test_environment_.SetPrimaryAccount("testaccount"); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + sync_service_.OnUserChoseDatatypes(false, syncer::UserSelectableTypes()); + syncer::SyncPrefs sync_prefs(&pref_service_); + EXPECT_FALSE(sync_prefs.HasKeepEverythingSynced()); + EXPECT_FALSE(consent_service_->IsUnifiedConsentGiven()); + + // Make sure sync is not active. + sync_service_.SetEngineInitialized(false); + EXPECT_FALSE(sync_service_.IsEngineInitialized()); + sync_service_.SetState(syncer::SyncService::State::INITIALIZING); + EXPECT_NE(sync_service_.GetState(), syncer::SyncService::State::ACTIVE); + + // Opt into unified consent. + consent_service_->SetUnifiedConsentGiven(true); + EXPECT_TRUE(consent_service_->IsUnifiedConsentGiven()); + + // Couldn't sync everything because sync is not active. + EXPECT_FALSE(sync_prefs.HasKeepEverythingSynced()); + + // Initalize sync engine and therefore activate sync. + sync_service_.SetEngineInitialized(true); + sync_service_.SetState(syncer::SyncService::State::ACTIVE); + EXPECT_EQ(sync_service_.GetState(), syncer::SyncService::State::ACTIVE); + sync_service_.FireStateChanged(); + + // UnifiedConsentService starts syncing everything. + EXPECT_TRUE(sync_prefs.HasKeepEverythingSynced()); +} + +TEST_F(UnifiedConsentServiceTest, EnableUnfiedConsent_WithCustomPassphrase) { + CreateConsentService(); + identity_test_environment_.SetPrimaryAccount("testaccount"); + EXPECT_FALSE(consent_service_->IsUnifiedConsentGiven()); + EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled()); + + // Enable Unified Consent. + consent_service_->SetUnifiedConsentGiven(true); + EXPECT_TRUE(consent_service_->IsUnifiedConsentGiven()); + EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled()); + + // Set custom passphrase. + sync_service_.SetIsUsingPassphrase(true); + sync_service_.FireStateChanged(); + + // Setting a custom passphrase forces off unified consent given. + EXPECT_FALSE(consent_service_->IsUnifiedConsentGiven()); +} + +// Test whether unified consent is disabled when any of its dependent services +// gets disabled. +TEST_F(UnifiedConsentServiceTest, DisableUnfiedConsentWhenServiceIsDisabled) { + CreateConsentService(); + identity_test_environment_.SetPrimaryAccount("testaccount"); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_FALSE(pref_service_.GetBoolean( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled)); + EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled()); + + // Enable Unified Consent enables all supported non-personalized features + pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, true); + EXPECT_TRUE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled()); + + // Disabling child service disables unified consent. + pref_service_.SetBoolean(kSpellCheckDummyEnabled, false); + EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled()); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); +} + +// Test whether unified consent is disabled when any of its dependent services +// gets disabled before startup. +TEST_F(UnifiedConsentServiceTest, + DisableUnfiedConsentWhenServiceIsDisabled_OnStartup) { + CreateConsentService(); + identity_test_environment_.SetPrimaryAccount("testaccount"); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_FALSE(pref_service_.GetBoolean( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled)); + EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled()); + + // Enable Unified Consent enables all supported non-personalized features + pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, true); + EXPECT_TRUE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled()); + + // Simulate shutdown. + consent_service_->Shutdown(); + consent_service_.reset(); + + // Disable child service. + pref_service_.SetBoolean(kSpellCheckDummyEnabled, false); + + // Unified Consent is disabled during creation of the consent service because + // not all non-personalized services are enabled. + CreateConsentService(); + EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled()); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); +} + +#if !defined(OS_CHROMEOS) +TEST_F(UnifiedConsentServiceTest, Migration_SyncingEverythingAndAllServicesOn) { + base::HistogramTester histogram_tester; + + // Create inconsistent state. + identity_test_environment_.SetPrimaryAccount("testaccount"); + sync_service_.OnUserChoseDatatypes(true, syncer::UserSelectableTypes()); + syncer::SyncPrefs sync_prefs(&pref_service_); + EXPECT_TRUE(sync_prefs.HasKeepEverythingSynced()); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + sync_service_.SetState( + syncer::SyncService::State::PENDING_DESIRED_CONFIGURATION); + EXPECT_FALSE(sync_service_.IsSyncActive()); + + CreateConsentService(true /* client services on by default */); + EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled()); + // After the creation of the consent service, the profile started to migrate + // (but waiting for sync init) and |ShouldShowConsentBump| should return true. + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_EQ(GetMigrationState(), + unified_consent::MigrationState::kInProgressWaitForSyncInit); + EXPECT_TRUE(consent_service_->ShouldShowConsentBump()); + // Sync-everything is still on because sync is not active yet. + EXPECT_TRUE(sync_prefs.HasKeepEverythingSynced()); + + // When sync is active, the migration should continue and finish. + sync_service_.SetState(syncer::SyncService::State::ACTIVE); + sync_service_.FireStateChanged(); + EXPECT_FALSE(sync_prefs.HasKeepEverythingSynced()); + + // No metric for the consent bump suppress reason should have been recorded at + // this point. + histogram_tester.ExpectTotalCount("UnifiedConsent.ConsentBump.SuppressReason", + 0); + + // When the user signs out, the migration state changes to completed and the + // consent bump doesn't need to be shown anymore. + identity_test_environment_.ClearPrimaryAccount(); + EXPECT_EQ(GetMigrationState(), unified_consent::MigrationState::kCompleted); + EXPECT_FALSE(consent_service_->ShouldShowConsentBump()); + // A metric for the consent bump suppress reason should have been recorded at + // this point. + histogram_tester.ExpectBucketCount( + "UnifiedConsent.ConsentBump.SuppressReason", + unified_consent::ConsentBumpSuppressReason::kUserSignedOut, 1); +} + +TEST_F(UnifiedConsentServiceTest, Migration_SyncingEverythingAndServicesOff) { + base::HistogramTester histogram_tester; + + // Create inconsistent state. + identity_test_environment_.SetPrimaryAccount("testaccount"); + sync_service_.OnUserChoseDatatypes(true, syncer::UserSelectableTypes()); + syncer::SyncPrefs sync_prefs(&pref_service_); + EXPECT_TRUE(sync_prefs.HasKeepEverythingSynced()); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_TRUE(sync_service_.IsSyncActive()); + + CreateConsentService(); + EXPECT_FALSE(AreAllOnByDefaultPrivacySettingsOn()); + // After the creation of the consent service, the profile is migrated and + // |ShouldShowConsentBump| should return false. + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_FALSE(sync_prefs.HasKeepEverythingSynced()); + EXPECT_EQ(GetMigrationState(), unified_consent::MigrationState::kCompleted); + EXPECT_FALSE(consent_service_->ShouldShowConsentBump()); + + // A metric for the consent bump suppress reason should have been recorded at + // this point. + histogram_tester.ExpectBucketCount( + "UnifiedConsent.ConsentBump.SuppressReason", + unified_consent::ConsentBumpSuppressReason::kPrivacySettingOff, 1); +} +#endif // !defined(OS_CHROMEOS) + +TEST_F(UnifiedConsentServiceTest, Migration_NotSyncingEverything) { + base::HistogramTester histogram_tester; + + identity_test_environment_.SetPrimaryAccount("testaccount"); + sync_service_.OnUserChoseDatatypes(false, syncer::UserSelectableTypes()); + syncer::SyncPrefs sync_prefs(&pref_service_); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_FALSE(sync_prefs.HasKeepEverythingSynced()); + + CreateConsentService(); + // When the user is not syncing everything the migration is completed after + // the creation of the consent service. + EXPECT_EQ(GetMigrationState(), unified_consent::MigrationState::kCompleted); + // The suppress reason for not showing the consent bump should be recorded. + histogram_tester.ExpectBucketCount( + "UnifiedConsent.ConsentBump.SuppressReason", + unified_consent::ConsentBumpSuppressReason::kSyncEverythingOff, 1); +} + +TEST_F(UnifiedConsentServiceTest, Migration_UpdateSettings) { + // Create user that syncs everything + identity_test_environment_.SetPrimaryAccount("testaccount"); + sync_service_.OnUserChoseDatatypes(true, syncer::UserSelectableTypes()); + syncer::SyncPrefs sync_prefs(&pref_service_); + EXPECT_TRUE(sync_prefs.HasKeepEverythingSynced()); + EXPECT_TRUE(sync_service_.IsSyncActive()); + EXPECT_TRUE(sync_service_.GetPreferredDataTypes().Has(syncer::USER_EVENTS)); + // Url keyed data collection is off before the migration. + EXPECT_FALSE(pref_service_.GetBoolean( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled)); + + CreateConsentService(); + EXPECT_EQ(GetMigrationState(), unified_consent::MigrationState::kCompleted); + // During the migration USER_EVENTS is disabled and Url keyed data collection + // is enabled. + EXPECT_FALSE(sync_prefs.HasKeepEverythingSynced()); + EXPECT_FALSE(sync_service_.GetPreferredDataTypes().Has(syncer::USER_EVENTS)); + EXPECT_TRUE(pref_service_.GetBoolean( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled)); +} + +#if !defined(OS_CHROMEOS) +TEST_F(UnifiedConsentServiceTest, ClearPrimaryAccountDisablesSomeServices) { + CreateConsentService(); + identity_test_environment_.SetPrimaryAccount("testaccount"); + + // Precondition: Enable unified consent. + pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, true); + EXPECT_TRUE(AreAllNonPersonalizedServicesEnabled()); + + // Clearing primary account revokes unfied consent and a couple of other + // non-personalized services. + identity_test_environment_.ClearPrimaryAccount(); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_FALSE(AreAllNonPersonalizedServicesEnabled()); + EXPECT_FALSE(pref_service_.GetBoolean( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled)); + EXPECT_EQ(service_client_->GetServiceState(Service::kSpellCheck), + ServiceState::kDisabled); + EXPECT_EQ( + service_client_->GetServiceState(Service::kSafeBrowsingExtendedReporting), + ServiceState::kDisabled); + + // Consent is not revoked for the following services. + EXPECT_EQ(service_client_->GetServiceState(Service::kAlternateErrorPages), + ServiceState::kEnabled); + EXPECT_EQ(service_client_->GetServiceState(Service::kMetricsReporting), + ServiceState::kEnabled); + EXPECT_EQ(service_client_->GetServiceState(Service::kNetworkPrediction), + ServiceState::kEnabled); + EXPECT_EQ(service_client_->GetServiceState(Service::kSearchSuggest), + ServiceState::kEnabled); + EXPECT_EQ(service_client_->GetServiceState(Service::kSafeBrowsing), + ServiceState::kEnabled); +} + +TEST_F(UnifiedConsentServiceTest, Migration_NotSignedIn) { + base::HistogramTester histogram_tester; + + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + + CreateConsentService(); + // Since there were not inconsistencies, the migration is completed after the + // creation of the consent service. + EXPECT_EQ(GetMigrationState(), unified_consent::MigrationState::kCompleted); + // The suppress reason for not showing the consent bump should be recorded. + histogram_tester.ExpectBucketCount( + "UnifiedConsent.ConsentBump.SuppressReason", + unified_consent::ConsentBumpSuppressReason::kNotSignedIn, 1); +} +#endif // !defined(OS_CHROMEOS) + +TEST_F(UnifiedConsentServiceTest, Rollback_WasSyncingEverything) { + identity_test_environment_.SetPrimaryAccount("testaccount"); + syncer::SyncPrefs sync_prefs(&pref_service_); + sync_service_.OnUserChoseDatatypes(true, syncer::UserSelectableTypes()); + EXPECT_TRUE(sync_prefs.HasKeepEverythingSynced()); + + // Migrate + CreateConsentService(true /* client services on by default */); + // Check expectations after migration. + EXPECT_FALSE(sync_prefs.HasKeepEverythingSynced()); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_EQ(unified_consent::MigrationState::kCompleted, GetMigrationState()); + EXPECT_TRUE(consent_service_->ShouldShowConsentBump()); + + consent_service_->Shutdown(); + consent_service_.reset(); + SetUnifiedConsentFeatureState(UnifiedConsentFeatureState::kDisabled); + + // Rollback + UnifiedConsentService::RollbackIfNeeded(&pref_service_, &sync_service_); + // Unified consent prefs should be cleared. + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_EQ(unified_consent::MigrationState::kNotInitialized, + GetMigrationState()); + // Sync everything should be back on. + EXPECT_TRUE(sync_prefs.HasKeepEverythingSynced()); + + // Run until idle so the RollbackHelper is deleted. + base::RunLoop().RunUntilIdle(); +} + +TEST_F(UnifiedConsentServiceTest, Rollback_WasNotSyncingEverything) { + identity_test_environment_.SetPrimaryAccount("testaccount"); + syncer::SyncPrefs sync_prefs(&pref_service_); + syncer::ModelTypeSet chosen_data_types = syncer::UserSelectableTypes(); + chosen_data_types.Remove(syncer::BOOKMARKS); + sync_service_.OnUserChoseDatatypes(false, chosen_data_types); + EXPECT_FALSE(sync_prefs.HasKeepEverythingSynced()); + EXPECT_FALSE(sync_service_.GetPreferredDataTypes().HasAll( + syncer::UserSelectableTypes())); + + // Migrate + CreateConsentService(); + // Check expectations after migration. + EXPECT_FALSE(sync_prefs.HasKeepEverythingSynced()); + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_EQ(unified_consent::MigrationState::kCompleted, GetMigrationState()); + + consent_service_->Shutdown(); + consent_service_.reset(); + + // Rollback + UnifiedConsentService::RollbackIfNeeded(&pref_service_, &sync_service_); + // Unified consent prefs should be cleared. + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kUnifiedConsentGiven)); + EXPECT_EQ(unified_consent::MigrationState::kNotInitialized, + GetMigrationState()); + + // Sync everything should be off because not all user types were on. + EXPECT_FALSE(sync_prefs.HasKeepEverythingSynced()); + + // Run until idle so the RollbackHelper is deleted. + base::RunLoop().RunUntilIdle(); +} + +TEST_F(UnifiedConsentServiceTest, SettingsHistogram_None) { + base::HistogramTester histogram_tester; + // Disable all services. + sync_service_.OnUserChoseDatatypes(false, syncer::ModelTypeSet()); + CreateConsentService(); + + histogram_tester.ExpectUniqueSample( + "UnifiedConsent.SyncAndGoogleServicesSettings", + SettingsHistogramValue::kNone, 1); +} + +TEST_F(UnifiedConsentServiceTest, SettingsHistogram_UnifiedConsentGiven) { + base::HistogramTester histogram_tester; + // Unified consent is given. + identity_test_environment_.SetPrimaryAccount("testaccount"); + pref_service_.SetInteger( + prefs::kUnifiedConsentMigrationState, + static_cast<int>(unified_consent::MigrationState::kCompleted)); + pref_service_.SetBoolean(prefs::kUnifiedConsentGiven, true); + CreateConsentService(true); + + histogram_tester.ExpectBucketCount( + "UnifiedConsent.SyncAndGoogleServicesSettings", + SettingsHistogramValue::kNone, 0); + histogram_tester.ExpectBucketCount( + "UnifiedConsent.SyncAndGoogleServicesSettings", + SettingsHistogramValue::kUnifiedConsentGiven, 1); + histogram_tester.ExpectBucketCount( + "UnifiedConsent.SyncAndGoogleServicesSettings", + SettingsHistogramValue::kUserEvents, 1); + histogram_tester.ExpectBucketCount( + "UnifiedConsent.SyncAndGoogleServicesSettings", + SettingsHistogramValue::kUrlKeyedAnonymizedDataCollection, 1); + histogram_tester.ExpectBucketCount( + "UnifiedConsent.SyncAndGoogleServicesSettings", + SettingsHistogramValue::kSafeBrowsingExtendedReporting, 1); + histogram_tester.ExpectBucketCount( + "UnifiedConsent.SyncAndGoogleServicesSettings", + SettingsHistogramValue::kSpellCheck, 1); + histogram_tester.ExpectTotalCount( + "UnifiedConsent.SyncAndGoogleServicesSettings", 5); +} + +TEST_F(UnifiedConsentServiceTest, SettingsHistogram_NoUnifiedConsentGiven) { + base::HistogramTester histogram_tester; + // Unified consent is not given. Only spellcheck is enabled. + pref_service_.SetBoolean(kSpellCheckDummyEnabled, true); + CreateConsentService(); + + // kUserEvents should have no sample even though the sync preference is set, + // because the user is not signed in. + histogram_tester.ExpectUniqueSample( + "UnifiedConsent.SyncAndGoogleServicesSettings", + SettingsHistogramValue::kSpellCheck, 1); +} + +} // namespace unified_consent diff --git a/chromium/components/unified_consent/url_keyed_data_collection_consent_helper.cc b/chromium/components/unified_consent/url_keyed_data_collection_consent_helper.cc new file mode 100644 index 00000000000..17f2e88e7a2 --- /dev/null +++ b/chromium/components/unified_consent/url_keyed_data_collection_consent_helper.cc @@ -0,0 +1,191 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/unified_consent/url_keyed_data_collection_consent_helper.h" + +#include "base/bind.h" +#include "components/prefs/pref_change_registrar.h" +#include "components/prefs/pref_service.h" +#include "components/sync/base/model_type.h" +#include "components/sync/driver/sync_service.h" +#include "components/sync/driver/sync_service_observer.h" +#include "components/sync/driver/sync_service_utils.h" +#include "components/unified_consent/pref_names.h" + +#include <map> +#include <set> + +namespace unified_consent { + +namespace { + +class PrefBasedUrlKeyedDataCollectionConsentHelper + : public UrlKeyedDataCollectionConsentHelper { + public: + explicit PrefBasedUrlKeyedDataCollectionConsentHelper( + PrefService* pref_service); + ~PrefBasedUrlKeyedDataCollectionConsentHelper() override = default; + + // UrlKeyedDataCollectionConsentHelper: + bool IsEnabled() override; + + private: + void OnPrefChanged(); + PrefService* pref_service_; // weak (must outlive this) + PrefChangeRegistrar pref_change_registrar_; + + DISALLOW_COPY_AND_ASSIGN(PrefBasedUrlKeyedDataCollectionConsentHelper); +}; + +class SyncBasedUrlKeyedDataCollectionConsentHelper + : public UrlKeyedDataCollectionConsentHelper, + syncer::SyncServiceObserver { + public: + SyncBasedUrlKeyedDataCollectionConsentHelper( + syncer::SyncService* sync_service, + std::set<syncer::ModelType> sync_data_types); + ~SyncBasedUrlKeyedDataCollectionConsentHelper() override; + + // UrlKeyedDataCollectionConsentHelper: + bool IsEnabled() override; + + // syncer::SyncServiceObserver: + void OnStateChanged(syncer::SyncService* sync) override; + void OnSyncShutdown(syncer::SyncService* sync) override; + + private: + void UpdateSyncDataTypeStates(); + + syncer::SyncService* sync_service_; + std::map<syncer::ModelType, syncer::UploadState> sync_data_type_states_; + + DISALLOW_COPY_AND_ASSIGN(SyncBasedUrlKeyedDataCollectionConsentHelper); +}; + +PrefBasedUrlKeyedDataCollectionConsentHelper:: + PrefBasedUrlKeyedDataCollectionConsentHelper(PrefService* pref_service) + : pref_service_(pref_service) { + pref_change_registrar_.Init(pref_service_); + pref_change_registrar_.Add( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled, + base::BindRepeating( + &PrefBasedUrlKeyedDataCollectionConsentHelper::OnPrefChanged, + base::Unretained(this))); +} + +bool PrefBasedUrlKeyedDataCollectionConsentHelper::IsEnabled() { + return pref_service_->GetBoolean( + prefs::kUrlKeyedAnonymizedDataCollectionEnabled); +} + +void PrefBasedUrlKeyedDataCollectionConsentHelper::OnPrefChanged() { + FireOnStateChanged(); +} + +SyncBasedUrlKeyedDataCollectionConsentHelper:: + SyncBasedUrlKeyedDataCollectionConsentHelper( + syncer::SyncService* sync_service, + std::set<syncer::ModelType> sync_data_types) + : sync_service_(sync_service) { + DCHECK(!sync_data_types.empty()); + + for (const auto& sync_data_type : sync_data_types) { + sync_data_type_states_[sync_data_type] = syncer::UploadState::NOT_ACTIVE; + } + UpdateSyncDataTypeStates(); + + if (sync_service_) + sync_service_->AddObserver(this); +} + +SyncBasedUrlKeyedDataCollectionConsentHelper:: + ~SyncBasedUrlKeyedDataCollectionConsentHelper() { + if (sync_service_) + sync_service_->RemoveObserver(this); +} + +bool SyncBasedUrlKeyedDataCollectionConsentHelper::IsEnabled() { + for (const auto& sync_data_type_states : sync_data_type_states_) { + if (sync_data_type_states.second != syncer::UploadState::ACTIVE) + return false; + } + return true; +} + +void SyncBasedUrlKeyedDataCollectionConsentHelper::OnStateChanged( + syncer::SyncService* sync_service) { + DCHECK_EQ(sync_service_, sync_service); + bool enabled_before_state_updated = IsEnabled(); + UpdateSyncDataTypeStates(); + if (enabled_before_state_updated != IsEnabled()) + FireOnStateChanged(); +} + +void SyncBasedUrlKeyedDataCollectionConsentHelper::OnSyncShutdown( + syncer::SyncService* sync_service) { + DCHECK_EQ(sync_service_, sync_service); + sync_service_->RemoveObserver(this); + sync_service_ = nullptr; +} + +void SyncBasedUrlKeyedDataCollectionConsentHelper::UpdateSyncDataTypeStates() { + for (auto iter = sync_data_type_states_.begin(); + iter != sync_data_type_states_.end(); ++iter) { + iter->second = syncer::GetUploadToGoogleState(sync_service_, iter->first); + } +} + +} // namespace + +UrlKeyedDataCollectionConsentHelper::UrlKeyedDataCollectionConsentHelper() = + default; +UrlKeyedDataCollectionConsentHelper::~UrlKeyedDataCollectionConsentHelper() = + default; + +// static +std::unique_ptr<UrlKeyedDataCollectionConsentHelper> +UrlKeyedDataCollectionConsentHelper::NewAnonymizedDataCollectionConsentHelper( + bool is_unified_consent_enabled, + PrefService* pref_service, + syncer::SyncService* sync_service) { + if (is_unified_consent_enabled) { + return std::make_unique<PrefBasedUrlKeyedDataCollectionConsentHelper>( + pref_service); + } + + return std::make_unique<SyncBasedUrlKeyedDataCollectionConsentHelper>( + sync_service, std::set<syncer::ModelType>( + {syncer::ModelType::HISTORY_DELETE_DIRECTIVES})); +} + +// static +std::unique_ptr<UrlKeyedDataCollectionConsentHelper> +UrlKeyedDataCollectionConsentHelper::NewPersonalizedDataCollectionConsentHelper( + bool is_unified_consent_enabled, + syncer::SyncService* sync_service) { + if (is_unified_consent_enabled) { + return std::make_unique<SyncBasedUrlKeyedDataCollectionConsentHelper>( + sync_service, std::set<syncer::ModelType>( + {syncer::ModelType::HISTORY_DELETE_DIRECTIVES, + syncer::ModelType::USER_EVENTS})); + } else { + return std::make_unique<SyncBasedUrlKeyedDataCollectionConsentHelper>( + sync_service, std::set<syncer::ModelType>( + {syncer::ModelType::HISTORY_DELETE_DIRECTIVES})); + } +} + +void UrlKeyedDataCollectionConsentHelper::AddObserver(Observer* observer) { + observer_list_.AddObserver(observer); +} +void UrlKeyedDataCollectionConsentHelper::RemoveObserver(Observer* observer) { + observer_list_.RemoveObserver(observer); +} + +void UrlKeyedDataCollectionConsentHelper::FireOnStateChanged() { + for (auto& observer : observer_list_) + observer.OnUrlKeyedDataCollectionConsentStateChanged(this); +} + +} // namespace unified_consent diff --git a/chromium/components/unified_consent/url_keyed_data_collection_consent_helper.h b/chromium/components/unified_consent/url_keyed_data_collection_consent_helper.h new file mode 100644 index 00000000000..f4d8486ab18 --- /dev/null +++ b/chromium/components/unified_consent/url_keyed_data_collection_consent_helper.h @@ -0,0 +1,88 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_UNIFIED_CONSENT_URL_KEYED_DATA_COLLECTION_CONSENT_HELPER_H_ +#define COMPONENTS_UNIFIED_CONSENT_URL_KEYED_DATA_COLLECTION_CONSENT_HELPER_H_ + +#include <memory> + +#include "base/observer_list.h" + +class PrefService; +namespace syncer { +class SyncService; +} + +namespace unified_consent { + +// Helper class that allows clients to check whether the user has consented +// for URL-keyed data collection. +class UrlKeyedDataCollectionConsentHelper { + public: + class Observer { + public: + // Called when the state of the URL-keyed data collection changes. + virtual void OnUrlKeyedDataCollectionConsentStateChanged( + UrlKeyedDataCollectionConsentHelper* consent_helper) = 0; + }; + + // Creates a new |UrlKeyedDataCollectionConsentHelper| instance that checks + // whether *anonymized* data collection is enabled. This should be used when + // the client needs to check whether the user has granted consent for + // *anonymized* URL-keyed data collection. + // + // Implementation-wise we distinguish the following cases: + // 1. If |is_unified_consent_enabled| true, then the instance is backed by + // |pref_service|. Url-keyed data collection is enabled if the preference + // |prefs::kUrlKeyedAnonymizedDataCollectionEnabled| is set to true. + // + // 2. If |is_unified_consent_enabled| is false, then the instance is backed by + // the sync service. Url-keyed data collection is enabled if sync is active + // and if sync history is enabled. + // + // Note: |pref_service| must outlive the retuned instance. + static std::unique_ptr<UrlKeyedDataCollectionConsentHelper> + NewAnonymizedDataCollectionConsentHelper(bool is_unified_consent_enabled, + PrefService* pref_service, + syncer::SyncService* sync_service); + + // Creates a new |UrlKeyedDataCollectionConsentHelper| instance that checks + // whether *personalized* data collection is enabled. This should be used when + // the client needs to check whether the user has granted consent for + // URL-keyed data collection keyed by their Google account. + // + // Implementation-wise we distinguish the following cases: + // 1. If |is_unified_consent_enabled| is true then URL-keyed data collection + // is enabled if sync is active and if sync event logger is enabled. + // 2. If |is_unified_consent_enabled| is false then URL-keyed data collection + // is enabled if sync is active and if sync history is enabled. + static std::unique_ptr<UrlKeyedDataCollectionConsentHelper> + NewPersonalizedDataCollectionConsentHelper(bool is_unified_consent_enabled, + syncer::SyncService* sync_service); + + virtual ~UrlKeyedDataCollectionConsentHelper(); + + // Returns true if the user has consented for URL keyed anonymized data + // collection. + virtual bool IsEnabled() = 0; + + // Methods to register or remove observers. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + protected: + UrlKeyedDataCollectionConsentHelper(); + + // Fires |OnUrlKeyedDataCollectionConsentStateChanged| on all the observers. + void FireOnStateChanged(); + + private: + base::ObserverList<Observer, true> observer_list_; + + DISALLOW_COPY_AND_ASSIGN(UrlKeyedDataCollectionConsentHelper); +}; + +} // namespace unified_consent + +#endif // COMPONENTS_UNIFIED_CONSENT_URL_KEYED_DATA_COLLECTION_CONSENT_HELPER_H_ diff --git a/chromium/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc b/chromium/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc new file mode 100644 index 00000000000..6359b80f460 --- /dev/null +++ b/chromium/components/unified_consent/url_keyed_data_collection_consent_helper_unittest.cc @@ -0,0 +1,220 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/unified_consent/url_keyed_data_collection_consent_helper.h" + +#include <vector> + +#include "components/sync/driver/fake_sync_service.h" +#include "components/sync/engine/cycle/sync_cycle_snapshot.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" +#include "components/unified_consent/pref_names.h" +#include "components/unified_consent/unified_consent_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace unified_consent { +namespace { + +class TestSyncService : public syncer::FakeSyncService { + public: + void set_sync_initialized(bool sync_initialized) { + sync_initialized_ = sync_initialized; + } + void AddActiveDataType(syncer::ModelType type) { + sync_active_data_types_.Put(type); + } + void ClearActiveDataTypes() { sync_active_data_types_.Clear(); } + void FireOnStateChangeOnAllObservers() { + for (auto& observer : observers_) + observer.OnStateChanged(this); + } + + // syncer::FakeSyncService: + int GetDisableReasons() const override { return DISABLE_REASON_NONE; } + syncer::ModelTypeSet GetPreferredDataTypes() const override { + return syncer::ModelTypeSet(syncer::ModelType::HISTORY_DELETE_DIRECTIVES, + syncer::ModelType::USER_EVENTS, + syncer::ModelType::EXTENSIONS); + } + bool IsFirstSetupComplete() const override { return true; } + bool IsEngineInitialized() const override { return true; } + + syncer::SyncCycleSnapshot GetLastCycleSnapshot() const override { + if (!sync_initialized_) + return syncer::SyncCycleSnapshot(); + return syncer::SyncCycleSnapshot( + syncer::ModelNeutralState(), syncer::ProgressMarkerMap(), false, 5, 2, + 7, false, 0, base::Time::Now(), base::Time::Now(), + std::vector<int>(syncer::MODEL_TYPE_COUNT, 0), + std::vector<int>(syncer::MODEL_TYPE_COUNT, 0), + sync_pb::SyncEnums::UNKNOWN_ORIGIN, + /*short_poll_interval=*/base::TimeDelta::FromMinutes(30), + /*long_poll_interval=*/base::TimeDelta::FromMinutes(180), + /*has_remaining_local_changes=*/false); + } + + syncer::ModelTypeSet GetActiveDataTypes() const override { + return sync_active_data_types_; + } + + void AddObserver(syncer::SyncServiceObserver* observer) override { + observers_.AddObserver(observer); + } + void RemoveObserver(syncer::SyncServiceObserver* observer) override { + observers_.RemoveObserver(observer); + } + + private: + bool sync_initialized_ = false; + syncer::ModelTypeSet sync_active_data_types_; + base::ObserverList<syncer::SyncServiceObserver> observers_; +}; + +class UrlKeyedDataCollectionConsentHelperTest + : public testing::Test, + public UrlKeyedDataCollectionConsentHelper::Observer { + public: + // testing::Test: + void SetUp() override { + UnifiedConsentService::RegisterPrefs(pref_service_.registry()); + } + + void OnUrlKeyedDataCollectionConsentStateChanged( + UrlKeyedDataCollectionConsentHelper* consent_helper) override { + state_changed_notifications.push_back(consent_helper->IsEnabled()); + } + + protected: + sync_preferences::TestingPrefServiceSyncable pref_service_; + std::vector<bool> state_changed_notifications; + TestSyncService sync_service_; +}; + +TEST_F(UrlKeyedDataCollectionConsentHelperTest, + AnonymizedDataCollection_UnifiedConsentEnabled) { + std::unique_ptr<UrlKeyedDataCollectionConsentHelper> helper = + UrlKeyedDataCollectionConsentHelper:: + NewAnonymizedDataCollectionConsentHelper(true, &pref_service_, + &sync_service_); + helper->AddObserver(this); + EXPECT_FALSE(helper->IsEnabled()); + EXPECT_TRUE(state_changed_notifications.empty()); + + pref_service_.SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled, + true); + EXPECT_TRUE(helper->IsEnabled()); + ASSERT_EQ(1U, state_changed_notifications.size()); + EXPECT_TRUE(state_changed_notifications[0]); + + state_changed_notifications.clear(); + pref_service_.SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled, + false); + EXPECT_FALSE(helper->IsEnabled()); + ASSERT_EQ(1U, state_changed_notifications.size()); + EXPECT_FALSE(state_changed_notifications[0]); + helper->RemoveObserver(this); +} + +TEST_F(UrlKeyedDataCollectionConsentHelperTest, + AnonymizedDataCollection_UnifiedConsentDisabled) { + std::unique_ptr<UrlKeyedDataCollectionConsentHelper> helper = + UrlKeyedDataCollectionConsentHelper:: + NewAnonymizedDataCollectionConsentHelper(false, &pref_service_, + &sync_service_); + helper->AddObserver(this); + EXPECT_FALSE(helper->IsEnabled()); + EXPECT_TRUE(state_changed_notifications.empty()); + + sync_service_.set_sync_initialized(true); + sync_service_.AddActiveDataType(syncer::ModelType::HISTORY_DELETE_DIRECTIVES); + sync_service_.FireOnStateChangeOnAllObservers(); + EXPECT_TRUE(helper->IsEnabled()); + EXPECT_EQ(1U, state_changed_notifications.size()); + helper->RemoveObserver(this); +} + +TEST_F(UrlKeyedDataCollectionConsentHelperTest, + AnonymizedDataCollection_UnifiedConsentDisabled_NullSyncService) { + std::unique_ptr<UrlKeyedDataCollectionConsentHelper> helper = + UrlKeyedDataCollectionConsentHelper:: + NewAnonymizedDataCollectionConsentHelper( + false /* is_unified_consent_enabled */, &pref_service_, + nullptr /* sync_service */); + EXPECT_FALSE(helper->IsEnabled()); +} + +TEST_F(UrlKeyedDataCollectionConsentHelperTest, + PersonalizeddDataCollection_UnifiedConsentEnabled) { + std::unique_ptr<UrlKeyedDataCollectionConsentHelper> helper = + UrlKeyedDataCollectionConsentHelper:: + NewPersonalizedDataCollectionConsentHelper(true, &sync_service_); + helper->AddObserver(this); + EXPECT_FALSE(helper->IsEnabled()); + EXPECT_TRUE(state_changed_notifications.empty()); + sync_service_.set_sync_initialized(true); + + // Peronalized data collection is disabled when only USER_EVENTS are enabled. + sync_service_.AddActiveDataType(syncer::ModelType::USER_EVENTS); + sync_service_.FireOnStateChangeOnAllObservers(); + EXPECT_FALSE(helper->IsEnabled()); + EXPECT_TRUE(state_changed_notifications.empty()); + + // Peronalized data collection is disabled when only HISTORY_DELETE_DIRECTIVES + // are enabled. + sync_service_.ClearActiveDataTypes(); + sync_service_.AddActiveDataType(syncer::ModelType::HISTORY_DELETE_DIRECTIVES); + sync_service_.FireOnStateChangeOnAllObservers(); + EXPECT_FALSE(helper->IsEnabled()); + EXPECT_TRUE(state_changed_notifications.empty()); + + // Personalized data collection is enabled iff USER_EVENTS and + // HISTORY_DELETE_DIRECTIVES are enabled. + sync_service_.ClearActiveDataTypes(); + sync_service_.AddActiveDataType(syncer::ModelType::HISTORY_DELETE_DIRECTIVES); + sync_service_.AddActiveDataType(syncer::ModelType::USER_EVENTS); + sync_service_.FireOnStateChangeOnAllObservers(); + EXPECT_TRUE(helper->IsEnabled()); + EXPECT_EQ(1U, state_changed_notifications.size()); + helper->RemoveObserver(this); +} + +TEST_F(UrlKeyedDataCollectionConsentHelperTest, + PersonalizedDataCollection_UnifiedConsentDisabled) { + std::unique_ptr<UrlKeyedDataCollectionConsentHelper> helper = + UrlKeyedDataCollectionConsentHelper:: + NewPersonalizedDataCollectionConsentHelper(false, &sync_service_); + helper->AddObserver(this); + EXPECT_FALSE(helper->IsEnabled()); + EXPECT_TRUE(state_changed_notifications.empty()); + + sync_service_.set_sync_initialized(true); + sync_service_.AddActiveDataType(syncer::ModelType::HISTORY_DELETE_DIRECTIVES); + sync_service_.FireOnStateChangeOnAllObservers(); + EXPECT_TRUE(helper->IsEnabled()); + EXPECT_EQ(1U, state_changed_notifications.size()); + helper->RemoveObserver(this); +} + +TEST_F(UrlKeyedDataCollectionConsentHelperTest, + PersonalizedDataCollection_NullSyncService) { + { + std::unique_ptr<UrlKeyedDataCollectionConsentHelper> helper = + UrlKeyedDataCollectionConsentHelper:: + NewPersonalizedDataCollectionConsentHelper( + false /* is_unified_consent_enabled */, + nullptr /* sync_service */); + EXPECT_FALSE(helper->IsEnabled()); + } + { + std::unique_ptr<UrlKeyedDataCollectionConsentHelper> helper = + UrlKeyedDataCollectionConsentHelper:: + NewPersonalizedDataCollectionConsentHelper( + true /* is_unified_consent_enabled */, + nullptr /* sync_service */); + EXPECT_FALSE(helper->IsEnabled()); + } +} + +} // namespace +} // namespace unified_consent |