Explicitly add python-numpy dependency to install-build-deps.
[chromium-blink-merge.git] / components / signin / core / browser / account_reconcilor.cc
blobafe79542177401d90306e3478e1791f564fa34c4
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/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/message_loop/message_loop_proxy.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "components/signin/core/browser/profile_oauth2_token_service.h"
16 #include "components/signin/core/browser/signin_client.h"
17 #include "components/signin/core/browser/signin_metrics.h"
18 #include "components/signin/core/common/profile_management_switches.h"
19 #include "google_apis/gaia/gaia_auth_fetcher.h"
20 #include "google_apis/gaia/gaia_auth_util.h"
21 #include "google_apis/gaia/gaia_constants.h"
22 #include "google_apis/gaia/gaia_oauth_client.h"
23 #include "google_apis/gaia/gaia_urls.h"
24 #include "net/cookies/canonical_cookie.h"
27 namespace {
29 class EmailEqualToFunc : public std::equal_to<std::pair<std::string, bool> > {
30 public:
31 bool operator()(const std::pair<std::string, bool>& p1,
32 const std::pair<std::string, bool>& p2) const;
35 bool EmailEqualToFunc::operator()(
36 const std::pair<std::string, bool>& p1,
37 const std::pair<std::string, bool>& p2) const {
38 return p1.second == p2.second && gaia::AreEmailsSame(p1.first, p2.first);
41 class AreEmailsSameFunc : public std::equal_to<std::string> {
42 public:
43 bool operator()(const std::string& p1,
44 const std::string& p2) const;
47 bool AreEmailsSameFunc::operator()(
48 const std::string& p1,
49 const std::string& p2) const {
50 return gaia::AreEmailsSame(p1, p2);
53 } // namespace
56 AccountReconcilor::AccountReconcilor(ProfileOAuth2TokenService* token_service,
57 SigninManagerBase* signin_manager,
58 SigninClient* client)
59 : token_service_(token_service),
60 signin_manager_(signin_manager),
61 client_(client),
62 merge_session_helper_(token_service_,
63 GaiaConstants::kReconcilorSource,
64 client->GetURLRequestContext(),
65 this),
66 registered_with_token_service_(false),
67 is_reconcile_started_(false),
68 first_execution_(true),
69 are_gaia_accounts_set_(false) {
70 VLOG(1) << "AccountReconcilor::AccountReconcilor";
73 AccountReconcilor::~AccountReconcilor() {
74 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
75 // Make sure shutdown was called first.
76 DCHECK(!registered_with_token_service_);
79 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
80 VLOG(1) << "AccountReconcilor::Initialize";
81 RegisterWithSigninManager();
83 // If this user is not signed in, the reconcilor should do nothing but
84 // wait for signin.
85 if (IsProfileConnected()) {
86 RegisterForCookieChanges();
87 RegisterWithTokenService();
89 // Start a reconcile if the tokens are already loaded.
90 if (start_reconcile_if_tokens_available &&
91 token_service_->GetAccounts().size() > 0) {
92 StartReconcile();
97 void AccountReconcilor::Shutdown() {
98 VLOG(1) << "AccountReconcilor::Shutdown";
99 merge_session_helper_.CancelAll();
100 merge_session_helper_.RemoveObserver(this);
101 gaia_fetcher_.reset();
102 get_gaia_accounts_callbacks_.clear();
103 UnregisterWithSigninManager();
104 UnregisterWithTokenService();
105 UnregisterForCookieChanges();
108 void AccountReconcilor::AddMergeSessionObserver(
109 MergeSessionHelper::Observer* observer) {
110 merge_session_helper_.AddObserver(observer);
113 void AccountReconcilor::RemoveMergeSessionObserver(
114 MergeSessionHelper::Observer* observer) {
115 merge_session_helper_.RemoveObserver(observer);
118 void AccountReconcilor::RegisterForCookieChanges() {
119 // First clear any existing registration to avoid DCHECKs that can otherwise
120 // go off in some embedders on reauth (e.g., ChromeSigninClient).
121 UnregisterForCookieChanges();
122 cookie_changed_subscription_ = client_->AddCookieChangedCallback(
123 GaiaUrls::GetInstance()->gaia_url(),
124 "LSID",
125 base::Bind(&AccountReconcilor::OnCookieChanged, base::Unretained(this)));
128 void AccountReconcilor::UnregisterForCookieChanges() {
129 cookie_changed_subscription_.reset();
132 void AccountReconcilor::RegisterWithSigninManager() {
133 signin_manager_->AddObserver(this);
136 void AccountReconcilor::UnregisterWithSigninManager() {
137 signin_manager_->RemoveObserver(this);
140 void AccountReconcilor::RegisterWithTokenService() {
141 VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
142 // During re-auth, the reconcilor will get a callback about successful signin
143 // even when the profile is already connected. Avoid re-registering
144 // with the token service since this will DCHECK.
145 if (registered_with_token_service_)
146 return;
148 token_service_->AddObserver(this);
149 registered_with_token_service_ = true;
152 void AccountReconcilor::UnregisterWithTokenService() {
153 if (!registered_with_token_service_)
154 return;
156 token_service_->RemoveObserver(this);
157 registered_with_token_service_ = false;
160 bool AccountReconcilor::IsProfileConnected() {
161 return signin_manager_->IsAuthenticated();
164 void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie& cookie,
165 bool removed) {
166 DCHECK_EQ("LSID", cookie.Name());
167 DCHECK_EQ(GaiaUrls::GetInstance()->gaia_url().host(), cookie.Domain());
168 if (cookie.IsSecure() && cookie.IsHttpOnly()) {
169 VLOG(1) << "AccountReconcilor::OnCookieChanged: LSID changed";
171 // It is possible that O2RT is not available at this moment.
172 if (!token_service_->GetAccounts().size()) {
173 VLOG(1) << "AccountReconcilor::OnCookieChanged: cookie change is ingored"
174 "because O2RT is not available yet.";
175 return;
178 StartReconcile();
182 void AccountReconcilor::OnEndBatchChanges() {
183 VLOG(1) << "AccountReconcilor::OnEndBatchChanges";
184 StartReconcile();
187 void AccountReconcilor::GoogleSigninSucceeded(const std::string& account_id,
188 const std::string& username,
189 const std::string& password) {
190 VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
191 RegisterForCookieChanges();
192 RegisterWithTokenService();
195 void AccountReconcilor::GoogleSignedOut(const std::string& account_id,
196 const std::string& username) {
197 VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
198 gaia_fetcher_.reset();
199 get_gaia_accounts_callbacks_.clear();
200 AbortReconcile();
201 UnregisterWithTokenService();
202 UnregisterForCookieChanges();
203 PerformLogoutAllAccountsAction();
206 void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
207 if (!switches::IsEnableAccountConsistency()) {
208 MarkAccountAsAddedToCookie(account_id);
209 return;
211 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
212 merge_session_helper_.LogIn(account_id);
215 void AccountReconcilor::PerformLogoutAllAccountsAction() {
216 if (!switches::IsEnableAccountConsistency())
217 return;
218 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
219 merge_session_helper_.LogOutAllAccounts();
222 void AccountReconcilor::StartReconcile() {
223 if (!IsProfileConnected() || is_reconcile_started_ ||
224 get_gaia_accounts_callbacks_.size() > 0 ||
225 merge_session_helper_.is_running())
226 return;
228 is_reconcile_started_ = true;
229 m_reconcile_start_time_ = base::Time::Now();
231 StartFetchingExternalCcResult();
233 // Reset state for validating gaia cookie.
234 are_gaia_accounts_set_ = false;
235 gaia_accounts_.clear();
237 // Reset state for validating oauth2 tokens.
238 primary_account_.clear();
239 chrome_accounts_.clear();
240 add_to_cookie_.clear();
241 ValidateAccountsFromTokenService();
243 // Start process by checking connections to external sites.
244 merge_session_helper_.StartFetchingExternalCcResult();
247 void AccountReconcilor::GetAccountsFromCookie(
248 GetAccountsFromCookieCallback callback) {
249 get_gaia_accounts_callbacks_.push_back(callback);
250 if (!gaia_fetcher_)
251 MayBeDoNextListAccounts();
254 void AccountReconcilor::StartFetchingExternalCcResult() {
255 merge_session_helper_.StartFetchingExternalCcResult();
258 void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
259 gaia_fetcher_.reset();
261 // Get account information from response data.
262 std::vector<std::pair<std::string, bool> > gaia_accounts;
263 bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts);
264 if (!valid_json) {
265 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
266 } else if (gaia_accounts.size() > 0) {
267 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
268 << "Gaia " << gaia_accounts.size() << " accounts, "
269 << "Primary is '" << gaia_accounts[0].first << "'";
270 } else {
271 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
274 // There must be at least one callback waiting for result.
275 DCHECK(!get_gaia_accounts_callbacks_.empty());
277 GoogleServiceAuthError error =
278 !valid_json ? GoogleServiceAuthError(
279 GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE)
280 : GoogleServiceAuthError::AuthErrorNone();
281 get_gaia_accounts_callbacks_.front().Run(error, gaia_accounts);
282 get_gaia_accounts_callbacks_.pop_front();
284 MayBeDoNextListAccounts();
287 void AccountReconcilor::OnListAccountsFailure(
288 const GoogleServiceAuthError& error) {
289 gaia_fetcher_.reset();
290 VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString();
291 std::vector<std::pair<std::string, bool> > empty_accounts;
293 // There must be at least one callback waiting for result.
294 DCHECK(!get_gaia_accounts_callbacks_.empty());
296 get_gaia_accounts_callbacks_.front().Run(error, empty_accounts);
297 get_gaia_accounts_callbacks_.pop_front();
299 MayBeDoNextListAccounts();
302 void AccountReconcilor::MayBeDoNextListAccounts() {
303 if (!get_gaia_accounts_callbacks_.empty()) {
304 gaia_fetcher_.reset(new GaiaAuthFetcher(
305 this, GaiaConstants::kReconcilorSource,
306 client_->GetURLRequestContext()));
307 gaia_fetcher_->StartListAccounts();
311 void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
312 const GoogleServiceAuthError& error,
313 const std::vector<std::pair<std::string, bool> >& accounts) {
314 if (error.state() == GoogleServiceAuthError::NONE) {
315 gaia_accounts_ = accounts;
316 are_gaia_accounts_set_ = true;
317 FinishReconcile();
318 } else {
319 AbortReconcile();
323 void AccountReconcilor::ValidateAccountsFromTokenService() {
324 primary_account_ = signin_manager_->GetAuthenticatedAccountId();
325 DCHECK(!primary_account_.empty());
327 chrome_accounts_ = token_service_->GetAccounts();
329 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
330 << "Chrome " << chrome_accounts_.size() << " accounts, "
331 << "Primary is '" << primary_account_ << "'";
334 void AccountReconcilor::OnNewProfileManagementFlagChanged(
335 bool new_flag_status) {
336 if (new_flag_status) {
337 // The reconciler may have been newly created just before this call, or may
338 // have already existed and in mid-reconcile. To err on the safe side, force
339 // a restart.
340 Shutdown();
341 Initialize(true);
342 } else {
343 Shutdown();
347 void AccountReconcilor::FinishReconcile() {
348 VLOG(1) << "AccountReconcilor::FinishReconcile";
349 DCHECK(are_gaia_accounts_set_);
350 DCHECK(add_to_cookie_.empty());
351 int number_gaia_accounts = gaia_accounts_.size();
352 bool are_primaries_equal = number_gaia_accounts > 0 &&
353 gaia::AreEmailsSame(primary_account_, gaia_accounts_[0].first);
355 // If there are any accounts in the gaia cookie but not in chrome, then
356 // those accounts need to be removed from the cookie. This means we need
357 // to blow the cookie away.
358 int removed_from_cookie = 0;
359 for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
360 const std::string& gaia_account = gaia_accounts_[i].first;
361 if (gaia_accounts_[i].second &&
362 chrome_accounts_.end() ==
363 std::find_if(chrome_accounts_.begin(),
364 chrome_accounts_.end(),
365 std::bind1st(AreEmailsSameFunc(), gaia_account))) {
366 ++removed_from_cookie;
370 bool rebuild_cookie = !are_primaries_equal || removed_from_cookie > 0;
371 std::vector<std::pair<std::string, bool> > original_gaia_accounts =
372 gaia_accounts_;
373 if (rebuild_cookie) {
374 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
375 // Really messed up state. Blow away the gaia cookie completely and
376 // rebuild it, making sure the primary account as specified by the
377 // SigninManager is the first session in the gaia cookie.
378 PerformLogoutAllAccountsAction();
379 gaia_accounts_.clear();
382 // Create a list of accounts that need to be added to the gaia cookie.
383 // The primary account must be first to make sure it becomes the default
384 // account in the case where chrome is completely rebuilding the cookie.
385 add_to_cookie_.push_back(primary_account_);
386 for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
387 if (chrome_accounts_[i] != primary_account_)
388 add_to_cookie_.push_back(chrome_accounts_[i]);
391 // For each account known to chrome, PerformMergeAction() if the account is
392 // not already in the cookie jar or its state is invalid, or signal merge
393 // completed otherwise. Make a copy of |add_to_cookie_| since calls to
394 // SignalComplete() will change the array.
395 std::vector<std::string> add_to_cookie_copy = add_to_cookie_;
396 int added_to_cookie = 0;
397 for (size_t i = 0; i < add_to_cookie_copy.size(); ++i) {
398 if (gaia_accounts_.end() !=
399 std::find_if(gaia_accounts_.begin(),
400 gaia_accounts_.end(),
401 std::bind1st(EmailEqualToFunc(),
402 std::make_pair(add_to_cookie_copy[i],
403 true)))) {
404 merge_session_helper_.SignalComplete(
405 add_to_cookie_copy[i],
406 GoogleServiceAuthError::AuthErrorNone());
407 } else {
408 PerformMergeAction(add_to_cookie_copy[i]);
409 if (original_gaia_accounts.end() ==
410 std::find_if(original_gaia_accounts.begin(),
411 original_gaia_accounts.end(),
412 std::bind1st(EmailEqualToFunc(),
413 std::make_pair(add_to_cookie_copy[i],
414 true)))) {
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 std::vector<std::string> reconciled_accounts(chrome_accounts_);
450 std::vector<std::string> new_chrome_accounts(token_service_->GetAccounts());
451 std::sort(reconciled_accounts.begin(), reconciled_accounts.end());
452 std::sort(new_chrome_accounts.begin(), new_chrome_accounts.end());
453 if (reconciled_accounts != new_chrome_accounts) {
454 base::MessageLoop::current()->PostTask(
455 FROM_HERE,
456 base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
460 // Remove the account from the list that is being merged.
461 bool AccountReconcilor::MarkAccountAsAddedToCookie(
462 const std::string& account_id) {
463 for (std::vector<std::string>::iterator i = add_to_cookie_.begin();
464 i != add_to_cookie_.end();
465 ++i) {
466 if (account_id == *i) {
467 add_to_cookie_.erase(i);
468 return true;
471 return false;
474 void AccountReconcilor::MergeSessionCompleted(
475 const std::string& account_id,
476 const GoogleServiceAuthError& error) {
477 VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
478 << account_id << " error=" << error.ToString();
480 if (MarkAccountAsAddedToCookie(account_id)) {
481 CalculateIfReconcileIsDone();
482 ScheduleStartReconcileIfChromeAccountsChanged();
486 void AccountReconcilor::GetCheckConnectionInfoCompleted(bool succeeded) {
487 base::TimeDelta time_to_check_connections =
488 base::Time::Now() - m_reconcile_start_time_;
489 signin_metrics::LogExternalCcResultFetches(succeeded,
490 time_to_check_connections);
491 GetAccountsFromCookie(base::Bind(
492 &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts,
493 base::Unretained(this)));