Skip to content

Commit

Permalink
Adding GCMAccountTracker to fetch tokens for GCM Checkin.
Browse files Browse the repository at this point in the history
* Listening on gaia::AccountTracker for SignIn events.
* Collecting GCM group tokens for all signed in accounts
* Reporting email to token mapping through callback with information if
  any account was removed
* Adding a check on gaia::AccountTracker if it is still fetching account
  information
* Adding ability to issue token fetch errors on FakeOAuth2TokenService.

R=jianli@chromium.org,rogerta@chromium.org,courage@chromium.org
BUG=374969

Review URL: https://codereview.chromium.org/341273010

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@280926 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
fgorski@chromium.org committed Jul 2, 2014
1 parent 9efc64b commit 641c8cc
Show file tree
Hide file tree
Showing 9 changed files with 730 additions and 1 deletion.
245 changes: 245 additions & 0 deletions chrome/browser/services/gcm/gcm_account_tracker.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
// 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 "chrome/browser/services/gcm/gcm_account_tracker.h"

#include <algorithm>
#include <vector>

#include "base/time/time.h"
#include "google_apis/gaia/google_service_auth_error.h"

namespace gcm {

namespace {
const char kGCMGroupServerScope[] =
"oauth2:https://www.googleapis.com/auth/gcm";
const char kGCMAccountTrackerName[] = "gcm_account_tracker";
} // namespace

GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
AccountState state)
: email(email), state(state) {
}

GCMAccountTracker::AccountInfo::~AccountInfo() {
}

GCMAccountTracker::GCMAccountTracker(
scoped_ptr<gaia::AccountTracker> account_tracker,
const UpdateAccountsCallback& callback)
: OAuth2TokenService::Consumer(kGCMAccountTrackerName),
account_tracker_(account_tracker.release()),
callback_(callback),
shutdown_called_(false) {
DCHECK(!callback_.is_null());
}

GCMAccountTracker::~GCMAccountTracker() {
DCHECK(shutdown_called_);
}

void GCMAccountTracker::Shutdown() {
Stop();
shutdown_called_ = true;
account_tracker_->Shutdown();
}

void GCMAccountTracker::Start() {
DCHECK(!shutdown_called_);
account_tracker_->AddObserver(this);

std::vector<gaia::AccountIds> accounts = account_tracker_->GetAccounts();
if (accounts.empty()) {
CompleteCollectingTokens();
return;
}

for (std::vector<gaia::AccountIds>::const_iterator iter = accounts.begin();
iter != accounts.end();
++iter) {
if (!iter->email.empty()) {
account_infos_.insert(std::make_pair(
iter->account_key, AccountInfo(iter->email, TOKEN_NEEDED)));
}
}

GetAllNeededTokens();
}

void GCMAccountTracker::Stop() {
DCHECK(!shutdown_called_);
account_tracker_->RemoveObserver(this);
pending_token_requests_.clear();
}

void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds& ids) {
// We listen for the account signing in, which happens after account is added.
}

void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds& ids) {
// We listen for the account signing out, which happens before account is
// removed.
}

void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds& ids,
bool is_signed_in) {
if (is_signed_in)
OnAccountSignedIn(ids);
else
OnAccountSignedOut(ids);
}

void GCMAccountTracker::OnGetTokenSuccess(
const OAuth2TokenService::Request* request,
const std::string& access_token,
const base::Time& expiration_time) {
DCHECK(request);
DCHECK(!request->GetAccountId().empty());

AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
DCHECK(iter != account_infos_.end());
if (iter != account_infos_.end()) {
DCHECK(iter->second.state == GETTING_TOKEN ||
iter->second.state == ACCOUNT_REMOVED);
// If OnAccountSignedOut(..) was called most recently, account is kept in
// ACCOUNT_REMOVED state.
if (iter->second.state == GETTING_TOKEN) {
iter->second.state = TOKEN_PRESENT;
iter->second.access_token = access_token;
}
}

DeleteTokenRequest(request);
CompleteCollectingTokens();
}

void GCMAccountTracker::OnGetTokenFailure(
const OAuth2TokenService::Request* request,
const GoogleServiceAuthError& error) {
DCHECK(request);
DCHECK(!request->GetAccountId().empty());

AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
DCHECK(iter != account_infos_.end());
if (iter != account_infos_.end()) {
DCHECK(iter->second.state == GETTING_TOKEN ||
iter->second.state == ACCOUNT_REMOVED);
// If OnAccountSignedOut(..) was called most recently, account is kept in
// ACCOUNT_REMOVED state.
if (iter->second.state == GETTING_TOKEN)
iter->second.state = TOKEN_NEEDED;
}

DeleteTokenRequest(request);
CompleteCollectingTokens();
}

void GCMAccountTracker::CompleteCollectingTokens() {
DCHECK(!callback_.is_null());
// Wait for gaia::AccountTracker to be done with fetching the user info, as
// well as all of the pending token requests from GCMAccountTracker to be done
// before you report the results.
if (!account_tracker_->IsAllUserInfoFetched() ||
!pending_token_requests_.empty()) {
return;
}

bool account_removed = false;
std::map<std::string, std::string> account_tokens;
for (AccountInfos::iterator iter = account_infos_.begin();
iter != account_infos_.end();) {
switch (iter->second.state) {
case ACCOUNT_REMOVED:
// We only mark accounts as removed when there was an account that was
// explicitly signed out.
account_removed = true;
// We also stop tracking the account, now that it will be reported as
// removed.
account_infos_.erase(iter++);
break;

case TOKEN_PRESENT:
account_tokens[iter->second.email] = iter->second.access_token;
++iter;
break;

case GETTING_TOKEN:
// This should not happen, as we are making a check that there are no
// pending requests above.
NOTREACHED();
++iter;
break;

case TOKEN_NEEDED:
// We failed to fetch an access token for the account, but it has not
// been signed out (perhaps there is a network issue). We don't report
// it, but next time there is a sign-in change we will update its state.
++iter;
break;
}
}

callback_.Run(account_tokens, account_removed);
}

void GCMAccountTracker::DeleteTokenRequest(
const OAuth2TokenService::Request* request) {
ScopedVector<OAuth2TokenService::Request>::iterator iter = std::find(
pending_token_requests_.begin(), pending_token_requests_.end(), request);
if (iter != pending_token_requests_.end())
pending_token_requests_.erase(iter);
}

void GCMAccountTracker::GetAllNeededTokens() {
for (AccountInfos::iterator 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(GetTokenService());
DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED);

OAuth2TokenService::ScopeSet scopes;
scopes.insert(kGCMGroupServerScope);
scoped_ptr<OAuth2TokenService::Request> request =
GetTokenService()->StartRequest(account_iter->first, scopes, this);

pending_token_requests_.push_back(request.release());
account_iter->second.state = GETTING_TOKEN;
}

void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds& ids) {
AccountInfos::iterator iter = account_infos_.find(ids.account_key);
if (iter == account_infos_.end()) {
DCHECK(!ids.email.empty());
account_infos_.insert(
std::make_pair(ids.account_key, AccountInfo(ids.email, TOKEN_NEEDED)));
} else if (iter->second.state == ACCOUNT_REMOVED) {
iter->second.state = TOKEN_NEEDED;
}

GetAllNeededTokens();
}

void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds& ids) {
AccountInfos::iterator iter = account_infos_.find(ids.account_key);
if (iter == account_infos_.end())
return;

iter->second.access_token.clear();
iter->second.state = ACCOUNT_REMOVED;
CompleteCollectingTokens();
}

OAuth2TokenService* GCMAccountTracker::GetTokenService() {
DCHECK(account_tracker_->identity_provider());
return account_tracker_->identity_provider()->GetTokenService();
}

} // namespace gcm
135 changes: 135 additions & 0 deletions chrome/browser/services/gcm/gcm_account_tracker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// 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.

#ifndef CHROME_BROWSER_SERVICES_GCM_GCM_ACCOUNT_TRACKER_H_
#define CHROME_BROWSER_SERVICES_GCM_GCM_ACCOUNT_TRACKER_H_

#include <map>
#include <string>

#include "base/memory/scoped_vector.h"
#include "google_apis/gaia/account_tracker.h"
#include "google_apis/gaia/oauth2_token_service.h"

namespace gcm {

// Class for reporting back which accounts are signed into. It is only meant to
// be used when the user is signed into sync.
class GCMAccountTracker : public gaia::AccountTracker::Observer,
public OAuth2TokenService::Consumer {
public:
// State of the account.
// Allowed transitions:
// TOKEN_NEEDED - account info was created.
// TOKEN_NEEDED -> GETTING_TOKEN - access token was requested.
// GETTING_TOKEN -> TOKEN_NEEDED - access token fetching failed.
// GETTING_TOKEN -> TOKEN_PRESENT - access token fetching succeeded.
// GETTING_TOKEN -> ACCOUNT_REMOVED - account was removed.
// TOKEN_NEEDED -> ACCOUNT_REMOVED - account was removed.
// TOKEN_PRESENT -> ACCOUNT_REMOVED - account was removed.
enum AccountState {
TOKEN_NEEDED, // Needs a token (AccountInfo was recently created or
// token request failed).
GETTING_TOKEN, // There is a pending token request.
TOKEN_PRESENT, // We have a token for the account.
ACCOUNT_REMOVED, // Account was removed, and we didn't report it yet.
};

// Stores necessary account information and state of token fetching.
struct AccountInfo {
AccountInfo(const std::string& email, AccountState state);
~AccountInfo();

// Email address of the tracked account.
std::string email;
// OAuth2 access token, when |state| is TOKEN_PRESENT.
std::string access_token;
// Status of the token fetching.
AccountState state;
};

// Callback for the GetAccountsForCheckin call. |account_tokens| maps email
// addresses to OAuth2 access tokens. |account_removed| indicates whether an
// account has been removed since the last time the callback was called.
typedef base::Callback<
void(const std::map<std::string, std::string>& account_tokens,
bool account_removed)> UpdateAccountsCallback;

// Creates an instance of GCMAccountTracker. |account_tracker| is used to
// deliver information about the account, while |callback| will be called
// once all of the accounts have been fetched a necessary OAuth2 token, as
// many times as the list of accounts is stable, meaning that all accounts
// are known and there is no related activity in progress for them, like
// fetching OAuth2 tokens.
GCMAccountTracker(scoped_ptr<gaia::AccountTracker> account_tracker,
const UpdateAccountsCallback& callback);
virtual ~GCMAccountTracker();

// Shuts down the tracker ensuring a proper clean up. After Shutdown() is
// called Start() and Stop() should no longer be used. Must be called before
// destruction.
void Shutdown();

// Starts tracking accounts.
void Start();
// Stops tracking accounts. Cancels all of the pending token requests.
void Stop();

private:
// Maps account keys to account states. Keyed by account_ids as used by
// OAuth2TokenService.
typedef std::map<std::string, AccountInfo> AccountInfos;

// AccountTracker::Observer overrides.
virtual void OnAccountAdded(const gaia::AccountIds& ids) OVERRIDE;
virtual void OnAccountRemoved(const gaia::AccountIds& ids) OVERRIDE;
virtual void OnAccountSignInChanged(const gaia::AccountIds& ids,
bool is_signed_in) OVERRIDE;

// OAuth2TokenService::Consumer overrides.
virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
const std::string& access_token,
const base::Time& expiration_time) OVERRIDE;
virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
const GoogleServiceAuthError& error) OVERRIDE;

// Report the list of accounts with OAuth2 tokens back using the |callback_|
// function. If there are token requests in progress, do nothing.
void CompleteCollectingTokens();
// Deletes a token request. Should be called from OnGetTokenSuccess(..) or
// OnGetTokenFailure(..).
void DeleteTokenRequest(const OAuth2TokenService::Request* request);
// Checks on all known accounts, and calls GetToken(..) for those with
// |state == TOKEN_NEEDED|.
void GetAllNeededTokens();
// Starts fetching the OAuth2 token for the GCM group scope.
void GetToken(AccountInfos::iterator& account_iter);

// Handling of actual sign in and sign out for accounts.
void OnAccountSignedIn(const gaia::AccountIds& ids);
void OnAccountSignedOut(const gaia::AccountIds& ids);

OAuth2TokenService* GetTokenService();

// Account tracker.
scoped_ptr<gaia::AccountTracker> account_tracker_;

// Callback to be called after all of the account and OAuth2 tokens are
// collected.
UpdateAccountsCallback callback_;

// State of the account.
AccountInfos account_infos_;

// Indicates whether shutdown has been called.
bool shutdown_called_;

ScopedVector<OAuth2TokenService::Request> pending_token_requests_;

DISALLOW_COPY_AND_ASSIGN(GCMAccountTracker);
};

} // namespace gcm

#endif // CHROME_BROWSER_SERVICES_GCM_GCM_ACCOUNT_TRACKER_H_
Loading

0 comments on commit 641c8cc

Please sign in to comment.