// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/it2me/it2me_native_messaging_host.h"

#include <memory>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/uuid.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/policy/policy_constants.h"
#include "net/base/url_util.h"
#include "net/socket/client_socket_factory.h"
#include "net/url_request/url_request_context_getter.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/errors.h"
#include "remoting/base/logging.h"
#include "remoting/base/oauth_token_getter_proxy.h"
#include "remoting/base/passthrough_oauth_token_getter.h"
#include "remoting/host/base/host_exit_codes.h"
#include "remoting/host/chromeos/chromeos_enterprise_params.h"
#include "remoting/host/chromoting_host_context.h"
#include "remoting/host/corp_register_support_host_request.h"
#include "remoting/host/it2me/it2me_confirmation_dialog.h"
#include "remoting/host/it2me/it2me_constants.h"
#include "remoting/host/it2me/it2me_helpers.h"
#include "remoting/host/it2me/it2me_host.h"
#include "remoting/host/native_messaging/native_messaging_helpers.h"
#include "remoting/host/policy_watcher.h"
#include "remoting/host/remoting_register_support_host_request.h"
#include "remoting/signaling/ftl_signal_strategy.h"
#include "remoting/signaling/ftl_support_host_device_id_provider.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"

#if BUILDFLAG(IS_WIN)
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "remoting/host/elevated_native_messaging_host.h"
#endif  // BUILDFLAG(IS_WIN)

namespace remoting {

using protocol::ErrorCode;

namespace {

#if BUILDFLAG(IS_WIN)
const base::FilePath::CharType kBaseHostBinaryName[] =
    FILE_PATH_LITERAL("remote_assistance_host.exe");
const base::FilePath::CharType kElevatedHostBinaryName[] =
    FILE_PATH_LITERAL("remote_assistance_host_uiaccess.exe");
#endif  // BUILDFLAG(IS_WIN)

// Helper functions to run |callback| asynchronously on the correct thread
// using |task_runner|.
void PolicyUpdateCallback(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    remoting::PolicyWatcher::PolicyUpdatedCallback callback,
    base::DictValue policies) {
  DCHECK(callback);
  task_runner->PostTask(FROM_HERE,
                        base::BindOnce(callback, std::move(policies)));
}

void PolicyErrorCallback(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
    remoting::PolicyWatcher::PolicyErrorCallback callback) {
  DCHECK(callback);
  task_runner->PostTask(FROM_HERE, callback);
}

// This function checks the email address provided to see if it is properly
// formatted. It does not validate the username or domain sections.
// TODO(joedow): Move to a shared location.
bool IsValidEmailAddress(const std::string& email) {
  return base::SplitString(email, "@", base::KEEP_WHITESPACE,
                           base::SPLIT_WANT_ALL)
             .size() == 2U;
}

std::unique_ptr<It2MeHost::DeferredConnectContext>
CreateNativeSignalingDeferredConnectContext(
    scoped_refptr<base::SequencedTaskRunner> oauth_token_getter_task_runner,
    base::WeakPtr<OAuthTokenGetter> signaling_token_getter,
    base::WeakPtr<OAuthTokenGetter> api_token_getter,
    const std::string& ftl_device_id,
    bool is_corp_user,
    ChromotingHostContext* host_context) {
  std::string device_id =
      ftl_device_id.empty() ? base::Uuid::GenerateRandomV4().AsLowercaseString()
                            : ftl_device_id;
  auto connection_context =
      std::make_unique<It2MeHost::DeferredConnectContext>();
  connection_context->is_corp_user = is_corp_user;
  connection_context->signal_strategy = std::make_unique<FtlSignalStrategy>(
      std::make_unique<OAuthTokenGetterProxy>(signaling_token_getter,
                                              oauth_token_getter_task_runner),
      host_context->url_loader_factory(),
      std::make_unique<FtlSupportHostDeviceIdProvider>(device_id));
  connection_context->ftl_device_id = std::move(device_id);
  if (is_corp_user) {
    connection_context->register_request =
        std::make_unique<CorpRegisterSupportHostRequest>(
            std::make_unique<OAuthTokenGetterProxy>(
                api_token_getter, oauth_token_getter_task_runner),
            host_context->url_loader_factory());
  } else {
    connection_context->register_request =
        std::make_unique<RemotingRegisterSupportHostRequest>(
            std::make_unique<OAuthTokenGetterProxy>(
                api_token_getter, oauth_token_getter_task_runner),
            host_context->url_loader_factory());
  }
  connection_context->signaling_token_getter =
      std::make_unique<OAuthTokenGetterProxy>(signaling_token_getter,
                                              oauth_token_getter_task_runner);
  connection_context->api_token_getter =
      std::make_unique<OAuthTokenGetterProxy>(api_token_getter,
                                              oauth_token_getter_task_runner);
  return connection_context;
}

}  // namespace

It2MeNativeMessagingHost::It2MeNativeMessagingHost(
    bool is_process_elevated,
    std::unique_ptr<PolicyWatcher> policy_watcher,
    std::unique_ptr<ChromotingHostContext> context,
    std::unique_ptr<It2MeHostFactory> factory)
    : is_process_elevated_(is_process_elevated),
      host_context_(std::move(context)),
      factory_(std::move(factory)),
      policy_watcher_(std::move(policy_watcher)) {
  weak_ptr_ = weak_factory_.GetWeakPtr();

  // The policy watcher runs on the |file_task_runner| but we want to run the
  // callbacks on |task_runner| so we use a shim to post them to it.
  PolicyWatcher::PolicyUpdatedCallback update_callback =
      base::BindRepeating(&It2MeNativeMessagingHost::OnPolicyUpdate, weak_ptr_);
  PolicyWatcher::PolicyErrorCallback error_callback =
      base::BindRepeating(&It2MeNativeMessagingHost::OnPolicyError, weak_ptr_);
  policy_watcher_->StartWatching(
      base::BindRepeating(&PolicyUpdateCallback, task_runner(),
                          update_callback),
      base::BindRepeating(&PolicyErrorCallback, task_runner(), error_callback));
}

It2MeNativeMessagingHost::~It2MeNativeMessagingHost() {
  DCHECK(task_runner()->BelongsToCurrentThread());

  if (it2me_host_.get()) {
    it2me_host_->Disconnect();
    it2me_host_ = nullptr;
  }
}

void It2MeNativeMessagingHost::OnMessage(const std::string& message) {
  DCHECK(task_runner()->BelongsToCurrentThread());

  std::string type;
  base::DictValue request;
  if (!ParseNativeMessageJson(message, type, request)) {
    client_->CloseChannel(std::string());
    return;
  }

  std::optional<base::DictValue> response =
      CreateNativeMessageResponse(request);
  if (!response.has_value()) {
    SendErrorAndExit(base::DictValue(), ErrorCode::INVALID_ARGUMENT);
    return;
  }

  if (type == kHelloMessage) {
    ProcessHello(std::move(request), std::move(*response));
  } else if (type == kConnectMessage) {
    ProcessConnect(std::move(request), std::move(*response));
  } else if (type == kDisconnectMessage) {
    ProcessDisconnect(std::move(request), std::move(*response));
  } else if (type == kUpdateAccessTokensMessage) {
    ProcessUpdateAccessTokens(std::move(request), std::move(*response));
  } else {
    LOG(ERROR) << "Unsupported request type: " << type;
    SendErrorAndExit(std::move(request), ErrorCode::INCOMPATIBLE_PROTOCOL);
  }
}

void It2MeNativeMessagingHost::Start(Client* client) {
  DCHECK(task_runner()->BelongsToCurrentThread());
  client_ = client;
#if !BUILDFLAG(IS_CHROMEOS)
  log_message_handler_ = std::make_unique<LogMessageHandler>(
      base::BindRepeating(&It2MeNativeMessagingHost::SendMessageToClient,
                          base::Unretained(this)));
#endif  // !BUILDFLAG(IS_CHROMEOS)
}

void It2MeNativeMessagingHost::SendMessageToClient(
    base::DictValue message) const {
  DCHECK(task_runner()->BelongsToCurrentThread());
  client_->PostMessageFromNativeHost(base::WriteJson(message).value_or(""));
}

void It2MeNativeMessagingHost::ProcessHello(base::DictValue message,
                                            base::DictValue response) const {
  DCHECK(task_runner()->BelongsToCurrentThread());

  // No need to forward to the elevated process since no internal state is set.

  base::ListValue features;
  features.Append(kFeatureAccessTokenAuth);
  features.Append(kFeatureAuthorizedHelper);

  ProcessNativeMessageHelloResponse(response, std::move(features));
  SendMessageToClient(std::move(response));
}

void It2MeNativeMessagingHost::ProcessConnect(base::DictValue message,
                                              base::DictValue response) {
  DCHECK(task_runner()->BelongsToCurrentThread());

  if (!policy_received_) {
    DCHECK(!pending_connect_);
    LOG(WARNING) << "Delaying connection request until we receive the policies";
    pending_connect_ =
        base::BindOnce(&It2MeNativeMessagingHost::ProcessConnect, weak_ptr_,
                       std::move(message), std::move(response));
    return;
  }

#if BUILDFLAG(IS_WIN)
  // Requests that the support host is launched with UiAccess on Windows.
  // This value, in conjuction with the platform policy, is used to determine
  // if an elevated host should be used.
  bool use_elevated_host = message.FindBool(kUseElevatedHost).value_or(false);

  if (!is_process_elevated_) {
    auto allow_elevation_policy = GetAllowElevatedHostPolicyValue();
    // Honor the platform policy value if it is set, otherwise use the value
    // provided through the native messaging host.
    use_elevated_host_ = allow_elevation_policy.value_or(use_elevated_host);
  }
#else
  CHECK(!is_process_elevated_) << "Unexpected value for this platform";
#endif

  if (use_elevated_host_) {
    // Attempt to pass the current message to the elevated process.  This method
    // will spin up the elevated process if it is not already running.  On
    // success, the elevated process will process the message and respond.
    // If the process cannot be started or message passing fails, then return an
    // error to the message sender.
    if (!DelegateToElevatedHost(std::move(message))) {
      LOG(ERROR) << "Failed to send message to elevated host.";
      SendErrorAndExit(std::move(response), ErrorCode::ELEVATION_ERROR);
    }
    return;
  }

  if (it2me_host_.get()) {
    LOG(ERROR) << "Connect can be called only when disconnected.";
    SendErrorAndExit(std::move(response), ErrorCode::UNKNOWN_ERROR);
    return;
  }

  std::string username;
  const std::string* username_from_message = message.FindString(kUserName);
  if (username_from_message) {
    username = *username_from_message;
  }

  std::string authorized_helper;
  const std::string* authorized_helper_value =
      message.FindString(kAuthorizedHelper);
  if (authorized_helper_value) {
    authorized_helper = *authorized_helper_value;
    if (!IsValidEmailAddress(authorized_helper)) {
      LOG(ERROR) << "Invalid authorized_helper value: " << authorized_helper;
      SendErrorAndExit(std::move(response), ErrorCode::INVALID_ARGUMENT);
      return;
    }
  }

#if BUILDFLAG(IS_CHROMEOS) || !defined(NDEBUG)
  bool is_enterprise_admin_user = enterprise_params_.has_value();
#endif

  It2MeHost::CreateDeferredConnectContext create_connection_context;
  if (!username.empty()) {
    signaling_token_getter_.set_username(username);
    api_token_getter_.set_username(username);
    std::string* signaling_access_token =
        message.FindString(kSignalingAccessToken);
    std::string* api_access_token = message.FindString(kApiAccessToken);
    if (signaling_access_token && api_access_token) {
      signaling_token_getter_.set_access_token(*signaling_access_token);
      api_token_getter_.set_access_token(*api_access_token);
    } else if (signaling_access_token || api_access_token) {
      LOG(ERROR) << "The website did not provide both the signaling access "
                 << "token and the API access token.";
      SendErrorAndExit(std::move(response), ErrorCode::INVALID_ARGUMENT);
      return;
    } else {
      HOST_LOG << "The website did not provide signaling and API access "
               << "tokens separately. Will use the same access token for "
               << "both scenarios.";
      std::string access_token = ExtractAccessToken(message);
      signaling_token_getter_.set_access_token(access_token);
      api_token_getter_.set_access_token(access_token);
    }
    std::string ftl_device_id;
    if (reconnect_params_.has_value()) {
      ftl_device_id = reconnect_params_->ftl_device_id;
    }
    bool is_corp_user = message.FindBool(kIsCorpUser).value_or(false);
    create_connection_context = base::BindOnce(
        &CreateNativeSignalingDeferredConnectContext, task_runner(),
        signaling_token_getter_.GetWeakPtr(), api_token_getter_.GetWeakPtr(),
        ftl_device_id, is_corp_user);
  } else {
    LOG(ERROR) << kUserName << " not found in request.";
  }
  if (!create_connection_context) {
    SendErrorAndExit(std::move(response), ErrorCode::INVALID_STATE);
    return;
  }

  base::DictValue policies = policy_watcher_->GetEffectivePolicies();
  if (policies.empty()) {
    // At this point policies have been read, so if there are none set then
    // it indicates an error. Since this can be fixed by end users it has a
    // dedicated message type rather than the generic "error" so that the
    // right error message can be displayed.
    SendPolicyErrorAndExit();
    return;
  }

  // Create the It2Me host and start connecting.
  it2me_host_ = factory_->CreateIt2MeHost();
  it2me_host_->set_authorized_helper(authorized_helper);

  auto dialog_style = It2MeConfirmationDialog::DialogStyle::kConsumer;
  base::TimeDelta connection_auto_accept_timeout;
#if BUILDFLAG(IS_CHROMEOS) || !defined(NDEBUG)
  if (is_enterprise_admin_user) {
    connection_auto_accept_timeout =
        enterprise_params_->connection_auto_accept_timeout;
    it2me_host_->set_chrome_os_enterprise_params(*enterprise_params_);

    dialog_style = It2MeConfirmationDialog::DialogStyle::kEnterprise;

    if (reconnect_params_.has_value()) {
      CHECK(enterprise_params_->allow_reconnections);
      it2me_host_->set_reconnect_params(*reconnect_params_);
    }
  }
#endif

  it2me_host_->Connect(host_context_->Copy(), std::move(policies),
                       std::make_unique<It2MeConfirmationDialogFactory>(
                           dialog_style, connection_auto_accept_timeout),
                       weak_ptr_, std::move(create_connection_context),
                       username);

  SendMessageToClient(std::move(response));
}

void It2MeNativeMessagingHost::ProcessDisconnect(base::DictValue message,
                                                 base::DictValue response) {
  DCHECK(task_runner()->BelongsToCurrentThread());
  DCHECK(policy_received_);

  if (use_elevated_host_) {
    // Attempt to pass the current message to the elevated process.  This method
    // will spin up the elevated process if it is not already running.  On
    // success, the elevated process will process the message and respond.
    // If the process cannot be started or message passing fails, then return an
    // error to the message sender.
    if (!DelegateToElevatedHost(std::move(message))) {
      LOG(ERROR) << "Failed to send message to elevated host.";
      SendErrorAndExit(std::move(response), ErrorCode::ELEVATION_ERROR);
    }
    return;
  }

  if (it2me_host_.get()) {
    it2me_host_->Disconnect();
    it2me_host_ = nullptr;
  }
  SendMessageToClient(std::move(response));
}

void It2MeNativeMessagingHost::ProcessUpdateAccessTokens(
    base::DictValue message,
    base::DictValue response) {
  DCHECK(task_runner()->BelongsToCurrentThread());

  const std::string* signaling_access_token =
      message.FindString(kSignalingAccessToken);
  if (!signaling_access_token) {
    LOG(ERROR) << "Cannot find " << kSignalingAccessToken << " in the "
               << kUpdateAccessTokensMessage << " message.";
    SendErrorAndExit(std::move(response), ErrorCode::INVALID_ARGUMENT);
    return;
  }

  const std::string* api_access_token = message.FindString(kApiAccessToken);
  if (!api_access_token) {
    LOG(ERROR) << "Cannot find " << kApiAccessToken << " in the "
               << kUpdateAccessTokensMessage << " message.";
    SendErrorAndExit(std::move(response), ErrorCode::INVALID_ARGUMENT);
    return;
  }

  signaling_token_getter_.set_access_token(*signaling_access_token);
  api_token_getter_.set_access_token(*api_access_token);

  HOST_LOG << "OAuth access tokens updated";
  SendMessageToClient(std::move(response));
}

void It2MeNativeMessagingHost::SendErrorAndExit(
    base::DictValue response,
    protocol::ErrorCode error_code) const {
  DCHECK(task_runner()->BelongsToCurrentThread());
  response.Set(kMessageType, kErrorMessage);
  response.Set(kErrorMessageCode, ErrorCodeToString(error_code));
  SendMessageToClient(std::move(response));

  // Trigger a host shutdown by sending an empty message.
  client_->CloseChannel(std::string());
}

void It2MeNativeMessagingHost::SendPolicyErrorAndExit() const {
  DCHECK(task_runner()->BelongsToCurrentThread());

  base::DictValue message;
  message.Set(kMessageType, kPolicyErrorMessage);
  SendMessageToClient(std::move(message));
  client_->CloseChannel(std::string());
}

void It2MeNativeMessagingHost::OnStateChanged(It2MeHostState state,
                                              protocol::ErrorCode error_code) {
  DCHECK(task_runner()->BelongsToCurrentThread());

  state_ = state;

  base::DictValue message;

  message.Set(kMessageType, kHostStateChangedMessage);
  message.Set(kState, It2MeHostStateToString(state));

  switch (state_) {
    case It2MeHostState::kReceivedAccessCode:
      message.Set(kAccessCode, access_code_);
      // base::DictValue does not support setting int64_t due to JSON spec's
      // limitation (see comments in values.h). The cast should be safe given
      // the lifetime is relatively short.
      message.Set(kAccessCodeLifetime,
                  static_cast<int>(access_code_lifetime_.InSeconds()));
      break;

    case It2MeHostState::kConnected: {
      message.Set(kClient, client_username_);
      auto reconnect_params = it2me_host_->CreateReconnectParams();
      if (reconnect_params.has_value()) {
        message.Set(kReconnectParamsDict,
                    ReconnectParams::ToDict(std::move(*reconnect_params)));
      }
      break;
    }
    case It2MeHostState::kDisconnected:
      message.Set(kDisconnectReason, ErrorCodeToString(error_code));
      client_username_.clear();
      break;

    case It2MeHostState::kError:
      // kError is an internal-only state, sent to the web-app by a separate
      // "error" message so that errors that occur before the "connect" message
      // is sent can be communicated.
      message.Set(kMessageType, kErrorMessage);
      message.Set(kErrorMessageCode, ErrorCodeToString(error_code));
      break;

    default:
      break;
  }

  SendMessageToClient(std::move(message));
}

void It2MeNativeMessagingHost::SetPolicyErrorClosureForTesting(
    base::OnceClosure closure) {
  policy_error_closure_for_testing_ = std::move(closure);
}

void It2MeNativeMessagingHost::OnNatPoliciesChanged(
    bool nat_traversal_enabled,
    bool relay_connections_allowed) {
  DCHECK(task_runner()->BelongsToCurrentThread());

  base::DictValue message;

  message.Set(kMessageType, kNatPolicyChangedMessage);
  message.Set(kNatPolicyChangedMessageNatEnabled, nat_traversal_enabled);
  message.Set(kNatPolicyChangedMessageRelayEnabled, relay_connections_allowed);
  SendMessageToClient(std::move(message));
}

void It2MeNativeMessagingHost::OnStoreAccessCode(
    const std::string& access_code,
    base::TimeDelta access_code_lifetime) {
  DCHECK(task_runner()->BelongsToCurrentThread());

  access_code_ = access_code;
  access_code_lifetime_ = access_code_lifetime;
}

void It2MeNativeMessagingHost::OnClientAuthenticated(
    const std::string& client_username) {
  DCHECK(task_runner()->BelongsToCurrentThread());

  client_username_ = client_username;
}

scoped_refptr<base::SingleThreadTaskRunner>
It2MeNativeMessagingHost::task_runner() const {
  return host_context_->ui_task_runner();
}

/* static */

void It2MeNativeMessagingHost::OnPolicyUpdate(base::DictValue policies) {
  // If an It2MeHost exists, provide it with the updated policies first.
  // That way it won't appear that the policies have changed if the pending
  // connect callback is run. If done the other way around, there is a race
  // condition which could cause the connection to be canceled before it starts.
  if (it2me_host_) {
    it2me_host_->OnPolicyUpdate(std::move(policies));
  }

  if (!policy_received_) {
    policy_received_ = true;

    if (pending_connect_) {
      std::move(pending_connect_).Run();
    }
  }
}

std::optional<bool>
It2MeNativeMessagingHost::GetAllowElevatedHostPolicyValue() {
  DCHECK(policy_received_);
#if BUILDFLAG(IS_WIN)
  base::DictValue platform_policies = policy_watcher_->GetPlatformPolicies();
  auto* platform_policy_value = platform_policies.FindByDottedPath(
      policy::key::kRemoteAccessHostAllowUiAccessForRemoteAssistance);
  if (platform_policy_value) {
    // Use the platform policy value.
    bool value = platform_policy_value->GetBool();
    LOG(INFO) << "Allow UiAccess for remote support policy value: " << value;
    return value;
  }
#endif  // BUILDFLAG(IS_WIN)

  return std::nullopt;
}

void It2MeNativeMessagingHost::OnPolicyError() {
  LOG(ERROR) << "Malformed policies detected.";
  policy_received_ = true;

  if (policy_error_closure_for_testing_) {
    std::move(policy_error_closure_for_testing_).Run();
  }

  if (it2me_host_) {
    // If there is already a connection, close it and notify the webapp.
    it2me_host_->Disconnect();
    it2me_host_ = nullptr;
    SendPolicyErrorAndExit();
  } else if (pending_connect_) {
    // If there is no connection, run the pending connection callback if there
    // is one, but otherwise do nothing. The policy error will be sent when a
    // connection is made; doing so beforehand would break assumptions made by
    // the Chrome app.
    std::move(pending_connect_).Run();
  }
}

std::string It2MeNativeMessagingHost::ExtractAccessToken(
    const base::DictValue& message) {
  const std::string* access_token = message.FindString(kAccessToken);
  if (!access_token) {
    LOG(ERROR) << kAccessToken << " field not found in request.";
    return {};
  }
  if (access_token->empty()) {
    LOG(ERROR) << "Empty token stored in " << kAccessToken << " field";
    return {};
  }

  // Log an error if an access token is provided which does not match the
  // expected format. Though this prefix is effectively stable, there are no
  // guarantees it won't change so we shouldn't reject requests based on it.
  if (!access_token->starts_with("ya29.")) {
    LOG(ERROR) << "Potentially invalid " << kAccessToken
               << " value: " << *access_token;
  }

  return *access_token;
}

#if BUILDFLAG(IS_WIN)

bool It2MeNativeMessagingHost::DelegateToElevatedHost(base::DictValue message) {
  DCHECK(task_runner()->BelongsToCurrentThread());
  DCHECK(use_elevated_host_);

  if (!elevated_host_) {
    base::FilePath binary_path =
        base::CommandLine::ForCurrentProcess()->GetProgram();
    CHECK(binary_path.BaseName() == base::FilePath(kBaseHostBinaryName));

    // The new process runs at an elevated level due to being granted uiAccess.
    // |parent_window_handle| can be used to position dialog windows but is not
    // currently used.
    elevated_host_ = std::make_unique<ElevatedNativeMessagingHost>(
        binary_path.DirName().Append(kElevatedHostBinaryName),
        /*parent_window_handle=*/0,
        /*elevate_process=*/false,
        /*host_timeout=*/base::TimeDelta(), client_);
  }

  if (elevated_host_->EnsureElevatedHostCreated() ==
      PROCESS_LAUNCH_RESULT_SUCCESS) {
    elevated_host_->SendMessage(message);
    return true;
  }

  return false;
}

#else  // !BUILDFLAG(IS_WIN)

bool It2MeNativeMessagingHost::DelegateToElevatedHost(base::DictValue message) {
  NOTREACHED();
}

#endif  // !BUILDFLAG(IS_WIN)

}  // namespace remoting
