• 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 "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 
AccountInfo(const std::string & email,AccountState state)22 GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
23                                             AccountState state)
24     : email(email), state(state) {
25 }
26 
~AccountInfo()27 GCMAccountTracker::AccountInfo::~AccountInfo() {
28 }
29 
GCMAccountTracker(scoped_ptr<gaia::AccountTracker> account_tracker,const UpdateAccountsCallback & callback)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 
~GCMAccountTracker()40 GCMAccountTracker::~GCMAccountTracker() {
41   DCHECK(shutdown_called_);
42 }
43 
Shutdown()44 void GCMAccountTracker::Shutdown() {
45   Stop();
46   shutdown_called_ = true;
47   account_tracker_->Shutdown();
48 }
49 
Start()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 
Stop()72 void GCMAccountTracker::Stop() {
73   DCHECK(!shutdown_called_);
74   account_tracker_->RemoveObserver(this);
75   pending_token_requests_.clear();
76 }
77 
OnAccountAdded(const gaia::AccountIds & ids)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 
OnAccountRemoved(const gaia::AccountIds & ids)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 
OnAccountSignInChanged(const gaia::AccountIds & ids,bool is_signed_in)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 
OnGetTokenSuccess(const OAuth2TokenService::Request * request,const std::string & access_token,const base::Time & expiration_time)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 
OnGetTokenFailure(const OAuth2TokenService::Request * request,const GoogleServiceAuthError & error)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 
CompleteCollectingTokens()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 
DeleteTokenRequest(const OAuth2TokenService::Request * request)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 
GetAllNeededTokens()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 
GetToken(AccountInfos::iterator & account_iter)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 
OnAccountSignedIn(const gaia::AccountIds & ids)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 
OnAccountSignedOut(const gaia::AccountIds & ids)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 
GetTokenService()254 OAuth2TokenService* GCMAccountTracker::GetTokenService() {
255   DCHECK(account_tracker_->identity_provider());
256   return account_tracker_->identity_provider()->GetTokenService();
257 }
258 
259 }  // namespace gcm
260