blob: ced7681a3d57865fbf4a39a8a0df98b426d43b4f [file] [log] [blame]
// Copyright 2019 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/it2me_cli_host.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "net/base/network_change_notifier.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/logging.h"
#include "remoting/host/chromoting_host_context.h"
#include "remoting/host/it2me/it2me_native_messaging_host.h"
#include "remoting/host/policy_watcher.h"
#include "remoting/test/test_oauth_token_getter.h"
#include "remoting/test/test_token_storage.h"
namespace remoting {
namespace {
// Communication with CRD Host, messages sent to host:
constexpr char kCRDMessageTypeKey[] = "type";
constexpr char kCRDMessageHello[] = "hello";
constexpr char kCRDMessageConnect[] = "connect";
constexpr char kCRDMessageDisconnect[] = "disconnect";
// Communication with CRD Host, messages received from host:
constexpr char kCRDResponseHello[] = "helloResponse";
constexpr char kCRDResponseConnect[] = "connectResponse";
constexpr char kCRDStateChanged[] = "hostStateChanged";
constexpr char kCRDResponseDisconnect[] = "disconnectResponse";
constexpr char kCRDDebugLog[] = "_debug_log";
// Connect message parameters:
constexpr char kCRDConnectUserName[] = "userName";
constexpr char kCRDConnectAuth[] = "authServiceWithToken";
constexpr char kCRDConnectSuppressUserDialogs[] = "suppressUserDialogs";
constexpr char kCRDConnectSuppressNotifications[] = "suppressNotifications";
// CRD host states we care about:
constexpr char kCRDStateKey[] = "state";
constexpr char kCRDStateError[] = "ERROR";
constexpr char kCRDStateStarting[] = "STARTING";
constexpr char kCRDStateAccessCodeRequested[] = "REQUESTED_ACCESS_CODE";
constexpr char kCRDStateDomainError[] = "INVALID_DOMAIN_ERROR";
constexpr char kCRDStateAccessCode[] = "RECEIVED_ACCESS_CODE";
constexpr char kCRDStateRemoteDisconnected[] = "DISCONNECTED";
constexpr char kCRDStateRemoteConnected[] = "CONNECTED";
constexpr char kCRDErrorCodeKey[] = "error_code";
constexpr char kCRDAccessCodeKey[] = "accessCode";
constexpr char kCRDAccessCodeLifetimeKey[] = "accessCodeLifetime";
constexpr char kCRDConnectClientKey[] = "client";
constexpr char kSwitchNameHelp[] = "help";
constexpr char kSwitchNameUsername[] = "username";
constexpr char kSwitchNameStoragePath[] = "storage-path";
std::unique_ptr<It2MeNativeMessagingHost> CreateNativeMessagingHost(
scoped_refptr<AutoThreadTaskRunner> ui_task_runner) {
auto context = ChromotingHostContext::Create(ui_task_runner);
std::unique_ptr<PolicyWatcher> policy_watcher =
PolicyWatcher::CreateWithTaskRunner(context->file_task_runner());
auto factory = std::make_unique<It2MeHostFactory>();
return std::make_unique<It2MeNativeMessagingHost>(
/* needs_elevation */ false, std::move(policy_watcher),
std::move(context), std::move(factory));
}
} // namespace
// static
bool It2MeCliHost::ShouldPrintHelp() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(kSwitchNameHelp);
}
// static
void It2MeCliHost::PrintHelp() {
fprintf(stderr,
"Usage: %s [--storage-path=<storage-path>] "
"[--username=<example@gmail.com>]\n ",
base::CommandLine::ForCurrentProcess()
->GetProgram()
.AsUTF8Unsafe()
.c_str());
}
It2MeCliHost::It2MeCliHost() {}
It2MeCliHost::~It2MeCliHost() = default;
void It2MeCliHost::Start() {
base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
std::string username = cmd_line->GetSwitchValueASCII(kSwitchNameUsername);
base::FilePath storage_path =
cmd_line->GetSwitchValuePath(kSwitchNameStoragePath);
storage_ = test::TestTokenStorage::OnDisk(username, storage_path);
token_getter_ = std::make_unique<test::TestOAuthTokenGetter>(storage_.get());
base::RunLoop initialize_token_getter_loop;
token_getter_->Initialize(initialize_token_getter_loop.QuitClosure());
initialize_token_getter_loop.Run();
base::RunLoop ui_loop;
ui_task_runner_ = new AutoThreadTaskRunner(
base::ThreadTaskRunnerHandle::Get(), ui_loop.QuitClosure());
token_getter_->CallWithToken(base::BindOnce(
&It2MeCliHost::StartCRDHostAndGetCode, base::Unretained(this)));
std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier(
net::NetworkChangeNotifier::CreateIfNeeded());
ui_loop.Run();
}
void It2MeCliHost::PostMessageFromNativeHost(const std::string& message) {
auto message_value = base::JSONReader::Read(message);
if (!message_value || !message_value->is_dict()) {
OnProtocolBroken("Message is not a dictionary");
return;
}
auto* type_value = message_value->FindKeyOfType(kCRDMessageTypeKey,
base::Value::Type::STRING);
if (!type_value) {
OnProtocolBroken("Message without type");
return;
}
std::string type = type_value->GetString();
if (type == kCRDResponseHello) {
OnHelloResponse();
} else if (type == kCRDResponseConnect) {
// Ok, just ignore.
} else if (type == kCRDResponseDisconnect) {
OnDisconnectResponse();
} else if (type == kCRDStateChanged) {
// Handle CRD host state changes
auto* state_value =
message_value->FindKeyOfType(kCRDStateKey, base::Value::Type::STRING);
if (!state_value) {
OnProtocolBroken("No state in message");
return;
}
std::string state = state_value->GetString();
if (state == kCRDStateAccessCode) {
OnStateReceivedAccessCode(*message_value);
} else if (state == kCRDStateRemoteConnected) {
OnStateRemoteConnected(*message_value);
} else if (state == kCRDStateRemoteDisconnected) {
OnStateRemoteDisconnected();
} else if (state == kCRDStateError || state == kCRDStateDomainError) {
OnStateError(state, *message_value);
} else if (state == kCRDStateStarting ||
state == kCRDStateAccessCodeRequested) {
// Just ignore these states.
} else {
LOG(WARNING) << "Unhandled state: " << state;
}
} else if (type == kCRDDebugLog) {
// The It2Me host already prints the log to stdout/stderr.
} else {
LOG(WARNING) << "Unknown message type: " << type;
}
}
void It2MeCliHost::CloseChannel(const std::string& error_message) {
LOG(ERROR) << "CRD Host closed channel: " << error_message;
command_awaiting_crd_access_code_ = false;
ShutdownHost();
}
void It2MeCliHost::SendMessageToHost(const std::string& type,
base::Value params) {
std::string message_json;
params.SetKey(kCRDMessageTypeKey, base::Value(type));
base::JSONWriter::Write(params, &message_json);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&It2MeCliHost::DoSendMessage,
weak_factory_.GetWeakPtr(), message_json));
}
void It2MeCliHost::DoSendMessage(const std::string& json) {
if (!host_)
return;
host_->OnMessage(json);
}
void It2MeCliHost::OnProtocolBroken(const std::string& message) {
LOG(ERROR) << "Error communicating with CRD Host : " << message;
command_awaiting_crd_access_code_ = false;
ShutdownHost();
}
void It2MeCliHost::StartCRDHostAndGetCode(OAuthTokenGetter::Status status,
const std::string& user_email,
const std::string& access_token) {
DCHECK(!host_);
// Store all parameters for future connect call.
base::Value connect_params(base::Value::Type::DICTIONARY);
connect_params.SetKey(kCRDConnectUserName, base::Value(user_email));
connect_params.SetKey(kCRDConnectAuth, base::Value("oauth2:" + access_token));
connect_params.SetKey(kCRDConnectSuppressUserDialogs, base::Value(true));
connect_params.SetKey(kCRDConnectSuppressNotifications, base::Value(true));
connect_params_ = std::move(connect_params);
remote_connected_ = false;
command_awaiting_crd_access_code_ = true;
host_ = CreateNativeMessagingHost(ui_task_runner_);
host_->Start(this);
base::Value params(base::Value::Type::DICTIONARY);
SendMessageToHost(kCRDMessageHello, std::move(params));
}
void It2MeCliHost::ShutdownHost() {
if (!host_)
return;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&It2MeCliHost::DoShutdownHost,
weak_factory_.GetWeakPtr()));
}
void It2MeCliHost::DoShutdownHost() {
host_.reset();
ui_task_runner_.reset();
}
void It2MeCliHost::OnHelloResponse() {
// Host is initialized, start connection.
SendMessageToHost(kCRDMessageConnect, std::move(connect_params_));
}
void It2MeCliHost::OnDisconnectResponse() {
// Should happen only when remoting session finished and we
// have requested host to shut down, or when we have got second auth code
// without receiving connection.
DCHECK(!command_awaiting_crd_access_code_);
DCHECK(!remote_connected_);
ShutdownHost();
}
void It2MeCliHost::OnStateError(const std::string& error_state,
const base::Value& message) {
std::string error_message;
if (error_state == kCRDStateDomainError) {
error_message = "CRD Error : Invalid domain";
} else {
auto* error_code_value =
message.FindKeyOfType(kCRDErrorCodeKey, base::Value::Type::STRING);
if (error_code_value)
error_message = error_code_value->GetString();
else
error_message = "Unknown CRD Error";
}
// Notify callback if command is still running.
if (command_awaiting_crd_access_code_) {
command_awaiting_crd_access_code_ = false;
LOG(ERROR) << "CRD Error state " + error_state;
}
// Shut down host, if any
ShutdownHost();
}
void It2MeCliHost::OnStateRemoteConnected(const base::Value& message) {
remote_connected_ = true;
auto* client_value =
message.FindKeyOfType(kCRDConnectClientKey, base::Value::Type::STRING);
if (client_value) {
HOST_LOG << "Remote connection by " << client_value->GetString();
}
}
void It2MeCliHost::OnStateRemoteDisconnected() {
// There could be a connection attempt that was not successful, we will
// receive "disconnected" message without actually receiving "connected".
if (!remote_connected_)
return;
remote_connected_ = false;
// Remote has disconnected, time to send "disconnect" that would result
// in shutting down the host.
base::Value params(base::Value::Type::DICTIONARY);
SendMessageToHost(kCRDMessageDisconnect, std::move(params));
}
void It2MeCliHost::OnStateReceivedAccessCode(const base::Value& message) {
if (!command_awaiting_crd_access_code_) {
if (!remote_connected_) {
// We have already sent the access code back to the server which initiated
// this CRD session through a remote command, and we can not send a new
// access code. Assuming that the old access code is no longer valid, we
// can only terminate the current CRD session.
base::Value params(base::Value::Type::DICTIONARY);
SendMessageToHost(kCRDMessageDisconnect, std::move(params));
}
return;
}
auto* code_value =
message.FindKeyOfType(kCRDAccessCodeKey, base::Value::Type::STRING);
auto* code_lifetime_value = message.FindKeyOfType(kCRDAccessCodeLifetimeKey,
base::Value::Type::INTEGER);
if (!code_value || !code_lifetime_value) {
OnProtocolBroken("Can not obtain access code");
return;
}
command_awaiting_crd_access_code_ = false;
// Prints the access code.
base::TimeDelta expires_in =
base::TimeDelta::FromSeconds(code_lifetime_value->GetInt());
HOST_LOG << "It2Me access code is generated: " << code_value->GetString();
HOST_LOG << "Expires at: " << (base::Time::Now() + expires_in);
}
} // namespace remoting