普通文本  |  181行  |  5.77 KB

// 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 "remoting/host/oauth_token_getter.h"

#include "base/bind.h"
#include "base/callback.h"
#include "base/strings/string_util.h"
#include "google_apis/google_api_keys.h"
#include "net/url_request/url_request_context_getter.h"
#include "remoting/base/logging.h"

namespace remoting {

namespace {

// Maximum number of retries on network/500 errors.
const int kMaxRetries = 3;

// Time when we we try to update OAuth token before its expiration.
const int kTokenUpdateTimeBeforeExpirySeconds = 60;

}  // namespace

OAuthTokenGetter::OAuthCredentials::OAuthCredentials(
    const std::string& login,
    const std::string& refresh_token,
    bool is_service_account)
    : login(login),
      refresh_token(refresh_token),
      is_service_account(is_service_account) {
}

OAuthTokenGetter::OAuthTokenGetter(
    scoped_ptr<OAuthCredentials> oauth_credentials,
    scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
    bool auto_refresh)
    : oauth_credentials_(oauth_credentials.Pass()),
      gaia_oauth_client_(
          new gaia::GaiaOAuthClient(url_request_context_getter.get())),
      url_request_context_getter_(url_request_context_getter),
      refreshing_oauth_token_(false) {
  if (auto_refresh) {
    refresh_timer_.reset(new base::OneShotTimer<OAuthTokenGetter>());
  }
}

OAuthTokenGetter::~OAuthTokenGetter() {}

void OAuthTokenGetter::OnGetTokensResponse(const std::string& user_email,
                                           const std::string& access_token,
                                           int expires_seconds) {
  NOTREACHED();
}

void OAuthTokenGetter::OnRefreshTokenResponse(
    const std::string& access_token,
    int expires_seconds) {
  DCHECK(CalledOnValidThread());
  DCHECK(oauth_credentials_.get());
  HOST_LOG << "Received OAuth token.";

  oauth_access_token_ = access_token;
  base::TimeDelta token_expiration =
      base::TimeDelta::FromSeconds(expires_seconds) -
      base::TimeDelta::FromSeconds(kTokenUpdateTimeBeforeExpirySeconds);
  auth_token_expiry_time_ = base::Time::Now() + token_expiration;

  if (refresh_timer_) {
    refresh_timer_->Stop();
    refresh_timer_->Start(FROM_HERE, token_expiration, this,
                          &OAuthTokenGetter::RefreshOAuthToken);
  }

  if (verified_email_.empty()) {
    gaia_oauth_client_->GetUserEmail(access_token, kMaxRetries, this);
  } else {
    refreshing_oauth_token_ = false;
    NotifyCallbacks(
        OAuthTokenGetter::SUCCESS, verified_email_, oauth_access_token_);
  }
}

void OAuthTokenGetter::OnGetUserEmailResponse(const std::string& user_email) {
  DCHECK(CalledOnValidThread());
  DCHECK(oauth_credentials_.get());
  HOST_LOG << "Received user info.";

  if (user_email != oauth_credentials_->login) {
    LOG(ERROR) << "OAuth token and email address do not refer to "
        "the same account.";
    OnOAuthError();
    return;
  }

  verified_email_ = user_email;
  refreshing_oauth_token_ = false;

  // Now that we've refreshed the token and verified that it's for the correct
  // user account, try to connect using the new token.
  NotifyCallbacks(OAuthTokenGetter::SUCCESS, user_email, oauth_access_token_);
}

void OAuthTokenGetter::NotifyCallbacks(Status status,
                     const std::string& user_email,
                     const std::string& access_token) {
  std::queue<TokenCallback> callbacks(pending_callbacks_);
  pending_callbacks_ = std::queue<TokenCallback>();

  while (!callbacks.empty()) {
    callbacks.front().Run(status, user_email, access_token);
    callbacks.pop();
  }
}

void OAuthTokenGetter::OnOAuthError() {
  DCHECK(CalledOnValidThread());
  LOG(ERROR) << "OAuth: invalid credentials.";
  refreshing_oauth_token_ = false;

  // Throw away invalid credentials and force a refresh.
  oauth_access_token_.clear();
  auth_token_expiry_time_ = base::Time();
  verified_email_.clear();

  NotifyCallbacks(OAuthTokenGetter::AUTH_ERROR, std::string(), std::string());
}

void OAuthTokenGetter::OnNetworkError(int response_code) {
  DCHECK(CalledOnValidThread());
  LOG(ERROR) << "Network error when trying to update OAuth token: "
             << response_code;
  refreshing_oauth_token_ = false;
  NotifyCallbacks(
      OAuthTokenGetter::NETWORK_ERROR, std::string(), std::string());
}

void OAuthTokenGetter::CallWithToken(const TokenCallback& on_access_token) {
  DCHECK(CalledOnValidThread());
  bool need_new_auth_token = auth_token_expiry_time_.is_null() ||
      base::Time::Now() >= auth_token_expiry_time_ ||
      verified_email_.empty();

  if (need_new_auth_token) {
    pending_callbacks_.push(on_access_token);
    if (!refreshing_oauth_token_)
      RefreshOAuthToken();
  } else {
    on_access_token.Run(
        SUCCESS, oauth_credentials_->login, oauth_access_token_);
  }
}

void OAuthTokenGetter::RefreshOAuthToken() {
  DCHECK(CalledOnValidThread());
  HOST_LOG << "Refreshing OAuth token.";
  DCHECK(!refreshing_oauth_token_);

  // Service accounts use different API keys, as they use the client app flow.
  google_apis::OAuth2Client oauth2_client =
      oauth_credentials_->is_service_account ?
      google_apis::CLIENT_REMOTING_HOST : google_apis::CLIENT_REMOTING;

  gaia::OAuthClientInfo client_info = {
    google_apis::GetOAuth2ClientID(oauth2_client),
    google_apis::GetOAuth2ClientSecret(oauth2_client),
    // Redirect URL is only used when getting tokens from auth code. It
    // is not required when getting access tokens.
    ""
  };

  refreshing_oauth_token_ = true;
  std::vector<std::string> empty_scope_list;  // Use scope from refresh token.
  gaia_oauth_client_->RefreshToken(
      client_info, oauth_credentials_->refresh_token, empty_scope_list,
      kMaxRetries, this);
}

}  // namespace remoting