// 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/chromoting_test_driver_environment.h" #include #include #include "base/bind.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/stringprintf.h" #include "remoting/test/access_token_fetcher.h" #include "remoting/test/host_list_fetcher.h" #include "remoting/test/refresh_token_store.h" namespace remoting { namespace test { ChromotingTestDriverEnvironment* g_chromoting_shared_data = nullptr; ChromotingTestDriverEnvironment::EnvironmentOptions::EnvironmentOptions() { } ChromotingTestDriverEnvironment::EnvironmentOptions::~EnvironmentOptions() { } ChromotingTestDriverEnvironment::ChromotingTestDriverEnvironment( const EnvironmentOptions& options) : host_name_(options.host_name), user_name_(options.user_name), pin_(options.pin), refresh_token_file_path_(options.refresh_token_file_path), test_access_token_fetcher_(nullptr), test_refresh_token_store_(nullptr), test_host_list_fetcher_(nullptr) { DCHECK(!user_name_.empty()); DCHECK(!host_name_.empty()); } ChromotingTestDriverEnvironment::~ChromotingTestDriverEnvironment() { } bool ChromotingTestDriverEnvironment::Initialize( const std::string& auth_code) { if (!access_token_.empty()) { return true; } if (!base::MessageLoop::current()) { message_loop_.reset(new base::MessageLoopForIO); } // 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 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_file_path_); 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. VLOG(2) << "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) || !RetrieveHostList()) { // If we cannot retrieve an access token or a host list, then nothing is // going to work. We should let the caller know that our object is not ready // to be used. return false; } return true; } void ChromotingTestDriverEnvironment::DisplayHostList() { const char kHostAvailabilityFormatString[] = "%-45s%-15s%-35s"; LOG(INFO) << base::StringPrintf(kHostAvailabilityFormatString, "Host Name", "Host Status", "Host JID"); LOG(INFO) << base::StringPrintf(kHostAvailabilityFormatString, "---------", "-----------", "--------"); std::string status; for (const HostInfo& host_info : host_list_) { HostStatus host_status = host_info.status; if (host_status == kHostStatusOnline) { status = "ONLINE"; } else if (host_status == kHostStatusOffline) { status = "OFFLINE"; } else { status = "UNKNOWN"; } LOG(INFO) << base::StringPrintf( kHostAvailabilityFormatString, host_info.host_name.c_str(), status.c_str(), host_info.host_jid.c_str()); } } bool ChromotingTestDriverEnvironment::WaitForHostOnline( const std::string& host_jid, const std::string& host_name) { // Refresh the |host_list_| periodically to check if expected JID is online. const int kMaxIterations = 12; const base::TimeDelta kSleepTimeInSeconds = base::TimeDelta::FromSeconds(5); int num_iterations = 0; while (num_iterations < kMaxIterations) { if (IsHostOnline(host_jid, host_name)) { if (num_iterations > 0) { VLOG(0) << "Host came online after " << num_iterations * kSleepTimeInSeconds.InSeconds() << " seconds."; } return true; } // Wait a while before refreshing host list. base::PlatformThread::Sleep(kSleepTimeInSeconds); RefreshHostList(); ++num_iterations; } LOG(ERROR) << "Host with JID '" << host_jid << "' still not online after " << num_iterations * kSleepTimeInSeconds.InSeconds() << " seconds."; return false; } bool ChromotingTestDriverEnvironment::IsHostOnline( const std::string host_jid, const std::string host_name) const { for (const HostInfo& host_info : host_list_) { if (host_jid == host_info.host_jid && host_name == host_info.host_name) { if (host_info.status == kHostStatusOnline) { return true; } else { LOG(WARNING) << "Host '" << host_name << "' with JID '" << host_jid << "' not online."; return false; } } } return false; } void ChromotingTestDriverEnvironment::SetAccessTokenFetcherForTest( AccessTokenFetcher* access_token_fetcher) { DCHECK(access_token_fetcher); test_access_token_fetcher_ = access_token_fetcher; } void ChromotingTestDriverEnvironment::SetRefreshTokenStoreForTest( RefreshTokenStore* refresh_token_store) { DCHECK(refresh_token_store); test_refresh_token_store_ = refresh_token_store; } void ChromotingTestDriverEnvironment::SetHostListFetcherForTest( HostListFetcher* host_list_fetcher) { DCHECK(host_list_fetcher); test_host_list_fetcher_ = host_list_fetcher; } void ChromotingTestDriverEnvironment::TearDown() { // Letting the MessageLoop tear down during the test destructor results in // errors after test completion, when the MessageLoop dtor touches the // registered AtExitManager. The AtExitManager is torn down before the test // destructor is executed, so we tear down the MessageLoop here, while it is // still valid. message_loop_.reset(); } bool ChromotingTestDriverEnvironment::RetrieveAccessToken( const std::string& auth_code) { base::RunLoop run_loop; access_token_.clear(); AccessTokenCallback access_token_callback = base::Bind(&ChromotingTestDriverEnvironment::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 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 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_file_path_); 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 ChromotingTestDriverEnvironment::OnAccessTokenRetrieved( base::Closure done_closure, const std::string& retrieved_access_token, const std::string& retrieved_refresh_token) { VLOG(1) << "OnAccessTokenRetrieved() Called"; VLOG(1) << "Access Token: " << retrieved_access_token; access_token_ = retrieved_access_token; refresh_token_ = retrieved_refresh_token; done_closure.Run(); } bool ChromotingTestDriverEnvironment::RefreshHostList() { host_list_.clear(); return RetrieveHostList(); } bool ChromotingTestDriverEnvironment::RetrieveHostList() { base::RunLoop run_loop; host_info_ = HostInfo(); // If a unit test has set |test_host_list_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 temporary_host_list_fetcher; HostListFetcher* host_list_fetcher = test_host_list_fetcher_; if (!host_list_fetcher) { temporary_host_list_fetcher.reset(new HostListFetcher()); host_list_fetcher = temporary_host_list_fetcher.get(); } remoting::test::HostListFetcher::HostlistCallback host_list_callback = base::Bind(&ChromotingTestDriverEnvironment::OnHostListRetrieved, base::Unretained(this), run_loop.QuitClosure()); host_list_fetcher->RetrieveHostlist(access_token_, host_list_callback); run_loop.Run(); if (host_list_.empty()) { // Note: Access token may have expired, but it is unlikely. LOG(ERROR) << "Retrieved host list is empty.\n" << "Does the account have hosts set up?"; return false; } // If the host or command line parameters are not setup correctly, we want to // let the user fix the issue before continuing. bool found_host_name = false; auto host_info_iter = std::find_if(host_list_.begin(), host_list_.end(), [this, &found_host_name](const remoting::test::HostInfo& host_info) { if (host_info.host_name == host_name_) { found_host_name = true; return host_info.IsReadyForConnection(); } return false; }); if (host_info_iter == host_list_.end()) { if (found_host_name) { LOG(ERROR) << this->host_name_ << " is not ready to connect."; } else { LOG(ERROR) << this->host_name_ << " was not found in the host list."; } DisplayHostList(); return false; } host_info_ = *host_info_iter; return true; } void ChromotingTestDriverEnvironment::OnHostListRetrieved( base::Closure done_closure, const std::vector& retrieved_host_list) { VLOG(1) << "OnHostListRetrieved() Called"; host_list_ = retrieved_host_list; done_closure.Run(); } } // namespace test } // namespace remoting