blob: 52f2914a3beb71d54488a45ed8bccab0b0f243ca [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
5#include "chrome/browser/services/gcm/gcm_account_tracker.h"
6
7#include <algorithm>
8#include <vector>
9
10#include "base/time/time.h"
fgorski83afd872014-10-16 01:11:5211#include "components/gcm_driver/gcm_driver.h"
[email protected]641c8cc72014-07-02 00:33:0612#include "google_apis/gaia/google_service_auth_error.h"
fgorski83afd872014-10-16 01:11:5213#include "net/base/ip_endpoint.h"
[email protected]641c8cc72014-07-02 00:33:0614
15namespace gcm {
16
17namespace {
fgorski9a405102014-11-19 01:25:1618
19// Scopes needed by the OAuth2 access tokens.
[email protected]7df5ef22014-07-17 07:35:5820const char kGCMGroupServerScope[] = "https://www.googleapis.com/auth/gcm";
[email protected]669b1622014-08-08 08:35:5021const char kGCMCheckinServerScope[] =
22 "https://www.googleapis.com/auth/android_checkin";
fgorski9a405102014-11-19 01:25:1623// Name of the GCM account tracker for the OAuth2TokenService.
[email protected]641c8cc72014-07-02 00:33:0624const char kGCMAccountTrackerName[] = "gcm_account_tracker";
fgorski9a405102014-11-19 01:25:1625// Minimum token validity when sending to GCM groups server.
fgorski83afd872014-10-16 01:11:5226const int64 kMinimumTokenValidityMs = 500;
fgorski9a405102014-11-19 01:25:1627// Token reporting interval, when no account changes are detected.
28const int64 kTokenReportingIntervalMs = 12 * 60 * 60 * 1000; // 12 hours in ms.
29
[email protected]641c8cc72014-07-02 00:33:0630} // namespace
31
32GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
33 AccountState state)
34 : email(email), state(state) {
35}
36
37GCMAccountTracker::AccountInfo::~AccountInfo() {
38}
39
40GCMAccountTracker::GCMAccountTracker(
41 scoped_ptr<gaia::AccountTracker> account_tracker,
fgorski83afd872014-10-16 01:11:5242 GCMDriver* driver)
[email protected]641c8cc72014-07-02 00:33:0643 : OAuth2TokenService::Consumer(kGCMAccountTrackerName),
44 account_tracker_(account_tracker.release()),
fgorski83afd872014-10-16 01:11:5245 driver_(driver),
fgorski9a405102014-11-19 01:25:1646 shutdown_called_(false),
47 reporting_weak_ptr_factory_(this) {
[email protected]641c8cc72014-07-02 00:33:0648}
49
50GCMAccountTracker::~GCMAccountTracker() {
51 DCHECK(shutdown_called_);
52}
53
54void GCMAccountTracker::Shutdown() {
[email protected]641c8cc72014-07-02 00:33:0655 shutdown_called_ = true;
fgorski83afd872014-10-16 01:11:5256 driver_->RemoveConnectionObserver(this);
57 account_tracker_->RemoveObserver(this);
[email protected]641c8cc72014-07-02 00:33:0658 account_tracker_->Shutdown();
59}
60
61void GCMAccountTracker::Start() {
62 DCHECK(!shutdown_called_);
63 account_tracker_->AddObserver(this);
fgorski83afd872014-10-16 01:11:5264 driver_->AddConnectionObserver(this);
[email protected]641c8cc72014-07-02 00:33:0665
66 std::vector<gaia::AccountIds> accounts = account_tracker_->GetAccounts();
[email protected]641c8cc72014-07-02 00:33:0667 for (std::vector<gaia::AccountIds>::const_iterator iter = accounts.begin();
68 iter != accounts.end();
69 ++iter) {
70 if (!iter->email.empty()) {
71 account_infos_.insert(std::make_pair(
72 iter->account_key, AccountInfo(iter->email, TOKEN_NEEDED)));
73 }
74 }
75
fgorski9a405102014-11-19 01:25:1676 if (IsTokenReportingRequired())
77 ReportTokens();
78 else
79 ScheduleReportTokens();
80}
81
82void GCMAccountTracker::ScheduleReportTokens() {
fgorski702e92ed2015-01-29 19:22:0283 // Shortcutting here, in case GCM Driver is not yet connected. In that case
84 // reporting will be scheduled/started when the connection is made.
85 if (!driver_->IsConnected())
86 return;
87
fgorski9a405102014-11-19 01:25:1688 DVLOG(1) << "Deferring the token reporting for: "
89 << GetTimeToNextTokenReporting().InSeconds() << " seconds.";
90
91 reporting_weak_ptr_factory_.InvalidateWeakPtrs();
92 base::MessageLoop::current()->PostDelayedTask(
93 FROM_HERE,
94 base::Bind(&GCMAccountTracker::ReportTokens,
95 reporting_weak_ptr_factory_.GetWeakPtr()),
96 GetTimeToNextTokenReporting());
[email protected]641c8cc72014-07-02 00:33:0697}
98
[email protected]641c8cc72014-07-02 00:33:0699void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds& ids) {
[email protected]7df5ef22014-07-17 07:35:58100 DVLOG(1) << "Account added: " << ids.email;
[email protected]641c8cc72014-07-02 00:33:06101 // We listen for the account signing in, which happens after account is added.
102}
103
104void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds& ids) {
[email protected]7df5ef22014-07-17 07:35:58105 DVLOG(1) << "Account removed: " << ids.email;
[email protected]641c8cc72014-07-02 00:33:06106 // We listen for the account signing out, which happens before account is
107 // removed.
108}
109
110void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds& ids,
111 bool is_signed_in) {
112 if (is_signed_in)
113 OnAccountSignedIn(ids);
114 else
115 OnAccountSignedOut(ids);
116}
117
118void GCMAccountTracker::OnGetTokenSuccess(
119 const OAuth2TokenService::Request* request,
120 const std::string& access_token,
121 const base::Time& expiration_time) {
122 DCHECK(request);
123 DCHECK(!request->GetAccountId().empty());
[email protected]7df5ef22014-07-17 07:35:58124 DVLOG(1) << "Get token success: " << request->GetAccountId();
[email protected]641c8cc72014-07-02 00:33:06125
126 AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
127 DCHECK(iter != account_infos_.end());
128 if (iter != account_infos_.end()) {
129 DCHECK(iter->second.state == GETTING_TOKEN ||
130 iter->second.state == ACCOUNT_REMOVED);
131 // If OnAccountSignedOut(..) was called most recently, account is kept in
132 // ACCOUNT_REMOVED state.
133 if (iter->second.state == GETTING_TOKEN) {
134 iter->second.state = TOKEN_PRESENT;
135 iter->second.access_token = access_token;
fgorski83afd872014-10-16 01:11:52136 iter->second.expiration_time = expiration_time;
[email protected]641c8cc72014-07-02 00:33:06137 }
138 }
139
140 DeleteTokenRequest(request);
fgorski9a405102014-11-19 01:25:16141 ReportTokens();
[email protected]641c8cc72014-07-02 00:33:06142}
143
144void GCMAccountTracker::OnGetTokenFailure(
145 const OAuth2TokenService::Request* request,
146 const GoogleServiceAuthError& error) {
147 DCHECK(request);
148 DCHECK(!request->GetAccountId().empty());
[email protected]7df5ef22014-07-17 07:35:58149 DVLOG(1) << "Get token failure: " << request->GetAccountId();
[email protected]641c8cc72014-07-02 00:33:06150
151 AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
152 DCHECK(iter != account_infos_.end());
153 if (iter != account_infos_.end()) {
154 DCHECK(iter->second.state == GETTING_TOKEN ||
155 iter->second.state == ACCOUNT_REMOVED);
156 // If OnAccountSignedOut(..) was called most recently, account is kept in
157 // ACCOUNT_REMOVED state.
fgorskibe981e72015-01-26 21:07:19158 if (iter->second.state == GETTING_TOKEN) {
159 // Given the fetcher has a built in retry logic, consider this situation
160 // to be invalid refresh token, that is only fixed when user signs in.
161 // Once the users signs in properly the minting will retry.
162 iter->second.access_token.clear();
163 iter->second.state = ACCOUNT_REMOVED;
164 }
[email protected]641c8cc72014-07-02 00:33:06165 }
166
167 DeleteTokenRequest(request);
fgorski9a405102014-11-19 01:25:16168 ReportTokens();
[email protected]641c8cc72014-07-02 00:33:06169}
170
fgorski83afd872014-10-16 01:11:52171void GCMAccountTracker::OnConnected(const net::IPEndPoint& ip_endpoint) {
fgorski702e92ed2015-01-29 19:22:02172 // We are sure here, that GCM is running and connected. We can start reporting
173 // tokens if reporting is due now, or schedule reporting for later.
fgorski9a405102014-11-19 01:25:16174 if (IsTokenReportingRequired())
175 ReportTokens();
fgorski702e92ed2015-01-29 19:22:02176 else
177 ScheduleReportTokens();
fgorski83afd872014-10-16 01:11:52178}
179
180void GCMAccountTracker::OnDisconnected() {
181 // We are disconnected, so no point in trying to work with tokens.
182}
183
fgorski9a405102014-11-19 01:25:16184void GCMAccountTracker::ReportTokens() {
185 SanitizeTokens();
fgorski83afd872014-10-16 01:11:52186 // Make sure all tokens are valid.
fgorski9a405102014-11-19 01:25:16187 if (IsTokenFetchingRequired()) {
fgorski83afd872014-10-16 01:11:52188 GetAllNeededTokens();
189 return;
190 }
191
[email protected]641c8cc72014-07-02 00:33:06192 // Wait for gaia::AccountTracker to be done with fetching the user info, as
193 // well as all of the pending token requests from GCMAccountTracker to be done
194 // before you report the results.
195 if (!account_tracker_->IsAllUserInfoFetched() ||
196 !pending_token_requests_.empty()) {
197 return;
198 }
199
200 bool account_removed = false;
fgorski83afd872014-10-16 01:11:52201 // Stop tracking the accounts, that were removed, as it will be reported to
202 // the driver.
[email protected]641c8cc72014-07-02 00:33:06203 for (AccountInfos::iterator iter = account_infos_.begin();
204 iter != account_infos_.end();) {
fgorski83afd872014-10-16 01:11:52205 if (iter->second.state == ACCOUNT_REMOVED) {
206 account_removed = true;
207 account_infos_.erase(iter++);
208 } else {
209 ++iter;
210 }
211 }
[email protected]641c8cc72014-07-02 00:33:06212
fgorski83afd872014-10-16 01:11:52213 std::vector<GCMClient::AccountTokenInfo> account_tokens;
214 for (AccountInfos::iterator iter = account_infos_.begin();
215 iter != account_infos_.end(); ++iter) {
216 if (iter->second.state == TOKEN_PRESENT) {
217 GCMClient::AccountTokenInfo token_info;
218 token_info.account_id = iter->first;
219 token_info.email = iter->second.email;
220 token_info.access_token = iter->second.access_token;
221 account_tokens.push_back(token_info);
222 } else {
223 // This should not happen, as we are making a check that there are no
224 // pending requests above, stopping tracking of removed accounts, or start
225 // fetching tokens.
226 NOTREACHED();
[email protected]641c8cc72014-07-02 00:33:06227 }
228 }
229
[email protected]7df5ef22014-07-17 07:35:58230 // Make sure that there is something to report, otherwise bail out.
231 if (!account_tokens.empty() || account_removed) {
fgorski83afd872014-10-16 01:11:52232 DVLOG(1) << "Reporting the tokens to driver: " << account_tokens.size();
233 driver_->SetAccountTokens(account_tokens);
fgorski9a405102014-11-19 01:25:16234 driver_->SetLastTokenFetchTime(base::Time::Now());
235 ScheduleReportTokens();
[email protected]7df5ef22014-07-17 07:35:58236 } else {
237 DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
238 }
[email protected]641c8cc72014-07-02 00:33:06239}
240
fgorski9a405102014-11-19 01:25:16241void GCMAccountTracker::SanitizeTokens() {
fgorski83afd872014-10-16 01:11:52242 for (AccountInfos::iterator iter = account_infos_.begin();
243 iter != account_infos_.end();
244 ++iter) {
245 if (iter->second.state == TOKEN_PRESENT &&
246 iter->second.expiration_time <
247 base::Time::Now() +
248 base::TimeDelta::FromMilliseconds(kMinimumTokenValidityMs)) {
249 iter->second.access_token.clear();
250 iter->second.state = TOKEN_NEEDED;
251 iter->second.expiration_time = base::Time();
252 }
fgorski9a405102014-11-19 01:25:16253 }
254}
fgorski83afd872014-10-16 01:11:52255
fgorski9a405102014-11-19 01:25:16256bool GCMAccountTracker::IsTokenReportingRequired() const {
257 if (GetTimeToNextTokenReporting() == base::TimeDelta())
258 return true;
259
260 bool reporting_required = false;
261 for (AccountInfos::const_iterator iter = account_infos_.begin();
262 iter != account_infos_.end();
263 ++iter) {
264 if (iter->second.state == ACCOUNT_REMOVED)
265 reporting_required = true;
fgorskiede41172014-11-08 00:38:35266 }
267
fgorski9a405102014-11-19 01:25:16268 return reporting_required;
269}
270
271bool GCMAccountTracker::IsTokenFetchingRequired() const {
272 bool token_needed = false;
273 for (AccountInfos::const_iterator iter = account_infos_.begin();
274 iter != account_infos_.end();
275 ++iter) {
276 if (iter->second.state == TOKEN_NEEDED)
277 token_needed = true;
278 }
279
280 return token_needed;
281}
282
283base::TimeDelta GCMAccountTracker::GetTimeToNextTokenReporting() const {
284 base::TimeDelta time_till_next_reporting =
285 driver_->GetLastTokenFetchTime() +
286 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs) -
287 base::Time::Now();
fgorski702e92ed2015-01-29 19:22:02288
289 // Case when token fetching is overdue.
290 if (time_till_next_reporting < base::TimeDelta())
291 return base::TimeDelta();
292
293 // Case when calculated period is larger than expected, including the
294 // situation when the method is called before GCM driver is completely
295 // initialized.
296 if (time_till_next_reporting >
297 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs)) {
298 return base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs);
299 }
300
301 return time_till_next_reporting;
fgorski83afd872014-10-16 01:11:52302}
303
[email protected]641c8cc72014-07-02 00:33:06304void GCMAccountTracker::DeleteTokenRequest(
305 const OAuth2TokenService::Request* request) {
306 ScopedVector<OAuth2TokenService::Request>::iterator iter = std::find(
307 pending_token_requests_.begin(), pending_token_requests_.end(), request);
308 if (iter != pending_token_requests_.end())
309 pending_token_requests_.erase(iter);
310}
311
312void GCMAccountTracker::GetAllNeededTokens() {
fgorski83afd872014-10-16 01:11:52313 // Only start fetching tokens if driver is running, they have a limited
314 // validity time and GCM connection is a good indication of network running.
fgorski9a405102014-11-19 01:25:16315 // If the GetAllNeededTokens was called as part of periodic schedule, it may
316 // not have network. In that case the next network change will trigger token
317 // fetching.
fgorski83afd872014-10-16 01:11:52318 if (!driver_->IsConnected())
319 return;
320
[email protected]641c8cc72014-07-02 00:33:06321 for (AccountInfos::iterator iter = account_infos_.begin();
322 iter != account_infos_.end();
323 ++iter) {
324 if (iter->second.state == TOKEN_NEEDED)
325 GetToken(iter);
326 }
327}
328
329void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) {
330 DCHECK(GetTokenService());
331 DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED);
332
333 OAuth2TokenService::ScopeSet scopes;
334 scopes.insert(kGCMGroupServerScope);
[email protected]669b1622014-08-08 08:35:50335 scopes.insert(kGCMCheckinServerScope);
[email protected]641c8cc72014-07-02 00:33:06336 scoped_ptr<OAuth2TokenService::Request> request =
337 GetTokenService()->StartRequest(account_iter->first, scopes, this);
338
339 pending_token_requests_.push_back(request.release());
340 account_iter->second.state = GETTING_TOKEN;
341}
342
343void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds& ids) {
[email protected]7df5ef22014-07-17 07:35:58344 DVLOG(1) << "Account signed in: " << ids.email;
[email protected]641c8cc72014-07-02 00:33:06345 AccountInfos::iterator iter = account_infos_.find(ids.account_key);
346 if (iter == account_infos_.end()) {
347 DCHECK(!ids.email.empty());
348 account_infos_.insert(
349 std::make_pair(ids.account_key, AccountInfo(ids.email, TOKEN_NEEDED)));
350 } else if (iter->second.state == ACCOUNT_REMOVED) {
351 iter->second.state = TOKEN_NEEDED;
352 }
353
354 GetAllNeededTokens();
355}
356
357void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds& ids) {
[email protected]7df5ef22014-07-17 07:35:58358 DVLOG(1) << "Account signed out: " << ids.email;
[email protected]641c8cc72014-07-02 00:33:06359 AccountInfos::iterator iter = account_infos_.find(ids.account_key);
360 if (iter == account_infos_.end())
361 return;
362
363 iter->second.access_token.clear();
364 iter->second.state = ACCOUNT_REMOVED;
fgorski9a405102014-11-19 01:25:16365 ReportTokens();
[email protected]641c8cc72014-07-02 00:33:06366}
367
368OAuth2TokenService* GCMAccountTracker::GetTokenService() {
369 DCHECK(account_tracker_->identity_provider());
370 return account_tracker_->identity_provider()->GetTokenService();
371}
372
373} // namespace gcm