• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "components/signin/core/browser/account_reconcilor.h"
6 
7 #include <algorithm>
8 
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 "base/time/time.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/browser/signin_oauth_helper.h"
20 #include "components/signin/core/common/profile_management_switches.h"
21 #include "google_apis/gaia/gaia_auth_fetcher.h"
22 #include "google_apis/gaia/gaia_auth_util.h"
23 #include "google_apis/gaia/gaia_constants.h"
24 #include "google_apis/gaia/gaia_oauth_client.h"
25 #include "google_apis/gaia/gaia_urls.h"
26 #include "net/cookies/canonical_cookie.h"
27 
28 
29 namespace {
30 
31 class EmailEqualToFunc : public std::equal_to<std::pair<std::string, bool> > {
32  public:
33   bool operator()(const std::pair<std::string, bool>& p1,
34                   const std::pair<std::string, bool>& p2) const;
35 };
36 
operator ()(const std::pair<std::string,bool> & p1,const std::pair<std::string,bool> & p2) const37 bool EmailEqualToFunc::operator()(
38     const std::pair<std::string, bool>& p1,
39     const std::pair<std::string, bool>& p2) const {
40   return p1.second == p2.second && gaia::AreEmailsSame(p1.first, p2.first);
41 }
42 
43 }  // namespace
44 
45 
46 // Fetches a refresh token from the given session in the GAIA cookie.  This is
47 // a best effort only.  If it should fail, another reconcile action will occur
48 // shortly anyway.
49 class AccountReconcilor::RefreshTokenFetcher
50     : public SigninOAuthHelper,
51       public SigninOAuthHelper::Consumer {
52  public:
53   RefreshTokenFetcher(AccountReconcilor* reconcilor,
54                       const std::string& account_id,
55                       int session_index);
~RefreshTokenFetcher()56   virtual ~RefreshTokenFetcher() {}
57 
58  private:
59   // Overridden from GaiaAuthConsumer:
60   virtual void OnSigninOAuthInformationAvailable(
61       const std::string& email,
62       const std::string& display_email,
63       const std::string& refresh_token) OVERRIDE;
64 
65   // Called when an error occurs while getting the information.
66   virtual void OnSigninOAuthInformationFailure(
67       const GoogleServiceAuthError& error) OVERRIDE;
68 
69   AccountReconcilor* reconcilor_;
70   const std::string account_id_;
71   int session_index_;
72 
73   DISALLOW_COPY_AND_ASSIGN(RefreshTokenFetcher);
74 };
75 
RefreshTokenFetcher(AccountReconcilor * reconcilor,const std::string & account_id,int session_index)76 AccountReconcilor::RefreshTokenFetcher::RefreshTokenFetcher(
77     AccountReconcilor* reconcilor,
78     const std::string& account_id,
79     int session_index)
80     : SigninOAuthHelper(reconcilor->client()->GetURLRequestContext(),
81                         base::IntToString(session_index),
82                         this),
83       reconcilor_(reconcilor),
84       account_id_(account_id),
85       session_index_(session_index) {
86   DCHECK(reconcilor_);
87   DCHECK(!account_id.empty());
88 }
89 
OnSigninOAuthInformationAvailable(const std::string & email,const std::string & display_email,const std::string & refresh_token)90 void AccountReconcilor::RefreshTokenFetcher::OnSigninOAuthInformationAvailable(
91     const std::string& email,
92     const std::string& display_email,
93     const std::string& refresh_token) {
94   VLOG(1) << "RefreshTokenFetcher::OnSigninOAuthInformationAvailable:"
95           << " account=" << account_id_ << " email=" << email
96           << " displayEmail=" << display_email;
97 
98   // TODO(rogerta): because of the problem with email vs displayEmail and
99   // emails that have been canonicalized, the argument |email| is used here
100   // to make sure the correct string is used when calling the token service.
101   // This will be cleaned up when chrome moves to using gaia obfuscated id.
102   reconcilor_->HandleRefreshTokenFetched(email, refresh_token);
103 }
104 
OnSigninOAuthInformationFailure(const GoogleServiceAuthError & error)105 void AccountReconcilor::RefreshTokenFetcher::OnSigninOAuthInformationFailure(
106     const GoogleServiceAuthError& error) {
107   VLOG(1) << "RefreshTokenFetcher::OnSigninOAuthInformationFailure:"
108           << " account=" << account_id_ << " session_index=" << session_index_;
109   reconcilor_->HandleRefreshTokenFetched(account_id_, std::string());
110 }
111 
operator ()(const std::string & s1,const std::string & s2) const112 bool AccountReconcilor::EmailLessFunc::operator()(const std::string& s1,
113                                                   const std::string& s2) const {
114   return gaia::CanonicalizeEmail(s1) < gaia::CanonicalizeEmail(s2);
115 }
116 
117 class AccountReconcilor::UserIdFetcher
118     : public gaia::GaiaOAuthClient::Delegate {
119  public:
120   UserIdFetcher(AccountReconcilor* reconcilor,
121                 const std::string& access_token,
122                 const std::string& account_id);
123 
124   // Returns the scopes needed by the UserIdFetcher.
125   static OAuth2TokenService::ScopeSet GetScopes();
126 
127  private:
128   // Overriden from gaia::GaiaOAuthClient::Delegate.
129   virtual void OnGetUserIdResponse(const std::string& user_id) OVERRIDE;
130   virtual void OnOAuthError() OVERRIDE;
131   virtual void OnNetworkError(int response_code) OVERRIDE;
132 
133   AccountReconcilor* const reconcilor_;
134   const std::string account_id_;
135   const std::string access_token_;
136   gaia::GaiaOAuthClient gaia_auth_client_;
137 
138   DISALLOW_COPY_AND_ASSIGN(UserIdFetcher);
139 };
140 
UserIdFetcher(AccountReconcilor * reconcilor,const std::string & access_token,const std::string & account_id)141 AccountReconcilor::UserIdFetcher::UserIdFetcher(AccountReconcilor* reconcilor,
142                                                 const std::string& access_token,
143                                                 const std::string& account_id)
144     : reconcilor_(reconcilor),
145       account_id_(account_id),
146       access_token_(access_token),
147       gaia_auth_client_(reconcilor_->client()->GetURLRequestContext()) {
148   DCHECK(reconcilor_);
149   DCHECK(!account_id_.empty());
150 
151   const int kMaxRetries = 5;
152   gaia_auth_client_.GetUserId(access_token_, kMaxRetries, this);
153 }
154 
155 // static
GetScopes()156 OAuth2TokenService::ScopeSet AccountReconcilor::UserIdFetcher::GetScopes() {
157   OAuth2TokenService::ScopeSet scopes;
158   scopes.insert("https://www.googleapis.com/auth/userinfo.profile");
159   return scopes;
160 }
161 
OnGetUserIdResponse(const std::string & user_id)162 void AccountReconcilor::UserIdFetcher::OnGetUserIdResponse(
163     const std::string& user_id) {
164   VLOG(1) << "AccountReconcilor::OnGetUserIdResponse: " << account_id_;
165 
166   // HandleSuccessfulAccountIdCheck() may delete |this|, so call it last.
167   reconcilor_->HandleSuccessfulAccountIdCheck(account_id_);
168 }
169 
OnOAuthError()170 void AccountReconcilor::UserIdFetcher::OnOAuthError() {
171   VLOG(1) << "AccountReconcilor::OnOAuthError: " << account_id_;
172 
173   // Invalidate the access token to force a refetch next time.
174   reconcilor_->token_service()->InvalidateToken(
175       account_id_, GetScopes(), access_token_);
176 
177   // HandleFailedAccountIdCheck() may delete |this|, so call it last.
178   reconcilor_->HandleFailedAccountIdCheck(account_id_);
179 }
180 
OnNetworkError(int response_code)181 void AccountReconcilor::UserIdFetcher::OnNetworkError(int response_code) {
182   VLOG(1) << "AccountReconcilor::OnNetworkError: " << account_id_
183           << " response_code=" << response_code;
184 
185   // TODO(rogerta): some response error should not be treated like
186   // permanent errors.  Figure out appropriate ones.
187   // HandleFailedAccountIdCheck() may delete |this|, so call it last.
188   reconcilor_->HandleFailedAccountIdCheck(account_id_);
189 }
190 
AccountReconcilor(ProfileOAuth2TokenService * token_service,SigninManagerBase * signin_manager,SigninClient * client)191 AccountReconcilor::AccountReconcilor(ProfileOAuth2TokenService* token_service,
192                                      SigninManagerBase* signin_manager,
193                                      SigninClient* client)
194     : OAuth2TokenService::Consumer("account_reconcilor"),
195       token_service_(token_service),
196       signin_manager_(signin_manager),
197       client_(client),
198       merge_session_helper_(token_service_,
199                             client->GetURLRequestContext(),
200                             this),
201       registered_with_token_service_(false),
202       is_reconcile_started_(false),
203       first_execution_(true),
204       are_gaia_accounts_set_(false),
205       requests_(NULL) {
206   VLOG(1) << "AccountReconcilor::AccountReconcilor";
207 }
208 
~AccountReconcilor()209 AccountReconcilor::~AccountReconcilor() {
210   VLOG(1) << "AccountReconcilor::~AccountReconcilor";
211   // Make sure shutdown was called first.
212   DCHECK(!registered_with_token_service_);
213   DCHECK(!requests_);
214   DCHECK_EQ(0u, user_id_fetchers_.size());
215   DCHECK_EQ(0u, refresh_token_fetchers_.size());
216 }
217 
Initialize(bool start_reconcile_if_tokens_available)218 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
219   VLOG(1) << "AccountReconcilor::Initialize";
220   RegisterWithSigninManager();
221 
222   // If this user is not signed in, the reconcilor should do nothing but
223   // wait for signin.
224   if (IsProfileConnected()) {
225     RegisterForCookieChanges();
226     RegisterWithTokenService();
227 
228     // Start a reconcile if the tokens are already loaded.
229     if (start_reconcile_if_tokens_available &&
230         token_service_->GetAccounts().size() > 0) {
231       StartReconcile();
232     }
233   }
234 }
235 
Shutdown()236 void AccountReconcilor::Shutdown() {
237   VLOG(1) << "AccountReconcilor::Shutdown";
238   merge_session_helper_.CancelAll();
239   merge_session_helper_.RemoveObserver(this);
240   gaia_fetcher_.reset();
241   get_gaia_accounts_callbacks_.clear();
242   DeleteFetchers();
243   UnregisterWithSigninManager();
244   UnregisterWithTokenService();
245   UnregisterForCookieChanges();
246 }
247 
AddMergeSessionObserver(MergeSessionHelper::Observer * observer)248 void AccountReconcilor::AddMergeSessionObserver(
249     MergeSessionHelper::Observer* observer) {
250   merge_session_helper_.AddObserver(observer);
251 }
252 
RemoveMergeSessionObserver(MergeSessionHelper::Observer * observer)253 void AccountReconcilor::RemoveMergeSessionObserver(
254     MergeSessionHelper::Observer* observer) {
255   merge_session_helper_.RemoveObserver(observer);
256 }
257 
DeleteFetchers()258 void AccountReconcilor::DeleteFetchers() {
259   delete[] requests_;
260   requests_ = NULL;
261 
262   user_id_fetchers_.clear();
263   refresh_token_fetchers_.clear();
264 }
265 
AreAllRefreshTokensChecked() const266 bool AccountReconcilor::AreAllRefreshTokensChecked() const {
267   return chrome_accounts_.size() ==
268          (valid_chrome_accounts_.size() + invalid_chrome_accounts_.size());
269 }
270 
RegisterForCookieChanges()271 void AccountReconcilor::RegisterForCookieChanges() {
272   // First clear any existing registration to avoid DCHECKs that can otherwise
273   // go off in some embedders on reauth (e.g., ChromeSigninClient).
274   UnregisterForCookieChanges();
275   client_->SetCookieChangedCallback(
276       base::Bind(&AccountReconcilor::OnCookieChanged, base::Unretained(this)));
277 }
278 
UnregisterForCookieChanges()279 void AccountReconcilor::UnregisterForCookieChanges() {
280   client_->SetCookieChangedCallback(SigninClient::CookieChangedCallback());
281 }
282 
RegisterWithSigninManager()283 void AccountReconcilor::RegisterWithSigninManager() {
284   signin_manager_->AddObserver(this);
285 }
286 
UnregisterWithSigninManager()287 void AccountReconcilor::UnregisterWithSigninManager() {
288   signin_manager_->RemoveObserver(this);
289 }
290 
RegisterWithTokenService()291 void AccountReconcilor::RegisterWithTokenService() {
292   VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
293   // During re-auth, the reconcilor will get a callback about successful signin
294   // even when the profile is already connected.  Avoid re-registering
295   // with the token service since this will DCHECK.
296   if (registered_with_token_service_)
297     return;
298 
299   token_service_->AddObserver(this);
300   registered_with_token_service_ = true;
301 }
302 
UnregisterWithTokenService()303 void AccountReconcilor::UnregisterWithTokenService() {
304   if (!registered_with_token_service_)
305     return;
306 
307   token_service_->RemoveObserver(this);
308   registered_with_token_service_ = false;
309 }
310 
IsProfileConnected()311 bool AccountReconcilor::IsProfileConnected() {
312   return !signin_manager_->GetAuthenticatedUsername().empty();
313 }
314 
OnCookieChanged(const net::CanonicalCookie * cookie)315 void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie* cookie) {
316   if (cookie->Name() == "LSID" &&
317       cookie->Domain() == GaiaUrls::GetInstance()->gaia_url().host() &&
318       cookie->IsSecure() && cookie->IsHttpOnly()) {
319     VLOG(1) << "AccountReconcilor::OnCookieChanged: LSID changed";
320 
321     // It is possible that O2RT is not available at this moment.
322     if (!token_service_->GetAccounts().size()) {
323       VLOG(1) << "AccountReconcilor::OnCookieChanged: cookie change is ingored"
324                  "because O2RT is not available yet.";
325       return;
326     }
327 
328     StartReconcile();
329   }
330 }
331 
OnRefreshTokenAvailable(const std::string & account_id)332 void AccountReconcilor::OnRefreshTokenAvailable(const std::string& account_id) {
333   VLOG(1) << "AccountReconcilor::OnRefreshTokenAvailable: " << account_id;
334   StartReconcile();
335 }
336 
OnRefreshTokenRevoked(const std::string & account_id)337 void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) {
338   VLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id;
339   PerformStartRemoveAction(account_id);
340 }
341 
OnRefreshTokensLoaded()342 void AccountReconcilor::OnRefreshTokensLoaded() {}
343 
GoogleSigninSucceeded(const std::string & username,const std::string & password)344 void AccountReconcilor::GoogleSigninSucceeded(const std::string& username,
345                                               const std::string& password) {
346   VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
347   RegisterForCookieChanges();
348   RegisterWithTokenService();
349 }
350 
GoogleSignedOut(const std::string & username)351 void AccountReconcilor::GoogleSignedOut(const std::string& username) {
352   VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
353   gaia_fetcher_.reset();
354   get_gaia_accounts_callbacks_.clear();
355   AbortReconcile();
356   UnregisterWithTokenService();
357   UnregisterForCookieChanges();
358   PerformLogoutAllAccountsAction();
359 }
360 
PerformMergeAction(const std::string & account_id)361 void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
362   if (!switches::IsNewProfileManagement()) {
363     MarkAccountAsAddedToCookie(account_id);
364     return;
365   }
366   VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
367   merge_session_helper_.LogIn(account_id);
368 }
369 
PerformStartRemoveAction(const std::string & account_id)370 void AccountReconcilor::PerformStartRemoveAction(
371     const std::string& account_id) {
372   VLOG(1) << "AccountReconcilor::PerformStartRemoveAction: " << account_id;
373   GetAccountsFromCookie(base::Bind(
374       &AccountReconcilor::PerformFinishRemoveAction,
375       base::Unretained(this),
376       account_id));
377 }
378 
PerformFinishRemoveAction(const std::string & account_id,const GoogleServiceAuthError & error,const std::vector<std::pair<std::string,bool>> & accounts)379 void AccountReconcilor::PerformFinishRemoveAction(
380     const std::string& account_id,
381     const GoogleServiceAuthError& error,
382     const std::vector<std::pair<std::string, bool> >& accounts) {
383   if (!switches::IsNewProfileManagement())
384     return;
385   VLOG(1) << "AccountReconcilor::PerformFinishRemoveAction:"
386           << " account=" << account_id << " error=" << error.ToString();
387   if (error.state() == GoogleServiceAuthError::NONE) {
388     AbortReconcile();
389     std::vector<std::string> accounts_only;
390     for (std::vector<std::pair<std::string, bool> >::const_iterator i =
391              accounts.begin();
392          i != accounts.end();
393          ++i) {
394       accounts_only.push_back(i->first);
395     }
396     merge_session_helper_.LogOut(account_id, accounts_only);
397   }
398   // Wait for the next ReconcileAction if there is an error.
399 }
400 
PerformAddToChromeAction(const std::string & account_id,int session_index)401 void AccountReconcilor::PerformAddToChromeAction(const std::string& account_id,
402                                                  int session_index) {
403   if (!switches::IsNewProfileManagement()) {
404     MarkAccountAsAddedToChrome(account_id);
405     return;
406   }
407   VLOG(1) << "AccountReconcilor::PerformAddToChromeAction:"
408           << " account=" << account_id << " session_index=" << session_index;
409 
410 #if !defined(OS_ANDROID) && !defined(OS_IOS)
411   refresh_token_fetchers_.push_back(
412       new RefreshTokenFetcher(this, account_id, session_index));
413 #endif
414 }
415 
PerformLogoutAllAccountsAction()416 void AccountReconcilor::PerformLogoutAllAccountsAction() {
417   if (!switches::IsNewProfileManagement())
418     return;
419   VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
420   merge_session_helper_.LogOutAllAccounts();
421 }
422 
StartReconcile()423 void AccountReconcilor::StartReconcile() {
424   if (!IsProfileConnected() || is_reconcile_started_)
425     return;
426 
427   is_reconcile_started_ = true;
428 
429   // Reset state for validating gaia cookie.
430   are_gaia_accounts_set_ = false;
431   gaia_accounts_.clear();
432   GetAccountsFromCookie(base::Bind(
433       &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts,
434       base::Unretained(this)));
435 
436   // Reset state for validating oauth2 tokens.
437   primary_account_.clear();
438   chrome_accounts_.clear();
439   DeleteFetchers();
440   valid_chrome_accounts_.clear();
441   invalid_chrome_accounts_.clear();
442   add_to_cookie_.clear();
443   add_to_chrome_.clear();
444   ValidateAccountsFromTokenService();
445 }
446 
GetAccountsFromCookie(GetAccountsFromCookieCallback callback)447 void AccountReconcilor::GetAccountsFromCookie(
448     GetAccountsFromCookieCallback callback) {
449   get_gaia_accounts_callbacks_.push_back(callback);
450   if (!gaia_fetcher_) {
451     // There is no list account request in flight.
452     gaia_fetcher_.reset(new GaiaAuthFetcher(
453         this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
454     gaia_fetcher_->StartListAccounts();
455   }
456 }
457 
OnListAccountsSuccess(const std::string & data)458 void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
459   gaia_fetcher_.reset();
460 
461   // Get account information from response data.
462   std::vector<std::pair<std::string, bool> > gaia_accounts;
463   bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts);
464   if (!valid_json) {
465     VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
466   } else if (gaia_accounts.size() > 0) {
467     VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
468             << "Gaia " << gaia_accounts.size() << " accounts, "
469             << "Primary is '" << gaia_accounts[0].first << "'";
470   } else {
471     VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
472   }
473 
474   // There must be at least one callback waiting for result.
475   DCHECK(!get_gaia_accounts_callbacks_.empty());
476 
477   GoogleServiceAuthError error =
478       !valid_json ? GoogleServiceAuthError(
479                         GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE)
480                   : GoogleServiceAuthError::AuthErrorNone();
481   get_gaia_accounts_callbacks_.front().Run(error, gaia_accounts);
482   get_gaia_accounts_callbacks_.pop_front();
483 
484   MayBeDoNextListAccounts();
485 }
486 
OnListAccountsFailure(const GoogleServiceAuthError & error)487 void AccountReconcilor::OnListAccountsFailure(
488     const GoogleServiceAuthError& error) {
489   gaia_fetcher_.reset();
490   VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString();
491   std::vector<std::pair<std::string, bool> > empty_accounts;
492 
493   // There must be at least one callback waiting for result.
494   DCHECK(!get_gaia_accounts_callbacks_.empty());
495 
496   get_gaia_accounts_callbacks_.front().Run(error, empty_accounts);
497   get_gaia_accounts_callbacks_.pop_front();
498 
499   MayBeDoNextListAccounts();
500 }
501 
MayBeDoNextListAccounts()502 void AccountReconcilor::MayBeDoNextListAccounts() {
503   if (!get_gaia_accounts_callbacks_.empty()) {
504     gaia_fetcher_.reset(new GaiaAuthFetcher(
505         this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
506     gaia_fetcher_->StartListAccounts();
507   }
508 }
509 
ContinueReconcileActionAfterGetGaiaAccounts(const GoogleServiceAuthError & error,const std::vector<std::pair<std::string,bool>> & accounts)510 void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
511     const GoogleServiceAuthError& error,
512     const std::vector<std::pair<std::string, bool> >& accounts) {
513   if (error.state() == GoogleServiceAuthError::NONE) {
514     gaia_accounts_ = accounts;
515     are_gaia_accounts_set_ = true;
516     FinishReconcile();
517   } else {
518     AbortReconcile();
519   }
520 }
521 
ValidateAccountsFromTokenService()522 void AccountReconcilor::ValidateAccountsFromTokenService() {
523   primary_account_ = signin_manager_->GetAuthenticatedUsername();
524   DCHECK(!primary_account_.empty());
525 
526   chrome_accounts_ = token_service_->GetAccounts();
527   DCHECK_GT(chrome_accounts_.size(), 0u);
528 
529   VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
530           << "Chrome " << chrome_accounts_.size() << " accounts, "
531           << "Primary is '" << primary_account_ << "'";
532 
533   DCHECK(!requests_);
534   requests_ =
535       new scoped_ptr<OAuth2TokenService::Request>[chrome_accounts_.size()];
536   const OAuth2TokenService::ScopeSet scopes =
537       AccountReconcilor::UserIdFetcher::GetScopes();
538   for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
539     requests_[i] =
540         token_service_->StartRequest(chrome_accounts_[i], scopes, this);
541   }
542 
543   DCHECK_EQ(0u, user_id_fetchers_.size());
544   user_id_fetchers_.resize(chrome_accounts_.size());
545 }
546 
OnGetTokenSuccess(const OAuth2TokenService::Request * request,const std::string & access_token,const base::Time & expiration_time)547 void AccountReconcilor::OnGetTokenSuccess(
548     const OAuth2TokenService::Request* request,
549     const std::string& access_token,
550     const base::Time& expiration_time) {
551   size_t index;
552   for (index = 0; index < chrome_accounts_.size(); ++index) {
553     if (request == requests_[index].get())
554       break;
555   }
556   DCHECK(index < chrome_accounts_.size());
557 
558   const std::string& account_id = chrome_accounts_[index];
559 
560   VLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid " << account_id;
561 
562   DCHECK(!user_id_fetchers_[index]);
563   user_id_fetchers_[index] = new UserIdFetcher(this, access_token, account_id);
564 }
565 
OnGetTokenFailure(const OAuth2TokenService::Request * request,const GoogleServiceAuthError & error)566 void AccountReconcilor::OnGetTokenFailure(
567     const OAuth2TokenService::Request* request,
568     const GoogleServiceAuthError& error) {
569   size_t index;
570   for (index = 0; index < chrome_accounts_.size(); ++index) {
571     if (request == requests_[index].get())
572       break;
573   }
574   DCHECK(index < chrome_accounts_.size());
575 
576   const std::string& account_id = chrome_accounts_[index];
577 
578   VLOG(1) << "AccountReconcilor::OnGetTokenFailure: invalid " << account_id;
579   HandleFailedAccountIdCheck(account_id);
580 }
581 
OnNewProfileManagementFlagChanged(bool new_flag_status)582 void AccountReconcilor::OnNewProfileManagementFlagChanged(
583     bool new_flag_status) {
584   if (new_flag_status) {
585     // The reconciler may have been newly created just before this call, or may
586     // have already existed and in mid-reconcile. To err on the safe side, force
587     // a restart.
588     Shutdown();
589     Initialize(true);
590   } else {
591     Shutdown();
592   }
593 }
594 
FinishReconcile()595 void AccountReconcilor::FinishReconcile() {
596   // Make sure that the process of validating the gaia cookie and the oauth2
597   // tokens individually is done before proceeding with reconciliation.
598   if (!are_gaia_accounts_set_ || !AreAllRefreshTokensChecked())
599     return;
600 
601   VLOG(1) << "AccountReconcilor::FinishReconcile";
602 
603   DeleteFetchers();
604 
605   DCHECK(add_to_cookie_.empty());
606   DCHECK(add_to_chrome_.empty());
607   int number_gaia_accounts = gaia_accounts_.size();
608   bool are_primaries_equal =
609       number_gaia_accounts > 0 &&
610       gaia::AreEmailsSame(primary_account_, gaia_accounts_[0].first);
611 
612 
613   if (are_primaries_equal) {
614     // Determine if we need to merge accounts from gaia cookie to chrome.
615     for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
616       const std::string& gaia_account = gaia_accounts_[i].first;
617       if (gaia_accounts_[i].second &&
618           valid_chrome_accounts_.find(gaia_account) ==
619               valid_chrome_accounts_.end()) {
620         add_to_chrome_.push_back(std::make_pair(gaia_account, i));
621       }
622     }
623   } else {
624     VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
625     // Really messed up state.  Blow away the gaia cookie completely and
626     // rebuild it, making sure the primary account as specified by the
627     // SigninManager is the first session in the gaia cookie.
628     PerformLogoutAllAccountsAction();
629     gaia_accounts_.clear();
630   }
631 
632   // Create a list of accounts that need to be added to the gaia cookie.
633   // The primary account must be first to make sure it becomes the default
634   // account in the case where chrome is completely rebuilding the cookie.
635   add_to_cookie_.push_back(primary_account_);
636   for (EmailSet::const_iterator i = valid_chrome_accounts_.begin();
637         i != valid_chrome_accounts_.end();
638         ++i) {
639     if (*i != primary_account_)
640       add_to_cookie_.push_back(*i);
641   }
642 
643   // For each account known to chrome, PerformMergeAction() if the account is
644   // not already in the cookie jar or its state is invalid, or signal merge
645   // completed otherwise.  Make a copy of |add_to_cookie_| since calls to
646   // SignalComplete() will change the array.
647   std::vector<std::string> add_to_cookie_copy = add_to_cookie_;
648   int added_to_cookie = 0;
649   for (size_t i = 0; i < add_to_cookie_copy.size(); ++i) {
650     if (gaia_accounts_.end() !=
651             std::find_if(gaia_accounts_.begin(),
652                          gaia_accounts_.end(),
653                          std::bind1st(EmailEqualToFunc(),
654                                       std::make_pair(add_to_cookie_copy[i],
655                                                      true)))) {
656       merge_session_helper_.SignalComplete(
657           add_to_cookie_copy[i],
658           GoogleServiceAuthError::AuthErrorNone());
659     } else {
660       PerformMergeAction(add_to_cookie_copy[i]);
661       added_to_cookie++;
662     }
663   }
664 
665   // For each account in the gaia cookie not known to chrome,
666   // PerformAddToChromeAction. Make a copy of |add_to_chrome| since calls to
667   // PerformAddToChromeAction() may modify this array.
668   std::vector<std::pair<std::string, int> > add_to_chrome_copy = add_to_chrome_;
669   for (std::vector<std::pair<std::string, int> >::const_iterator i =
670            add_to_chrome_copy.begin();
671        i != add_to_chrome_copy.end();
672        ++i) {
673     PerformAddToChromeAction(i->first, i->second);
674   }
675 
676   signin_metrics::LogSigninAccountReconciliation(valid_chrome_accounts_.size(),
677                                                  added_to_cookie,
678                                                  add_to_chrome_.size(),
679                                                  are_primaries_equal,
680                                                  first_execution_,
681                                                  number_gaia_accounts);
682   first_execution_ = false;
683   CalculateIfReconcileIsDone();
684   ScheduleStartReconcileIfChromeAccountsChanged();
685 }
686 
AbortReconcile()687 void AccountReconcilor::AbortReconcile() {
688   VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
689   DeleteFetchers();
690   add_to_cookie_.clear();
691   add_to_chrome_.clear();
692   CalculateIfReconcileIsDone();
693 }
694 
CalculateIfReconcileIsDone()695 void AccountReconcilor::CalculateIfReconcileIsDone() {
696   is_reconcile_started_ = !add_to_cookie_.empty() || !add_to_chrome_.empty();
697   if (!is_reconcile_started_)
698     VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
699 }
700 
ScheduleStartReconcileIfChromeAccountsChanged()701 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
702   if (is_reconcile_started_)
703     return;
704 
705   // Start a reconcile as the token accounts have changed.
706   VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
707   std::vector<std::string> reconciled_accounts(chrome_accounts_);
708   std::vector<std::string> new_chrome_accounts(token_service_->GetAccounts());
709   std::sort(reconciled_accounts.begin(), reconciled_accounts.end());
710   std::sort(new_chrome_accounts.begin(), new_chrome_accounts.end());
711   if (reconciled_accounts != new_chrome_accounts) {
712     base::MessageLoop::current()->PostTask(
713         FROM_HERE,
714         base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
715   }
716 }
717 
718 // Remove the account from the list that is being merged.
MarkAccountAsAddedToCookie(const std::string & account_id)719 void AccountReconcilor::MarkAccountAsAddedToCookie(
720     const std::string& account_id) {
721   for (std::vector<std::string>::iterator i = add_to_cookie_.begin();
722        i != add_to_cookie_.end();
723        ++i) {
724     if (account_id == *i) {
725       add_to_cookie_.erase(i);
726       break;
727     }
728   }
729 }
730 
MergeSessionCompleted(const std::string & account_id,const GoogleServiceAuthError & error)731 void AccountReconcilor::MergeSessionCompleted(
732     const std::string& account_id,
733     const GoogleServiceAuthError& error) {
734   VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
735           << account_id;
736 
737   MarkAccountAsAddedToCookie(account_id);
738   CalculateIfReconcileIsDone();
739   ScheduleStartReconcileIfChromeAccountsChanged();
740 }
741 
HandleSuccessfulAccountIdCheck(const std::string & account_id)742 void AccountReconcilor::HandleSuccessfulAccountIdCheck(
743     const std::string& account_id) {
744   valid_chrome_accounts_.insert(account_id);
745   FinishReconcile();
746 }
747 
HandleFailedAccountIdCheck(const std::string & account_id)748 void AccountReconcilor::HandleFailedAccountIdCheck(
749     const std::string& account_id) {
750   invalid_chrome_accounts_.insert(account_id);
751   FinishReconcile();
752 }
753 
PerformAddAccountToTokenService(const std::string & account_id,const std::string & refresh_token)754 void AccountReconcilor::PerformAddAccountToTokenService(
755     const std::string& account_id,
756     const std::string& refresh_token) {
757   // The flow should never get to this method if new_profile_management is
758   // false, but better safe than sorry.
759   if (!switches::IsNewProfileManagement())
760     return;
761   token_service_->UpdateCredentials(account_id, refresh_token);
762 }
763 
764 // Remove the account from the list that is being updated.
MarkAccountAsAddedToChrome(const std::string & account_id)765 void AccountReconcilor::MarkAccountAsAddedToChrome(
766     const std::string& account_id) {
767   for (std::vector<std::pair<std::string, int> >::iterator i =
768            add_to_chrome_.begin();
769        i != add_to_chrome_.end();
770        ++i) {
771     if (gaia::AreEmailsSame(account_id, i->first)) {
772       add_to_chrome_.erase(i);
773       break;
774     }
775   }
776 }
777 
HandleRefreshTokenFetched(const std::string & account_id,const std::string & refresh_token)778 void AccountReconcilor::HandleRefreshTokenFetched(
779     const std::string& account_id,
780     const std::string& refresh_token) {
781   if (!refresh_token.empty())
782     PerformAddAccountToTokenService(account_id, refresh_token);
783 
784   MarkAccountAsAddedToChrome(account_id);
785   CalculateIfReconcileIsDone();
786 }
787