1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
9 #include "base/command_line.h"
10 #include "base/files/file_path.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/run_loop.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/test/test_suite.h"
16 #include "base/test/test_switches.h"
17 #include "google_apis/google_api_keys.h"
18 #include "net/base/escape.h"
19 #include "remoting/test/access_token_fetcher.h"
20 #include "remoting/test/host_info.h"
21 #include "remoting/test/host_list_fetcher.h"
22 #include "remoting/test/refresh_token_store.h"
23 #include "remoting/test/test_chromoting_client.h"
24 #include "testing/gtest/include/gtest/gtest.h"
27 const char kAuthCodeSwitchName
[] = "authcode";
28 const char kPinSwitchName
[] = "pin";
29 const char kHelpSwitchName
[] = "help";
30 const char kHostNameSwitchName
[] = "hostname";
31 const char kLoggingLevelSwitchName
[] = "verbosity";
32 const char kRefreshTokenPathSwitchName
[] = "refresh-token-path";
33 const char kSingleProcessTestsSwitchName
[] = "single-process-tests";
34 const char kUserNameSwitchName
[] = "username";
38 const char kChromotingAuthScopeValues
[] =
39 "https://www.googleapis.com/auth/chromoting "
40 "https://www.googleapis.com/auth/googletalk "
41 "https://www.googleapis.com/auth/userinfo.email";
43 const unsigned int kConnectionTimeoutSeconds
= 10;
45 std::string
GetAuthorizationCodeUri() {
46 // Replace space characters with a '+' sign when formatting.
48 return base::StringPrintf(
49 "https://accounts.google.com/o/oauth2/auth"
51 "&redirect_uri=https://chromoting-oauth.talkgadget.google.com/"
52 "talkgadget/oauth/chrome-remote-desktop/dev"
55 "&access_type=offline"
56 "&approval_prompt=force",
57 net::EscapeUrlEncodedData(kChromotingAuthScopeValues
, use_plus
).c_str(),
58 net::EscapeUrlEncodedData(
59 google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING
),
64 printf("\n************************************\n");
65 printf("*** Chromoting Test Driver Usage ***\n");
66 printf("************************************\n");
69 printf(" chromoting_test_driver --username=<example@gmail.com> [options]"
70 " --hostname=<example hostname>\n");
71 printf("\nRequired Parameters:\n");
72 printf(" %s: Specifies which account to use when running tests\n",
73 switches::kUserNameSwitchName
);
74 printf(" %s: Specifies which host to connect to when running tests\n",
75 switches::kHostNameSwitchName
);
76 printf("\nOptional Parameters:\n");
77 printf(" %s: Exchanged for a refresh and access token for authentication\n",
78 switches::kAuthCodeSwitchName
);
79 printf(" %s: Displays additional usage information\n",
80 switches::kHelpSwitchName
);
81 printf(" %s: Path to a JSON file containing username/refresh_token KVPs\n",
82 switches::kRefreshTokenPathSwitchName
);
83 printf(" %s: Used to authenticate a chromoting connection with the host\n",
84 switches::kPinSwitchName
);
85 printf(" %s: Specifies the optional logging level of the tool (0-3)."
87 switches::kLoggingLevelSwitchName
);
90 void PrintAuthCodeInfo() {
91 printf("\n*******************************\n");
92 printf("*** Auth Code Example Usage ***\n");
93 printf("*******************************\n\n");
95 printf("If this is the first time you are running the tool,\n");
96 printf("you will need to provide an authorization code.\n");
97 printf("This code will be exchanged for a long term refresh token which\n");
98 printf("will be stored locally and used to acquire a short lived access\n");
99 printf("token to connect to the remoting service apis and establish a\n");
100 printf("remote host connection.\n\n");
102 printf("Note: You may need to repeat this step if the stored refresh token");
103 printf("\n has been revoked or expired.\n");
104 printf(" Passing in the same auth code twice will result in an error\n");
106 printf("\nFollow these steps to produce an auth code:\n"
107 " - Open the Authorization URL link shown below in your browser\n"
108 " - Approve the requested permissions for the tool\n"
109 " - Copy the 'code' value in the redirected URL\n"
110 " - Run the tool and pass in copied auth code as a parameter\n");
112 printf("\nAuthorization URL:\n");
113 printf("%s\n", GetAuthorizationCodeUri().c_str());
115 printf("\nRedirected URL Example:\n");
116 printf("https://chromoting-oauth.talkgadget.google.com/talkgadget/oauth/"
117 "chrome-remote-desktop/dev?code=4/AKtf...\n");
119 printf("\nTool usage example with the newly created auth code:\n");
120 printf("chromoting_test_driver --%s=example@gmail.com --%s=example_host_name"
121 " --%s=4/AKtf...\n\n",
122 switches::kUserNameSwitchName
,
123 switches::kHostNameSwitchName
,
124 switches::kAuthCodeSwitchName
);
127 void PrintJsonFileInfo() {
128 printf("\n****************************************\n");
129 printf("*** Refresh Token File Example Usage ***\n");
130 printf("****************************************\n\n");
132 printf("In order to use this option, a valid JSON file must exist, be\n");
133 printf("properly formatted, and contain a username/token KVP.\n");
134 printf("Contents of example_file.json\n");
136 printf(" \"username1@fauxdomain.com\": \"1/3798Gsdf898shksdvfyi8sshad\",\n");
137 printf(" \"username2@fauxdomain.com\": \"1/8974sdf87asdgadfgaerhfRsAa\",\n");
140 printf("\nTool usage example:\n");
141 printf("chromoting_test_driver --%s=%s --%s=example_host_name"
142 " --%s=./example_file.json\n\n",
143 switches::kUserNameSwitchName
, "username1@fauxdomain.com",
144 switches::kHostNameSwitchName
, switches::kRefreshTokenPathSwitchName
);
149 void OnHostlistRetrieved(
150 base::Closure done_closure
,
151 std::vector
<remoting::test::HostInfo
>* hostlist
,
152 const std::vector
<remoting::test::HostInfo
>& retrieved_hostlist
) {
154 VLOG(1) << "OnHostlistRetrieved() Called";
158 *hostlist
= retrieved_hostlist
;
160 VLOG(1) << "There are " << hostlist
->size() << " hosts in the hostlist";
165 void OnAccessTokenRetrieved(
166 base::Closure done_closure
,
167 std::string
* access_token
,
168 const std::string
& retrieved_access_token
,
169 const std::string
& retrieved_refresh_token
) {
171 VLOG(1) << "OnAccessTokenRetrieved() Called";
172 VLOG(1) << "Access Token: " << retrieved_access_token
;
174 *access_token
= retrieved_access_token
;
179 int main(int argc
, char* argv
[]) {
180 testing::InitGoogleTest(&argc
, argv
);
181 base::TestSuite
test_suite(argc
, argv
);
183 base::CommandLine
* command_line
= base::CommandLine::ForCurrentProcess();
184 DCHECK(command_line
);
186 // Do not retry if tests fails.
187 command_line
->AppendSwitchASCII(switches::kTestLauncherRetryLimit
, "0");
189 // Different tests may require access to the same host if run in parallel.
190 // To avoid shared resource contention, tests will be run one at a time.
191 command_line
->AppendSwitch(switches::kSingleProcessTestsSwitchName
);
193 if (command_line
->HasSwitch(switches::kHelpSwitchName
)) {
200 // Update the logging verbosity level is user specified one.
201 std::string
verbosity_level(
202 command_line
->GetSwitchValueASCII(switches::kLoggingLevelSwitchName
));
203 if (!verbosity_level
.empty()) {
204 // Turn on logging for the test_driver and remoting components.
205 // This switch is parsed during logging::InitLogging.
206 command_line
->AppendSwitchASCII("vmodule",
207 "*/remoting/*=" + verbosity_level
);
208 logging::LoggingSettings logging_settings
;
209 logging::InitLogging(logging_settings
);
212 // The username is used to run the tests and determines which refresh token to
213 // select in the refresh token file.
214 const std::string username
=
215 command_line
->GetSwitchValueASCII(switches::kUserNameSwitchName
);
216 if (username
.empty()) {
217 LOG(ERROR
) << "No username passed in, can't authenticate or run tests!";
220 VLOG(1) << "Running chromoting tests as: " << username
;
222 // Check to see if the user passed in a one time use auth_code for
223 // refreshing their credentials.
224 std::string auth_code
=
225 command_line
->GetSwitchValueASCII(switches::kAuthCodeSwitchName
);
227 const base::FilePath refresh_token_path
=
228 command_line
->GetSwitchValuePath(switches::kRefreshTokenPathSwitchName
);
230 // The hostname determines which host to initiate a session with from the list
231 // returned from the directory service.
232 const std::string hostname
=
233 command_line
->GetSwitchValueASCII(switches::kHostNameSwitchName
);
234 if (hostname
.empty()) {
235 LOG(ERROR
) << "No hostname passed in, finding the host requires hostname!";
238 VLOG(1) << "Chromoting tests will connect to: " << hostname
;
240 const std::string pin
=
241 command_line
->GetSwitchValueASCII(switches::kPinSwitchName
);
243 // TODO(TonyChun): Move this logic into a shared environment class.
244 scoped_ptr
<remoting::test::RefreshTokenStore
> refresh_token_store
=
245 remoting::test::RefreshTokenStore::OnDisk(username
, refresh_token_path
);
247 std::string refresh_token
= refresh_token_store
->FetchRefreshToken();
248 if (auth_code
.empty() && refresh_token
.empty()) {
249 // RefreshTokenStore already logs which specific error occured.
253 // Used for running network request tasks.
254 // TODO(TonyChun): Move this logic into a shared environment class.
255 base::MessageLoopForIO message_loop
;
257 // Uses the refresh token to get the access token from GAIA.
258 remoting::test::AccessTokenFetcher access_token_fetcher
;
260 // A RunLoop that yields to the thread's MessageLoop.
261 scoped_ptr
<base::RunLoop
> run_loop
;
263 // RunLoop to handle callback from GAIA.
264 run_loop
.reset(new base::RunLoop());
266 std::string access_token
;
267 remoting::test::AccessTokenCallback access_token_callback
=
268 base::Bind(&OnAccessTokenRetrieved
,
269 run_loop
->QuitClosure(),
272 if (!auth_code
.empty()) {
273 access_token_fetcher
.GetAccessTokenFromAuthCode(auth_code
,
274 access_token_callback
);
276 DCHECK(!refresh_token
.empty());
277 access_token_fetcher
.GetAccessTokenFromRefreshToken(refresh_token
,
278 access_token_callback
);
283 run_loop
.reset(new base::RunLoop());
285 std::vector
<remoting::test::HostInfo
> hostlist
;
286 remoting::test::HostListFetcher::HostlistCallback hostlist_request_callback
=
287 base::Bind(&OnHostlistRetrieved
, run_loop
->QuitClosure(), &hostlist
);
289 // Uses the access token to get the hostlist from the directory service.
290 remoting::test::HostListFetcher hostlist_fetcher
;
291 hostlist_fetcher
.RetrieveHostlist(access_token
, hostlist_request_callback
);
295 run_loop
.reset(new base::RunLoop());
297 remoting::test::TestChromotingClient test_chromoting_client
;
299 // Check if requested host is online and ready to receive connections.
300 auto it
= std::find_if(hostlist
.begin(), hostlist
.end(),
301 [&hostname
](const remoting::test::HostInfo
& host_info
) {
302 return host_info
.host_name
== hostname
&&
303 host_info
.IsReadyForConnection();
305 if (it
!= hostlist
.end()) {
306 // Host is online and ready, initiate a remote session.
307 base::Timer
timer(true, false);
308 timer
.Start(FROM_HERE
,
309 base::TimeDelta::FromSeconds(kConnectionTimeoutSeconds
),
310 run_loop
->QuitClosure());
311 test_chromoting_client
.StartConnection(
312 it
->GenerateConnectionSetupInfo(access_token
, username
, pin
));
314 test_chromoting_client
.EndConnection();
316 LOG(ERROR
) << "Requested host not found or not ready to connect";