chromium/chrome/browser/ash/dbus/vm/vm_launch_service_provider.cc

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

#include "chrome/browser/ash/dbus/vm/vm_launch_service_provider.h"

#include <dbus/dbus-protocol.h>

#include <memory>
#include <sstream>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "chrome/browser/ash/borealis/borealis_app_launcher.h"
#include "chrome/browser/ash/borealis/borealis_features.h"
#include "chrome/browser/ash/borealis/borealis_metrics.h"
#include "chrome/browser/ash/borealis/borealis_service.h"
#include "chrome/browser/ash/borealis/borealis_util.h"
#include "chrome/browser/ash/guest_os/guest_os_launcher.h"
#include "chrome/browser/ash/guest_os/public/guest_os_wayland_server.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chromeos/ash/components/dbus/vm_launch/launch.pb.h"
#include "dbus/message.h"
#include "third_party/cros_system_api/dbus/vm_launch/dbus-constants.h"

namespace ash {

namespace {

void OnExported(const std::string& interface_name,
                const std::string& method_name,
                bool success) {
  LOG_IF(ERROR, !success) << "Failed to export " << interface_name << "."
                          << method_name;
}

std::unique_ptr<dbus::Response> AllowStatusToResponse(
    borealis::BorealisFeatures::AllowStatus status,
    dbus::MethodCall* method_call) {
  if (status != borealis::BorealisFeatures::AllowStatus::kAllowed) {
    return dbus::ErrorResponse::FromMethodCall(method_call, DBUS_ERROR_FAILED,
                                               "");
  }

  std::unique_ptr<dbus::Response> response =
      dbus::Response::FromMethodCall(method_call);
  dbus::MessageWriter writer(response.get());
  writer.AppendString("");
  return response;
}

void OnAllowChecked(Profile* profile,
                    dbus::MethodCall* method_call,
                    dbus::ExportedObject::ResponseSender response_sender,
                    bool launch,
                    borealis::BorealisFeatures::AllowStatus new_allowed) {
  if (launch) {
    // When requested, setting the correct token should have the effect of
    // running the client app, which will bring up the installer or launch the
    // client as needed.
    borealis::BorealisService::GetForProfile(profile)->AppLauncher().Launch(
        borealis::kClientAppId, borealis::BorealisLaunchSource::kInsertCoin,
        base::DoNothing());
  }
  std::move(response_sender)
      .Run(AllowStatusToResponse(new_allowed, method_call));
}

template <typename T>
void HandleReturn(dbus::MethodCall* method_call,
                  dbus::ExportedObject::ResponseSender response_sender,
                  base::expected<T, std::string> response) {
  if (!response.has_value()) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(method_call, DBUS_ERROR_FAILED,
                                                 response.error()));
    return;
  }
  std::unique_ptr<dbus::Response> dbus_response =
      dbus::Response::FromMethodCall(method_call);
  dbus::MessageWriter writer(dbus_response.get());
  writer.AppendProtoAsArrayOfBytes(response.value());
  std::move(response_sender).Run(std::move(dbus_response));
}

}  // namespace

VmLaunchServiceProvider::VmLaunchServiceProvider() = default;

VmLaunchServiceProvider::~VmLaunchServiceProvider() = default;

void VmLaunchServiceProvider::Start(
    scoped_refptr<dbus::ExportedObject> exported_object) {
  exported_object->ExportMethod(
      vm_tools::launch::kVmLaunchServiceInterface,
      vm_tools::launch::kVmLaunchServiceProvideVmTokenMethod,
      base::BindRepeating(&VmLaunchServiceProvider::ProvideVmToken,
                          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&OnExported));

  exported_object->ExportMethod(
      vm_tools::launch::kVmLaunchServiceInterface,
      vm_tools::launch::kVmLaunchServiceEnsureVmLaunchedMethod,
      base::BindRepeating(&VmLaunchServiceProvider::EnsureVmLaunched,
                          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&OnExported));
}

void VmLaunchServiceProvider::ProvideVmToken(
    dbus::MethodCall* method_call,
    dbus::ExportedObject::ResponseSender response_sender) {
  std::string token;
  dbus::MessageReader reader(method_call);
  if (!reader.PopString(&token)) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS, "Token not provided"));
    return;
  }

  bool launch;
  if (!reader.PopBool(&launch)) {
    launch = true;
  }

  Profile* profile = ProfileManager::GetPrimaryUserProfile();
  if (!profile) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_FAILED,
            "Unable to determine primary profile"));
    return;
  }

  // TODO(b/317157600): Tokens are no longer required so we have the option to
  // remove this dbus method entirely.
  borealis::BorealisService::GetForProfile(profile)->Features().IsAllowed(
      base::BindOnce(&OnAllowChecked, profile, method_call,
                     std::move(response_sender), launch));
}

void VmLaunchServiceProvider::EnsureVmLaunched(
    dbus::MethodCall* method_call,
    dbus::ExportedObject::ResponseSender response_sender) {
  vm_tools::launch::EnsureVmLaunchedRequest request;
  if (!dbus::MessageReader(method_call).PopArrayOfBytesAsProto(&request)) {
    std::move(response_sender)
        .Run(dbus::ErrorResponse::FromMethodCall(
            method_call, DBUS_ERROR_INVALID_ARGS,
            "Unable to parse EnsureVmLaunchedRequest from message"));
    return;
  }

  guest_os::launcher::EnsureLaunched(
      request,
      base::BindOnce(&HandleReturn<vm_tools::launch::EnsureVmLaunchedResponse>,
                     method_call, std::move(response_sender)));
}

}  // namespace ash