blob: ab5708e958bb05c13657f4f73d1ccd06d953a739 [file] [log] [blame]
[email protected]641c8cc72014-07-02 00:33:061// 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
jitendra.ks70c5b4c2015-08-19 18:53:345#include "components/gcm_driver/gcm_account_tracker.h"
[email protected]641c8cc72014-07-02 00:33:066
avi26062922015-12-26 00:14:187#include <stdint.h>
8
[email protected]641c8cc72014-07-02 00:33:069#include <algorithm>
10#include <vector>
11
eromanc9a6b722015-06-03 22:19:0012#include "base/bind.h"
skyostil02598352015-06-12 12:37:2513#include "base/location.h"
14#include "base/single_thread_task_runner.h"
15#include "base/thread_task_runner_handle.h"
[email protected]641c8cc72014-07-02 00:33:0616#include "base/time/time.h"
fgorski83afd872014-10-16 01:11:5217#include "components/gcm_driver/gcm_driver.h"
[email protected]641c8cc72014-07-02 00:33:0618#include "google_apis/gaia/google_service_auth_error.h"
fgorski83afd872014-10-16 01:11:5219#include "net/base/ip_endpoint.h"
[email protected]641c8cc72014-07-02 00:33:0620
21namespace gcm {
22
23namespace {
fgorski9a405102014-11-19 01:25:1624
25// Scopes needed by the OAuth2 access tokens.
[email protected]7df5ef22014-07-17 07:35:5826const char kGCMGroupServerScope[] = "https://www.googleapis.com/auth/gcm";
[email protected]669b1622014-08-08 08:35:5027const char kGCMCheckinServerScope[] =
28 "https://www.googleapis.com/auth/android_checkin";
fgorski9a405102014-11-19 01:25:1629// Name of the GCM account tracker for the OAuth2TokenService.
[email protected]641c8cc72014-07-02 00:33:0630const char kGCMAccountTrackerName[] = "gcm_account_tracker";
fgorski9a405102014-11-19 01:25:1631// Minimum token validity when sending to GCM groups server.
avi26062922015-12-26 00:14:1832const int64_t kMinimumTokenValidityMs = 500;
fgorski9a405102014-11-19 01:25:1633// Token reporting interval, when no account changes are detected.
avi26062922015-12-26 00:14:1834const int64_t kTokenReportingIntervalMs =
35 12 * 60 * 60 * 1000; // 12 hours in ms.
fgorski9a405102014-11-19 01:25:1636
[email protected]641c8cc72014-07-02 00:33:0637} // namespace
38
39GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
40 AccountState state)
41 : email(email), state(state) {
42}
43
44GCMAccountTracker::AccountInfo::~AccountInfo() {
45}
46
47GCMAccountTracker::GCMAccountTracker(
dchenga77e28eb2016-04-21 21:34:3748 std::unique_ptr<gaia::AccountTracker> account_tracker,
fgorski83afd872014-10-16 01:11:5249 GCMDriver* driver)
[email protected]641c8cc72014-07-02 00:33:0650 : OAuth2TokenService::Consumer(kGCMAccountTrackerName),
51 account_tracker_(account_tracker.release()),
fgorski83afd872014-10-16 01:11:5252 driver_(driver),
fgorski9a405102014-11-19 01:25:1653 shutdown_called_(false),
dchenga77e28eb2016-04-21 21:34:3754 reporting_weak_ptr_factory_(this) {}
[email protected]641c8cc72014-07-02 00:33:0655
56GCMAccountTracker::~GCMAccountTracker() {
57 DCHECK(shutdown_called_);
58}
59
60void GCMAccountTracker::Shutdown() {
[email protected]641c8cc72014-07-02 00:33:0661 shutdown_called_ = true;
fgorski83afd872014-10-16 01:11:5262 driver_->RemoveConnectionObserver(this);
63 account_tracker_->RemoveObserver(this);
[email protected]641c8cc72014-07-02 00:33:0664 account_tracker_->Shutdown();
65}
66
67void GCMAccountTracker::Start() {
68 DCHECK(!shutdown_called_);
69 account_tracker_->AddObserver(this);
fgorski83afd872014-10-16 01:11:5270 driver_->AddConnectionObserver(this);
[email protected]641c8cc72014-07-02 00:33:0671
72 std::vector<gaia::AccountIds> accounts = account_tracker_->GetAccounts();
[email protected]641c8cc72014-07-02 00:33:0673 for (std::vector<gaia::AccountIds>::const_iterator iter = accounts.begin();
74 iter != accounts.end();
75 ++iter) {
76 if (!iter->email.empty()) {
77 account_infos_.insert(std::make_pair(
78 iter->account_key, AccountInfo(iter->email, TOKEN_NEEDED)));
79 }
80 }
81
fgorski9a405102014-11-19 01:25:1682 if (IsTokenReportingRequired())
83 ReportTokens();
84 else
85 ScheduleReportTokens();
86}
87
88void GCMAccountTracker::ScheduleReportTokens() {
fgorski702e92ed2015-01-29 19:22:0289 // Shortcutting here, in case GCM Driver is not yet connected. In that case
90 // reporting will be scheduled/started when the connection is made.
91 if (!driver_->IsConnected())
92 return;
93
fgorski9a405102014-11-19 01:25:1694 DVLOG(1) << "Deferring the token reporting for: "
95 << GetTimeToNextTokenReporting().InSeconds() << " seconds.";
96
97 reporting_weak_ptr_factory_.InvalidateWeakPtrs();
skyostil02598352015-06-12 12:37:2598 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
99 FROM_HERE, base::Bind(&GCMAccountTracker::ReportTokens,
100 reporting_weak_ptr_factory_.GetWeakPtr()),
fgorski9a405102014-11-19 01:25:16101 GetTimeToNextTokenReporting());
[email protected]641c8cc72014-07-02 00:33:06102}
103
[email protected]641c8cc72014-07-02 00:33:06104void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds& ids) {
[email protected]7df5ef22014-07-17 07:35:58105 DVLOG(1) << "Account added: " << ids.email;
[email protected]641c8cc72014-07-02 00:33:06106 // We listen for the account signing in, which happens after account is added.
107}
108
109void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds& ids) {
[email protected]7df5ef22014-07-17 07:35:58110 DVLOG(1) << "Account removed: " << ids.email;
[email protected]641c8cc72014-07-02 00:33:06111 // We listen for the account signing out, which happens before account is
112 // removed.
113}
114
115void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds& ids,
116 bool is_signed_in) {
117 if (is_signed_in)
118 OnAccountSignedIn(ids);
119 else
120 OnAccountSignedOut(ids);
121}
122
123void GCMAccountTracker::OnGetTokenSuccess(
124 const OAuth2TokenService::Request* request,
125 const std::string& access_token,
126 const base::Time& expiration_time) {
127 DCHECK(request);
128 DCHECK(!request->GetAccountId().empty());
[email protected]7df5ef22014-07-17 07:35:58129 DVLOG(1) << "Get token success: " << request->GetAccountId();
[email protected]641c8cc72014-07-02 00:33:06130
131 AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
132 DCHECK(iter != account_infos_.end());
133 if (iter != account_infos_.end()) {
134 DCHECK(iter->second.state == GETTING_TOKEN ||
135 iter->second.state == ACCOUNT_REMOVED);
136 // If OnAccountSignedOut(..) was called most recently, account is kept in
137 // ACCOUNT_REMOVED state.
138 if (iter->second.state == GETTING_TOKEN) {
139 iter->second.state = TOKEN_PRESENT;
140 iter->second.access_token = access_token;
fgorski83afd872014-10-16 01:11:52141 iter->second.expiration_time = expiration_time;
[email protected]641c8cc72014-07-02 00:33:06142 }
143 }
144
145 DeleteTokenRequest(request);
fgorski9a405102014-11-19 01:25:16146 ReportTokens();
[email protected]641c8cc72014-07-02 00:33:06147}
148
149void GCMAccountTracker::OnGetTokenFailure(
150 const OAuth2TokenService::Request* request,
151 const GoogleServiceAuthError& error) {
152 DCHECK(request);
153 DCHECK(!request->GetAccountId().empty());
[email protected]7df5ef22014-07-17 07:35:58154 DVLOG(1) << "Get token failure: " << request->GetAccountId();
[email protected]641c8cc72014-07-02 00:33:06155
156 AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
157 DCHECK(iter != account_infos_.end());
158 if (iter != account_infos_.end()) {
159 DCHECK(iter->second.state == GETTING_TOKEN ||
160 iter->second.state == ACCOUNT_REMOVED);
161 // If OnAccountSignedOut(..) was called most recently, account is kept in
162 // ACCOUNT_REMOVED state.
fgorskibe981e72015-01-26 21:07:19163 if (iter->second.state == GETTING_TOKEN) {
164 // Given the fetcher has a built in retry logic, consider this situation
165 // to be invalid refresh token, that is only fixed when user signs in.
166 // Once the users signs in properly the minting will retry.
167 iter->second.access_token.clear();
168 iter->second.state = ACCOUNT_REMOVED;
169 }
[email protected]641c8cc72014-07-02 00:33:06170 }
171
172 DeleteTokenRequest(request);
fgorski9a405102014-11-19 01:25:16173 ReportTokens();
[email protected]641c8cc72014-07-02 00:33:06174}
175
fgorski83afd872014-10-16 01:11:52176void GCMAccountTracker::OnConnected(const net::IPEndPoint& ip_endpoint) {
fgorski702e92ed2015-01-29 19:22:02177 // We are sure here, that GCM is running and connected. We can start reporting
178 // tokens if reporting is due now, or schedule reporting for later.
fgorski9a405102014-11-19 01:25:16179 if (IsTokenReportingRequired())
180 ReportTokens();
fgorski702e92ed2015-01-29 19:22:02181 else
182 ScheduleReportTokens();
fgorski83afd872014-10-16 01:11:52183}
184
185void GCMAccountTracker::OnDisconnected() {
186 // We are disconnected, so no point in trying to work with tokens.
187}
188
fgorski9a405102014-11-19 01:25:16189void GCMAccountTracker::ReportTokens() {
190 SanitizeTokens();
fgorski83afd872014-10-16 01:11:52191 // Make sure all tokens are valid.
fgorski9a405102014-11-19 01:25:16192 if (IsTokenFetchingRequired()) {
fgorski83afd872014-10-16 01:11:52193 GetAllNeededTokens();
194 return;
195 }
196
[email protected]641c8cc72014-07-02 00:33:06197 // Wait for gaia::AccountTracker to be done with fetching the user info, as
198 // well as all of the pending token requests from GCMAccountTracker to be done
199 // before you report the results.
200 if (!account_tracker_->IsAllUserInfoFetched() ||
201 !pending_token_requests_.empty()) {
202 return;
203 }
204
205 bool account_removed = false;
fgorski83afd872014-10-16 01:11:52206 // Stop tracking the accounts, that were removed, as it will be reported to
207 // the driver.
[email protected]641c8cc72014-07-02 00:33:06208 for (AccountInfos::iterator iter = account_infos_.begin();
209 iter != account_infos_.end();) {
fgorski83afd872014-10-16 01:11:52210 if (iter->second.state == ACCOUNT_REMOVED) {
211 account_removed = true;
212 account_infos_.erase(iter++);
213 } else {
214 ++iter;
215 }
216 }
[email protected]641c8cc72014-07-02 00:33:06217
fgorski83afd872014-10-16 01:11:52218 std::vector<GCMClient::AccountTokenInfo> account_tokens;
219 for (AccountInfos::iterator iter = account_infos_.begin();
220 iter != account_infos_.end(); ++iter) {
221 if (iter->second.state == TOKEN_PRESENT) {
222 GCMClient::AccountTokenInfo token_info;
223 token_info.account_id = iter->first;
224 token_info.email = iter->second.email;
225 token_info.access_token = iter->second.access_token;
226 account_tokens.push_back(token_info);
227 } else {
228 // This should not happen, as we are making a check that there are no
229 // pending requests above, stopping tracking of removed accounts, or start
230 // fetching tokens.
231 NOTREACHED();
[email protected]641c8cc72014-07-02 00:33:06232 }
233 }
234
[email protected]7df5ef22014-07-17 07:35:58235 // Make sure that there is something to report, otherwise bail out.
236 if (!account_tokens.empty() || account_removed) {
fgorski83afd872014-10-16 01:11:52237 DVLOG(1) << "Reporting the tokens to driver: " << account_tokens.size();
238 driver_->SetAccountTokens(account_tokens);
fgorski9a405102014-11-19 01:25:16239 driver_->SetLastTokenFetchTime(base::Time::Now());
240 ScheduleReportTokens();
[email protected]7df5ef22014-07-17 07:35:58241 } else {
242 DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
243 }
[email protected]641c8cc72014-07-02 00:33:06244}
245
fgorski9a405102014-11-19 01:25:16246void GCMAccountTracker::SanitizeTokens() {
fgorski83afd872014-10-16 01:11:52247 for (AccountInfos::iterator iter = account_infos_.begin();
248 iter != account_infos_.end();
249 ++iter) {
250 if (iter->second.state == TOKEN_PRESENT &&
251 iter->second.expiration_time <
252 base::Time::Now() +
253 base::TimeDelta::FromMilliseconds(kMinimumTokenValidityMs)) {
254 iter->second.access_token.clear();
255 iter->second.state = TOKEN_NEEDED;
256 iter->second.expiration_time = base::Time();
257 }
fgorski9a405102014-11-19 01:25:16258 }
259}
fgorski83afd872014-10-16 01:11:52260
fgorski9a405102014-11-19 01:25:16261bool GCMAccountTracker::IsTokenReportingRequired() const {
262 if (GetTimeToNextTokenReporting() == base::TimeDelta())
263 return true;
264
265 bool reporting_required = false;
266 for (AccountInfos::const_iterator iter = account_infos_.begin();
267 iter != account_infos_.end();
268 ++iter) {
269 if (iter->second.state == ACCOUNT_REMOVED)
270 reporting_required = true;
fgorskiede41172014-11-08 00:38:35271 }
272
fgorski9a405102014-11-19 01:25:16273 return reporting_required;
274}
275
276bool GCMAccountTracker::IsTokenFetchingRequired() const {
277 bool token_needed = false;
278 for (AccountInfos::const_iterator iter = account_infos_.begin();
279 iter != account_infos_.end();
280 ++iter) {
281 if (iter->second.state == TOKEN_NEEDED)
282 token_needed = true;
283 }
284
285 return token_needed;
286}
287
288base::TimeDelta GCMAccountTracker::GetTimeToNextTokenReporting() const {
289 base::TimeDelta time_till_next_reporting =
290 driver_->GetLastTokenFetchTime() +
291 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs) -
292 base::Time::Now();
fgorski702e92ed2015-01-29 19:22:02293
294 // Case when token fetching is overdue.
295 if (time_till_next_reporting < base::TimeDelta())
296 return base::TimeDelta();
297
298 // Case when calculated period is larger than expected, including the
299 // situation when the method is called before GCM driver is completely
300 // initialized.
301 if (time_till_next_reporting >
302 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs)) {
303 return base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs);
304 }
305
306 return time_till_next_reporting;
fgorski83afd872014-10-16 01:11:52307}
308
[email protected]641c8cc72014-07-02 00:33:06309void GCMAccountTracker::DeleteTokenRequest(
310 const OAuth2TokenService::Request* request) {
311 ScopedVector<OAuth2TokenService::Request>::iterator iter = std::find(
312 pending_token_requests_.begin(), pending_token_requests_.end(), request);
313 if (iter != pending_token_requests_.end())
314 pending_token_requests_.erase(iter);
315}
316
317void GCMAccountTracker::GetAllNeededTokens() {
fgorski83afd872014-10-16 01:11:52318 // Only start fetching tokens if driver is running, they have a limited
319 // validity time and GCM connection is a good indication of network running.
fgorski9a405102014-11-19 01:25:16320 // If the GetAllNeededTokens was called as part of periodic schedule, it may
321 // not have network. In that case the next network change will trigger token
322 // fetching.
fgorski83afd872014-10-16 01:11:52323 if (!driver_->IsConnected())
324 return;
325
[email protected]641c8cc72014-07-02 00:33:06326 for (AccountInfos::iterator iter = account_infos_.begin();
327 iter != account_infos_.end();
328 ++iter) {
329 if (iter->second.state == TOKEN_NEEDED)
330 GetToken(iter);
331 }
332}
333
334void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) {
335 DCHECK(GetTokenService());
336 DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED);
337
338 OAuth2TokenService::ScopeSet scopes;
339 scopes.insert(kGCMGroupServerScope);
[email protected]669b1622014-08-08 08:35:50340 scopes.insert(kGCMCheckinServerScope);
dchenga77e28eb2016-04-21 21:34:37341 std::unique_ptr<OAuth2TokenService::Request> request =
[email protected]641c8cc72014-07-02 00:33:06342 GetTokenService()->StartRequest(account_iter->first, scopes, this);
343
344 pending_token_requests_.push_back(request.release());
345 account_iter->second.state = GETTING_TOKEN;
346}
347
348void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds& ids) {
[email protected]7df5ef22014-07-17 07:35:58349 DVLOG(1) << "Account signed in: " << ids.email;
[email protected]641c8cc72014-07-02 00:33:06350 AccountInfos::iterator iter = account_infos_.find(ids.account_key);
351 if (iter == account_infos_.end()) {
352 DCHECK(!ids.email.empty());
353 account_infos_.insert(
354 std::make_pair(ids.account_key, AccountInfo(ids.email, TOKEN_NEEDED)));
355 } else if (iter->second.state == ACCOUNT_REMOVED) {
356 iter->second.state = TOKEN_NEEDED;
357 }
358
359 GetAllNeededTokens();
360}
361
362void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds& ids) {
[email protected]7df5ef22014-07-17 07:35:58363 DVLOG(1) << "Account signed out: " << ids.email;
[email protected]641c8cc72014-07-02 00:33:06364 AccountInfos::iterator iter = account_infos_.find(ids.account_key);
365 if (iter == account_infos_.end())
366 return;
367
368 iter->second.access_token.clear();
369 iter->second.state = ACCOUNT_REMOVED;
fgorski9a405102014-11-19 01:25:16370 ReportTokens();
[email protected]641c8cc72014-07-02 00:33:06371}
372
373OAuth2TokenService* GCMAccountTracker::GetTokenService() {
374 DCHECK(account_tracker_->identity_provider());
375 return account_tracker_->identity_provider()->GetTokenService();
376}
377
378} // namespace gcm