gcm_account_tracker.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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. #include "components/gcm_driver/gcm_account_tracker.h"
  5. #include <stdint.h>
  6. #include <algorithm>
  7. #include <vector>
  8. #include "base/bind.h"
  9. #include "base/location.h"
  10. #include "base/task/single_thread_task_runner.h"
  11. #include "base/threading/thread_task_runner_handle.h"
  12. #include "base/time/time.h"
  13. #include "components/gcm_driver/gcm_driver.h"
  14. #include "components/signin/public/identity_manager/access_token_fetcher.h"
  15. #include "components/signin/public/identity_manager/access_token_info.h"
  16. #include "components/signin/public/identity_manager/identity_manager.h"
  17. #include "components/signin/public/identity_manager/scope_set.h"
  18. #include "google_apis/gaia/gaia_constants.h"
  19. #include "google_apis/gaia/google_service_auth_error.h"
  20. #include "net/base/ip_endpoint.h"
  21. namespace gcm {
  22. namespace {
  23. // Name of the GCM account tracker for fetching access tokens.
  24. const char kGCMAccountTrackerName[] = "gcm_account_tracker";
  25. // Minimum token validity when sending to GCM groups server.
  26. const int64_t kMinimumTokenValidityMs = 500;
  27. // Token reporting interval, when no account changes are detected.
  28. const int64_t kTokenReportingIntervalMs =
  29. 12 * 60 * 60 * 1000; // 12 hours in ms.
  30. } // namespace
  31. GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
  32. AccountState state)
  33. : email(email), state(state) {
  34. }
  35. GCMAccountTracker::AccountInfo::~AccountInfo() {
  36. }
  37. GCMAccountTracker::GCMAccountTracker(
  38. std::unique_ptr<AccountTracker> account_tracker,
  39. signin::IdentityManager* identity_manager,
  40. GCMDriver* driver)
  41. : account_tracker_(account_tracker.release()),
  42. identity_manager_(identity_manager),
  43. driver_(driver),
  44. shutdown_called_(false) {}
  45. GCMAccountTracker::~GCMAccountTracker() {
  46. DCHECK(shutdown_called_);
  47. }
  48. void GCMAccountTracker::Shutdown() {
  49. shutdown_called_ = true;
  50. driver_->RemoveConnectionObserver(this);
  51. account_tracker_->RemoveObserver(this);
  52. account_tracker_->Shutdown();
  53. }
  54. void GCMAccountTracker::Start() {
  55. DCHECK(!shutdown_called_);
  56. account_tracker_->AddObserver(this);
  57. driver_->AddConnectionObserver(this);
  58. std::vector<CoreAccountInfo> accounts = account_tracker_->GetAccounts();
  59. for (std::vector<CoreAccountInfo>::const_iterator iter = accounts.begin();
  60. iter != accounts.end(); ++iter) {
  61. if (!iter->email.empty()) {
  62. account_infos_.insert(std::make_pair(
  63. iter->account_id, AccountInfo(iter->email, TOKEN_NEEDED)));
  64. }
  65. }
  66. if (IsTokenReportingRequired())
  67. ReportTokens();
  68. else
  69. ScheduleReportTokens();
  70. }
  71. void GCMAccountTracker::ScheduleReportTokens() {
  72. // Shortcutting here, in case GCM Driver is not yet connected. In that case
  73. // reporting will be scheduled/started when the connection is made.
  74. if (!driver_->IsConnected())
  75. return;
  76. DVLOG(1) << "Deferring the token reporting for: "
  77. << GetTimeToNextTokenReporting().InSeconds() << " seconds.";
  78. reporting_weak_ptr_factory_.InvalidateWeakPtrs();
  79. base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
  80. FROM_HERE,
  81. base::BindOnce(&GCMAccountTracker::ReportTokens,
  82. reporting_weak_ptr_factory_.GetWeakPtr()),
  83. GetTimeToNextTokenReporting());
  84. }
  85. void GCMAccountTracker::OnAccountSignInChanged(const CoreAccountInfo& account,
  86. bool is_signed_in) {
  87. if (is_signed_in)
  88. OnAccountSignedIn(account);
  89. else
  90. OnAccountSignedOut(account);
  91. }
  92. void GCMAccountTracker::OnAccessTokenFetchCompleteForAccount(
  93. CoreAccountId account_id,
  94. GoogleServiceAuthError error,
  95. signin::AccessTokenInfo access_token_info) {
  96. auto iter = account_infos_.find(account_id);
  97. DCHECK(iter != account_infos_.end());
  98. if (iter != account_infos_.end()) {
  99. DCHECK_EQ(GETTING_TOKEN, iter->second.state);
  100. if (error.state() == GoogleServiceAuthError::NONE) {
  101. DVLOG(1) << "Get token success: " << account_id;
  102. iter->second.state = TOKEN_PRESENT;
  103. iter->second.access_token = access_token_info.token;
  104. iter->second.expiration_time = access_token_info.expiration_time;
  105. } else {
  106. DVLOG(1) << "Get token failure: " << account_id;
  107. // Given the fetcher has a built in retry logic, consider this situation
  108. // to be invalid refresh token, that is only fixed when user signs in.
  109. // Once the users signs in properly the minting will retry.
  110. iter->second.access_token.clear();
  111. iter->second.state = ACCOUNT_REMOVED;
  112. }
  113. }
  114. pending_token_requests_.erase(account_id);
  115. ReportTokens();
  116. }
  117. void GCMAccountTracker::OnConnected(const net::IPEndPoint& ip_endpoint) {
  118. // We are sure here, that GCM is running and connected. We can start reporting
  119. // tokens if reporting is due now, or schedule reporting for later.
  120. if (IsTokenReportingRequired())
  121. ReportTokens();
  122. else
  123. ScheduleReportTokens();
  124. }
  125. void GCMAccountTracker::OnDisconnected() {
  126. // We are disconnected, so no point in trying to work with tokens.
  127. }
  128. void GCMAccountTracker::ReportTokens() {
  129. SanitizeTokens();
  130. // Make sure all tokens are valid.
  131. if (IsTokenFetchingRequired()) {
  132. GetAllNeededTokens();
  133. return;
  134. }
  135. // Wait for all of the pending token requests from GCMAccountTracker to be
  136. // done before you report the results.
  137. if (!pending_token_requests_.empty()) {
  138. return;
  139. }
  140. bool account_removed = false;
  141. // Stop tracking the accounts, that were removed, as it will be reported to
  142. // the driver.
  143. for (auto iter = account_infos_.begin(); iter != account_infos_.end();) {
  144. if (iter->second.state == ACCOUNT_REMOVED) {
  145. account_removed = true;
  146. account_infos_.erase(iter++);
  147. } else {
  148. ++iter;
  149. }
  150. }
  151. std::vector<GCMClient::AccountTokenInfo> account_tokens;
  152. for (auto iter = account_infos_.begin(); iter != account_infos_.end();
  153. ++iter) {
  154. if (iter->second.state == TOKEN_PRESENT) {
  155. GCMClient::AccountTokenInfo token_info;
  156. token_info.account_id = iter->first;
  157. token_info.email = iter->second.email;
  158. token_info.access_token = iter->second.access_token;
  159. account_tokens.push_back(token_info);
  160. } else {
  161. // This should not happen, as we are making a check that there are no
  162. // pending requests above, stopping tracking of removed accounts, or start
  163. // fetching tokens.
  164. NOTREACHED();
  165. }
  166. }
  167. // Make sure that there is something to report, otherwise bail out.
  168. if (!account_tokens.empty() || account_removed) {
  169. DVLOG(1) << "Reporting the tokens to driver: " << account_tokens.size();
  170. driver_->SetAccountTokens(account_tokens);
  171. driver_->SetLastTokenFetchTime(base::Time::Now());
  172. ScheduleReportTokens();
  173. } else {
  174. DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
  175. }
  176. }
  177. void GCMAccountTracker::SanitizeTokens() {
  178. for (auto iter = account_infos_.begin(); iter != account_infos_.end();
  179. ++iter) {
  180. if (iter->second.state == TOKEN_PRESENT &&
  181. iter->second.expiration_time <
  182. base::Time::Now() + base::Milliseconds(kMinimumTokenValidityMs)) {
  183. iter->second.access_token.clear();
  184. iter->second.state = TOKEN_NEEDED;
  185. iter->second.expiration_time = base::Time();
  186. }
  187. }
  188. }
  189. bool GCMAccountTracker::IsTokenReportingRequired() const {
  190. if (GetTimeToNextTokenReporting().is_zero())
  191. return true;
  192. bool reporting_required = false;
  193. for (auto iter = account_infos_.begin(); iter != account_infos_.end();
  194. ++iter) {
  195. if (iter->second.state == ACCOUNT_REMOVED)
  196. reporting_required = true;
  197. }
  198. return reporting_required;
  199. }
  200. bool GCMAccountTracker::IsTokenFetchingRequired() const {
  201. bool token_needed = false;
  202. for (auto iter = account_infos_.begin(); iter != account_infos_.end();
  203. ++iter) {
  204. if (iter->second.state == TOKEN_NEEDED)
  205. token_needed = true;
  206. }
  207. return token_needed;
  208. }
  209. base::TimeDelta GCMAccountTracker::GetTimeToNextTokenReporting() const {
  210. base::TimeDelta time_till_next_reporting =
  211. driver_->GetLastTokenFetchTime() +
  212. base::Milliseconds(kTokenReportingIntervalMs) - base::Time::Now();
  213. // Case when token fetching is overdue.
  214. if (time_till_next_reporting.is_negative())
  215. return base::TimeDelta();
  216. // Case when calculated period is larger than expected, including the
  217. // situation when the method is called before GCM driver is completely
  218. // initialized.
  219. if (time_till_next_reporting >
  220. base::Milliseconds(kTokenReportingIntervalMs)) {
  221. return base::Milliseconds(kTokenReportingIntervalMs);
  222. }
  223. return time_till_next_reporting;
  224. }
  225. void GCMAccountTracker::GetAllNeededTokens() {
  226. // Only start fetching tokens if driver is running, they have a limited
  227. // validity time and GCM connection is a good indication of network running.
  228. // If the GetAllNeededTokens was called as part of periodic schedule, it may
  229. // not have network. In that case the next network change will trigger token
  230. // fetching.
  231. if (!driver_->IsConnected())
  232. return;
  233. // Only start fetching access tokens if the user consented for sync.
  234. if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync))
  235. return;
  236. for (auto iter = account_infos_.begin(); iter != account_infos_.end();
  237. ++iter) {
  238. if (iter->second.state == TOKEN_NEEDED)
  239. GetToken(iter);
  240. }
  241. }
  242. void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) {
  243. DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED);
  244. signin::ScopeSet scopes;
  245. scopes.insert(GaiaConstants::kGCMGroupServerOAuth2Scope);
  246. scopes.insert(GaiaConstants::kGCMCheckinServerOAuth2Scope);
  247. // NOTE: It is safe to use base::Unretained() here as |token_fetcher| is owned
  248. // by this object and guarantees that it will not invoke its callback after
  249. // its destruction.
  250. std::unique_ptr<signin::AccessTokenFetcher> token_fetcher =
  251. identity_manager_->CreateAccessTokenFetcherForAccount(
  252. account_iter->first, kGCMAccountTrackerName, scopes,
  253. base::BindOnce(
  254. &GCMAccountTracker::OnAccessTokenFetchCompleteForAccount,
  255. base::Unretained(this), account_iter->first),
  256. signin::AccessTokenFetcher::Mode::kImmediate);
  257. DCHECK(pending_token_requests_.count(account_iter->first) == 0);
  258. pending_token_requests_.emplace(account_iter->first,
  259. std::move(token_fetcher));
  260. account_iter->second.state = GETTING_TOKEN;
  261. }
  262. void GCMAccountTracker::OnAccountSignedIn(const CoreAccountInfo& account) {
  263. DVLOG(1) << "Account signed in: " << account.email;
  264. auto iter = account_infos_.find(account.account_id);
  265. if (iter == account_infos_.end()) {
  266. DCHECK(!account.email.empty());
  267. account_infos_.insert(std::make_pair(
  268. account.account_id, AccountInfo(account.email, TOKEN_NEEDED)));
  269. } else if (iter->second.state == ACCOUNT_REMOVED) {
  270. iter->second.state = TOKEN_NEEDED;
  271. }
  272. GetAllNeededTokens();
  273. }
  274. void GCMAccountTracker::OnAccountSignedOut(const CoreAccountInfo& account) {
  275. DVLOG(1) << "Account signed out: " << account.email;
  276. auto iter = account_infos_.find(account.account_id);
  277. if (iter == account_infos_.end())
  278. return;
  279. iter->second.access_token.clear();
  280. iter->second.state = ACCOUNT_REMOVED;
  281. // Delete any ongoing access token request now so that if the account is later
  282. // re-added and a new access token request made, we do not break this class'
  283. // invariant that there is at most one ongoing access token request per
  284. // account.
  285. pending_token_requests_.erase(account.account_id);
  286. ReportTokens();
  287. }
  288. } // namespace gcm