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