Stack sampling profiler: add fire-and-forget interface
[chromium-blink-merge.git] / components / signin / core / browser / account_reconcilor.cc
blob934458da1d24b04edd00f3d552bf55d521d4df7d
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/signin/core/browser/account_reconcilor.h"
7 #include <algorithm>
9 #include "base/bind.h"
10 #include "base/json/json_reader.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "components/signin/core/browser/profile_oauth2_token_service.h"
17 #include "components/signin/core/browser/signin_client.h"
18 #include "components/signin/core/browser/signin_metrics.h"
19 #include "components/signin/core/common/profile_management_switches.h"
20 #include "google_apis/gaia/gaia_auth_util.h"
21 #include "google_apis/gaia/gaia_oauth_client.h"
22 #include "google_apis/gaia/gaia_urls.h"
25 namespace {
27 class AccountEqualToFunc : public std::equal_to<gaia::ListedAccount> {
28 public:
29 bool operator()(const gaia::ListedAccount& p1,
30 const gaia::ListedAccount& p2) const;
33 bool AccountEqualToFunc::operator()(
34 const gaia::ListedAccount& p1,
35 const gaia::ListedAccount& p2) const {
36 return p1.valid == p2.valid && p1.id == p2.id;
39 gaia::ListedAccount AccountForId(const std::string& account_id) {
40 gaia::ListedAccount account;
41 account.id = account_id;
42 account.gaia_id = std::string();
43 account.email = std::string();
44 account.valid = true;
45 return account;
48 } // namespace
51 AccountReconcilor::AccountReconcilor(
52 ProfileOAuth2TokenService* token_service,
53 SigninManagerBase* signin_manager,
54 SigninClient* client,
55 GaiaCookieManagerService* cookie_manager_service)
56 : token_service_(token_service),
57 signin_manager_(signin_manager),
58 client_(client),
59 cookie_manager_service_(cookie_manager_service),
60 registered_with_token_service_(false),
61 registered_with_cookie_manager_service_(false),
62 registered_with_content_settings_(false),
63 is_reconcile_started_(false),
64 first_execution_(true),
65 error_during_last_reconcile_(false),
66 chrome_accounts_changed_(false) {
67 VLOG(1) << "AccountReconcilor::AccountReconcilor";
70 AccountReconcilor::~AccountReconcilor() {
71 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
72 // Make sure shutdown was called first.
73 DCHECK(!registered_with_token_service_);
74 DCHECK(!registered_with_cookie_manager_service_);
77 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
78 VLOG(1) << "AccountReconcilor::Initialize";
79 RegisterWithSigninManager();
81 // If this user is not signed in, the reconcilor should do nothing but
82 // wait for signin.
83 if (IsProfileConnected()) {
84 RegisterWithCookieManagerService();
85 RegisterWithContentSettings();
86 RegisterWithTokenService();
88 // Start a reconcile if the tokens are already loaded.
89 if (start_reconcile_if_tokens_available &&
90 token_service_->GetAccounts().size() > 0) {
91 StartReconcile();
96 void AccountReconcilor::Shutdown() {
97 VLOG(1) << "AccountReconcilor::Shutdown";
98 UnregisterWithCookieManagerService();
99 UnregisterWithSigninManager();
100 UnregisterWithTokenService();
101 UnregisterWithContentSettings();
104 void AccountReconcilor::RegisterWithSigninManager() {
105 signin_manager_->AddObserver(this);
108 void AccountReconcilor::UnregisterWithSigninManager() {
109 signin_manager_->RemoveObserver(this);
112 void AccountReconcilor::RegisterWithContentSettings() {
113 VLOG(1) << "AccountReconcilor::RegisterWithContentSettings";
114 // During re-auth, the reconcilor will get a callback about successful signin
115 // even when the profile is already connected. Avoid re-registering
116 // with the token service since this will DCHECK.
117 if (registered_with_content_settings_)
118 return;
120 client_->AddContentSettingsObserver(this);
121 registered_with_content_settings_ = true;
124 void AccountReconcilor::UnregisterWithContentSettings() {
125 VLOG(1) << "AccountReconcilor::UnregisterWithContentSettings";
126 if (!registered_with_content_settings_)
127 return;
129 client_->RemoveContentSettingsObserver(this);
130 registered_with_content_settings_ = false;
133 void AccountReconcilor::RegisterWithTokenService() {
134 VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
135 // During re-auth, the reconcilor will get a callback about successful signin
136 // even when the profile is already connected. Avoid re-registering
137 // with the token service since this will DCHECK.
138 if (registered_with_token_service_)
139 return;
141 token_service_->AddObserver(this);
142 registered_with_token_service_ = true;
145 void AccountReconcilor::UnregisterWithTokenService() {
146 VLOG(1) << "AccountReconcilor::UnregisterWithTokenService";
147 if (!registered_with_token_service_)
148 return;
150 token_service_->RemoveObserver(this);
151 registered_with_token_service_ = false;
154 void AccountReconcilor::RegisterWithCookieManagerService() {
155 VLOG(1) << "AccountReconcilor::RegisterWithCookieManagerService";
156 // During re-auth, the reconcilor will get a callback about successful signin
157 // even when the profile is already connected. Avoid re-registering
158 // with the helper since this will DCHECK.
159 if (registered_with_cookie_manager_service_)
160 return;
162 cookie_manager_service_->AddObserver(this);
163 registered_with_cookie_manager_service_ = true;
165 void AccountReconcilor::UnregisterWithCookieManagerService() {
166 VLOG(1) << "AccountReconcilor::UnregisterWithCookieManagerService";
167 if (!registered_with_cookie_manager_service_)
168 return;
170 cookie_manager_service_->RemoveObserver(this);
171 registered_with_cookie_manager_service_ = false;
174 bool AccountReconcilor::IsProfileConnected() {
175 return signin_manager_->IsAuthenticated();
178 signin_metrics::AccountReconcilorState AccountReconcilor::GetState() {
179 if (!is_reconcile_started_) {
180 return error_during_last_reconcile_
181 ? signin_metrics::ACCOUNT_RECONCILOR_ERROR
182 : signin_metrics::ACCOUNT_RECONCILOR_OK;
185 return signin_metrics::ACCOUNT_RECONCILOR_RUNNING;
188 void AccountReconcilor::OnContentSettingChanged(
189 const ContentSettingsPattern& primary_pattern,
190 const ContentSettingsPattern& secondary_pattern,
191 ContentSettingsType content_type,
192 std::string resource_identifier) {
193 // If this is not a change to cookie settings, just ignore.
194 if (content_type != CONTENT_SETTINGS_TYPE_COOKIES)
195 return;
197 // If this does not affect GAIA, just ignore. If the primary pattern is
198 // invalid, then assume it could affect GAIA. The secondary pattern is
199 // not needed.
200 if (primary_pattern.IsValid() &&
201 !primary_pattern.Matches(GaiaUrls::GetInstance()->gaia_url())) {
202 return;
205 VLOG(1) << "AccountReconcilor::OnContentSettingChanged";
206 StartReconcile();
209 void AccountReconcilor::OnEndBatchChanges() {
210 VLOG(1) << "AccountReconcilor::OnEndBatchChanges. "
211 << "Reconcilor state: " << is_reconcile_started_;
212 // Remember that accounts have changed if a reconcile is already started.
213 chrome_accounts_changed_ = is_reconcile_started_;
214 StartReconcile();
217 void AccountReconcilor::GoogleSigninSucceeded(const std::string& account_id,
218 const std::string& username,
219 const std::string& password) {
220 VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
221 RegisterWithCookieManagerService();
222 RegisterWithContentSettings();
223 RegisterWithTokenService();
226 void AccountReconcilor::GoogleSignedOut(const std::string& account_id,
227 const std::string& username) {
228 VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
229 AbortReconcile();
230 UnregisterWithCookieManagerService();
231 UnregisterWithTokenService();
232 UnregisterWithContentSettings();
233 PerformLogoutAllAccountsAction();
236 void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
237 if (!switches::IsEnableAccountConsistency()) {
238 MarkAccountAsAddedToCookie(account_id);
239 return;
241 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
242 cookie_manager_service_->AddAccountToCookie(account_id);
245 void AccountReconcilor::PerformLogoutAllAccountsAction() {
246 if (!switches::IsEnableAccountConsistency())
247 return;
248 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
249 cookie_manager_service_->LogOutAllAccounts();
252 void AccountReconcilor::StartReconcile() {
253 if (!IsProfileConnected() || !client_->AreSigninCookiesAllowed()) {
254 VLOG(1) << "AccountReconcilor::StartReconcile: !connected or no cookies";
255 return;
258 if (is_reconcile_started_)
259 return;
261 is_reconcile_started_ = true;
262 error_during_last_reconcile_ = false;
264 // Reset state for validating gaia cookie.
265 gaia_accounts_.clear();
267 // Reset state for validating oauth2 tokens.
268 primary_account_.clear();
269 chrome_accounts_.clear();
270 add_to_cookie_.clear();
271 ValidateAccountsFromTokenService();
273 // Rely on the GCMS to manage calls to and responses from ListAccounts.
274 if (cookie_manager_service_->ListAccounts(&gaia_accounts_)) {
275 OnGaiaAccountsInCookieUpdated(
276 gaia_accounts_, GoogleServiceAuthError(GoogleServiceAuthError::NONE));
280 void AccountReconcilor::OnGaiaAccountsInCookieUpdated(
281 const std::vector<gaia::ListedAccount>& accounts,
282 const GoogleServiceAuthError& error) {
283 VLOG(1) << "AccountReconcilor::OnGaiaAccountsInCookieUpdated: "
284 << "CookieJar " << accounts.size() << " accounts, "
285 << "Reconcilor's state is " << is_reconcile_started_ << ", "
286 << "Error was " << error.ToString();
287 if (error.state() == GoogleServiceAuthError::NONE) {
288 gaia_accounts_ = accounts;
290 // It is possible that O2RT is not available at this moment.
291 if (token_service_->GetAccounts().empty())
292 return;
294 is_reconcile_started_ ? FinishReconcile() : StartReconcile();
295 } else {
296 if (is_reconcile_started_)
297 error_during_last_reconcile_ = true;
298 AbortReconcile();
302 void AccountReconcilor::ValidateAccountsFromTokenService() {
303 primary_account_ = signin_manager_->GetAuthenticatedAccountId();
304 DCHECK(!primary_account_.empty());
306 chrome_accounts_ = token_service_->GetAccounts();
308 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
309 << "Chrome " << chrome_accounts_.size() << " accounts, "
310 << "Primary is '" << primary_account_ << "'";
313 void AccountReconcilor::OnNewProfileManagementFlagChanged(
314 bool new_flag_status) {
315 if (new_flag_status) {
316 // The reconciler may have been newly created just before this call, or may
317 // have already existed and in mid-reconcile. To err on the safe side, force
318 // a restart.
319 Shutdown();
320 Initialize(true);
321 } else {
322 Shutdown();
326 void AccountReconcilor::FinishReconcile() {
327 VLOG(1) << "AccountReconcilor::FinishReconcile";
328 DCHECK(add_to_cookie_.empty());
329 int number_gaia_accounts = gaia_accounts_.size();
330 bool are_primaries_equal = number_gaia_accounts > 0 &&
331 primary_account_ == gaia_accounts_[0].id;
333 // If there are any accounts in the gaia cookie but not in chrome, then
334 // those accounts need to be removed from the cookie. This means we need
335 // to blow the cookie away.
336 int removed_from_cookie = 0;
337 for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
338 if (gaia_accounts_[i].valid &&
339 chrome_accounts_.end() == std::find(chrome_accounts_.begin(),
340 chrome_accounts_.end(),
341 gaia_accounts_[i].id)) {
342 ++removed_from_cookie;
346 bool rebuild_cookie = !are_primaries_equal || removed_from_cookie > 0;
347 std::vector<gaia::ListedAccount> original_gaia_accounts =
348 gaia_accounts_;
349 if (rebuild_cookie) {
350 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
351 // Really messed up state. Blow away the gaia cookie completely and
352 // rebuild it, making sure the primary account as specified by the
353 // SigninManager is the first session in the gaia cookie.
354 PerformLogoutAllAccountsAction();
355 gaia_accounts_.clear();
358 // Create a list of accounts that need to be added to the gaia cookie.
359 // The primary account must be first to make sure it becomes the default
360 // account in the case where chrome is completely rebuilding the cookie.
361 add_to_cookie_.push_back(primary_account_);
362 for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
363 if (chrome_accounts_[i] != primary_account_)
364 add_to_cookie_.push_back(chrome_accounts_[i]);
367 // For each account known to chrome, PerformMergeAction() if the account is
368 // not already in the cookie jar or its state is invalid, or signal merge
369 // completed otherwise. Make a copy of |add_to_cookie_| since calls to
370 // SignalComplete() will change the array.
371 std::vector<std::string> add_to_cookie_copy = add_to_cookie_;
372 int added_to_cookie = 0;
373 for (size_t i = 0; i < add_to_cookie_copy.size(); ++i) {
374 if (gaia_accounts_.end() !=
375 std::find_if(gaia_accounts_.begin(),
376 gaia_accounts_.end(),
377 std::bind1st(AccountEqualToFunc(),
378 AccountForId(add_to_cookie_copy[i])))) {
379 cookie_manager_service_->SignalComplete(
380 add_to_cookie_copy[i],
381 GoogleServiceAuthError::AuthErrorNone());
382 } else {
383 PerformMergeAction(add_to_cookie_copy[i]);
384 if (original_gaia_accounts.end() ==
385 std::find_if(original_gaia_accounts.begin(),
386 original_gaia_accounts.end(),
387 std::bind1st(AccountEqualToFunc(),
388 AccountForId(add_to_cookie_copy[i])))) {
389 added_to_cookie++;
394 signin_metrics::LogSigninAccountReconciliation(chrome_accounts_.size(),
395 added_to_cookie,
396 removed_from_cookie,
397 are_primaries_equal,
398 first_execution_,
399 number_gaia_accounts);
400 first_execution_ = false;
401 CalculateIfReconcileIsDone();
402 ScheduleStartReconcileIfChromeAccountsChanged();
405 void AccountReconcilor::AbortReconcile() {
406 VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
407 add_to_cookie_.clear();
408 CalculateIfReconcileIsDone();
411 void AccountReconcilor::CalculateIfReconcileIsDone() {
412 is_reconcile_started_ = !add_to_cookie_.empty();
413 if (!is_reconcile_started_)
414 VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
417 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
418 if (is_reconcile_started_)
419 return;
421 // Start a reconcile as the token accounts have changed.
422 VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
423 if (chrome_accounts_changed_) {
424 chrome_accounts_changed_ = false;
425 base::ThreadTaskRunnerHandle::Get()->PostTask(
426 FROM_HERE,
427 base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
431 // Remove the account from the list that is being merged.
432 bool AccountReconcilor::MarkAccountAsAddedToCookie(
433 const std::string& account_id) {
434 for (std::vector<std::string>::iterator i = add_to_cookie_.begin();
435 i != add_to_cookie_.end();
436 ++i) {
437 if (account_id == *i) {
438 add_to_cookie_.erase(i);
439 return true;
442 return false;
445 void AccountReconcilor::OnAddAccountToCookieCompleted(
446 const std::string& account_id,
447 const GoogleServiceAuthError& error) {
448 VLOG(1) << "AccountReconcilor::OnAddAccountToCookieCompleted: "
449 << "Account added: " << account_id << ", "
450 << "Error was " << error.ToString();
451 // Always listens to GaiaCookieManagerService. Only proceed if reconciling.
452 if (is_reconcile_started_ && MarkAccountAsAddedToCookie(account_id)) {
453 if (error.state() != GoogleServiceAuthError::State::NONE)
454 error_during_last_reconcile_ = true;
455 CalculateIfReconcileIsDone();
456 ScheduleStartReconcileIfChromeAccountsChanged();