Home | History | Annotate | Download | only in gcm
      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 "chrome/browser/services/gcm/gcm_account_tracker.h"
      6 
      7 #include <algorithm>
      8 #include <vector>
      9 
     10 #include "base/time/time.h"
     11 #include "google_apis/gaia/google_service_auth_error.h"
     12 
     13 namespace gcm {
     14 
     15 namespace {
     16 const char kGCMGroupServerScope[] = "https://www.googleapis.com/auth/gcm";
     17 const char kGCMCheckinServerScope[] =
     18     "https://www.googleapis.com/auth/android_checkin";
     19 const char kGCMAccountTrackerName[] = "gcm_account_tracker";
     20 }  // namespace
     21 
     22 GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
     23                                             AccountState state)
     24     : email(email), state(state) {
     25 }
     26 
     27 GCMAccountTracker::AccountInfo::~AccountInfo() {
     28 }
     29 
     30 GCMAccountTracker::GCMAccountTracker(
     31     scoped_ptr<gaia::AccountTracker> account_tracker,
     32     const UpdateAccountsCallback& callback)
     33     : OAuth2TokenService::Consumer(kGCMAccountTrackerName),
     34       account_tracker_(account_tracker.release()),
     35       callback_(callback),
     36       shutdown_called_(false) {
     37   DCHECK(!callback_.is_null());
     38 }
     39 
     40 GCMAccountTracker::~GCMAccountTracker() {
     41   DCHECK(shutdown_called_);
     42 }
     43 
     44 void GCMAccountTracker::Shutdown() {
     45   Stop();
     46   shutdown_called_ = true;
     47   account_tracker_->Shutdown();
     48 }
     49 
     50 void GCMAccountTracker::Start() {
     51   DCHECK(!shutdown_called_);
     52   account_tracker_->AddObserver(this);
     53 
     54   std::vector<gaia::AccountIds> accounts = account_tracker_->GetAccounts();
     55   if (accounts.empty()) {
     56     CompleteCollectingTokens();
     57     return;
     58   }
     59 
     60   for (std::vector<gaia::AccountIds>::const_iterator iter = accounts.begin();
     61        iter != accounts.end();
     62        ++iter) {
     63     if (!iter->email.empty()) {
     64       account_infos_.insert(std::make_pair(
     65           iter->account_key, AccountInfo(iter->email, TOKEN_NEEDED)));
     66     }
     67   }
     68 
     69   GetAllNeededTokens();
     70 }
     71 
     72 void GCMAccountTracker::Stop() {
     73   DCHECK(!shutdown_called_);
     74   account_tracker_->RemoveObserver(this);
     75   pending_token_requests_.clear();
     76 }
     77 
     78 void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds& ids) {
     79   DVLOG(1) << "Account added: " << ids.email;
     80   // We listen for the account signing in, which happens after account is added.
     81 }
     82 
     83 void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds& ids) {
     84   DVLOG(1) << "Account removed: " << ids.email;
     85   // We listen for the account signing out, which happens before account is
     86   // removed.
     87 }
     88 
     89 void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds& ids,
     90                                                bool is_signed_in) {
     91   if (is_signed_in)
     92     OnAccountSignedIn(ids);
     93   else
     94     OnAccountSignedOut(ids);
     95 }
     96 
     97 void GCMAccountTracker::OnGetTokenSuccess(
     98     const OAuth2TokenService::Request* request,
     99     const std::string& access_token,
    100     const base::Time& expiration_time) {
    101   DCHECK(request);
    102   DCHECK(!request->GetAccountId().empty());
    103   DVLOG(1) << "Get token success: " << request->GetAccountId();
    104 
    105   AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
    106   DCHECK(iter != account_infos_.end());
    107   if (iter != account_infos_.end()) {
    108     DCHECK(iter->second.state == GETTING_TOKEN ||
    109            iter->second.state == ACCOUNT_REMOVED);
    110     // If OnAccountSignedOut(..) was called most recently, account is kept in
    111     // ACCOUNT_REMOVED state.
    112     if (iter->second.state == GETTING_TOKEN) {
    113       iter->second.state = TOKEN_PRESENT;
    114       iter->second.access_token = access_token;
    115     }
    116   }
    117 
    118   DeleteTokenRequest(request);
    119   CompleteCollectingTokens();
    120 }
    121 
    122 void GCMAccountTracker::OnGetTokenFailure(
    123     const OAuth2TokenService::Request* request,
    124     const GoogleServiceAuthError& error) {
    125   DCHECK(request);
    126   DCHECK(!request->GetAccountId().empty());
    127   DVLOG(1) << "Get token failure: " << request->GetAccountId();
    128 
    129   AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
    130   DCHECK(iter != account_infos_.end());
    131   if (iter != account_infos_.end()) {
    132     DCHECK(iter->second.state == GETTING_TOKEN ||
    133            iter->second.state == ACCOUNT_REMOVED);
    134     // If OnAccountSignedOut(..) was called most recently, account is kept in
    135     // ACCOUNT_REMOVED state.
    136     if (iter->second.state == GETTING_TOKEN)
    137       iter->second.state = TOKEN_NEEDED;
    138   }
    139 
    140   DeleteTokenRequest(request);
    141   CompleteCollectingTokens();
    142 }
    143 
    144 void GCMAccountTracker::CompleteCollectingTokens() {
    145   DCHECK(!callback_.is_null());
    146   // Wait for gaia::AccountTracker to be done with fetching the user info, as
    147   // well as all of the pending token requests from GCMAccountTracker to be done
    148   // before you report the results.
    149   if (!account_tracker_->IsAllUserInfoFetched() ||
    150       !pending_token_requests_.empty()) {
    151     return;
    152   }
    153 
    154   bool account_removed = false;
    155   std::map<std::string, std::string> account_tokens;
    156   for (AccountInfos::iterator iter = account_infos_.begin();
    157        iter != account_infos_.end();) {
    158     switch (iter->second.state) {
    159       case ACCOUNT_REMOVED:
    160         // We only mark accounts as removed when there was an account that was
    161         // explicitly signed out.
    162         account_removed = true;
    163         // We also stop tracking the account, now that it will be reported as
    164         // removed.
    165         account_infos_.erase(iter++);
    166         break;
    167 
    168       case TOKEN_PRESENT:
    169         account_tokens[iter->second.email] = iter->second.access_token;
    170         ++iter;
    171         break;
    172 
    173       case GETTING_TOKEN:
    174         // This should not happen, as we are making a check that there are no
    175         // pending requests above.
    176         NOTREACHED();
    177         ++iter;
    178         break;
    179 
    180       case TOKEN_NEEDED:
    181         // We failed to fetch an access token for the account, but it has not
    182         // been signed out (perhaps there is a network issue). We don't report
    183         // it, but next time there is a sign-in change we will update its state.
    184         ++iter;
    185         break;
    186     }
    187   }
    188 
    189   // Make sure that there is something to report, otherwise bail out.
    190   if (!account_tokens.empty() || account_removed) {
    191     DVLOG(1) << "Calling callback: " << account_tokens.size();
    192     callback_.Run(account_tokens);
    193   } else {
    194     DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
    195   }
    196 }
    197 
    198 void GCMAccountTracker::DeleteTokenRequest(
    199     const OAuth2TokenService::Request* request) {
    200   ScopedVector<OAuth2TokenService::Request>::iterator iter = std::find(
    201       pending_token_requests_.begin(), pending_token_requests_.end(), request);
    202   if (iter != pending_token_requests_.end())
    203     pending_token_requests_.erase(iter);
    204 }
    205 
    206 void GCMAccountTracker::GetAllNeededTokens() {
    207   for (AccountInfos::iterator iter = account_infos_.begin();
    208        iter != account_infos_.end();
    209        ++iter) {
    210     if (iter->second.state == TOKEN_NEEDED)
    211       GetToken(iter);
    212   }
    213 }
    214 
    215 void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) {
    216   DCHECK(GetTokenService());
    217   DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED);
    218 
    219   OAuth2TokenService::ScopeSet scopes;
    220   scopes.insert(kGCMGroupServerScope);
    221   scopes.insert(kGCMCheckinServerScope);
    222   scoped_ptr<OAuth2TokenService::Request> request =
    223       GetTokenService()->StartRequest(account_iter->first, scopes, this);
    224 
    225   pending_token_requests_.push_back(request.release());
    226   account_iter->second.state = GETTING_TOKEN;
    227 }
    228 
    229 void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds& ids) {
    230   DVLOG(1) << "Account signed in: " << ids.email;
    231   AccountInfos::iterator iter = account_infos_.find(ids.account_key);
    232   if (iter == account_infos_.end()) {
    233     DCHECK(!ids.email.empty());
    234     account_infos_.insert(
    235         std::make_pair(ids.account_key, AccountInfo(ids.email, TOKEN_NEEDED)));
    236   } else if (iter->second.state == ACCOUNT_REMOVED) {
    237     iter->second.state = TOKEN_NEEDED;
    238   }
    239 
    240   GetAllNeededTokens();
    241 }
    242 
    243 void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds& ids) {
    244   DVLOG(1) << "Account signed out: " << ids.email;
    245   AccountInfos::iterator iter = account_infos_.find(ids.account_key);
    246   if (iter == account_infos_.end())
    247     return;
    248 
    249   iter->second.access_token.clear();
    250   iter->second.state = ACCOUNT_REMOVED;
    251   CompleteCollectingTokens();
    252 }
    253 
    254 OAuth2TokenService* GCMAccountTracker::GetTokenService() {
    255   DCHECK(account_tracker_->identity_provider());
    256   return account_tracker_->identity_provider()->GetTokenService();
    257 }
    258 
    259 }  // namespace gcm
    260