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.
5 #include "components/gcm_driver/gcm_account_tracker.h"
10 #include "base/bind.h"
11 #include "base/location.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "base/time/time.h"
15 #include "components/gcm_driver/gcm_driver.h"
16 #include "google_apis/gaia/google_service_auth_error.h"
17 #include "net/base/ip_endpoint.h"
23 // Scopes needed by the OAuth2 access tokens.
24 const char kGCMGroupServerScope
[] = "https://www.googleapis.com/auth/gcm";
25 const char kGCMCheckinServerScope
[] =
26 "https://www.googleapis.com/auth/android_checkin";
27 // Name of the GCM account tracker for the OAuth2TokenService.
28 const char kGCMAccountTrackerName
[] = "gcm_account_tracker";
29 // Minimum token validity when sending to GCM groups server.
30 const int64 kMinimumTokenValidityMs
= 500;
31 // Token reporting interval, when no account changes are detected.
32 const int64 kTokenReportingIntervalMs
= 12 * 60 * 60 * 1000; // 12 hours in ms.
36 GCMAccountTracker::AccountInfo::AccountInfo(const std::string
& email
,
38 : email(email
), state(state
) {
41 GCMAccountTracker::AccountInfo::~AccountInfo() {
44 GCMAccountTracker::GCMAccountTracker(
45 scoped_ptr
<gaia::AccountTracker
> account_tracker
,
47 : OAuth2TokenService::Consumer(kGCMAccountTrackerName
),
48 account_tracker_(account_tracker
.release()),
50 shutdown_called_(false),
51 reporting_weak_ptr_factory_(this) {
54 GCMAccountTracker::~GCMAccountTracker() {
55 DCHECK(shutdown_called_
);
58 void GCMAccountTracker::Shutdown() {
59 shutdown_called_
= true;
60 driver_
->RemoveConnectionObserver(this);
61 account_tracker_
->RemoveObserver(this);
62 account_tracker_
->Shutdown();
65 void GCMAccountTracker::Start() {
66 DCHECK(!shutdown_called_
);
67 account_tracker_
->AddObserver(this);
68 driver_
->AddConnectionObserver(this);
70 std::vector
<gaia::AccountIds
> accounts
= account_tracker_
->GetAccounts();
71 for (std::vector
<gaia::AccountIds
>::const_iterator iter
= accounts
.begin();
72 iter
!= accounts
.end();
74 if (!iter
->email
.empty()) {
75 account_infos_
.insert(std::make_pair(
76 iter
->account_key
, AccountInfo(iter
->email
, TOKEN_NEEDED
)));
80 if (IsTokenReportingRequired())
83 ScheduleReportTokens();
86 void GCMAccountTracker::ScheduleReportTokens() {
87 // Shortcutting here, in case GCM Driver is not yet connected. In that case
88 // reporting will be scheduled/started when the connection is made.
89 if (!driver_
->IsConnected())
92 DVLOG(1) << "Deferring the token reporting for: "
93 << GetTimeToNextTokenReporting().InSeconds() << " seconds.";
95 reporting_weak_ptr_factory_
.InvalidateWeakPtrs();
96 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
97 FROM_HERE
, base::Bind(&GCMAccountTracker::ReportTokens
,
98 reporting_weak_ptr_factory_
.GetWeakPtr()),
99 GetTimeToNextTokenReporting());
102 void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds
& ids
) {
103 DVLOG(1) << "Account added: " << ids
.email
;
104 // We listen for the account signing in, which happens after account is added.
107 void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds
& ids
) {
108 DVLOG(1) << "Account removed: " << ids
.email
;
109 // We listen for the account signing out, which happens before account is
113 void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds
& ids
,
116 OnAccountSignedIn(ids
);
118 OnAccountSignedOut(ids
);
121 void GCMAccountTracker::OnGetTokenSuccess(
122 const OAuth2TokenService::Request
* request
,
123 const std::string
& access_token
,
124 const base::Time
& expiration_time
) {
126 DCHECK(!request
->GetAccountId().empty());
127 DVLOG(1) << "Get token success: " << request
->GetAccountId();
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_PRESENT
;
138 iter
->second
.access_token
= access_token
;
139 iter
->second
.expiration_time
= expiration_time
;
143 DeleteTokenRequest(request
);
147 void GCMAccountTracker::OnGetTokenFailure(
148 const OAuth2TokenService::Request
* request
,
149 const GoogleServiceAuthError
& error
) {
151 DCHECK(!request
->GetAccountId().empty());
152 DVLOG(1) << "Get token failure: " << request
->GetAccountId();
154 AccountInfos::iterator iter
= account_infos_
.find(request
->GetAccountId());
155 DCHECK(iter
!= account_infos_
.end());
156 if (iter
!= account_infos_
.end()) {
157 DCHECK(iter
->second
.state
== GETTING_TOKEN
||
158 iter
->second
.state
== ACCOUNT_REMOVED
);
159 // If OnAccountSignedOut(..) was called most recently, account is kept in
160 // ACCOUNT_REMOVED state.
161 if (iter
->second
.state
== GETTING_TOKEN
) {
162 // Given the fetcher has a built in retry logic, consider this situation
163 // to be invalid refresh token, that is only fixed when user signs in.
164 // Once the users signs in properly the minting will retry.
165 iter
->second
.access_token
.clear();
166 iter
->second
.state
= ACCOUNT_REMOVED
;
170 DeleteTokenRequest(request
);
174 void GCMAccountTracker::OnConnected(const net::IPEndPoint
& ip_endpoint
) {
175 // We are sure here, that GCM is running and connected. We can start reporting
176 // tokens if reporting is due now, or schedule reporting for later.
177 if (IsTokenReportingRequired())
180 ScheduleReportTokens();
183 void GCMAccountTracker::OnDisconnected() {
184 // We are disconnected, so no point in trying to work with tokens.
187 void GCMAccountTracker::ReportTokens() {
189 // Make sure all tokens are valid.
190 if (IsTokenFetchingRequired()) {
191 GetAllNeededTokens();
195 // Wait for gaia::AccountTracker to be done with fetching the user info, as
196 // well as all of the pending token requests from GCMAccountTracker to be done
197 // before you report the results.
198 if (!account_tracker_
->IsAllUserInfoFetched() ||
199 !pending_token_requests_
.empty()) {
203 bool account_removed
= false;
204 // Stop tracking the accounts, that were removed, as it will be reported to
206 for (AccountInfos::iterator iter
= account_infos_
.begin();
207 iter
!= account_infos_
.end();) {
208 if (iter
->second
.state
== ACCOUNT_REMOVED
) {
209 account_removed
= true;
210 account_infos_
.erase(iter
++);
216 std::vector
<GCMClient::AccountTokenInfo
> account_tokens
;
217 for (AccountInfos::iterator iter
= account_infos_
.begin();
218 iter
!= account_infos_
.end(); ++iter
) {
219 if (iter
->second
.state
== TOKEN_PRESENT
) {
220 GCMClient::AccountTokenInfo token_info
;
221 token_info
.account_id
= iter
->first
;
222 token_info
.email
= iter
->second
.email
;
223 token_info
.access_token
= iter
->second
.access_token
;
224 account_tokens
.push_back(token_info
);
226 // This should not happen, as we are making a check that there are no
227 // pending requests above, stopping tracking of removed accounts, or start
233 // Make sure that there is something to report, otherwise bail out.
234 if (!account_tokens
.empty() || account_removed
) {
235 DVLOG(1) << "Reporting the tokens to driver: " << account_tokens
.size();
236 driver_
->SetAccountTokens(account_tokens
);
237 driver_
->SetLastTokenFetchTime(base::Time::Now());
238 ScheduleReportTokens();
240 DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
244 void GCMAccountTracker::SanitizeTokens() {
245 for (AccountInfos::iterator iter
= account_infos_
.begin();
246 iter
!= account_infos_
.end();
248 if (iter
->second
.state
== TOKEN_PRESENT
&&
249 iter
->second
.expiration_time
<
251 base::TimeDelta::FromMilliseconds(kMinimumTokenValidityMs
)) {
252 iter
->second
.access_token
.clear();
253 iter
->second
.state
= TOKEN_NEEDED
;
254 iter
->second
.expiration_time
= base::Time();
259 bool GCMAccountTracker::IsTokenReportingRequired() const {
260 if (GetTimeToNextTokenReporting() == base::TimeDelta())
263 bool reporting_required
= false;
264 for (AccountInfos::const_iterator iter
= account_infos_
.begin();
265 iter
!= account_infos_
.end();
267 if (iter
->second
.state
== ACCOUNT_REMOVED
)
268 reporting_required
= true;
271 return reporting_required
;
274 bool GCMAccountTracker::IsTokenFetchingRequired() const {
275 bool token_needed
= false;
276 for (AccountInfos::const_iterator iter
= account_infos_
.begin();
277 iter
!= account_infos_
.end();
279 if (iter
->second
.state
== TOKEN_NEEDED
)
286 base::TimeDelta
GCMAccountTracker::GetTimeToNextTokenReporting() const {
287 base::TimeDelta time_till_next_reporting
=
288 driver_
->GetLastTokenFetchTime() +
289 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs
) -
292 // Case when token fetching is overdue.
293 if (time_till_next_reporting
< base::TimeDelta())
294 return base::TimeDelta();
296 // Case when calculated period is larger than expected, including the
297 // situation when the method is called before GCM driver is completely
299 if (time_till_next_reporting
>
300 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs
)) {
301 return base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs
);
304 return time_till_next_reporting
;
307 void GCMAccountTracker::DeleteTokenRequest(
308 const OAuth2TokenService::Request
* request
) {
309 ScopedVector
<OAuth2TokenService::Request
>::iterator iter
= std::find(
310 pending_token_requests_
.begin(), pending_token_requests_
.end(), request
);
311 if (iter
!= pending_token_requests_
.end())
312 pending_token_requests_
.erase(iter
);
315 void GCMAccountTracker::GetAllNeededTokens() {
316 // Only start fetching tokens if driver is running, they have a limited
317 // validity time and GCM connection is a good indication of network running.
318 // If the GetAllNeededTokens was called as part of periodic schedule, it may
319 // not have network. In that case the next network change will trigger token
321 if (!driver_
->IsConnected())
324 for (AccountInfos::iterator iter
= account_infos_
.begin();
325 iter
!= account_infos_
.end();
327 if (iter
->second
.state
== TOKEN_NEEDED
)
332 void GCMAccountTracker::GetToken(AccountInfos::iterator
& account_iter
) {
333 DCHECK(GetTokenService());
334 DCHECK_EQ(account_iter
->second
.state
, TOKEN_NEEDED
);
336 OAuth2TokenService::ScopeSet scopes
;
337 scopes
.insert(kGCMGroupServerScope
);
338 scopes
.insert(kGCMCheckinServerScope
);
339 scoped_ptr
<OAuth2TokenService::Request
> request
=
340 GetTokenService()->StartRequest(account_iter
->first
, scopes
, this);
342 pending_token_requests_
.push_back(request
.release());
343 account_iter
->second
.state
= GETTING_TOKEN
;
346 void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds
& ids
) {
347 DVLOG(1) << "Account signed in: " << ids
.email
;
348 AccountInfos::iterator iter
= account_infos_
.find(ids
.account_key
);
349 if (iter
== account_infos_
.end()) {
350 DCHECK(!ids
.email
.empty());
351 account_infos_
.insert(
352 std::make_pair(ids
.account_key
, AccountInfo(ids
.email
, TOKEN_NEEDED
)));
353 } else if (iter
->second
.state
== ACCOUNT_REMOVED
) {
354 iter
->second
.state
= TOKEN_NEEDED
;
357 GetAllNeededTokens();
360 void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds
& ids
) {
361 DVLOG(1) << "Account signed out: " << ids
.email
;
362 AccountInfos::iterator iter
= account_infos_
.find(ids
.account_key
);
363 if (iter
== account_infos_
.end())
366 iter
->second
.access_token
.clear();
367 iter
->second
.state
= ACCOUNT_REMOVED
;
371 OAuth2TokenService
* GCMAccountTracker::GetTokenService() {
372 DCHECK(account_tracker_
->identity_provider());
373 return account_tracker_
->identity_provider()->GetTokenService();