blob: 7d27759397a965819e8ddc5a1dcfb84d74c81485 [file] [log] [blame]
// Copyright 2013 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/setup/me2me_native_messaging_host.h"
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/stringize_macros.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "google_apis/gaia/gaia_oauth_client.h"
#include "google_apis/google_api_keys.h"
#include "net/base/network_interfaces.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/oauth_client.h"
#include "remoting/base/rsa_key_pair.h"
#include "remoting/host/chromoting_host_context.h"
#include "remoting/host/native_messaging/log_message_handler.h"
#include "remoting/host/pin_hash.h"
#include "remoting/protocol/pairing_registry.h"
#if defined(OS_WIN)
#include "remoting/host/win/elevated_native_messaging_host.h"
#endif // defined(OS_WIN)
namespace {
#if defined(OS_WIN)
const int kElevatedHostTimeoutSeconds = 300;
#endif // defined(OS_WIN)
// redirect_uri to use when authenticating service accounts (service account
// codes are obtained "out-of-band", i.e., not through an OAuth redirect).
const char* kServiceAccountRedirectUri = "oob";
// Features supported in addition to the base protocol.
const char* kSupportedFeatures[] = {
"pairingRegistry",
"oauthClient",
"getRefreshTokenFromAuthCode",
};
// Helper to extract the "config" part of a message as a DictionaryValue.
// Returns nullptr on failure, and logs an error message.
std::unique_ptr<base::DictionaryValue> ConfigDictionaryFromMessage(
std::unique_ptr<base::DictionaryValue> message) {
std::unique_ptr<base::DictionaryValue> result;
const base::DictionaryValue* config_dict;
if (message->GetDictionary("config", &config_dict)) {
result = config_dict->CreateDeepCopy();
}
return result;
}
} // namespace
namespace remoting {
Me2MeNativeMessagingHost::Me2MeNativeMessagingHost(
bool needs_elevation,
intptr_t parent_window_handle,
std::unique_ptr<ChromotingHostContext> host_context,
scoped_refptr<DaemonController> daemon_controller,
scoped_refptr<protocol::PairingRegistry> pairing_registry,
std::unique_ptr<OAuthClient> oauth_client)
: needs_elevation_(needs_elevation),
#if defined(OS_WIN)
parent_window_handle_(parent_window_handle),
#endif
host_context_(std::move(host_context)),
daemon_controller_(daemon_controller),
pairing_registry_(pairing_registry),
oauth_client_(std::move(oauth_client)),
weak_factory_(this) {
weak_ptr_ = weak_factory_.GetWeakPtr();
}
Me2MeNativeMessagingHost::~Me2MeNativeMessagingHost() {
DCHECK(task_runner()->BelongsToCurrentThread());
}
void Me2MeNativeMessagingHost::OnMessage(const std::string& message) {
DCHECK(task_runner()->BelongsToCurrentThread());
auto response = std::make_unique<base::DictionaryValue>();
std::unique_ptr<base::Value> message_value =
base::JSONReader::ReadDeprecated(message);
if (!message_value->is_dict()) {
OnError("Received a message that's not a dictionary.");
return;
}
std::unique_ptr<base::DictionaryValue> message_dict(
static_cast<base::DictionaryValue*>(message_value.release()));
// If the client supplies an ID, it will expect it in the response. This
// might be a string or a number, so cope with both.
const base::Value* id;
if (message_dict->Get("id", &id))
response->SetKey("id", id->Clone());
std::string type;
if (!message_dict->GetString("type", &type)) {
OnError("'type' not found");
return;
}
response->SetString("type", type + "Response");
if (type == "hello") {
ProcessHello(std::move(message_dict), std::move(response));
} else if (type == "clearPairedClients") {
ProcessClearPairedClients(std::move(message_dict), std::move(response));
} else if (type == "deletePairedClient") {
ProcessDeletePairedClient(std::move(message_dict), std::move(response));
} else if (type == "getHostName") {
ProcessGetHostName(std::move(message_dict), std::move(response));
} else if (type == "getPinHash") {
ProcessGetPinHash(std::move(message_dict), std::move(response));
} else if (type == "generateKeyPair") {
ProcessGenerateKeyPair(std::move(message_dict), std::move(response));
} else if (type == "updateDaemonConfig") {
ProcessUpdateDaemonConfig(std::move(message_dict), std::move(response));
} else if (type == "getDaemonConfig") {
ProcessGetDaemonConfig(std::move(message_dict), std::move(response));
} else if (type == "getPairedClients") {
ProcessGetPairedClients(std::move(message_dict), std::move(response));
} else if (type == "getUsageStatsConsent") {
ProcessGetUsageStatsConsent(std::move(message_dict), std::move(response));
} else if (type == "startDaemon") {
ProcessStartDaemon(std::move(message_dict), std::move(response));
} else if (type == "stopDaemon") {
ProcessStopDaemon(std::move(message_dict), std::move(response));
} else if (type == "getDaemonState") {
ProcessGetDaemonState(std::move(message_dict), std::move(response));
} else if (type == "getHostClientId") {
ProcessGetHostClientId(std::move(message_dict), std::move(response));
} else if (type == "getCredentialsFromAuthCode") {
ProcessGetCredentialsFromAuthCode(
std::move(message_dict), std::move(response), true);
} else if (type == "getRefreshTokenFromAuthCode") {
ProcessGetCredentialsFromAuthCode(
std::move(message_dict), std::move(response), false);
} else {
OnError("Unsupported request type: " + type);
}
}
void Me2MeNativeMessagingHost::Start(Client* client) {
DCHECK(task_runner()->BelongsToCurrentThread());
client_ = client;
log_message_handler_.reset(new LogMessageHandler(
base::Bind(&Me2MeNativeMessagingHost::SendMessageToClient, weak_ptr_)));
}
scoped_refptr<base::SingleThreadTaskRunner>
Me2MeNativeMessagingHost::task_runner() const {
return host_context_->ui_task_runner();
}
void Me2MeNativeMessagingHost::ProcessHello(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
response->SetString("version", STRINGIZE(VERSION));
std::unique_ptr<base::ListValue> supported_features_list(
new base::ListValue());
supported_features_list->AppendStrings(std::vector<std::string>(
kSupportedFeatures, kSupportedFeatures + base::size(kSupportedFeatures)));
response->Set("supportedFeatures", std::move(supported_features_list));
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::ProcessClearPairedClients(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
if (needs_elevation_) {
if (DelegateToElevatedHost(std::move(message)) != DELEGATION_SUCCESS) {
SendBooleanResult(std::move(response), false);
}
return;
}
if (pairing_registry_.get()) {
pairing_registry_->ClearAllPairings(
base::Bind(&Me2MeNativeMessagingHost::SendBooleanResult, weak_ptr_,
base::Passed(&response)));
} else {
SendBooleanResult(std::move(response), false);
}
}
void Me2MeNativeMessagingHost::ProcessDeletePairedClient(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
if (needs_elevation_) {
if (DelegateToElevatedHost(std::move(message)) != DELEGATION_SUCCESS) {
SendBooleanResult(std::move(response), false);
}
return;
}
std::string client_id;
if (!message->GetString(protocol::PairingRegistry::kClientIdKey,
&client_id)) {
OnError("'" + std::string(protocol::PairingRegistry::kClientIdKey) +
"' string not found.");
return;
}
if (pairing_registry_.get()) {
pairing_registry_->DeletePairing(
client_id, base::Bind(&Me2MeNativeMessagingHost::SendBooleanResult,
weak_ptr_, base::Passed(&response)));
} else {
SendBooleanResult(std::move(response), false);
}
}
void Me2MeNativeMessagingHost::ProcessGetHostName(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
response->SetString("hostname", net::GetHostName());
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::ProcessGetPinHash(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
std::string host_id;
if (!message->GetString("hostId", &host_id)) {
std::string message_json;
base::JSONWriter::Write(*message, &message_json);
OnError("'hostId' not found: " + message_json);
return;
}
std::string pin;
if (!message->GetString("pin", &pin)) {
std::string message_json;
base::JSONWriter::Write(*message, &message_json);
OnError("'pin' not found: " + message_json);
return;
}
response->SetString("hash", MakeHostPinHash(host_id, pin));
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::ProcessGenerateKeyPair(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
scoped_refptr<RsaKeyPair> key_pair = RsaKeyPair::Generate();
response->SetString("privateKey", key_pair->ToString());
response->SetString("publicKey", key_pair->GetPublicKey());
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::ProcessUpdateDaemonConfig(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
if (needs_elevation_) {
DelegationResult result = DelegateToElevatedHost(std::move(message));
switch (result) {
case DELEGATION_SUCCESS:
return; // Result will be returned by elevated host.
case DELEGATION_CANCELLED:
SendAsyncResult(std::move(response),
DaemonController::RESULT_CANCELLED);
return;
default:
SendAsyncResult(std::move(response), DaemonController::RESULT_FAILED);
return;
}
}
std::unique_ptr<base::DictionaryValue> config_dict =
ConfigDictionaryFromMessage(std::move(message));
if (!config_dict) {
OnError("'config' dictionary not found");
return;
}
daemon_controller_->UpdateConfig(
std::move(config_dict),
base::Bind(&Me2MeNativeMessagingHost::SendAsyncResult, weak_ptr_,
base::Passed(&response)));
}
void Me2MeNativeMessagingHost::ProcessGetDaemonConfig(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
daemon_controller_->GetConfig(
base::Bind(&Me2MeNativeMessagingHost::SendConfigResponse, weak_ptr_,
base::Passed(&response)));
}
void Me2MeNativeMessagingHost::ProcessGetPairedClients(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
if (pairing_registry_.get()) {
pairing_registry_->GetAllPairings(
base::Bind(&Me2MeNativeMessagingHost::SendPairedClientsResponse,
weak_ptr_, base::Passed(&response)));
} else {
std::unique_ptr<base::ListValue> no_paired_clients(new base::ListValue);
SendPairedClientsResponse(std::move(response),
std::move(no_paired_clients));
}
}
void Me2MeNativeMessagingHost::ProcessGetUsageStatsConsent(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
daemon_controller_->GetUsageStatsConsent(
base::Bind(&Me2MeNativeMessagingHost::SendUsageStatsConsentResponse,
weak_ptr_, base::Passed(&response)));
}
void Me2MeNativeMessagingHost::ProcessStartDaemon(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
if (needs_elevation_) {
DelegationResult result = DelegateToElevatedHost(std::move(message));
switch (result) {
case DELEGATION_SUCCESS:
return; // Result will be returned by elevated host.
case DELEGATION_CANCELLED:
SendAsyncResult(std::move(response),
DaemonController::RESULT_CANCELLED);
return;
default:
SendAsyncResult(std::move(response), DaemonController::RESULT_FAILED);
return;
}
}
bool consent;
if (!message->GetBoolean("consent", &consent)) {
OnError("'consent' not found.");
return;
}
std::unique_ptr<base::DictionaryValue> config_dict =
ConfigDictionaryFromMessage(std::move(message));
if (!config_dict) {
OnError("'config' dictionary not found");
return;
}
daemon_controller_->SetConfigAndStart(
std::move(config_dict), consent,
base::Bind(&Me2MeNativeMessagingHost::SendAsyncResult, weak_ptr_,
base::Passed(&response)));
}
void Me2MeNativeMessagingHost::ProcessStopDaemon(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
if (needs_elevation_) {
DelegationResult result = DelegateToElevatedHost(std::move(message));
switch (result) {
case DELEGATION_SUCCESS:
return; // Result will be returned by elevated host.
case DELEGATION_CANCELLED:
SendAsyncResult(std::move(response),
DaemonController::RESULT_CANCELLED);
return;
default:
SendAsyncResult(std::move(response), DaemonController::RESULT_FAILED);
return;
}
}
daemon_controller_->Stop(
base::Bind(&Me2MeNativeMessagingHost::SendAsyncResult, weak_ptr_,
base::Passed(&response)));
}
void Me2MeNativeMessagingHost::ProcessGetDaemonState(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
DaemonController::State state = daemon_controller_->GetState();
switch (state) {
case DaemonController::STATE_NOT_IMPLEMENTED:
response->SetString("state", "NOT_IMPLEMENTED");
break;
case DaemonController::STATE_STOPPED:
response->SetString("state", "STOPPED");
break;
case DaemonController::STATE_STARTING:
response->SetString("state", "STARTING");
break;
case DaemonController::STATE_STARTED:
response->SetString("state", "STARTED");
break;
case DaemonController::STATE_STOPPING:
response->SetString("state", "STOPPING");
break;
case DaemonController::STATE_UNKNOWN:
response->SetString("state", "UNKNOWN");
break;
}
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::ProcessGetHostClientId(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response) {
DCHECK(task_runner()->BelongsToCurrentThread());
response->SetString("clientId", google_apis::GetOAuth2ClientID(
google_apis::CLIENT_REMOTING_HOST));
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::ProcessGetCredentialsFromAuthCode(
std::unique_ptr<base::DictionaryValue> message,
std::unique_ptr<base::DictionaryValue> response,
bool need_user_email) {
DCHECK(task_runner()->BelongsToCurrentThread());
std::string auth_code;
if (!message->GetString("authorizationCode", &auth_code)) {
OnError("'authorizationCode' string not found.");
return;
}
gaia::OAuthClientInfo oauth_client_info = {
google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING_HOST),
google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_REMOTING_HOST),
kServiceAccountRedirectUri
};
oauth_client_->GetCredentialsFromAuthCode(
oauth_client_info, auth_code, need_user_email, base::Bind(
&Me2MeNativeMessagingHost::SendCredentialsResponse, weak_ptr_,
base::Passed(&response)));
}
void Me2MeNativeMessagingHost::SendConfigResponse(
std::unique_ptr<base::DictionaryValue> response,
std::unique_ptr<base::DictionaryValue> config) {
DCHECK(task_runner()->BelongsToCurrentThread());
if (config) {
response->Set("config", std::move(config));
} else {
response->Set("config", std::make_unique<base::Value>());
}
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::SendPairedClientsResponse(
std::unique_ptr<base::DictionaryValue> response,
std::unique_ptr<base::ListValue> pairings) {
DCHECK(task_runner()->BelongsToCurrentThread());
response->Set("pairedClients", std::move(pairings));
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::SendUsageStatsConsentResponse(
std::unique_ptr<base::DictionaryValue> response,
const DaemonController::UsageStatsConsent& consent) {
DCHECK(task_runner()->BelongsToCurrentThread());
response->SetBoolean("supported", consent.supported);
response->SetBoolean("allowed", consent.allowed);
response->SetBoolean("setByPolicy", consent.set_by_policy);
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::SendAsyncResult(
std::unique_ptr<base::DictionaryValue> response,
DaemonController::AsyncResult result) {
DCHECK(task_runner()->BelongsToCurrentThread());
switch (result) {
case DaemonController::RESULT_OK:
response->SetString("result", "OK");
break;
case DaemonController::RESULT_FAILED:
response->SetString("result", "FAILED");
break;
case DaemonController::RESULT_CANCELLED:
response->SetString("result", "CANCELLED");
break;
}
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::SendBooleanResult(
std::unique_ptr<base::DictionaryValue> response,
bool result) {
DCHECK(task_runner()->BelongsToCurrentThread());
response->SetBoolean("result", result);
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::SendCredentialsResponse(
std::unique_ptr<base::DictionaryValue> response,
const std::string& user_email,
const std::string& refresh_token) {
DCHECK(task_runner()->BelongsToCurrentThread());
if (!user_email.empty()) {
response->SetString("userEmail", user_email);
}
response->SetString("refreshToken", refresh_token);
SendMessageToClient(std::move(response));
}
void Me2MeNativeMessagingHost::SendMessageToClient(
std::unique_ptr<base::Value> message) const {
DCHECK(task_runner()->BelongsToCurrentThread());
std::string message_json;
base::JSONWriter::Write(*message, &message_json);
client_->PostMessageFromNativeHost(message_json);
}
void Me2MeNativeMessagingHost::OnError(const std::string& error_message) {
DCHECK(task_runner()->BelongsToCurrentThread());
if (!error_message.empty()) {
LOG(ERROR) << error_message;
}
// Trigger a host shutdown by sending an empty message.
client_->CloseChannel(std::string());
}
#if defined(OS_WIN)
Me2MeNativeMessagingHost::DelegationResult
Me2MeNativeMessagingHost::DelegateToElevatedHost(
std::unique_ptr<base::DictionaryValue> message) {
DCHECK(task_runner()->BelongsToCurrentThread());
DCHECK(needs_elevation_);
if (!elevated_host_) {
elevated_host_.reset(new ElevatedNativeMessagingHost(
base::CommandLine::ForCurrentProcess()->GetProgram(),
parent_window_handle_,
/*elevate_process=*/true,
base::TimeDelta::FromSeconds(kElevatedHostTimeoutSeconds),
client_));
}
ProcessLaunchResult result = elevated_host_->EnsureElevatedHostCreated();
if (result == PROCESS_LAUNCH_RESULT_SUCCESS) {
elevated_host_->SendMessage(std::move(message));
}
switch (result) {
case PROCESS_LAUNCH_RESULT_SUCCESS:
return DELEGATION_SUCCESS;
case PROCESS_LAUNCH_RESULT_CANCELLED:
return DELEGATION_CANCELLED;
case PROCESS_LAUNCH_RESULT_FAILED:
return DELEGATION_FAILED;
}
}
#else // defined(OS_WIN)
Me2MeNativeMessagingHost::DelegationResult
Me2MeNativeMessagingHost::DelegateToElevatedHost(
std::unique_ptr<base::DictionaryValue> message) {
NOTREACHED();
return DELEGATION_FAILED;
}
#endif // !defined(OS_WIN)
} // namespace remoting