summaryrefslogtreecommitdiff
path: root/chromium/components/gcm_driver/gcm_account_tracker.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/gcm_driver/gcm_account_tracker.cc')
-rw-r--r--chromium/components/gcm_driver/gcm_account_tracker.cc343
1 files changed, 343 insertions, 0 deletions
diff --git a/chromium/components/gcm_driver/gcm_account_tracker.cc b/chromium/components/gcm_driver/gcm_account_tracker.cc
new file mode 100644
index 00000000000..1fed7bf7d6e
--- /dev/null
+++ b/chromium/components/gcm_driver/gcm_account_tracker.cc
@@ -0,0 +1,343 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/gcm_driver/gcm_account_tracker.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/gcm_driver/gcm_driver.h"
+#include "components/signin/public/identity_manager/access_token_fetcher.h"
+#include "components/signin/public/identity_manager/access_token_info.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/signin/public/identity_manager/scope_set.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "net/base/ip_endpoint.h"
+
+namespace gcm {
+
+namespace {
+
+// Name of the GCM account tracker for fetching access tokens.
+const char kGCMAccountTrackerName[] = "gcm_account_tracker";
+// Minimum token validity when sending to GCM groups server.
+const int64_t kMinimumTokenValidityMs = 500;
+// Token reporting interval, when no account changes are detected.
+const int64_t kTokenReportingIntervalMs =
+ 12 * 60 * 60 * 1000; // 12 hours in ms.
+
+} // namespace
+
+GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
+ AccountState state)
+ : email(email), state(state) {
+}
+
+GCMAccountTracker::AccountInfo::~AccountInfo() {
+}
+
+GCMAccountTracker::GCMAccountTracker(
+ std::unique_ptr<AccountTracker> account_tracker,
+ signin::IdentityManager* identity_manager,
+ GCMDriver* driver)
+ : account_tracker_(account_tracker.release()),
+ identity_manager_(identity_manager),
+ driver_(driver),
+ shutdown_called_(false) {}
+
+GCMAccountTracker::~GCMAccountTracker() {
+ DCHECK(shutdown_called_);
+}
+
+void GCMAccountTracker::Shutdown() {
+ shutdown_called_ = true;
+ driver_->RemoveConnectionObserver(this);
+ account_tracker_->RemoveObserver(this);
+ account_tracker_->Shutdown();
+}
+
+void GCMAccountTracker::Start() {
+ DCHECK(!shutdown_called_);
+ account_tracker_->AddObserver(this);
+ driver_->AddConnectionObserver(this);
+
+ std::vector<CoreAccountInfo> accounts = account_tracker_->GetAccounts();
+ for (std::vector<CoreAccountInfo>::const_iterator iter = accounts.begin();
+ iter != accounts.end(); ++iter) {
+ if (!iter->email.empty()) {
+ account_infos_.insert(std::make_pair(
+ iter->account_id, AccountInfo(iter->email, TOKEN_NEEDED)));
+ }
+ }
+
+ if (IsTokenReportingRequired())
+ ReportTokens();
+ else
+ ScheduleReportTokens();
+}
+
+void GCMAccountTracker::ScheduleReportTokens() {
+ // Shortcutting here, in case GCM Driver is not yet connected. In that case
+ // reporting will be scheduled/started when the connection is made.
+ if (!driver_->IsConnected())
+ return;
+
+ DVLOG(1) << "Deferring the token reporting for: "
+ << GetTimeToNextTokenReporting().InSeconds() << " seconds.";
+
+ reporting_weak_ptr_factory_.InvalidateWeakPtrs();
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&GCMAccountTracker::ReportTokens,
+ reporting_weak_ptr_factory_.GetWeakPtr()),
+ GetTimeToNextTokenReporting());
+}
+
+void GCMAccountTracker::OnAccountSignInChanged(const CoreAccountInfo& account,
+ bool is_signed_in) {
+ if (is_signed_in)
+ OnAccountSignedIn(account);
+ else
+ OnAccountSignedOut(account);
+}
+
+void GCMAccountTracker::OnAccessTokenFetchCompleteForAccount(
+ CoreAccountId account_id,
+ GoogleServiceAuthError error,
+ signin::AccessTokenInfo access_token_info) {
+ auto iter = account_infos_.find(account_id);
+ DCHECK(iter != account_infos_.end());
+ if (iter != account_infos_.end()) {
+ DCHECK_EQ(GETTING_TOKEN, iter->second.state);
+
+ if (error.state() == GoogleServiceAuthError::NONE) {
+ DVLOG(1) << "Get token success: " << account_id;
+
+ iter->second.state = TOKEN_PRESENT;
+ iter->second.access_token = access_token_info.token;
+ iter->second.expiration_time = access_token_info.expiration_time;
+ } else {
+ DVLOG(1) << "Get token failure: " << account_id;
+
+ // Given the fetcher has a built in retry logic, consider this situation
+ // to be invalid refresh token, that is only fixed when user signs in.
+ // Once the users signs in properly the minting will retry.
+ iter->second.access_token.clear();
+ iter->second.state = ACCOUNT_REMOVED;
+ }
+ }
+
+ pending_token_requests_.erase(account_id);
+ ReportTokens();
+}
+
+void GCMAccountTracker::OnConnected(const net::IPEndPoint& ip_endpoint) {
+ // We are sure here, that GCM is running and connected. We can start reporting
+ // tokens if reporting is due now, or schedule reporting for later.
+ if (IsTokenReportingRequired())
+ ReportTokens();
+ else
+ ScheduleReportTokens();
+}
+
+void GCMAccountTracker::OnDisconnected() {
+ // We are disconnected, so no point in trying to work with tokens.
+}
+
+void GCMAccountTracker::ReportTokens() {
+ SanitizeTokens();
+ // Make sure all tokens are valid.
+ if (IsTokenFetchingRequired()) {
+ GetAllNeededTokens();
+ return;
+ }
+
+ // Wait for all of the pending token requests from GCMAccountTracker to be
+ // done before you report the results.
+ if (!pending_token_requests_.empty()) {
+ return;
+ }
+
+ bool account_removed = false;
+ // Stop tracking the accounts, that were removed, as it will be reported to
+ // the driver.
+ for (auto iter = account_infos_.begin(); iter != account_infos_.end();) {
+ if (iter->second.state == ACCOUNT_REMOVED) {
+ account_removed = true;
+ account_infos_.erase(iter++);
+ } else {
+ ++iter;
+ }
+ }
+
+ std::vector<GCMClient::AccountTokenInfo> account_tokens;
+ for (auto iter = account_infos_.begin(); iter != account_infos_.end();
+ ++iter) {
+ if (iter->second.state == TOKEN_PRESENT) {
+ GCMClient::AccountTokenInfo token_info;
+ token_info.account_id = iter->first;
+ token_info.email = iter->second.email;
+ token_info.access_token = iter->second.access_token;
+ account_tokens.push_back(token_info);
+ } else {
+ // This should not happen, as we are making a check that there are no
+ // pending requests above, stopping tracking of removed accounts, or start
+ // fetching tokens.
+ NOTREACHED();
+ }
+ }
+
+ // Make sure that there is something to report, otherwise bail out.
+ if (!account_tokens.empty() || account_removed) {
+ DVLOG(1) << "Reporting the tokens to driver: " << account_tokens.size();
+ driver_->SetAccountTokens(account_tokens);
+ driver_->SetLastTokenFetchTime(base::Time::Now());
+ ScheduleReportTokens();
+ } else {
+ DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
+ }
+}
+
+void GCMAccountTracker::SanitizeTokens() {
+ for (auto iter = account_infos_.begin(); iter != account_infos_.end();
+ ++iter) {
+ if (iter->second.state == TOKEN_PRESENT &&
+ iter->second.expiration_time <
+ base::Time::Now() + base::Milliseconds(kMinimumTokenValidityMs)) {
+ iter->second.access_token.clear();
+ iter->second.state = TOKEN_NEEDED;
+ iter->second.expiration_time = base::Time();
+ }
+ }
+}
+
+bool GCMAccountTracker::IsTokenReportingRequired() const {
+ if (GetTimeToNextTokenReporting().is_zero())
+ return true;
+
+ bool reporting_required = false;
+ for (auto iter = account_infos_.begin(); iter != account_infos_.end();
+ ++iter) {
+ if (iter->second.state == ACCOUNT_REMOVED)
+ reporting_required = true;
+ }
+
+ return reporting_required;
+}
+
+bool GCMAccountTracker::IsTokenFetchingRequired() const {
+ bool token_needed = false;
+ for (auto iter = account_infos_.begin(); iter != account_infos_.end();
+ ++iter) {
+ if (iter->second.state == TOKEN_NEEDED)
+ token_needed = true;
+ }
+
+ return token_needed;
+}
+
+base::TimeDelta GCMAccountTracker::GetTimeToNextTokenReporting() const {
+ base::TimeDelta time_till_next_reporting =
+ driver_->GetLastTokenFetchTime() +
+ base::Milliseconds(kTokenReportingIntervalMs) - base::Time::Now();
+
+ // Case when token fetching is overdue.
+ if (time_till_next_reporting.is_negative())
+ return base::TimeDelta();
+
+ // Case when calculated period is larger than expected, including the
+ // situation when the method is called before GCM driver is completely
+ // initialized.
+ if (time_till_next_reporting >
+ base::Milliseconds(kTokenReportingIntervalMs)) {
+ return base::Milliseconds(kTokenReportingIntervalMs);
+ }
+
+ return time_till_next_reporting;
+}
+
+void GCMAccountTracker::GetAllNeededTokens() {
+ // Only start fetching tokens if driver is running, they have a limited
+ // validity time and GCM connection is a good indication of network running.
+ // If the GetAllNeededTokens was called as part of periodic schedule, it may
+ // not have network. In that case the next network change will trigger token
+ // fetching.
+ if (!driver_->IsConnected())
+ return;
+
+ // Only start fetching access tokens if the user consented for sync.
+ if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSync))
+ return;
+
+ for (auto iter = account_infos_.begin(); iter != account_infos_.end();
+ ++iter) {
+ if (iter->second.state == TOKEN_NEEDED)
+ GetToken(iter);
+ }
+}
+
+void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) {
+ DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED);
+
+ signin::ScopeSet scopes;
+ scopes.insert(GaiaConstants::kGCMGroupServerOAuth2Scope);
+ scopes.insert(GaiaConstants::kGCMCheckinServerOAuth2Scope);
+
+ // NOTE: It is safe to use base::Unretained() here as |token_fetcher| is owned
+ // by this object and guarantees that it will not invoke its callback after
+ // its destruction.
+ std::unique_ptr<signin::AccessTokenFetcher> token_fetcher =
+ identity_manager_->CreateAccessTokenFetcherForAccount(
+ account_iter->first, kGCMAccountTrackerName, scopes,
+ base::BindOnce(
+ &GCMAccountTracker::OnAccessTokenFetchCompleteForAccount,
+ base::Unretained(this), account_iter->first),
+ signin::AccessTokenFetcher::Mode::kImmediate);
+
+ DCHECK(pending_token_requests_.count(account_iter->first) == 0);
+ pending_token_requests_.emplace(account_iter->first,
+ std::move(token_fetcher));
+ account_iter->second.state = GETTING_TOKEN;
+}
+
+void GCMAccountTracker::OnAccountSignedIn(const CoreAccountInfo& account) {
+ DVLOG(1) << "Account signed in: " << account.email;
+ auto iter = account_infos_.find(account.account_id);
+ if (iter == account_infos_.end()) {
+ DCHECK(!account.email.empty());
+ account_infos_.insert(std::make_pair(
+ account.account_id, AccountInfo(account.email, TOKEN_NEEDED)));
+ } else if (iter->second.state == ACCOUNT_REMOVED) {
+ iter->second.state = TOKEN_NEEDED;
+ }
+
+ GetAllNeededTokens();
+}
+
+void GCMAccountTracker::OnAccountSignedOut(const CoreAccountInfo& account) {
+ DVLOG(1) << "Account signed out: " << account.email;
+ auto iter = account_infos_.find(account.account_id);
+ if (iter == account_infos_.end())
+ return;
+
+ iter->second.access_token.clear();
+ iter->second.state = ACCOUNT_REMOVED;
+
+ // Delete any ongoing access token request now so that if the account is later
+ // re-added and a new access token request made, we do not break this class'
+ // invariant that there is at most one ongoing access token request per
+ // account.
+ pending_token_requests_.erase(account.account_id);
+ ReportTokens();
+}
+
+} // namespace gcm