Home | History | Annotate | Download | only in signin
      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 "base/logging.h"
      6 #include "base/time/time.h"
      7 #include "chrome/browser/chrome_notification_types.h"
      8 #include "chrome/browser/net/chrome_cookie_notification_details.h"
      9 #include "chrome/browser/profiles/profile.h"
     10 #include "chrome/browser/signin/account_reconcilor.h"
     11 #include "chrome/browser/signin/google_auto_login_helper.h"
     12 #include "chrome/browser/signin/profile_oauth2_token_service.h"
     13 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
     14 #include "chrome/browser/signin/signin_manager.h"
     15 #include "chrome/browser/signin/signin_manager_factory.h"
     16 #include "content/public/browser/browser_thread.h"
     17 #include "content/public/browser/notification_details.h"
     18 #include "content/public/browser/notification_source.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 
     23 AccountReconcilor::AccountReconcilor(Profile* profile)
     24     : profile_(profile),
     25       are_gaia_accounts_set_(false),
     26       requests_(NULL) {
     27   DVLOG(1) << "AccountReconcilor::AccountReconcilor";
     28   RegisterWithSigninManager();
     29   RegisterWithCookieMonster();
     30 
     31   // If this profile is not connected, the reconcilor should do nothing but
     32   // wait for the connection.
     33   if (IsProfileConnected()) {
     34     RegisterWithTokenService();
     35     StartPeriodicReconciliation();
     36   }
     37 }
     38 
     39 AccountReconcilor::~AccountReconcilor() {
     40   // Make sure shutdown was called first.
     41   DCHECK(registrar_.IsEmpty());
     42   DCHECK(!reconciliation_timer_.IsRunning());
     43   DCHECK(!requests_);
     44 }
     45 
     46 void AccountReconcilor::Shutdown() {
     47   DVLOG(1) << "AccountReconcilor::Shutdown";
     48   DeleteAccessTokenRequests();
     49   UnregisterWithSigninManager();
     50   UnregisterWithTokenService();
     51   UnregisterWithCookieMonster();
     52   StopPeriodicReconciliation();
     53 }
     54 
     55 void AccountReconcilor::DeleteAccessTokenRequests() {
     56   delete[] requests_;
     57   requests_ = NULL;
     58 }
     59 
     60 void AccountReconcilor::RegisterWithCookieMonster() {
     61   content::Source<Profile> source(profile_);
     62   registrar_.Add(this, chrome::NOTIFICATION_COOKIE_CHANGED, source);
     63 }
     64 
     65 void AccountReconcilor::UnregisterWithCookieMonster() {
     66   content::Source<Profile> source(profile_);
     67   registrar_.Remove(this, chrome::NOTIFICATION_COOKIE_CHANGED, source);
     68 }
     69 
     70 void AccountReconcilor::RegisterWithSigninManager() {
     71   content::Source<Profile> source(profile_);
     72   registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, source);
     73   registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, source);
     74 }
     75 
     76 void AccountReconcilor::UnregisterWithSigninManager() {
     77   content::Source<Profile> source(profile_);
     78   registrar_.Remove(
     79       this, chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, source);
     80   registrar_.Remove(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, source);
     81 }
     82 
     83 void AccountReconcilor::RegisterWithTokenService() {
     84   DVLOG(1) << "AccountReconcilor::RegisterWithTokenService";
     85   ProfileOAuth2TokenService* token_service =
     86       ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
     87   token_service->AddObserver(this);
     88 }
     89 
     90 void AccountReconcilor::UnregisterWithTokenService() {
     91   ProfileOAuth2TokenService* token_service =
     92       ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
     93   token_service->RemoveObserver(this);
     94 }
     95 
     96 bool AccountReconcilor::IsProfileConnected() {
     97   return !SigninManagerFactory::GetForProfile(profile_)->
     98       GetAuthenticatedUsername().empty();
     99 }
    100 
    101 void AccountReconcilor::StartPeriodicReconciliation() {
    102   DVLOG(1) << "AccountReconcilor::StartPeriodicReconciliation";
    103   // TODO(rogerta): pick appropriate thread and timeout value.
    104   reconciliation_timer_.Start(
    105       FROM_HERE,
    106       base::TimeDelta::FromSeconds(300),
    107       this,
    108       &AccountReconcilor::PeriodicReconciliation);
    109 }
    110 
    111 void AccountReconcilor::StopPeriodicReconciliation() {
    112   DVLOG(1) << "AccountReconcilor::StopPeriodicReconciliation";
    113   reconciliation_timer_.Stop();
    114 }
    115 
    116 void AccountReconcilor::PeriodicReconciliation() {
    117   DVLOG(1) << "AccountReconcilor::PeriodicReconciliation";
    118   StartReconcileAction();
    119 }
    120 
    121 void AccountReconcilor::Observe(int type,
    122                                 const content::NotificationSource& source,
    123                                 const content::NotificationDetails& details) {
    124   switch (type) {
    125     case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL:
    126       DVLOG(1) << "AccountReconcilor::Observe: signed in";
    127       RegisterWithTokenService();
    128       StartPeriodicReconciliation();
    129       break;
    130     case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT:
    131       DVLOG(1) << "AccountReconcilor::Observe: signed out";
    132       UnregisterWithTokenService();
    133       StopPeriodicReconciliation();
    134       break;
    135     case chrome::NOTIFICATION_COOKIE_CHANGED:
    136       OnCookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
    137       break;
    138     default:
    139       NOTREACHED();
    140       break;
    141   }
    142 }
    143 
    144 void AccountReconcilor::OnCookieChanged(ChromeCookieDetails* details) {
    145   // TODO(acleung): Filter out cookies by looking at the domain.
    146   // StartReconcileAction();
    147 }
    148 
    149 void AccountReconcilor::OnRefreshTokenAvailable(const std::string& account_id) {
    150   DVLOG(1) << "AccountReconcilor::OnRefreshTokenAvailable: " << account_id;
    151   PerformMergeAction(account_id);
    152 }
    153 
    154 void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) {
    155   DVLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id;
    156   PerformRemoveAction(account_id);
    157 }
    158 
    159 void AccountReconcilor::OnRefreshTokensLoaded() {}
    160 
    161 void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
    162   // GoogleAutoLoginHelper deletes itself upon success / failure.
    163   GoogleAutoLoginHelper* helper = new GoogleAutoLoginHelper(profile_);
    164   helper->LogIn(account_id);
    165 }
    166 
    167 void AccountReconcilor::PerformRemoveAction(const std::string& account_id) {
    168   // TODO(acleung): Implement this:
    169 }
    170 
    171 void AccountReconcilor::StartReconcileAction() {
    172   if (!IsProfileConnected())
    173     return;
    174 
    175   // Reset state for validating gaia cookie.
    176   are_gaia_accounts_set_ = false;
    177   gaia_accounts_.clear();
    178   GetAccountsFromCookie();
    179 
    180   // Reset state for validating oauth2 tokens.
    181   primary_account_.clear();
    182   chrome_accounts_.clear();
    183   DeleteAccessTokenRequests();
    184   valid_chrome_accounts_.clear();
    185   invalid_chrome_accounts_.clear();
    186   ValidateAccountsFromTokenService();
    187 }
    188 
    189 void AccountReconcilor::GetAccountsFromCookie() {
    190   gaia_fetcher_.reset(new GaiaAuthFetcher(this, GaiaConstants::kChromeSource,
    191                                           profile_->GetRequestContext()));
    192   gaia_fetcher_->StartListAccounts();
    193 }
    194 
    195 void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
    196   gaia_fetcher_.reset();
    197 
    198   // Get account information from response data.
    199   gaia_accounts_ = gaia::ParseListAccountsData(data);
    200   if (gaia_accounts_.size() > 0) {
    201     DVLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
    202              << "Gaia " << gaia_accounts_.size() << " accounts, "
    203              << "Primary is '" << gaia_accounts_[0] << "'";
    204   } else {
    205     DVLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
    206   }
    207 
    208   are_gaia_accounts_set_ = true;
    209   FinishReconcileAction();
    210 }
    211 
    212 void AccountReconcilor::OnListAccountsFailure(
    213     const GoogleServiceAuthError& error) {
    214   gaia_fetcher_.reset();
    215   DVLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString();
    216 
    217   are_gaia_accounts_set_ = true;
    218   FinishReconcileAction();
    219 }
    220 
    221 void AccountReconcilor::ValidateAccountsFromTokenService() {
    222   primary_account_ =
    223       SigninManagerFactory::GetForProfile(profile_)->GetAuthenticatedUsername();
    224   DCHECK(!primary_account_.empty());
    225 
    226   ProfileOAuth2TokenService* token_service =
    227       ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
    228   chrome_accounts_ = token_service->GetAccounts();
    229   DCHECK(chrome_accounts_.size() > 0);
    230 
    231   DVLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
    232             << "Chrome " << chrome_accounts_.size() << " accounts, "
    233             << "Primary is '" << primary_account_ << "'";
    234 
    235   DCHECK(!requests_);
    236   requests_ =
    237       new scoped_ptr<OAuth2TokenService::Request>[chrome_accounts_.size()];
    238   for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
    239     requests_[i] = token_service->StartRequest(chrome_accounts_[i],
    240                                                OAuth2TokenService::ScopeSet(),
    241                                                this);
    242   }
    243 }
    244 
    245 void AccountReconcilor::OnGetTokenSuccess(
    246     const OAuth2TokenService::Request* request,
    247     const std::string& access_token,
    248     const base::Time& expiration_time) {
    249   DVLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid "
    250            << request->GetAccountId();
    251   valid_chrome_accounts_.insert(request->GetAccountId());
    252   FinishReconcileAction();
    253 }
    254 
    255 void AccountReconcilor::OnGetTokenFailure(
    256     const OAuth2TokenService::Request* request,
    257     const GoogleServiceAuthError& error) {
    258   DVLOG(1) << "AccountReconcilor::OnGetTokenSuccess: invalid "
    259            << request->GetAccountId();
    260   invalid_chrome_accounts_.insert(request->GetAccountId());
    261   FinishReconcileAction();
    262 }
    263 
    264 void AccountReconcilor::FinishReconcileAction() {
    265   // Make sure that the process of validating the gaia cookie and the oauth2
    266   // tokens individually is done before proceeding with reconciliation.
    267   if (!are_gaia_accounts_set_ ||
    268       (chrome_accounts_.size() != (valid_chrome_accounts_.size() +
    269                                    invalid_chrome_accounts_.size()))) {
    270     return;
    271   }
    272 
    273   DVLOG(1) << "AccountReconcilor::FinishReconcileAction";
    274 
    275   bool are_primaries_equal =
    276       gaia_accounts_.size() > 0 && primary_account_ == gaia_accounts_[0];
    277   bool have_same_accounts = chrome_accounts_.size() == gaia_accounts_.size();
    278   if (have_same_accounts) {
    279     for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
    280       if (std::find(chrome_accounts_.begin(), chrome_accounts_.end(),
    281               gaia_accounts_[i]) == chrome_accounts_.end()) {
    282         have_same_accounts = false;
    283         break;
    284       }
    285     }
    286   }
    287 
    288   if (!are_primaries_equal || !have_same_accounts) {
    289     // TODO(rogerta): fix things up.
    290   }
    291 }
    292