// 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 oauth_credentials, scoped_refptr 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() {} 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 callbacks(pending_callbacks_); pending_callbacks_ = std::queue(); 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 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