OLD | NEW |
| (Empty) |
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/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" | |
18 | |
19 namespace gcm { | |
20 | |
21 namespace { | |
22 | |
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. | |
33 | |
34 } // namespace | |
35 | |
36 GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email, | |
37 AccountState state) | |
38 : email(email), state(state) { | |
39 } | |
40 | |
41 GCMAccountTracker::AccountInfo::~AccountInfo() { | |
42 } | |
43 | |
44 GCMAccountTracker::GCMAccountTracker( | |
45 scoped_ptr<gaia::AccountTracker> account_tracker, | |
46 GCMDriver* driver) | |
47 : OAuth2TokenService::Consumer(kGCMAccountTrackerName), | |
48 account_tracker_(account_tracker.release()), | |
49 driver_(driver), | |
50 shutdown_called_(false), | |
51 reporting_weak_ptr_factory_(this) { | |
52 } | |
53 | |
54 GCMAccountTracker::~GCMAccountTracker() { | |
55 DCHECK(shutdown_called_); | |
56 } | |
57 | |
58 void GCMAccountTracker::Shutdown() { | |
59 shutdown_called_ = true; | |
60 driver_->RemoveConnectionObserver(this); | |
61 account_tracker_->RemoveObserver(this); | |
62 account_tracker_->Shutdown(); | |
63 } | |
64 | |
65 void GCMAccountTracker::Start() { | |
66 DCHECK(!shutdown_called_); | |
67 account_tracker_->AddObserver(this); | |
68 driver_->AddConnectionObserver(this); | |
69 | |
70 std::vector<gaia::AccountIds> accounts = account_tracker_->GetAccounts(); | |
71 for (std::vector<gaia::AccountIds>::const_iterator iter = accounts.begin(); | |
72 iter != accounts.end(); | |
73 ++iter) { | |
74 if (!iter->email.empty()) { | |
75 account_infos_.insert(std::make_pair( | |
76 iter->account_key, AccountInfo(iter->email, TOKEN_NEEDED))); | |
77 } | |
78 } | |
79 | |
80 if (IsTokenReportingRequired()) | |
81 ReportTokens(); | |
82 else | |
83 ScheduleReportTokens(); | |
84 } | |
85 | |
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()) | |
90 return; | |
91 | |
92 DVLOG(1) << "Deferring the token reporting for: " | |
93 << GetTimeToNextTokenReporting().InSeconds() << " seconds."; | |
94 | |
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()); | |
100 } | |
101 | |
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. | |
105 } | |
106 | |
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 | |
110 // removed. | |
111 } | |
112 | |
113 void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds& ids, | |
114 bool is_signed_in) { | |
115 if (is_signed_in) | |
116 OnAccountSignedIn(ids); | |
117 else | |
118 OnAccountSignedOut(ids); | |
119 } | |
120 | |
121 void GCMAccountTracker::OnGetTokenSuccess( | |
122 const OAuth2TokenService::Request* request, | |
123 const std::string& access_token, | |
124 const base::Time& expiration_time) { | |
125 DCHECK(request); | |
126 DCHECK(!request->GetAccountId().empty()); | |
127 DVLOG(1) << "Get token success: " << 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_PRESENT; | |
138 iter->second.access_token = access_token; | |
139 iter->second.expiration_time = expiration_time; | |
140 } | |
141 } | |
142 | |
143 DeleteTokenRequest(request); | |
144 ReportTokens(); | |
145 } | |
146 | |
147 void GCMAccountTracker::OnGetTokenFailure( | |
148 const OAuth2TokenService::Request* request, | |
149 const GoogleServiceAuthError& error) { | |
150 DCHECK(request); | |
151 DCHECK(!request->GetAccountId().empty()); | |
152 DVLOG(1) << "Get token failure: " << request->GetAccountId(); | |
153 | |
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; | |
167 } | |
168 } | |
169 | |
170 DeleteTokenRequest(request); | |
171 ReportTokens(); | |
172 } | |
173 | |
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()) | |
178 ReportTokens(); | |
179 else | |
180 ScheduleReportTokens(); | |
181 } | |
182 | |
183 void GCMAccountTracker::OnDisconnected() { | |
184 // We are disconnected, so no point in trying to work with tokens. | |
185 } | |
186 | |
187 void GCMAccountTracker::ReportTokens() { | |
188 SanitizeTokens(); | |
189 // Make sure all tokens are valid. | |
190 if (IsTokenFetchingRequired()) { | |
191 GetAllNeededTokens(); | |
192 return; | |
193 } | |
194 | |
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()) { | |
200 return; | |
201 } | |
202 | |
203 bool account_removed = false; | |
204 // Stop tracking the accounts, that were removed, as it will be reported to | |
205 // the driver. | |
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++); | |
211 } else { | |
212 ++iter; | |
213 } | |
214 } | |
215 | |
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); | |
225 } else { | |
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 | |
228 // fetching tokens. | |
229 NOTREACHED(); | |
230 } | |
231 } | |
232 | |
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(); | |
239 } else { | |
240 DVLOG(1) << "No tokens and nothing removed. Skipping callback."; | |
241 } | |
242 } | |
243 | |
244 void GCMAccountTracker::SanitizeTokens() { | |
245 for (AccountInfos::iterator iter = account_infos_.begin(); | |
246 iter != account_infos_.end(); | |
247 ++iter) { | |
248 if (iter->second.state == TOKEN_PRESENT && | |
249 iter->second.expiration_time < | |
250 base::Time::Now() + | |
251 base::TimeDelta::FromMilliseconds(kMinimumTokenValidityMs)) { | |
252 iter->second.access_token.clear(); | |
253 iter->second.state = TOKEN_NEEDED; | |
254 iter->second.expiration_time = base::Time(); | |
255 } | |
256 } | |
257 } | |
258 | |
259 bool GCMAccountTracker::IsTokenReportingRequired() const { | |
260 if (GetTimeToNextTokenReporting() == base::TimeDelta()) | |
261 return true; | |
262 | |
263 bool reporting_required = false; | |
264 for (AccountInfos::const_iterator iter = account_infos_.begin(); | |
265 iter != account_infos_.end(); | |
266 ++iter) { | |
267 if (iter->second.state == ACCOUNT_REMOVED) | |
268 reporting_required = true; | |
269 } | |
270 | |
271 return reporting_required; | |
272 } | |
273 | |
274 bool GCMAccountTracker::IsTokenFetchingRequired() const { | |
275 bool token_needed = false; | |
276 for (AccountInfos::const_iterator iter = account_infos_.begin(); | |
277 iter != account_infos_.end(); | |
278 ++iter) { | |
279 if (iter->second.state == TOKEN_NEEDED) | |
280 token_needed = true; | |
281 } | |
282 | |
283 return token_needed; | |
284 } | |
285 | |
286 base::TimeDelta GCMAccountTracker::GetTimeToNextTokenReporting() const { | |
287 base::TimeDelta time_till_next_reporting = | |
288 driver_->GetLastTokenFetchTime() + | |
289 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs) - | |
290 base::Time::Now(); | |
291 | |
292 // Case when token fetching is overdue. | |
293 if (time_till_next_reporting < base::TimeDelta()) | |
294 return base::TimeDelta(); | |
295 | |
296 // Case when calculated period is larger than expected, including the | |
297 // situation when the method is called before GCM driver is completely | |
298 // initialized. | |
299 if (time_till_next_reporting > | |
300 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs)) { | |
301 return base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs); | |
302 } | |
303 | |
304 return time_till_next_reporting; | |
305 } | |
306 | |
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); | |
313 } | |
314 | |
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 | |
320 // fetching. | |
321 if (!driver_->IsConnected()) | |
322 return; | |
323 | |
324 for (AccountInfos::iterator iter = account_infos_.begin(); | |
325 iter != account_infos_.end(); | |
326 ++iter) { | |
327 if (iter->second.state == TOKEN_NEEDED) | |
328 GetToken(iter); | |
329 } | |
330 } | |
331 | |
332 void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) { | |
333 DCHECK(GetTokenService()); | |
334 DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED); | |
335 | |
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); | |
341 | |
342 pending_token_requests_.push_back(request.release()); | |
343 account_iter->second.state = GETTING_TOKEN; | |
344 } | |
345 | |
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; | |
355 } | |
356 | |
357 GetAllNeededTokens(); | |
358 } | |
359 | |
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()) | |
364 return; | |
365 | |
366 iter->second.access_token.clear(); | |
367 iter->second.state = ACCOUNT_REMOVED; | |
368 ReportTokens(); | |
369 } | |
370 | |
371 OAuth2TokenService* GCMAccountTracker::GetTokenService() { | |
372 DCHECK(account_tracker_->identity_provider()); | |
373 return account_tracker_->identity_provider()->GetTokenService(); | |
374 } | |
375 | |
376 } // namespace gcm | |
OLD | NEW |