Roll src/third_party/WebKit 9f7fb92:f103b33 (svn 202621:202622)
[chromium-blink-merge.git] / components / signin / core / browser / account_reconcilor.cc
blobf4969bb8dbf355809ab0ac7c48e6dffe2dcb08df
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"
24 namespace {
26 class AccountEqualToFunc : public std::equal_to<gaia::ListedAccount> {
27 public:
28 bool operator()(const gaia::ListedAccount& p1,
29 const gaia::ListedAccount& p2) const;
32 bool AccountEqualToFunc::operator()(
33 const gaia::ListedAccount& p1,
34 const gaia::ListedAccount& p2) const {
35 return p1.valid == p2.valid && p1.id == p2.id;
38 gaia::ListedAccount AccountForId(const std::string& account_id) {
39 gaia::ListedAccount account;
40 account.id = account_id;
41 account.gaia_id = std::string();
42 account.email = std::string();
43 account.valid = true;
44 return account;
47 } // namespace
50 AccountReconcilor::AccountReconcilor(
51 ProfileOAuth2TokenService* token_service,
52 SigninManagerBase* signin_manager,
53 SigninClient* client,
54 GaiaCookieManagerService* cookie_manager_service)
55 : token_service_(token_service),
56 signin_manager_(signin_manager),
57 client_(client),
58 cookie_manager_service_(cookie_manager_service),
59 registered_with_token_service_(false),
60 registered_with_cookie_manager_service_(false),
61 registered_with_content_settings_(false),
62 is_reconcile_started_(false),
63 first_execution_(true),
64 error_during_last_reconcile_(false),
65 chrome_accounts_changed_(false) {
66 VLOG(1) << "AccountReconcilor::AccountReconcilor";
69 AccountReconcilor::~AccountReconcilor() {
70 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
71 // Make sure shutdown was called first.
72 DCHECK(!registered_with_token_service_);
73 DCHECK(!registered_with_cookie_manager_service_);
76 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
77 VLOG(1) << "AccountReconcilor::Initialize";
78 RegisterWithSigninManager();
80 // If this user is not signed in, the reconcilor should do nothing but
81 // wait for signin.
82 if (IsProfileConnected()) {
83 RegisterWithCookieManagerService();
84 RegisterWithContentSettings();
85 RegisterWithTokenService();
87 // Start a reconcile if the tokens are already loaded.
88 if (start_reconcile_if_tokens_available &&
89 token_service_->GetAccounts().size() > 0) {
90 StartReconcile();
95 void AccountReconcilor::Shutdown() {
96 VLOG(1) << "AccountReconcilor::Shutdown";
97 UnregisterWithCookieManagerService();
98 UnregisterWithSigninManager();
99 UnregisterWithTokenService();
100 UnregisterWithContentSettings();
103 void AccountReconcilor::RegisterWithSigninManager() {
104 signin_manager_->AddObserver(this);
107 void AccountReconcilor::UnregisterWithSigninManager() {
108 signin_manager_->RemoveObserver(this);
111 void AccountReconcilor::RegisterWithContentSettings() {
112 VLOG(1) << "AccountReconcilor::RegisterWithContentSettings";
113 // During re-auth, the reconcilor will get a callback about successful signin
114 // even when the profile is already connected. Avoid re-registering
115 // with the token service since this will DCHECK.
116 if (registered_with_content_settings_)
117 return;
119 client_->AddContentSettingsObserver(this);
120 registered_with_content_settings_ = true;
123 void AccountReconcilor::UnregisterWithContentSettings() {
124 VLOG(1) << "AccountReconcilor::UnregisterWithContentSettings";
125 if (!registered_with_content_settings_)
126 return;
128 client_->RemoveContentSettingsObserver(this);
129 registered_with_content_settings_ = false;
132 void AccountReconcilor::RegisterWithTokenService() {
133 VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
134 // During re-auth, the reconcilor will get a callback about successful signin
135 // even when the profile is already connected. Avoid re-registering
136 // with the token service since this will DCHECK.
137 if (registered_with_token_service_)
138 return;
140 token_service_->AddObserver(this);
141 registered_with_token_service_ = true;
144 void AccountReconcilor::UnregisterWithTokenService() {
145 VLOG(1) << "AccountReconcilor::UnregisterWithTokenService";
146 if (!registered_with_token_service_)
147 return;
149 token_service_->RemoveObserver(this);
150 registered_with_token_service_ = false;
153 void AccountReconcilor::RegisterWithCookieManagerService() {
154 VLOG(1) << "AccountReconcilor::RegisterWithCookieManagerService";
155 // During re-auth, the reconcilor will get a callback about successful signin
156 // even when the profile is already connected. Avoid re-registering
157 // with the helper since this will DCHECK.
158 if (registered_with_cookie_manager_service_)
159 return;
161 cookie_manager_service_->AddObserver(this);
162 registered_with_cookie_manager_service_ = true;
164 void AccountReconcilor::UnregisterWithCookieManagerService() {
165 VLOG(1) << "AccountReconcilor::UnregisterWithCookieManagerService";
166 if (!registered_with_cookie_manager_service_)
167 return;
169 cookie_manager_service_->RemoveObserver(this);
170 registered_with_cookie_manager_service_ = false;
173 bool AccountReconcilor::IsProfileConnected() {
174 return signin_manager_->IsAuthenticated();
177 signin_metrics::AccountReconcilorState AccountReconcilor::GetState() {
178 if (!is_reconcile_started_) {
179 return error_during_last_reconcile_
180 ? signin_metrics::ACCOUNT_RECONCILOR_ERROR
181 : signin_metrics::ACCOUNT_RECONCILOR_OK;
184 return signin_metrics::ACCOUNT_RECONCILOR_RUNNING;
187 void AccountReconcilor::OnContentSettingChanged(
188 const ContentSettingsPattern& primary_pattern,
189 const ContentSettingsPattern& secondary_pattern,
190 ContentSettingsType content_type,
191 std::string resource_identifier) {
192 // If this is not a change to cookie settings, just ignore.
193 if (content_type != CONTENT_SETTINGS_TYPE_COOKIES)
194 return;
196 // If this does not affect GAIA, just ignore. If the primary pattern is
197 // invalid, then assume it could affect GAIA. The secondary pattern is
198 // not needed.
199 if (primary_pattern.IsValid() &&
200 !primary_pattern.Matches(GaiaUrls::GetInstance()->gaia_url())) {
201 return;
204 VLOG(1) << "AccountReconcilor::OnContentSettingChanged";
205 StartReconcile();
208 void AccountReconcilor::OnEndBatchChanges() {
209 VLOG(1) << "AccountReconcilor::OnEndBatchChanges. "
210 << "Reconcilor state: " << is_reconcile_started_;
211 // Remember that accounts have changed if a reconcile is already started.
212 chrome_accounts_changed_ = is_reconcile_started_;
213 StartReconcile();
216 void AccountReconcilor::GoogleSigninSucceeded(const std::string& account_id,
217 const std::string& username,
218 const std::string& password) {
219 VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
220 RegisterWithCookieManagerService();
221 RegisterWithContentSettings();
222 RegisterWithTokenService();
225 void AccountReconcilor::GoogleSignedOut(const std::string& account_id,
226 const std::string& username) {
227 VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
228 AbortReconcile();
229 UnregisterWithCookieManagerService();
230 UnregisterWithTokenService();
231 UnregisterWithContentSettings();
232 PerformLogoutAllAccountsAction();
235 void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
236 if (!switches::IsEnableAccountConsistency()) {
237 MarkAccountAsAddedToCookie(account_id);
238 return;
240 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
241 cookie_manager_service_->AddAccountToCookie(account_id);
244 void AccountReconcilor::PerformLogoutAllAccountsAction() {
245 if (!switches::IsEnableAccountConsistency())
246 return;
247 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
248 cookie_manager_service_->LogOutAllAccounts();
251 void AccountReconcilor::StartReconcile() {
252 if (!IsProfileConnected() || !client_->AreSigninCookiesAllowed()) {
253 VLOG(1) << "AccountReconcilor::StartReconcile: !connected or no cookies";
254 return;
257 if (is_reconcile_started_)
258 return;
260 // Reset state for validating gaia cookie.
261 gaia_accounts_.clear();
263 // Reset state for validating oauth2 tokens.
264 primary_account_.clear();
265 chrome_accounts_.clear();
266 add_to_cookie_.clear();
267 ValidateAccountsFromTokenService();
269 if (primary_account_.empty()) {
270 VLOG(1) << "AccountReconcilor::StartReconcile: primary has error";
271 return;
274 is_reconcile_started_ = true;
275 error_during_last_reconcile_ = false;
277 // Rely on the GCMS to manage calls to and responses from ListAccounts.
278 if (cookie_manager_service_->ListAccounts(&gaia_accounts_)) {
279 OnGaiaAccountsInCookieUpdated(
280 gaia_accounts_, GoogleServiceAuthError(GoogleServiceAuthError::NONE));
284 void AccountReconcilor::OnGaiaAccountsInCookieUpdated(
285 const std::vector<gaia::ListedAccount>& accounts,
286 const GoogleServiceAuthError& error) {
287 VLOG(1) << "AccountReconcilor::OnGaiaAccountsInCookieUpdated: "
288 << "CookieJar " << accounts.size() << " accounts, "
289 << "Reconcilor's state is " << is_reconcile_started_ << ", "
290 << "Error was " << error.ToString();
291 if (error.state() == GoogleServiceAuthError::NONE) {
292 gaia_accounts_ = accounts;
294 // It is possible that O2RT is not available at this moment.
295 if (token_service_->GetAccounts().empty())
296 return;
298 is_reconcile_started_ ? FinishReconcile() : StartReconcile();
299 } else {
300 if (is_reconcile_started_)
301 error_during_last_reconcile_ = true;
302 AbortReconcile();
306 void AccountReconcilor::ValidateAccountsFromTokenService() {
307 primary_account_ = signin_manager_->GetAuthenticatedAccountId();
308 DCHECK(!primary_account_.empty());
310 chrome_accounts_ = token_service_->GetAccounts();
312 // Remove any accounts that have an error. There is no point in trying to
313 // reconcile them, since it won't work anyway. If the list ends up being
314 // empty, or if the primary account is in error, then don't reconcile any
315 // accounts.
316 for (auto i = chrome_accounts_.begin(); i != chrome_accounts_.end(); ++i) {
317 if (token_service_->GetDelegate()->RefreshTokenHasError(*i)) {
318 if (primary_account_ == *i) {
319 primary_account_.clear();
320 chrome_accounts_.clear();
321 break;
322 } else {
323 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
324 << *i << " has error, won't reconcile";
325 i->clear();
329 chrome_accounts_.erase(std::remove(chrome_accounts_.begin(),
330 chrome_accounts_.end(),
331 std::string()),
332 chrome_accounts_.end());
334 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
335 << "Chrome " << chrome_accounts_.size() << " accounts, "
336 << "Primary is '" << primary_account_ << "'";
339 void AccountReconcilor::OnNewProfileManagementFlagChanged(
340 bool new_flag_status) {
341 if (new_flag_status) {
342 // The reconciler may have been newly created just before this call, or may
343 // have already existed and in mid-reconcile. To err on the safe side, force
344 // a restart.
345 Shutdown();
346 Initialize(true);
347 } else {
348 Shutdown();
352 void AccountReconcilor::FinishReconcile() {
353 VLOG(1) << "AccountReconcilor::FinishReconcile";
354 DCHECK(add_to_cookie_.empty());
355 int number_gaia_accounts = gaia_accounts_.size();
356 bool are_primaries_equal = number_gaia_accounts > 0 &&
357 primary_account_ == gaia_accounts_[0].id;
359 // If there are any accounts in the gaia cookie but not in chrome, then
360 // those accounts need to be removed from the cookie. This means we need
361 // to blow the cookie away.
362 int removed_from_cookie = 0;
363 for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
364 if (gaia_accounts_[i].valid &&
365 chrome_accounts_.end() == std::find(chrome_accounts_.begin(),
366 chrome_accounts_.end(),
367 gaia_accounts_[i].id)) {
368 ++removed_from_cookie;
372 bool rebuild_cookie = !are_primaries_equal || removed_from_cookie > 0;
373 std::vector<gaia::ListedAccount> original_gaia_accounts =
374 gaia_accounts_;
375 if (rebuild_cookie) {
376 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
377 // Really messed up state. Blow away the gaia cookie completely and
378 // rebuild it, making sure the primary account as specified by the
379 // SigninManager is the first session in the gaia cookie.
380 PerformLogoutAllAccountsAction();
381 gaia_accounts_.clear();
384 // Create a list of accounts that need to be added to the gaia cookie.
385 // The primary account must be first to make sure it becomes the default
386 // account in the case where chrome is completely rebuilding the cookie.
387 add_to_cookie_.push_back(primary_account_);
388 for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
389 if (chrome_accounts_[i] != primary_account_)
390 add_to_cookie_.push_back(chrome_accounts_[i]);
393 // For each account known to chrome, PerformMergeAction() if the account is
394 // not already in the cookie jar or its state is invalid, or signal merge
395 // completed otherwise. Make a copy of |add_to_cookie_| since calls to
396 // SignalComplete() will change the array.
397 std::vector<std::string> add_to_cookie_copy = add_to_cookie_;
398 int added_to_cookie = 0;
399 for (size_t i = 0; i < add_to_cookie_copy.size(); ++i) {
400 if (gaia_accounts_.end() !=
401 std::find_if(gaia_accounts_.begin(),
402 gaia_accounts_.end(),
403 std::bind1st(AccountEqualToFunc(),
404 AccountForId(add_to_cookie_copy[i])))) {
405 cookie_manager_service_->SignalComplete(
406 add_to_cookie_copy[i],
407 GoogleServiceAuthError::AuthErrorNone());
408 } else {
409 PerformMergeAction(add_to_cookie_copy[i]);
410 if (original_gaia_accounts.end() ==
411 std::find_if(original_gaia_accounts.begin(),
412 original_gaia_accounts.end(),
413 std::bind1st(AccountEqualToFunc(),
414 AccountForId(add_to_cookie_copy[i])))) {
415 added_to_cookie++;
420 signin_metrics::LogSigninAccountReconciliation(chrome_accounts_.size(),
421 added_to_cookie,
422 removed_from_cookie,
423 are_primaries_equal,
424 first_execution_,
425 number_gaia_accounts);
426 first_execution_ = false;
427 CalculateIfReconcileIsDone();
428 ScheduleStartReconcileIfChromeAccountsChanged();
431 void AccountReconcilor::AbortReconcile() {
432 VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
433 add_to_cookie_.clear();
434 CalculateIfReconcileIsDone();
437 void AccountReconcilor::CalculateIfReconcileIsDone() {
438 is_reconcile_started_ = !add_to_cookie_.empty();
439 if (!is_reconcile_started_)
440 VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
443 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
444 if (is_reconcile_started_)
445 return;
447 // Start a reconcile as the token accounts have changed.
448 VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
449 if (chrome_accounts_changed_) {
450 chrome_accounts_changed_ = false;
451 base::ThreadTaskRunnerHandle::Get()->PostTask(
452 FROM_HERE,
453 base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
457 // Remove the account from the list that is being merged.
458 bool AccountReconcilor::MarkAccountAsAddedToCookie(
459 const std::string& account_id) {
460 for (std::vector<std::string>::iterator i = add_to_cookie_.begin();
461 i != add_to_cookie_.end();
462 ++i) {
463 if (account_id == *i) {
464 add_to_cookie_.erase(i);
465 return true;
468 return false;
471 void AccountReconcilor::OnAddAccountToCookieCompleted(
472 const std::string& account_id,
473 const GoogleServiceAuthError& error) {
474 VLOG(1) << "AccountReconcilor::OnAddAccountToCookieCompleted: "
475 << "Account added: " << account_id << ", "
476 << "Error was " << error.ToString();
477 // Always listens to GaiaCookieManagerService. Only proceed if reconciling.
478 if (is_reconcile_started_ && MarkAccountAsAddedToCookie(account_id)) {
479 if (error.state() != GoogleServiceAuthError::State::NONE)
480 error_during_last_reconcile_ = true;
481 CalculateIfReconcileIsDone();
482 ScheduleStartReconcileIfChromeAccountsChanged();