blob: 7faf2ebd20019071ca65e9514c4dc8f4417f9e80 [file] [log] [blame]
// Copyright 2021 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/webauthn/remote_webauthn_native_messaging_host.h"
#include <memory>
#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/values.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/system/isolated_connection.h"
#include "remoting/host/native_messaging/native_messaging_constants.h"
#include "remoting/host/native_messaging/native_messaging_helpers.h"
#include "remoting/host/webauthn/remote_webauthn_constants.h"
#include "remoting/host/webauthn/remote_webauthn_message_handler.h"
namespace remoting {
RemoteWebAuthnNativeMessagingHost::RemoteWebAuthnNativeMessagingHost(
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: task_runner_(task_runner) {}
RemoteWebAuthnNativeMessagingHost::~RemoteWebAuthnNativeMessagingHost() {
DCHECK(task_runner_->BelongsToCurrentThread());
}
void RemoteWebAuthnNativeMessagingHost::OnMessage(const std::string& message) {
DCHECK(task_runner_->BelongsToCurrentThread());
std::string type;
base::Value request;
if (!ParseNativeMessageJson(message, type, request)) {
return;
}
base::Value response = CreateNativeMessageResponse(request);
if (response.is_none()) {
return;
}
if (type == kHelloMessage) {
ProcessHello(std::move(response));
} else if (type == kIsUvpaaMessageType) {
ProcessIsUvpaa(request, std::move(response));
} else if (type == kGetRemoteStateMessageType) {
ProcessGetRemoteState(std::move(response));
} else {
LOG(ERROR) << "Unsupported request type: " << type;
}
}
void RemoteWebAuthnNativeMessagingHost::Start(
extensions::NativeMessageHost::Client* client) {
DCHECK(task_runner_->BelongsToCurrentThread());
client_ = client;
}
scoped_refptr<base::SingleThreadTaskRunner>
RemoteWebAuthnNativeMessagingHost::task_runner() const {
return task_runner_;
}
void RemoteWebAuthnNativeMessagingHost::ProcessHello(base::Value response) {
// Hello request: {id: string, type: 'hello'}
// Hello response: {id: string, type: 'helloResponse', hostVersion: string}
DCHECK(task_runner_->BelongsToCurrentThread());
ProcessNativeMessageHelloResponse(response);
SendMessageToClient(std::move(response));
}
void RemoteWebAuthnNativeMessagingHost::ProcessIsUvpaa(
const base::Value& request,
base::Value response) {
// IsUvpaa request: {id: string, type: 'isUvpaa'}
// IsUvpaa response:
// {id: string, type: 'isUvpaaResponse', isAvailable: boolean}
DCHECK(task_runner_->BelongsToCurrentThread());
if (!EnsureIpcConnection()) {
response.SetBoolKey(kIsUvpaaResponseIsAvailableKey, false);
SendMessageToClient(std::move(response));
return;
}
remote_->IsUserVerifyingPlatformAuthenticatorAvailable(
base::BindOnce(&RemoteWebAuthnNativeMessagingHost::OnIsUvpaaResponse,
base::Unretained(this), std::move(response)));
}
void RemoteWebAuthnNativeMessagingHost::ProcessGetRemoteState(
base::Value response) {
// GetRemoteState request: {id: string, type: 'getRemoteState'}
// GetRemoteState response: {id: string, type: 'getRemoteStateResponse'}
DCHECK(task_runner_->BelongsToCurrentThread());
// We query and report the remote state one at a time to prevent race
// conditions caused by multiple requests coming in while there is already a
// pending request (e.g. WebAuthn channel connected and AttachToDesktop on
// Windows).
get_remote_state_responses_.push(std::move(response));
if (get_remote_state_responses_.size() == 1) {
QueryNextRemoteState();
}
// Otherwise it means there is already a pending remote state request.
}
void RemoteWebAuthnNativeMessagingHost::OnQueryVersionResult(uint32_t version) {
DCHECK(task_runner_->BelongsToCurrentThread());
SendNextRemoteState(true);
}
void RemoteWebAuthnNativeMessagingHost::OnIpcDisconnected() {
DCHECK(task_runner_->BelongsToCurrentThread());
// TODO(yuweih): Feed pending callbacks with error responses.
remote_.reset();
if (!get_remote_state_responses_.empty()) {
SendNextRemoteState(false);
}
}
void RemoteWebAuthnNativeMessagingHost::OnIsUvpaaResponse(base::Value response,
bool is_available) {
DCHECK(task_runner_->BelongsToCurrentThread());
response.SetBoolKey(kIsUvpaaResponseIsAvailableKey, is_available);
SendMessageToClient(std::move(response));
}
void RemoteWebAuthnNativeMessagingHost::QueryNextRemoteState() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (!EnsureIpcConnection()) {
SendNextRemoteState(false);
return;
}
// QueryVersion() is simply used to determine if the receiving end actually
// accepts the connection. If it doesn't, then the callback will be silently
// dropped, and OnIpcDisconnected() will be called instead.
remote_.QueryVersion(
base::BindOnce(&RemoteWebAuthnNativeMessagingHost::OnQueryVersionResult,
base::Unretained(this)));
}
void RemoteWebAuthnNativeMessagingHost::SendNextRemoteState(bool is_remoted) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!get_remote_state_responses_.empty());
auto response = std::move(get_remote_state_responses_.front());
get_remote_state_responses_.pop();
DCHECK(response.is_dict());
response.SetBoolKey(kGetRemoteStateResponseIsRemotedKey, is_remoted);
SendMessageToClient(std::move(response));
if (!get_remote_state_responses_.empty()) {
QueryNextRemoteState();
}
}
bool RemoteWebAuthnNativeMessagingHost::EnsureIpcConnection() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (remote_.is_bound()) {
return true;
}
auto* api = host_service_api_client_.GetSessionServices();
if (!api) {
return false;
}
api->BindWebAuthnProxy(remote_.BindNewPipeAndPassReceiver());
remote_.set_disconnect_handler(
base::BindOnce(&RemoteWebAuthnNativeMessagingHost::OnIpcDisconnected,
base::Unretained(this)));
return true;
}
void RemoteWebAuthnNativeMessagingHost::SendMessageToClient(
base::Value message) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!message.is_none());
std::string message_json;
if (!base::JSONWriter::Write(message, &message_json)) {
LOG(ERROR) << "Failed to write message to JSON";
return;
}
client_->PostMessageFromNativeHost(message_json);
}
} // namespace remoting