File: unified_consent_service.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (370 lines) | stat: -rw-r--r-- 14,345 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
// Copyright 2018 The Chromium Authors
// 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/check_op.h"
#include "build/build_config.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/sync/base/features.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "components/unified_consent/pref_names.h"

namespace unified_consent {

// static
UnifiedConsentService::SyncState UnifiedConsentService::GetSyncState(
    const syncer::SyncService* sync_service) {
  CHECK(
      base::FeatureList::IsEnabled(syncer::kReplaceSyncPromosWithSignInPromos));

  if (sync_service->HasDisableReason(
          syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN)) {
    return SyncState::kSignedOut;
  }

  if (!sync_service->GetUserSettings()->GetSelectedTypes().Has(
          syncer::UserSelectableType::kHistory)) {
    return SyncState::kSignedInWithoutHistory;
  }

  std::optional<syncer::PassphraseType> passphrase_type =
      sync_service->GetUserSettings()->GetPassphraseType();

  if (!passphrase_type.has_value()) {
    return SyncState::kSignedInWithHistoryWaitingForPassphrase;
  }

  if (syncer::IsExplicitPassphrase(*passphrase_type)) {
    return SyncState::kSignedInWithHistoryAndExplicitPassphrase;
  }

  return SyncState::kSignedInWithHistoryAndNoPassphrase;
}

// static
bool UnifiedConsentService::ShouldEnableUrlKeyedAnonymizedDataCollection(
    SyncState old_sync_state,
    SyncState new_sync_state) {
  CHECK(
      base::FeatureList::IsEnabled(syncer::kReplaceSyncPromosWithSignInPromos));

  // If nothing changed, leave UrlKeyedAnonymizedDataCollection alone.
  if (old_sync_state == new_sync_state) {
    return false;
  }

  // UrlKeyedAnonymizedDataCollection is only ever automatically enabled when
  // entering the history-on-without-passphrase or -waiting-for-passphrase
  // state.
  switch (new_sync_state) {
    case SyncState::kSignedOut:
    case SyncState::kSignedInWithoutHistory:
    case SyncState::kSignedInWithHistoryAndExplicitPassphrase:
      return false;
    case SyncState::kSignedInWithHistoryWaitingForPassphrase:
    case SyncState::kSignedInWithHistoryAndNoPassphrase:
      // UrlKeyedAnonymizedDataCollection can maybe be enabled, depending on
      // `old_sync_state`.
      // Note that `kSignedInWithHistoryWaitingForPassphrase` will *usually*
      // be entered from `kSignedOut`, but it's not the only possibility: It's
      // also possible to get there from any of the "signed-in" state, e.g. if
      // the user reset their sync data via the dashboard.
      break;
  }

  switch (old_sync_state) {
    case SyncState::kSignedOut:
    case SyncState::kSignedInWithoutHistory:
      // History got turned on, and there's no explicit passphrase, so
      // UrlKeyedAnonymizedDataCollection can be enabled.
      return true;
    case SyncState::kSignedInWithHistoryWaitingForPassphrase:
    case SyncState::kSignedInWithHistoryAndNoPassphrase:
      // UrlKeyedAnonymizedDataCollection would've been turned on based on the
      // old state already, so nothing to do here.
      return false;
    case SyncState::kSignedInWithHistoryAndExplicitPassphrase:
      // Explicit-passphrase was turned off, and history is on,
      // UrlKeyedAnonymizedDataCollection can be enabled.
      // Note that while turning off an explicit passphrase involves resetting
      // all the server-side data and starting over fresh, this process is not
      // expressed in the SyncState.
      return true;
  }
}

// static
bool UnifiedConsentService::ShouldDisableUrlKeyedAnonymizedDataCollection(
    SyncState old_sync_state,
    SyncState new_sync_state) {
  CHECK(
      base::FeatureList::IsEnabled(syncer::kReplaceSyncPromosWithSignInPromos));

  // If nothing changed, leave UrlKeyedAnonymizedDataCollection alone.
  if (old_sync_state == new_sync_state) {
    return false;
  }

  // UrlKeyedAnonymizedDataCollection only ever needs to be automatically
  // disabled if it was previously automatically enabled, which means the old
  // state was history-on-without-passphrase (or -waiting-for-passphrase) state.
  switch (old_sync_state) {
    case SyncState::kSignedOut:
    case SyncState::kSignedInWithoutHistory:
    case SyncState::kSignedInWithHistoryAndExplicitPassphrase:
      return false;
    case SyncState::kSignedInWithHistoryWaitingForPassphrase:
    case SyncState::kSignedInWithHistoryAndNoPassphrase:
      // UrlKeyedAnonymizedDataCollection might have to be disabled, depending
      // on `new_sync_state`.
      break;
  }

  switch (new_sync_state) {
    case SyncState::kSignedOut:
      // User signed out; UrlKeyedAnonymizedDataCollection should be disabled.
      return true;
    case SyncState::kSignedInWithoutHistory:
      // History was turned off, UrlKeyedAnonymizedDataCollection should be
      // disabled.
      return true;
    case SyncState::kSignedInWithHistoryAndNoPassphrase:
      // Passphrase state became known, and there's no explicit passphrase.
      // Nothing to be done.
      return false;
    case SyncState::kSignedInWithHistoryWaitingForPassphrase:
      // Nothing to do - wait until the passphrase state becomes known.
      // Note that this transition (from `kSignedInWithHistoryAndNoPassphrase`
      // to `kSignedInWithHistoryWaitingForPassphrase`) is not typically
      // possible, but it can happen if the local sync (meta)data gets reset and
      // sync starts over.
      return false;
    case SyncState::kSignedInWithHistoryAndExplicitPassphrase:
      // If explicit-passphrase was turned on, UrlKeyedAnonymizedDataCollection
      // should be disabled.
      return true;
  }
}

UnifiedConsentService::UnifiedConsentService(
    sync_preferences::PrefServiceSyncable* pref_service,
    signin::IdentityManager* identity_manager,
    syncer::SyncService* sync_service,
    const std::vector<std::string>& service_pref_names)
    : pref_service_(pref_service),
      identity_manager_(identity_manager),
      sync_service_(sync_service),
      service_pref_names_(service_pref_names) {
  DCHECK(pref_service_);
  DCHECK(identity_manager_);
  DCHECK(sync_service_);

#if BUILDFLAG(IS_CHROMEOS)
  if (GetMigrationState() == MigrationState::kNotInitialized)
    MigrateProfileToUnifiedConsent();
#endif

  if (base::FeatureList::IsEnabled(
          syncer::kReplaceSyncPromosWithSignInPromos)) {
    last_sync_state_ = GetSyncState(sync_service_);
  }

  pref_service_->AddObserver(this);
  identity_manager_->AddObserver(this);
  sync_service_->AddObserver(this);
}

UnifiedConsentService::~UnifiedConsentService() = default;

// static
void UnifiedConsentService::RegisterPrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterBooleanPref(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
                                false);
#if BUILDFLAG(IS_CHROMEOS)
  registry->RegisterIntegerPref(
      prefs::kUnifiedConsentMigrationState,
      static_cast<int>(MigrationState::kNotInitialized));
#endif
}

void UnifiedConsentService::SetUrlKeyedAnonymizedDataCollectionEnabled(
    bool enabled) {
#if BUILDFLAG(IS_CHROMEOS)
  if (GetMigrationState() != MigrationState::kCompleted)
    SetMigrationState(MigrationState::kCompleted);
#endif

  pref_service_->SetBoolean(prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
                            enabled);
}

void UnifiedConsentService::Shutdown() {
  pref_service_->RemoveObserver(this);
  identity_manager_->RemoveObserver(this);
  sync_service_->RemoveObserver(this);
}

void UnifiedConsentService::OnPrimaryAccountChanged(
    const signin::PrimaryAccountChangeEvent& event) {
  // TODO(crbug.com/40066949): Simplify once kSync becomes unreachable or is
  // deleted from the codebase. See ConsentLevel::kSync documentation for
  // details.
  if (event.GetEventTypeFor(signin::ConsentLevel::kSync) ==
      signin::PrimaryAccountChangeEvent::Type::kCleared) {
    // By design, clearing the primary account disables URL-keyed data
    // collection.
    SetUrlKeyedAnonymizedDataCollectionEnabled(false);
  }
}

void UnifiedConsentService::OnStateChanged(syncer::SyncService* sync) {
  // Update the UrlKeyedAnonymizedDataCollectionEnabled if user changed the
  // history opt-in state or the explicit-passphrase state.
  if (base::FeatureList::IsEnabled(
          syncer::kReplaceSyncPromosWithSignInPromos)) {
    const SyncState new_sync_state = GetSyncState(sync_service_);

    // Before updating the cached state, remember the old value, to detect
    // if anything changed.
    const SyncState old_sync_state = last_sync_state_;
    last_sync_state_ = new_sync_state;

    // Ignore syncing users - UrlKeyedAnonymizedDataCollectionEnabled is
    // updated based on their sync opt-in state.
    if (!sync_service_->HasSyncConsent()) {
      if (ShouldDisableUrlKeyedAnonymizedDataCollection(old_sync_state,
                                                        new_sync_state)) {
        SetUrlKeyedAnonymizedDataCollectionEnabled(false);
      } else if (ShouldEnableUrlKeyedAnonymizedDataCollection(old_sync_state,
                                                              new_sync_state)) {
        SetUrlKeyedAnonymizedDataCollectionEnabled(true);
      }
    }
  }

  // Start observing pref changes when the user enters sync setup.
  // Note: Only |sync->IsSetupInProgress()| is used (i.e. no check for
  // |IsInitialSyncFeatureSetupComplete()|), because on Android
  // |SetInitialSyncFeatureSetupComplete()| is called automatically during the
  // first setup, i.e. the value could change in the meantime.
  // TODO(crbug.com/40067025): Simplify (remove the following block) once
  // kReplaceSyncPromosWithSigninPromos is rolled out on all platforms, and thus
  // IsSetupInProgress() always returns false. See ConsentLevel::kSync
  // documentation for details.
  if (sync->IsSetupInProgress() && !pref_service_->IsSyncing()) {
    StartObservingServicePrefChanges();
  } else {
    StopObservingServicePrefChanges();

    // If the user cancelled the sync setup, clear all observed changes.
    if (!sync->CanSyncFeatureStart())
      service_pref_changes_.clear();
  }

#if BUILDFLAG(IS_CHROMEOS)
  // TODO(crbug.com/40066949): Simplify (remove the following block) after
  // Sync-the-feature users are migrated to ConsentLevel::kSignin (and thus
  // CanSyncFeatureStart() always returns false).
  if (!sync_service_->CanSyncFeatureStart() ||
      !sync_service_->IsEngineInitialized()) {
    return;
  }

  if (GetMigrationState() == MigrationState::kInProgressWaitForSyncInit)
    UpdateSettingsForMigration();
#endif
}

void UnifiedConsentService::OnIsSyncingChanged() {
  if (pref_service_->IsSyncing() && !service_pref_changes_.empty()) {
    // Re-apply all observed service pref changes.
    // If any service prefs had a value coming in through Sync, then that
    // would've overridden any changes that the user made during the first sync
    // setup. So re-apply the local changes to make sure they stick.
    for (const auto& pref_change : service_pref_changes_) {
      pref_service_->Set(pref_change.first, pref_change.second);
    }
    service_pref_changes_.clear();
  }
}

void UnifiedConsentService::StartObservingServicePrefChanges() {
  if (!service_pref_change_registrar_.IsEmpty())
    return;

  service_pref_change_registrar_.Init(pref_service_);
  for (const std::string& pref_name : service_pref_names_) {
    service_pref_change_registrar_.Add(
        pref_name,
        base::BindRepeating(&UnifiedConsentService::ServicePrefChanged,
                            base::Unretained(this)));
  }
}

void UnifiedConsentService::StopObservingServicePrefChanges() {
  service_pref_change_registrar_.RemoveAll();
}

void UnifiedConsentService::ServicePrefChanged(const std::string& name) {
  DCHECK(sync_service_->IsSetupInProgress());
  const base::Value& value = pref_service_->GetValue(name);
  service_pref_changes_[name] = value.Clone();
}

#if BUILDFLAG(IS_CHROMEOS)
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);

  // TODO(crbug.com/40066949): Simplify once kSync becomes unreachable or is
  // deleted from the codebase. See ConsentLevel::kSync documentation for
  // details.
  if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
    SetMigrationState(MigrationState::kCompleted);
    return;
  }

  UpdateSettingsForMigration();
}

void UnifiedConsentService::UpdateSettingsForMigration() {
  if (!sync_service_->IsEngineInitialized()) {
    SetMigrationState(MigrationState::kInProgressWaitForSyncInit);
    return;
  }

  // Set URL-keyed anonymized metrics to the state it had before unified
  // consent.
  // TODO(crbug.com/40066949): Simplify (remove the following block) after
  // Sync-the-feature users are migrated to ConsentLevel::kSignin, and thus
  // IsSyncFeatureEnabled() always returns false. (The UKM state for kSignin
  // users is set in OnStateChanged(), so no need for the logic here.)
  bool url_keyed_metrics_enabled =
      sync_service_->IsSyncFeatureEnabled() &&
      sync_service_->GetUserSettings()->GetSelectedTypes().Has(
          syncer::UserSelectableType::kHistory) &&
      !sync_service_->GetUserSettings()->IsUsingExplicitPassphrase();
  SetUrlKeyedAnonymizedDataCollectionEnabled(url_keyed_metrics_enabled);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  //  namespace unified_consent