Commit ea77cec7 authored by joedow's avatar joedow Committed by Commit bot

Adding the AccessTokenFetcher and Environment class to the app remoting test

driver along with unit tests.

In this change, the test driver can now call GAIA and retrieve an access and
refresh token given an authorization code.  It can then store the refresh token
locally and retrieve it on subsequent tool runs.

BUG=

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

Cr-Commit-Position: refs/heads/master@{#318137}
parent ec6d20ec
...@@ -29,6 +29,11 @@ ...@@ -29,6 +29,11 @@
'defines': [ 'defines': [
'VERSION=<(version_full)', 'VERSION=<(version_full)',
], ],
'sources': [
'test/access_token_fetcher.cc',
'test/app_remoting_test_driver_environment.cc',
'test/refresh_token_store.cc',
],
}, # end of target 'ar_test_driver_common' }, # end of target 'ar_test_driver_common'
{ {
# An external version of the test driver tool which includes minimal tests # An external version of the test driver tool which includes minimal tests
......
...@@ -41,6 +41,9 @@ ...@@ -41,6 +41,9 @@
'signaling/fake_signal_strategy.h', 'signaling/fake_signal_strategy.h',
'signaling/mock_signal_strategy.cc', 'signaling/mock_signal_strategy.cc',
'signaling/mock_signal_strategy.h', 'signaling/mock_signal_strategy.h',
'test/access_token_fetcher.cc',
'test/app_remoting_test_driver_environment.cc',
'test/fake_access_token_fetcher.cc',
'test/fake_network_dispatcher.cc', 'test/fake_network_dispatcher.cc',
'test/fake_network_dispatcher.h', 'test/fake_network_dispatcher.h',
'test/fake_network_manager.cc', 'test/fake_network_manager.cc',
...@@ -51,6 +54,8 @@ ...@@ -51,6 +54,8 @@
'test/fake_socket_factory.h', 'test/fake_socket_factory.h',
'test/leaky_bucket.cc', 'test/leaky_bucket.cc',
'test/leaky_bucket.h', 'test/leaky_bucket.h',
'test/mock_access_token_fetcher.cc',
'test/refresh_token_store.cc',
], ],
'conditions': [ 'conditions': [
['enable_remoting_host == 0', { ['enable_remoting_host == 0', {
...@@ -221,6 +226,8 @@ ...@@ -221,6 +226,8 @@
'signaling/log_to_server_unittest.cc', 'signaling/log_to_server_unittest.cc',
'signaling/server_log_entry_unittest.cc', 'signaling/server_log_entry_unittest.cc',
'signaling/server_log_entry_unittest.h', 'signaling/server_log_entry_unittest.h',
'test/access_token_fetcher_unittest.cc',
'test/app_remoting_test_driver_environment_unittest.cc'
], ],
'conditions': [ 'conditions': [
[ 'OS=="win"', { [ 'OS=="win"', {
......
// Copyright 2015 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/test/access_token_fetcher.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/thread_task_runner_handle.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/google_api_keys.h"
#include "net/url_request/url_fetcher.h"
#include "remoting/base/url_request_context_getter.h"
namespace {
const int kMaxGetTokensRetries = 3;
const char kOauthRedirectUrl[] =
"https://chromoting-oauth.talkgadget."
"google.com/talkgadget/oauth/chrome-remote-desktop/dev";
// Factory function used to initialize our scope vector below, this is needed
// because initializer lists are only supported on C++11 compilers.
const std::vector<std::string> MakeAppRemotingScopeVector() {
std::vector<std::string> app_remoting_scopes;
// Populate the vector with the required permissions for app remoting.
app_remoting_scopes.push_back(
"https://www.googleapis.com/auth/appremoting.runapplication");
app_remoting_scopes.push_back("https://www.googleapis.com/auth/googletalk");
app_remoting_scopes.push_back(
"https://www.googleapis.com/auth/userinfo.email");
app_remoting_scopes.push_back("https://docs.google.com/feeds");
app_remoting_scopes.push_back("https://www.googleapis.com/auth/drive");
return app_remoting_scopes;
}
const std::vector<std::string> kAppRemotingScopeVector =
MakeAppRemotingScopeVector();
}
namespace remoting {
namespace test {
AccessTokenFetcher::AccessTokenFetcher() {
oauth_client_info_ = {
google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING),
google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_REMOTING),
kOauthRedirectUrl};
}
AccessTokenFetcher::~AccessTokenFetcher() {
}
void AccessTokenFetcher::GetAccessTokenFromAuthCode(
const std::string& auth_code,
const AccessTokenCallback& callback) {
DCHECK(!auth_code.empty());
DCHECK(!callback.is_null());
DCHECK(access_token_callback_.is_null());
DVLOG(2) << "Calling GetTokensFromAuthCode to exchange auth_code for token";
access_token_.clear();
refresh_token_.clear();
access_token_callback_ = callback;
// Create a new GaiaOAuthClient for each request to GAIA.
CreateNewGaiaOAuthClientInstance();
auth_client_->GetTokensFromAuthCode(
oauth_client_info_, auth_code, kMaxGetTokensRetries,
this); // GaiaOAuthClient::Delegate* delegate
}
void AccessTokenFetcher::GetAccessTokenFromRefreshToken(
const std::string& refresh_token,
const AccessTokenCallback& callback) {
DCHECK(!refresh_token.empty());
DCHECK(!callback.is_null());
DCHECK(access_token_callback_.is_null());
DVLOG(2) << "Calling RefreshToken to generate a new access token";
access_token_.clear();
refresh_token_ = refresh_token;
access_token_callback_ = callback;
// Create a new GaiaOAuthClient for each request to GAIA.
CreateNewGaiaOAuthClientInstance();
auth_client_->RefreshToken(
oauth_client_info_,
refresh_token_,
kAppRemotingScopeVector,
kMaxGetTokensRetries,
this); // GaiaOAuthClient::Delegate* delegate
}
void AccessTokenFetcher::CreateNewGaiaOAuthClientInstance() {
scoped_refptr<remoting::URLRequestContextGetter> request_context_getter;
request_context_getter = new remoting::URLRequestContextGetter(
base::ThreadTaskRunnerHandle::Get(), // network_runner
base::ThreadTaskRunnerHandle::Get()); // file_runner
auth_client_.reset(new gaia::GaiaOAuthClient(request_context_getter.get()));
}
void AccessTokenFetcher::OnGetTokensResponse(const std::string& refresh_token,
const std::string& access_token,
int expires_in_seconds) {
DVLOG(1) << "AccessTokenFetcher::OnGetTokensResponse() Called";
DVLOG(1) << "--refresh_token: " << refresh_token;
DVLOG(1) << "--access_token: " << access_token;
DVLOG(1) << "--expires_in_seconds: " << expires_in_seconds;
refresh_token_ = refresh_token;
access_token_ = access_token;
ValidateAccessToken();
}
void AccessTokenFetcher::OnRefreshTokenResponse(const std::string& access_token,
int expires_in_seconds) {
DVLOG(1) << "AccessTokenFetcher::OnRefreshTokenResponse() Called";
DVLOG(1) << "--access_token: " << access_token;
DVLOG(1) << "--expires_in_seconds: " << expires_in_seconds;
access_token_ = access_token;
ValidateAccessToken();
}
void AccessTokenFetcher::OnGetUserEmailResponse(const std::string& user_email) {
// This callback should not be called as we do not request the user's email.
NOTREACHED();
}
void AccessTokenFetcher::OnGetUserIdResponse(const std::string& user_id) {
// This callback should not be called as we do not request the user's id.
NOTREACHED();
}
void AccessTokenFetcher::OnGetUserInfoResponse(
scoped_ptr<base::DictionaryValue> user_info) {
// This callback should not be called as we do not request user info.
NOTREACHED();
}
void AccessTokenFetcher::OnGetTokenInfoResponse(
scoped_ptr<base::DictionaryValue> token_info) {
DVLOG(1) << "AccessTokenFetcher::OnGetTokenInfoResponse() Called";
std::string error_string;
std::string error_description;
// Check to see if the token_info we received had any errors,
// otherwise we will assume that it is valid for our purposes.
if (token_info->HasKey("error")) {
token_info->GetString("error", &error_string);
token_info->GetString("error_description", &error_description);
LOG(ERROR) << "OnGetTokenInfoResponse returned an error. "
<< ", "
<< "error: " << error_string << ", "
<< "description: " << error_description;
access_token_.clear();
refresh_token_.clear();
} else {
DVLOG(1) << "Access Token has been validated";
}
access_token_callback_.Run(access_token_, refresh_token_);
access_token_callback_.Reset();
}
void AccessTokenFetcher::OnOAuthError() {
LOG(ERROR) << "AccessTokenFetcher::OnOAuthError() Called";
access_token_.clear();
refresh_token_.clear();
access_token_callback_.Run(access_token_, refresh_token_);
access_token_callback_.Reset();
}
void AccessTokenFetcher::OnNetworkError(int response_code) {
LOG(ERROR) << "AccessTokenFetcher::OnNetworkError() Called";
LOG(ERROR) << "response code: " << response_code;
access_token_.clear();
refresh_token_.clear();
access_token_callback_.Run(access_token_, refresh_token_);
access_token_callback_.Reset();
}
void AccessTokenFetcher::ValidateAccessToken() {
DVLOG(2) << "Calling GetTokenInfo to validate access token";
// Create a new GaiaOAuthClient for each request to GAIA.
CreateNewGaiaOAuthClientInstance();
auth_client_->GetTokenInfo(
access_token_,
kMaxGetTokensRetries,
this); // GaiaOAuthClient::Delegate* delegate
}
} // namespace test
} // namespace remoting
// Copyright 2015 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 REMOTING_TEST_ACCESS_TOKEN_FETCHER_H_
#define REMOTING_TEST_ACCESS_TOKEN_FETCHER_H_
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "google_apis/gaia/gaia_oauth_client.h"
namespace remoting {
namespace test {
// Supplied by the client for each request to GAIA and returns valid tokens on
// success or empty tokens on failure.
typedef base::Callback<void(
const std::string& access_token,
const std::string& refresh_token)> AccessTokenCallback;
// Retrieves an access token from either an authorization code or a refresh
// token. Destroying the AccessTokenFetcher while a request is outstanding will
// cancel the request. It is safe to delete the fetcher from within a completion
// callback. Must be used from a thread running an IO message loop.
// The public methods are virtual to allow for mocking and fakes.
class AccessTokenFetcher : public gaia::GaiaOAuthClient::Delegate {
public:
AccessTokenFetcher();
~AccessTokenFetcher() override;
// Retrieve an access token from a one time use authorization code.
virtual void GetAccessTokenFromAuthCode(const std::string& auth_code,
const AccessTokenCallback& callback);
// Retrieve an access token from a refresh token.
virtual void GetAccessTokenFromRefreshToken(
const std::string& refresh_token,
const AccessTokenCallback& callback);
private:
// gaia::GaiaOAuthClient::Delegate Interface.
void OnGetTokensResponse(const std::string& refresh_token,
const std::string& access_token,
int expires_in_seconds) override;
void OnRefreshTokenResponse(const std::string& access_token,
int expires_in_seconds) override;
void OnGetUserEmailResponse(const std::string& user_email) override;
void OnGetUserIdResponse(const std::string& user_id) override;
void OnGetUserInfoResponse(
scoped_ptr<base::DictionaryValue> user_info) override;
void OnGetTokenInfoResponse(
scoped_ptr<base::DictionaryValue> token_info) override;
void OnOAuthError() override;
void OnNetworkError(int response_code) override;
// Updates |auth_client_| with a new GaiaOAuthClient instance.
void CreateNewGaiaOAuthClientInstance();
// Validates the retrieved access token.
void ValidateAccessToken();
// Caller-supplied callback used to return valid tokens on success or empty
// tokens on failure.
AccessTokenCallback access_token_callback_;
// Retrieved based on the |refresh_token_|.
std::string access_token_;
// Supplied by the caller or retrieved from a caller-supplied auth token.
std::string refresh_token_;
// Holds the client id, secret, and redirect url used to make
// the Gaia service request.
gaia::OAuthClientInfo oauth_client_info_;
// Used to make token requests to GAIA.
scoped_ptr<gaia::GaiaOAuthClient> auth_client_;
DISALLOW_COPY_AND_ASSIGN(AccessTokenFetcher);
};
} // namespace test
} // namespace remoting
#endif // REMOTING_TEST_ACCESS_TOKEN_FETCHER_H_
This diff is collapsed.
...@@ -11,14 +11,16 @@ ...@@ -11,14 +11,16 @@
#include "base/test/test_switches.h" #include "base/test/test_switches.h"
#include "google_apis/google_api_keys.h" #include "google_apis/google_api_keys.h"
#include "net/base/escape.h" #include "net/base/escape.h"
#include "remoting/test/app_remoting_test_driver_environment.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace switches { namespace switches {
const char kUserNameSwitchName[] = "username"; const char kAuthCodeSwitchName[] = "authcode";
const char kAuthCodeSwitchName[] = "authcode"; const char kHelpSwitchName[] = "help";
const char kServiceEnvironmentSwitchName[] = "environment"; const char kLoggingLevelSwitchName[] = "verbosity";
const char kHelpSwitchName[] = "help"; const char kServiceEnvironmentSwitchName[] = "environment";
const char kSingleProcessTestsSwitchName[] = "single-process-tests"; const char kSingleProcessTestsSwitchName[] = "single-process-tests";
const char kUserNameSwitchName[] = "username";
} }
namespace { namespace {
...@@ -66,6 +68,10 @@ void PrintUsage() { ...@@ -66,6 +68,10 @@ void PrintUsage() {
switches::kHelpSwitchName); switches::kHelpSwitchName);
printf(" %s: Specifies the service api to use (dev|test) [default: dev]\n", printf(" %s: Specifies the service api to use (dev|test) [default: dev]\n",
switches::kServiceEnvironmentSwitchName); switches::kServiceEnvironmentSwitchName);
printf(
" %s: Specifies the optional logging level of the tool (0-3)."
" [default: off]\n",
switches::kLoggingLevelSwitchName);
} }
void PrintAuthCodeInfo() { void PrintAuthCodeInfo() {
...@@ -145,6 +151,63 @@ int main(int argc, char** argv) { ...@@ -145,6 +151,63 @@ int main(int argc, char** argv) {
return -1; return -1;
} }
std::string user_name;
user_name = command_line->GetSwitchValueASCII(switches::kUserNameSwitchName);
DVLOG(1) << "Running tests as: " << user_name;
std::string auth_code;
// Check to see if the user passed in a one time use auth_code for
// refreshing their credentials.
auth_code = command_line->GetSwitchValueASCII(switches::kAuthCodeSwitchName);
std::string service_environment;
// If the user passed in a service environment, use it, otherwise set a
// default value.
service_environment = command_line->GetSwitchValueASCII(
switches::kServiceEnvironmentSwitchName);
if (service_environment.empty()) {
// Default to the development service environment.
service_environment = "dev";
} else if (service_environment != "test" && service_environment != "dev") {
// Only two values are allowed, so validate them before proceeding.
LOG(ERROR) << "Invalid " << switches::kServiceEnvironmentSwitchName
<< " argument passed in.";
PrintUsage();
return -1;
}
// Update the logging verbosity level is user specified one.
std::string verbosity_level;
verbosity_level =
command_line->GetSwitchValueASCII(switches::kLoggingLevelSwitchName);
if (!verbosity_level.empty()) {
// Turn on logging for the test_driver and remoting components.
// This switch is parsed during logging::InitLogging.
command_line->AppendSwitchASCII("vmodule",
"*/remoting/*=" + verbosity_level);
logging::LoggingSettings logging_settings;
logging::InitLogging(logging_settings);
}
// Create and register our global test data object. It will handle
// retrieving an access token for the user and spinning up VMs.
// The GTest framework will own the lifetime of this object once
// it is registered below.
scoped_ptr<remoting::test::AppRemotingTestDriverEnvironment> shared_data;
shared_data.reset(new remoting::test::AppRemotingTestDriverEnvironment(
user_name, service_environment));
if (!shared_data->Initialize(auth_code)) {
// If we failed to initialize our shared data object, then bail.
return -1;
}
// Since we've successfully set up our shared_data object, we'll assign the
// value to our global* and transfer ownership to the framework.
remoting::test::AppRemotingSharedData = shared_data.release();
testing::AddGlobalTestEnvironment(remoting::test::AppRemotingSharedData);
// Because many tests may access the same remoting host(s), we need to run // Because many tests may access the same remoting host(s), we need to run
// the tests sequentially so they do not interfere with each other. // the tests sequentially so they do not interfere with each other.
return base::LaunchUnitTestsSerially( return base::LaunchUnitTestsSerially(
......
// Copyright 2015 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/test/app_remoting_test_driver_environment.h"
#include "base/bind.h"
#include "base/callback_forward.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
namespace remoting {
namespace test {
AppRemotingTestDriverEnvironment* AppRemotingSharedData;
AppRemotingTestDriverEnvironment::AppRemotingTestDriverEnvironment(
const std::string& user_name,
const std::string& service_environment)
: user_name_(user_name),
service_environment_(service_environment),
test_access_token_fetcher_(nullptr),
test_refresh_token_store_(nullptr) {
DCHECK(!user_name_.empty());
DCHECK(!service_environment.empty());
}
AppRemotingTestDriverEnvironment::~AppRemotingTestDriverEnvironment() {
}
bool AppRemotingTestDriverEnvironment::Initialize(
const std::string& auth_code) {
if (!access_token_.empty()) {
return true;
}
// If a unit test has set |test_refresh_token_store_| then we should use it
// below. Note that we do not want to destroy the test object.
scoped_ptr<RefreshTokenStore> temporary_refresh_token_store;
RefreshTokenStore* refresh_token_store = test_refresh_token_store_;
if (!refresh_token_store) {
temporary_refresh_token_store = RefreshTokenStore::OnDisk(user_name_);
refresh_token_store = temporary_refresh_token_store.get();
}
// Check to see if we have a refresh token stored for this user.
refresh_token_ = refresh_token_store->FetchRefreshToken();
if (refresh_token_.empty()) {
// This isn't necessarily an error as this might be a first run scenario.
DVLOG(1) << "No refresh token stored for " << user_name_;
if (auth_code.empty()) {
// No token and no Auth code means no service connectivity, bail!
LOG(ERROR) << "Cannot retrieve an access token without a stored refresh"
<< " token on disk or an auth_code passed into the tool";
return false;
}
}
if (!RetrieveAccessToken(auth_code)) {
// If we cannot retrieve an access token, then nothing is going to work and
// we should let the caller know that our object is not ready to be used.
return false;
}
return true;
}
bool AppRemotingTestDriverEnvironment::RefreshAccessToken() {
DCHECK(!refresh_token_.empty());
// Empty auth code is used when refreshing.
return RetrieveAccessToken(std::string());
}
void AppRemotingTestDriverEnvironment::SetAccessTokenFetcherForTest(
AccessTokenFetcher* access_token_fetcher) {
DCHECK(access_token_fetcher);
test_access_token_fetcher_ = access_token_fetcher;
}
void AppRemotingTestDriverEnvironment::SetRefreshTokenStoreForTest(
RefreshTokenStore* refresh_token_store) {
DCHECK(refresh_token_store);
test_refresh_token_store_ = refresh_token_store;
}
bool AppRemotingTestDriverEnvironment::RetrieveAccessToken(
const std::string& auth_code) {
scoped_ptr<base::MessageLoopForIO> message_loop;
if (!base::MessageLoop::current()) {
// Create a temporary message loop if the current thread does not already
// have one so we can use its task runner for our network request.
message_loop.reset(new base::MessageLoopForIO);
}
base::RunLoop run_loop;
access_token_.clear();
AccessTokenCallback access_token_callback =
base::Bind(&AppRemotingTestDriverEnvironment::OnAccessTokenRetrieved,
base::Unretained(this),
run_loop.QuitClosure());
// If a unit test has set |test_access_token_fetcher_| then we should use it
// below. Note that we do not want to destroy the test object at the end of
// the function which is why we have the dance below.
scoped_ptr<AccessTokenFetcher> temporary_access_token_fetcher;
AccessTokenFetcher* access_token_fetcher = test_access_token_fetcher_;
if (!access_token_fetcher) {
temporary_access_token_fetcher.reset(new AccessTokenFetcher());
access_token_fetcher = temporary_access_token_fetcher.get();
}
if (!auth_code.empty()) {
// If the user passed in an authcode, then use it to retrieve an
// updated access/refresh token.
access_token_fetcher->GetAccessTokenFromAuthCode(
auth_code,
access_token_callback);
} else {
DCHECK(!refresh_token_.empty());
access_token_fetcher->GetAccessTokenFromRefreshToken(
refresh_token_,
access_token_callback);
}
run_loop.Run();
// If we were using an auth_code and received a valid refresh token,
// then we want to store it locally. If we had an auth code and did not
// receive a refresh token, then we should let the user know and exit.
if (!auth_code.empty()) {
if (!refresh_token_.empty()) {
// If a unit test has set |test_refresh_token_store_| then we should use
// it below. Note that we do not want to destroy the test object.
scoped_ptr<RefreshTokenStore> temporary_refresh_token_store;
RefreshTokenStore* refresh_token_store = test_refresh_token_store_;
if (!refresh_token_store) {
temporary_refresh_token_store = RefreshTokenStore::OnDisk(user_name_);
refresh_token_store = temporary_refresh_token_store.get();
}
if (!refresh_token_store->StoreRefreshToken(refresh_token_)) {
// If we failed to persist the refresh token, then we should let the
// user sort out the issue before continuing.
return false;
}
} else {
LOG(ERROR) << "Failed to use AUTH CODE to retrieve a refresh token.\n"
<< "Was the one-time use AUTH CODE used more than once?";
return false;
}
}
if (access_token_.empty()) {
LOG(ERROR) << "Failed to retrieve access token.";
return false;
}
return true;
}
void AppRemotingTestDriverEnvironment::OnAccessTokenRetrieved(
base::Closure done_closure,
const std::string& access_token,
const std::string& refresh_token) {
access_token_ = access_token;
refresh_token_ = refresh_token;
done_closure.Run();
}
} // namespace test
} // namespace remoting
// Copyright 2015 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 REMOTING_TEST_APP_REMOTING_TEST_DRIVER_ENVIRONMENT_H_
#define REMOTING_TEST_APP_REMOTING_TEST_DRIVER_ENVIRONMENT_H_
#include "base/memory/scoped_ptr.h"
#include "remoting/test/access_token_fetcher.h"
#include "remoting/test/refresh_token_store.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace test {
// Globally accessible to all test fixtures and cases and has its
// lifetime managed by the GTest framework. It is responsible for managing
// access tokens and caching remote host connection information.
// TODO(joedow) Add remote host connection functionality.
class AppRemotingTestDriverEnvironment : public testing::Environment {
public:
AppRemotingTestDriverEnvironment(
const std::string& user_name,
const std::string& service_environment);
~AppRemotingTestDriverEnvironment() override;
// Returns false if a valid access token cannot be retrieved.
bool Initialize(const std::string& auth_code);
// Synchronously request a new access token using |refresh_token_|.
// Returns true if a valid access token has been retrieved.
bool RefreshAccessToken();
// Used to set fake/mock objects for AppRemotingTestDriverEnvironment tests.
void SetAccessTokenFetcherForTest(AccessTokenFetcher* access_token_fetcher);
void SetRefreshTokenStoreForTest(RefreshTokenStore* refresh_token_store);
// Accessors for fields used by tests.
const std::string& access_token() const { return access_token_; }
const std::string& user_name() const { return user_name_; }
private:
// Used to retrieve an access token. If |auth_code| is empty, then the stored
// refresh_token will be used instead of |auth_code|.
// Returns true if a new, valid access token has been retrieved.
bool RetrieveAccessToken(const std::string& auth_code);
// Called after the access token fetcher completes.
// The tokens will be empty on failure.
void OnAccessTokenRetrieved(
base::Closure done_closure,
const std::string& access_token,
const std::string& refresh_token);
// Used for authenticating with the app remoting service API.
std::string access_token_;
// Used to retrieve an access token.
std::string refresh_token_;
// Used for authentication.
std::string user_name_;
// Service API to target when retrieving remote host connection information.
// TODO(joedow): Turn this into an enum when remote_host code is added.
std::string service_environment_;
// Access token fetcher used by TestDriverEnvironment tests.
remoting::test::AccessTokenFetcher* test_access_token_fetcher_;
// RefreshTokenStore used by TestDriverEnvironment tests.
remoting::test::RefreshTokenStore* test_refresh_token_store_;
DISALLOW_COPY_AND_ASSIGN(AppRemotingTestDriverEnvironment);
};
// Unfortunately a global var is how the GTEST framework handles sharing data
// between tests and keeping long-lived objects around. Used to share auth
// tokens and remote host connection information across tests.
extern AppRemotingTestDriverEnvironment* AppRemotingSharedData;
} // namespace test
} // namespace remoting
#endif // REMOTING_TEST_APP_REMOTING_TEST_DRIVER_ENVIRONMENT_H_
This diff is collapsed.
// Copyright 2015 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/test/fake_access_token_fetcher.h"
namespace remoting {
namespace test {
FakeAccessTokenFetcher::FakeAccessTokenFetcher() :
fail_access_token_from_auth_code_(false),
fail_access_token_from_refresh_token_(false) {}
FakeAccessTokenFetcher::~FakeAccessTokenFetcher() {}
void FakeAccessTokenFetcher::GetAccessTokenFromAuthCode(
const std::string& auth_code,
const AccessTokenCallback& callback) {
if (fail_access_token_from_auth_code_) {
// Empty strings are returned in failure cases.
callback.Run(std::string(), std::string());
} else {
callback.Run(kFakeAccessTokenFetcherAccessTokenValue,
kFakeAccessTokenFetcherRefreshTokenValue);
}
}
void FakeAccessTokenFetcher::GetAccessTokenFromRefreshToken(
const std::string& refresh_token,
const AccessTokenCallback& callback) {
if (fail_access_token_from_refresh_token_) {
// Empty strings are returned in failure cases.
callback.Run(std::string(), std::string());
} else {
callback.Run(kFakeAccessTokenFetcherAccessTokenValue,
kFakeAccessTokenFetcherRefreshTokenValue);
}
}
} // namespace test
} // namespace remoting
// Copyright 2015 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 REMOTING_TEST_FAKE_ACCESS_TOKEN_FETCHER_H_
#define REMOTING_TEST_FAKE_ACCESS_TOKEN_FETCHER_H_
#include "remoting/test/access_token_fetcher.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace test {
const char kFakeAccessTokenFetcherRefreshTokenValue[] = "fake_refresh_token";
const char kFakeAccessTokenFetcherAccessTokenValue[] = "fake_access_token";
// Used for testing classes which rely on the AccessTokenFetcher and want to
// simulate success and failure scenarios without using the actual class and
// network connection.
class FakeAccessTokenFetcher : public AccessTokenFetcher {
public:
FakeAccessTokenFetcher();
~FakeAccessTokenFetcher() override;
// AccessTokenFetcher interface.
void GetAccessTokenFromAuthCode(
const std::string& auth_code,
const AccessTokenCallback& callback) override;
void GetAccessTokenFromRefreshToken(
const std::string& refresh_token,
const AccessTokenCallback& callback) override;
void set_fail_access_token_from_auth_code(bool fail) {
fail_access_token_from_auth_code_ = fail;
}
void set_fail_access_token_from_refresh_token(bool fail) {
fail_access_token_from_refresh_token_ = fail;
}
private:
// True if GetAccessTokenFromAuthCode() should fail.
bool fail_access_token_from_auth_code_;
// True if GetAccessTokenFromRefreshToken() should fail.
bool fail_access_token_from_refresh_token_;
DISALLOW_COPY_AND_ASSIGN(FakeAccessTokenFetcher);
};
} // namespace test
} // namespace remoting
#endif // REMOTING_TEST_FAKE_ACCESS_TOKEN_FETCHER_H_
// Copyright 2015 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/test/mock_access_token_fetcher.h"
namespace remoting {
namespace test {
using ::testing::_;
using ::testing::Invoke;
MockAccessTokenFetcher::MockAccessTokenFetcher() {
}
MockAccessTokenFetcher::~MockAccessTokenFetcher() {
}
void MockAccessTokenFetcher::SetAccessTokenFetcher(
scoped_ptr<AccessTokenFetcher> fetcher) {
internal_access_token_fetcher_ = fetcher.Pass();
ON_CALL(*this, GetAccessTokenFromAuthCode(_, _)).WillByDefault(
Invoke(internal_access_token_fetcher_.get(),
&AccessTokenFetcher::GetAccessTokenFromAuthCode));
ON_CALL(*this, GetAccessTokenFromRefreshToken(_, _)).WillByDefault(
Invoke(internal_access_token_fetcher_.get(),
&AccessTokenFetcher::GetAccessTokenFromRefreshToken));
}
} // namespace test
} // namespace remoting
// Copyright 2015 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 REMOTING_TEST_MOCK_ACCESS_TOKEN_FETCHER_H_
#define REMOTING_TEST_MOCK_ACCESS_TOKEN_FETCHER_H_
#include "remoting/test/access_token_fetcher.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace test {
// Used for testing classes which rely on the AccessTokenFetcher and want to
// simulate success and failure scenarios without using the actual class and
// network connection.
class MockAccessTokenFetcher : public AccessTokenFetcher {
public:
MockAccessTokenFetcher();
~MockAccessTokenFetcher() override;
MOCK_METHOD2(GetAccessTokenFromAuthCode,
void(const std::string& auth_code,
const AccessTokenCallback& callback));
MOCK_METHOD2(GetAccessTokenFromRefreshToken,
void(const std::string& refresh_token,
const AccessTokenCallback& callback));
// Stores an access token fetcher object and wires up the mock methods to call
// through to the appropriate method on it. This method is typically used to
// pass a FakeAccessTokenFetcher.
void SetAccessTokenFetcher(scoped_ptr<AccessTokenFetcher> fetcher);
private:
scoped_ptr<AccessTokenFetcher> internal_access_token_fetcher_;
DISALLOW_COPY_AND_ASSIGN(MockAccessTokenFetcher);
};
} // namespace test
} // namespace remoting
#endif // REMOTING_TEST_MOCK_ACCESS_TOKEN_FETCHER_H_
// Copyright 2015 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/test/refresh_token_store.h"
#include "base/files/file_util.h"
#include "base/logging.h"
namespace {
const base::FilePath::CharType kTokenFileName[] =
FILE_PATH_LITERAL("refresh_token.txt");
const base::FilePath::CharType kRemotingFolder[] =
FILE_PATH_LITERAL("remoting");
const base::FilePath::CharType kRefreshTokenStoreFolder[] =
FILE_PATH_LITERAL("refresh_token_store");
// Returns the FilePath of the token store file for |user_name|.
base::FilePath GetRefreshTokenDirPath(const std::string& user_name) {
base::FilePath refresh_token_dir_path;
if (!GetTempDir(&refresh_token_dir_path)) {
LOG(WARNING) << "Failed to retrieve temporary directory path.";
return base::FilePath();
}
refresh_token_dir_path = refresh_token_dir_path.Append(kRemotingFolder);
refresh_token_dir_path = refresh_token_dir_path.Append(
kRefreshTokenStoreFolder);
// We call AppendASCII here our user_name is a std::string but wide strings
// are used on WIN platforms. ApendASCII will convert our std::string into
// the correct type for windows platforms.
refresh_token_dir_path = refresh_token_dir_path.AppendASCII(user_name);
return refresh_token_dir_path;
}
} // namespace
namespace remoting {
namespace test {
// Provides functionality to write a refresh token to a local folder on disk and
// read it back during subsequent tool runs.
class RefreshTokenStoreOnDisk : public RefreshTokenStore {
public:
RefreshTokenStoreOnDisk(const std::string user_name);
~RefreshTokenStoreOnDisk() override;
// RefreshTokenStore interface.
std::string FetchRefreshToken() override;
bool StoreRefreshToken(const std::string& refresh_token) override;
private:
// Used to access the user specific token file.
std::string user_name_;
DISALLOW_COPY_AND_ASSIGN(RefreshTokenStoreOnDisk);
};
RefreshTokenStoreOnDisk::RefreshTokenStoreOnDisk(const std::string user_name) :
user_name_(user_name) {}
RefreshTokenStoreOnDisk::~RefreshTokenStoreOnDisk() {}
std::string RefreshTokenStoreOnDisk::FetchRefreshToken() {
base::FilePath token_dir_path(GetRefreshTokenDirPath(user_name_));
DCHECK(!token_dir_path.empty());
DVLOG(2) << "Reading token from path: " << token_dir_path.value();
base::FilePath token_file_path(token_dir_path.Append(kTokenFileName));
std::string refresh_token;
if (!base::ReadFileToString(token_file_path, &refresh_token)) {
DVLOG(1) << "Failed to read token file from: " << token_dir_path.value();
return std::string();
}
return refresh_token;
}
bool RefreshTokenStoreOnDisk::StoreRefreshToken(
const std::string& refresh_token) {
DCHECK(!refresh_token.empty());
base::FilePath token_dir_path(GetRefreshTokenDirPath(user_name_));
if (token_dir_path.empty()) {
return false;
}
base::FilePath token_file_path(token_dir_path.Append(kTokenFileName));
if (!base::DirectoryExists(token_dir_path) &&
!base::CreateDirectory(token_dir_path)) {
LOG(ERROR) << "Failed to create directory, refresh token not stored.";
return false;
}
#if defined(OS_POSIX)
// For POSIX we can set permissions on the token file so we do so here.
// The test code should not run on other platforms since the code to safely
// store the token has not been implemented yet.
// Create an empty stub file if one does not exist.
if (!base::PathExists(token_file_path) &&
base::WriteFile(token_file_path, "", 0) < 0) {
LOG(ERROR) << "Failed to create stub file, refresh token not stored.";
return false;
}
// Set permissions on the stub file.
int mode =
base::FILE_PERMISSION_READ_BY_USER | base::FILE_PERMISSION_WRITE_BY_USER;
if (!SetPosixFilePermissions(token_file_path, mode)) {
LOG(ERROR) << "Failed to set file permissions, refresh token not stored.";
return false;
}
// Write the refresh token to our newly created file.
if (base::WriteFile(token_file_path, refresh_token.c_str(),
refresh_token.size()) < 0) {
LOG(ERROR) << "Failed to save refresh token to the file on disk.";
return false;
}
return true;
#else
NOTIMPLEMENTED();
return false;
#endif // OS_POSIX
}
scoped_ptr<RefreshTokenStore> RefreshTokenStore::OnDisk(
const std::string& user_name) {
return make_scoped_ptr<RefreshTokenStore>(new RefreshTokenStoreOnDisk(
user_name));
}
} // namespace test
} // namespace remoting
// Copyright 2015 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 REMOTING_TEST_REFRESH_TOKEN_STORE_H_
#define REMOTING_TEST_REFRESH_TOKEN_STORE_H_
#include <string>
#include "base/memory/scoped_ptr.h"
namespace remoting {
namespace test {
// Used to store and retrieve refresh tokens. This interface is provided to
// allow for stubbing out the storage mechanism for testing.
class RefreshTokenStore {
public:
RefreshTokenStore() {}
virtual ~RefreshTokenStore() {}
virtual std::string FetchRefreshToken() = 0;
virtual bool StoreRefreshToken(const std::string& refresh_token) = 0;
// Returns a RefreshTokenStore which reads/writes to a user specific token
// file on the local disk.
static scoped_ptr<RefreshTokenStore> OnDisk(const std::string& user_name);
};
} // namespace test
} // namespace remoting
#endif // REMOTING_TEST_REFRESH_TOKEN_STORE_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment