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