blob: c91e85e052322983f2baaa913b9eb84b781c21a2 [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 <stddef.h>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include "base/compiler_specific.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/strings/stringize_macros.h"
#include "base/values.h"
#include "google_apis/gaia/gaia_oauth_client.h"
#include "net/base/file_stream.h"
#include "net/base/network_interfaces.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/mock_oauth_client.h"
#include "remoting/host/chromoting_host_context.h"
#include "remoting/host/native_messaging/log_message_handler.h"
#include "remoting/host/native_messaging/native_messaging_pipe.h"
#include "remoting/host/native_messaging/pipe_messaging_channel.h"
#include "remoting/host/pin_hash.h"
#include "remoting/host/setup/test_util.h"
#include "remoting/protocol/pairing_registry.h"
#include "remoting/protocol/protocol_mock_objects.h"
#include "testing/gtest/include/gtest/gtest.h"
using remoting::protocol::MockPairingRegistryDelegate;
using remoting::protocol::PairingRegistry;
using remoting::protocol::SynchronousPairingRegistry;
namespace {
void VerifyHelloResponse(std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("helloResponse", value);
EXPECT_TRUE(response->GetString("version", &value));
// The check below will compile but fail if VERSION isn't defined (STRINGIZE
// silently converts undefined values).
#ifndef VERSION
#error VERSION must be defined
#endif
EXPECT_EQ(STRINGIZE(VERSION), value);
}
void VerifyGetHostNameResponse(
std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("getHostNameResponse", value);
EXPECT_TRUE(response->GetString("hostname", &value));
EXPECT_EQ(net::GetHostName(), value);
}
void VerifyGetPinHashResponse(std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("getPinHashResponse", value);
EXPECT_TRUE(response->GetString("hash", &value));
EXPECT_EQ(remoting::MakeHostPinHash("my_host", "1234"), value);
}
void VerifyGenerateKeyPairResponse(
std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("generateKeyPairResponse", value);
EXPECT_TRUE(response->GetString("privateKey", &value));
EXPECT_TRUE(response->GetString("publicKey", &value));
}
void VerifyGetDaemonConfigResponse(
std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("getDaemonConfigResponse", value);
const base::DictionaryValue* config = nullptr;
EXPECT_TRUE(response->GetDictionary("config", &config));
EXPECT_TRUE(base::DictionaryValue().Equals(config));
}
void VerifyGetUsageStatsConsentResponse(
std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("getUsageStatsConsentResponse", value);
bool supported, allowed, set_by_policy;
EXPECT_TRUE(response->GetBoolean("supported", &supported));
EXPECT_TRUE(response->GetBoolean("allowed", &allowed));
EXPECT_TRUE(response->GetBoolean("setByPolicy", &set_by_policy));
EXPECT_TRUE(supported);
EXPECT_TRUE(allowed);
EXPECT_TRUE(set_by_policy);
}
void VerifyStopDaemonResponse(std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("stopDaemonResponse", value);
EXPECT_TRUE(response->GetString("result", &value));
EXPECT_EQ("OK", value);
}
void VerifyGetDaemonStateResponse(
std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("getDaemonStateResponse", value);
EXPECT_TRUE(response->GetString("state", &value));
EXPECT_EQ("STARTED", value);
}
void VerifyUpdateDaemonConfigResponse(
std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("updateDaemonConfigResponse", value);
EXPECT_TRUE(response->GetString("result", &value));
EXPECT_EQ("OK", value);
}
void VerifyStartDaemonResponse(
std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("startDaemonResponse", value);
EXPECT_TRUE(response->GetString("result", &value));
EXPECT_EQ("OK", value);
}
void VerifyGetCredentialsFromAuthCodeResponse(
std::unique_ptr<base::DictionaryValue> response) {
ASSERT_TRUE(response);
std::string value;
EXPECT_TRUE(response->GetString("type", &value));
EXPECT_EQ("getCredentialsFromAuthCodeResponse", value);
EXPECT_TRUE(response->GetString("userEmail", &value));
EXPECT_EQ("fake_user_email", value);
EXPECT_TRUE(response->GetString("refreshToken", &value));
EXPECT_EQ("fake_refresh_token", value);
}
} // namespace
namespace remoting {
class MockDaemonControllerDelegate : public DaemonController::Delegate {
public:
MockDaemonControllerDelegate();
~MockDaemonControllerDelegate() override;
// DaemonController::Delegate interface.
DaemonController::State GetState() override;
std::unique_ptr<base::DictionaryValue> GetConfig() override;
void SetConfigAndStart(
std::unique_ptr<base::DictionaryValue> config,
bool consent,
const DaemonController::CompletionCallback& done) override;
void UpdateConfig(std::unique_ptr<base::DictionaryValue> config,
const DaemonController::CompletionCallback& done) override;
void Stop(const DaemonController::CompletionCallback& done) override;
DaemonController::UsageStatsConsent GetUsageStatsConsent() override;
private:
DISALLOW_COPY_AND_ASSIGN(MockDaemonControllerDelegate);
};
MockDaemonControllerDelegate::MockDaemonControllerDelegate() = default;
MockDaemonControllerDelegate::~MockDaemonControllerDelegate() = default;
DaemonController::State MockDaemonControllerDelegate::GetState() {
return DaemonController::STATE_STARTED;
}
std::unique_ptr<base::DictionaryValue>
MockDaemonControllerDelegate::GetConfig() {
return std::make_unique<base::DictionaryValue>();
}
void MockDaemonControllerDelegate::SetConfigAndStart(
std::unique_ptr<base::DictionaryValue> config,
bool consent,
const DaemonController::CompletionCallback& done) {
// Verify parameters passed in.
if (consent && config && config->HasKey("start")) {
done.Run(DaemonController::RESULT_OK);
} else {
done.Run(DaemonController::RESULT_FAILED);
}
}
void MockDaemonControllerDelegate::UpdateConfig(
std::unique_ptr<base::DictionaryValue> config,
const DaemonController::CompletionCallback& done) {
if (config && config->HasKey("update")) {
done.Run(DaemonController::RESULT_OK);
} else {
done.Run(DaemonController::RESULT_FAILED);
}
}
void MockDaemonControllerDelegate::Stop(
const DaemonController::CompletionCallback& done) {
done.Run(DaemonController::RESULT_OK);
}
DaemonController::UsageStatsConsent
MockDaemonControllerDelegate::GetUsageStatsConsent() {
DaemonController::UsageStatsConsent consent;
consent.supported = true;
consent.allowed = true;
consent.set_by_policy = true;
return consent;
}
class Me2MeNativeMessagingHostTest : public testing::Test {
public:
Me2MeNativeMessagingHostTest();
~Me2MeNativeMessagingHostTest() override;
void SetUp() override;
void TearDown() override;
std::unique_ptr<base::DictionaryValue> ReadMessageFromOutputPipe();
void WriteMessageToInputPipe(const base::Value& message);
// The Host process should shut down when it receives a malformed request.
// This is tested by sending a known-good request, followed by |message|,
// followed by the known-good request again. The response file should only
// contain a single response from the first good request.
void TestBadRequest(const base::Value& message);
protected:
// Reference to the MockDaemonControllerDelegate, which is owned by
// |channel_|.
MockDaemonControllerDelegate* daemon_controller_delegate_;
private:
void StartHost();
void StopHost();
void ExitTest();
// Each test creates two unidirectional pipes: "input" and "output".
// Me2MeNativeMessagingHost reads from input_read_handle and writes to
// output_write_file. The unittest supplies data to input_write_handle, and
// verifies output from output_read_handle.
//
// unittest -> [input] -> Me2MeNativeMessagingHost -> [output] -> unittest
base::File input_write_file_;
base::File output_read_file_;
// Message loop of the test thread.
std::unique_ptr<base::MessageLoop> test_message_loop_;
std::unique_ptr<base::RunLoop> test_run_loop_;
std::unique_ptr<base::Thread> host_thread_;
std::unique_ptr<base::RunLoop> host_run_loop_;
// Task runner of the host thread.
scoped_refptr<AutoThreadTaskRunner> host_task_runner_;
std::unique_ptr<NativeMessagingPipe> native_messaging_pipe_;
DISALLOW_COPY_AND_ASSIGN(Me2MeNativeMessagingHostTest);
};
Me2MeNativeMessagingHostTest::Me2MeNativeMessagingHostTest() = default;
Me2MeNativeMessagingHostTest::~Me2MeNativeMessagingHostTest() = default;
void Me2MeNativeMessagingHostTest::SetUp() {
base::File input_read_file;
base::File output_write_file;
ASSERT_TRUE(MakePipe(&input_read_file, &input_write_file_));
ASSERT_TRUE(MakePipe(&output_read_file_, &output_write_file));
test_message_loop_.reset(new base::MessageLoop());
test_run_loop_.reset(new base::RunLoop());
// Run the host on a dedicated thread.
host_thread_.reset(new base::Thread("host_thread"));
host_thread_->Start();
// Arrange to run |test_message_loop_| until no components depend on it.
host_task_runner_ = new AutoThreadTaskRunner(
host_thread_->task_runner(),
base::Bind(&Me2MeNativeMessagingHostTest::ExitTest,
base::Unretained(this)));
host_task_runner_->PostTask(
FROM_HERE,
base::Bind(&Me2MeNativeMessagingHostTest::StartHost,
base::Unretained(this)));
// Wait until the host finishes starting.
test_run_loop_->Run();
}
void Me2MeNativeMessagingHostTest::StartHost() {
DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
base::File input_read_file;
base::File output_write_file;
ASSERT_TRUE(MakePipe(&input_read_file, &input_write_file_));
ASSERT_TRUE(MakePipe(&output_read_file_, &output_write_file));
daemon_controller_delegate_ = new MockDaemonControllerDelegate();
scoped_refptr<DaemonController> daemon_controller(
new DaemonController(base::WrapUnique(daemon_controller_delegate_)));
scoped_refptr<PairingRegistry> pairing_registry =
new SynchronousPairingRegistry(
base::WrapUnique(new MockPairingRegistryDelegate()));
native_messaging_pipe_.reset(new NativeMessagingPipe());
std::unique_ptr<extensions::NativeMessagingChannel> channel(
new PipeMessagingChannel(std::move(input_read_file),
std::move(output_write_file)));
std::unique_ptr<OAuthClient> oauth_client(
new MockOAuthClient("fake_user_email", "fake_refresh_token"));
std::unique_ptr<ChromotingHostContext> context =
ChromotingHostContext::Create(new remoting::AutoThreadTaskRunner(
host_task_runner_, base::Bind(&Me2MeNativeMessagingHostTest::StopHost,
base::Unretained(this))));
std::unique_ptr<remoting::Me2MeNativeMessagingHost> host(
new Me2MeNativeMessagingHost(false, 0, std::move(context),
daemon_controller, pairing_registry,
std::move(oauth_client)));
host->Start(native_messaging_pipe_.get());
native_messaging_pipe_->Start(std::move(host), std::move(channel));
// Notify the test that the host has finished starting up.
test_message_loop_->task_runner()->PostTask(
FROM_HERE, test_run_loop_->QuitClosure());
}
void Me2MeNativeMessagingHostTest::StopHost() {
DCHECK(host_task_runner_->RunsTasksInCurrentSequence());
native_messaging_pipe_.reset();
// Wait till all shutdown tasks have completed.
base::RunLoop().RunUntilIdle();
// Trigger a test shutdown via ExitTest().
host_task_runner_ = nullptr;
}
void Me2MeNativeMessagingHostTest::ExitTest() {
if (!test_message_loop_->task_runner()->RunsTasksInCurrentSequence()) {
test_message_loop_->task_runner()->PostTask(
FROM_HERE,
base::Bind(&Me2MeNativeMessagingHostTest::ExitTest,
base::Unretained(this)));
return;
}
test_run_loop_->Quit();
}
void Me2MeNativeMessagingHostTest::TearDown() {
// Closing the write-end of the input will send an EOF to the native
// messaging reader. This will trigger a host shutdown.
input_write_file_.Close();
// Start a new RunLoop and Wait until the host finishes shutting down.
test_run_loop_.reset(new base::RunLoop());
test_run_loop_->Run();
// Verify there are no more message in the output pipe.
std::unique_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
EXPECT_FALSE(response);
// The It2MeMe2MeNativeMessagingHost dtor closes the handles that are passed
// to it. So the only handle left to close is |output_read_file_|.
output_read_file_.Close();
}
std::unique_ptr<base::DictionaryValue>
Me2MeNativeMessagingHostTest::ReadMessageFromOutputPipe() {
while (true) {
uint32_t length;
int read_result = output_read_file_.ReadAtCurrentPos(
reinterpret_cast<char*>(&length), sizeof(length));
if (read_result != sizeof(length)) {
return nullptr;
}
std::string message_json(length, '\0');
read_result =
output_read_file_.ReadAtCurrentPos(base::data(message_json), length);
if (read_result != static_cast<int>(length)) {
return nullptr;
}
std::unique_ptr<base::Value> message = base::JSONReader::Read(message_json);
if (!message || !message->is_dict()) {
return nullptr;
}
std::unique_ptr<base::DictionaryValue> result = base::WrapUnique(
static_cast<base::DictionaryValue*>(message.release()));
std::string type;
// If this is a debug message log, ignore it, otherwise return it.
if (!result->GetString("type", &type) ||
type != LogMessageHandler::kDebugMessageTypeName) {
return result;
}
}
}
void Me2MeNativeMessagingHostTest::WriteMessageToInputPipe(
const base::Value& message) {
std::string message_json;
base::JSONWriter::Write(message, &message_json);
uint32_t length = message_json.length();
input_write_file_.WriteAtCurrentPos(reinterpret_cast<char*>(&length),
sizeof(length));
input_write_file_.WriteAtCurrentPos(message_json.data(), length);
}
void Me2MeNativeMessagingHostTest::TestBadRequest(const base::Value& message) {
base::DictionaryValue good_message;
good_message.SetString("type", "hello");
// This test currently relies on synchronous processing of hello messages and
// message parameters verification.
WriteMessageToInputPipe(good_message);
WriteMessageToInputPipe(message);
WriteMessageToInputPipe(good_message);
// Read from output pipe, and verify responses.
std::unique_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
VerifyHelloResponse(std::move(response));
response = ReadMessageFromOutputPipe();
EXPECT_FALSE(response);
}
// TODO (weitaosu): crbug.com/323306. Re-enable these tests.
// Test all valid request-types.
TEST_F(Me2MeNativeMessagingHostTest, All) {
int next_id = 0;
base::DictionaryValue message;
message.SetInteger("id", next_id++);
message.SetString("type", "hello");
WriteMessageToInputPipe(message);
message.SetInteger("id", next_id++);
message.SetString("type", "getHostName");
WriteMessageToInputPipe(message);
message.SetInteger("id", next_id++);
message.SetString("type", "getPinHash");
message.SetString("hostId", "my_host");
message.SetString("pin", "1234");
WriteMessageToInputPipe(message);
message.Clear();
message.SetInteger("id", next_id++);
message.SetString("type", "generateKeyPair");
WriteMessageToInputPipe(message);
message.SetInteger("id", next_id++);
message.SetString("type", "getDaemonConfig");
WriteMessageToInputPipe(message);
message.SetInteger("id", next_id++);
message.SetString("type", "getUsageStatsConsent");
WriteMessageToInputPipe(message);
message.SetInteger("id", next_id++);
message.SetString("type", "stopDaemon");
WriteMessageToInputPipe(message);
message.SetInteger("id", next_id++);
message.SetString("type", "getDaemonState");
WriteMessageToInputPipe(message);
// Following messages require a "config" dictionary.
base::DictionaryValue config;
config.SetBoolean("update", true);
message.Set("config", config.CreateDeepCopy());
message.SetInteger("id", next_id++);
message.SetString("type", "updateDaemonConfig");
WriteMessageToInputPipe(message);
config.Clear();
config.SetBoolean("start", true);
message.Set("config", config.CreateDeepCopy());
message.SetBoolean("consent", true);
message.SetInteger("id", next_id++);
message.SetString("type", "startDaemon");
WriteMessageToInputPipe(message);
message.SetInteger("id", next_id++);
message.SetString("type", "getCredentialsFromAuthCode");
message.SetString("authorizationCode", "fake_auth_code");
WriteMessageToInputPipe(message);
void (*verify_routines[])(std::unique_ptr<base::DictionaryValue>) = {
&VerifyHelloResponse,
&VerifyGetHostNameResponse,
&VerifyGetPinHashResponse,
&VerifyGenerateKeyPairResponse,
&VerifyGetDaemonConfigResponse,
&VerifyGetUsageStatsConsentResponse,
&VerifyStopDaemonResponse,
&VerifyGetDaemonStateResponse,
&VerifyUpdateDaemonConfigResponse,
&VerifyStartDaemonResponse,
&VerifyGetCredentialsFromAuthCodeResponse,
};
ASSERT_EQ(base::size(verify_routines), static_cast<size_t>(next_id));
// Read all responses from output pipe, and verify them.
for (int i = 0; i < next_id; ++i) {
std::unique_ptr<base::DictionaryValue> response =
ReadMessageFromOutputPipe();
// Make sure that id is available and is in the range.
int id;
ASSERT_TRUE(response->GetInteger("id", &id));
ASSERT_TRUE(0 <= id && id < next_id);
// Call the verification routine corresponding to the message id.
ASSERT_TRUE(verify_routines[id]);
verify_routines[id](std::move(response));
// Clear the pointer so that the routine cannot be called the second time.
verify_routines[id] = nullptr;
}
}
// Verify that response ID matches request ID.
TEST_F(Me2MeNativeMessagingHostTest, Id) {
base::DictionaryValue message;
message.SetString("type", "hello");
WriteMessageToInputPipe(message);
message.SetString("id", "42");
WriteMessageToInputPipe(message);
std::unique_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
EXPECT_TRUE(response);
std::string value;
EXPECT_FALSE(response->GetString("id", &value));
response = ReadMessageFromOutputPipe();
EXPECT_TRUE(response);
EXPECT_TRUE(response->GetString("id", &value));
EXPECT_EQ("42", value);
}
// Verify non-Dictionary requests are rejected.
TEST_F(Me2MeNativeMessagingHostTest, WrongFormat) {
base::ListValue message;
TestBadRequest(message);
}
// Verify requests with no type are rejected.
TEST_F(Me2MeNativeMessagingHostTest, MissingType) {
base::DictionaryValue message;
TestBadRequest(message);
}
// Verify rejection if type is unrecognized.
TEST_F(Me2MeNativeMessagingHostTest, InvalidType) {
base::DictionaryValue message;
message.SetString("type", "xxx");
TestBadRequest(message);
}
// Verify rejection if getPinHash request has no hostId.
TEST_F(Me2MeNativeMessagingHostTest, GetPinHashNoHostId) {
base::DictionaryValue message;
message.SetString("type", "getPinHash");
message.SetString("pin", "1234");
TestBadRequest(message);
}
// Verify rejection if getPinHash request has no pin.
TEST_F(Me2MeNativeMessagingHostTest, GetPinHashNoPin) {
base::DictionaryValue message;
message.SetString("type", "getPinHash");
message.SetString("hostId", "my_host");
TestBadRequest(message);
}
// Verify rejection if updateDaemonConfig request has invalid config.
TEST_F(Me2MeNativeMessagingHostTest, UpdateDaemonConfigInvalidConfig) {
base::DictionaryValue message;
message.SetString("type", "updateDaemonConfig");
message.SetString("config", "xxx");
TestBadRequest(message);
}
// Verify rejection if startDaemon request has invalid config.
TEST_F(Me2MeNativeMessagingHostTest, StartDaemonInvalidConfig) {
base::DictionaryValue message;
message.SetString("type", "startDaemon");
message.SetString("config", "xxx");
message.SetBoolean("consent", true);
TestBadRequest(message);
}
// Verify rejection if startDaemon request has no "consent" parameter.
TEST_F(Me2MeNativeMessagingHostTest, StartDaemonNoConsent) {
base::DictionaryValue message;
message.SetString("type", "startDaemon");
message.Set("config", base::DictionaryValue().CreateDeepCopy());
TestBadRequest(message);
}
// Verify rejection if getCredentialsFromAuthCode has no auth code.
TEST_F(Me2MeNativeMessagingHostTest, GetCredentialsFromAuthCodeNoAuthCode) {
base::DictionaryValue message;
message.SetString("type", "getCredentialsFromAuthCode");
TestBadRequest(message);
}
} // namespace remoting