chromium/chrome/browser/chromeos/extensions/vpn_provider/vpn_service.cc

// Copyright 2014 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/chromeos/extensions/vpn_provider/vpn_service.h"

#include <optional>
#include <utility>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "chrome/common/extensions/api/vpn_provider.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/pepper_vpn_provider_resource_host_proxy.h"
#include "content/public/browser/vpn_service_proxy.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_event_histogram_value.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/unloaded_extension_reason.h"
#include "extensions/common/permissions/permissions_data.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crosapi/vpn_service_ash.h"
#endif

#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/lacros/lacros_service.h"
#endif

namespace chromeos {

namespace {

namespace api_vpn = extensions::api::vpn_provider;

// All events that our EventRouter::Observer should be listening to.
// api_vpn::OnConfigCreated is intentionally omitted -- it was never
// implemented.
const char* const kEventNames[] = {
    api_vpn::OnUIEvent::kEventName,
    api_vpn::OnConfigRemoved::kEventName,
    api_vpn::OnPlatformMessage::kEventName,
    api_vpn::OnPacketReceived::kEventName,
};

void RunSuccessCallback(chromeos::VpnService::SuccessCallback success) {
  std::move(success).Run();
}

void RunFailureCallback(chromeos::VpnService::FailureCallback failure,
                        const std::optional<std::string>& error_name,
                        const std::optional<std::string>& error_message) {
  std::move(failure).Run(error_name.value_or(std::string{}),
                         error_message.value_or(std::string{}));
}

using SuccessOrFailureCallback =
    base::OnceCallback<void(crosapi::mojom::VpnErrorResponsePtr)>;

// crosapi::mojom::VpnService expects a single callback, whereas the API is
// designed to pass in two (one for success, one for failure). This function
// glues the two callbacks in one; for the reverse transformation see
// chrome/browser/ash/crosapi/vpn_service_ash.cc
SuccessOrFailureCallback AdaptCallback(
    chromeos::VpnService::SuccessCallback success,
    chromeos::VpnService::FailureCallback failure) {
  return base::BindOnce(
      [](chromeos::VpnService::SuccessCallback success,
         chromeos::VpnService::FailureCallback failure,
         crosapi::mojom::VpnErrorResponsePtr error) {
        if (error) {
          RunFailureCallback(std::move(failure), error->name, error->message);
        } else {
          RunSuccessCallback(std::move(success));
        }
      },
      std::move(success), std::move(failure));
}

bool IsVpnProvider(const extensions::Extension* extension) {
  return extension->permissions_data()->HasAPIPermission(
      extensions::mojom::APIPermissionID::kVpnProvider);
}

}  // namespace

class VpnService::VpnServiceProxyImpl : public content::VpnServiceProxy {
 public:
  explicit VpnServiceProxyImpl(base::WeakPtr<VpnService> vpn_service);

  VpnServiceProxyImpl(const VpnServiceProxyImpl&) = delete;
  VpnServiceProxyImpl& operator=(const VpnServiceProxyImpl&) = delete;

  void Bind(const std::string& extension_id,
            const std::string& configuration_id,
            const std::string& configuration_name,
            SuccessCallback success,
            FailureCallback failure,
            std::unique_ptr<content::PepperVpnProviderResourceHostProxy>
                pepper_vpn_provider_proxy) override;

  void SendPacket(const std::string& extension_id,
                  const std::vector<char>& data,
                  SuccessCallback success,
                  FailureCallback failure) override;

 private:
  base::WeakPtr<VpnService> vpn_service_;
};

VpnService::VpnServiceProxyImpl::VpnServiceProxyImpl(
    base::WeakPtr<VpnService> vpn_service)
    : vpn_service_(vpn_service) {}

void VpnService::VpnServiceProxyImpl::Bind(
    const std::string& extension_id,
    const std::string& /*configuration_id*/,
    const std::string& configuration_name,
    SuccessCallback success,
    FailureCallback failure,
    std::unique_ptr<content::PepperVpnProviderResourceHostProxy>
        pepper_vpn_provider_proxy) {
  if (!vpn_service_) {
    return;
  }

  vpn_service_->BindPepperVpnProxy(extension_id, configuration_name,
                                   std::move(success), std::move(failure),
                                   std::move(pepper_vpn_provider_proxy));
}

void VpnService::VpnServiceProxyImpl::SendPacket(
    const std::string& extension_id,
    const std::vector<char>& data,
    SuccessCallback success,
    FailureCallback failure) {
  if (!vpn_service_) {
    return;
  }

  vpn_service_->SendPacket(extension_id, data, std::move(success),
                           std::move(failure));
}

VpnServiceForExtension::VpnServiceForExtension(
    const std::string& extension_id,
    content::BrowserContext* browser_context)
    : extension_id_(extension_id), browser_context_(browser_context) {
  auto* service_remote = VpnService::GetVpnService();
  CHECK(service_remote);
  service_remote->RegisterVpnServiceForExtension(
      extension_id, vpn_service_.BindNewPipeAndPassReceiver(),
      receiver_.BindNewPipeAndPassRemote());
}

VpnServiceForExtension::~VpnServiceForExtension() = default;

void VpnServiceForExtension::OnAddDialog() {
  DispatchEvent(std::make_unique<extensions::Event>(
      extensions::events::HistogramValue::VPN_PROVIDER_ON_UI_EVENT,
      api_vpn::OnUIEvent::kEventName,
      api_vpn::OnUIEvent::Create(api_vpn::UIEvent::kShowAddDialog,
                                 std::string()),
      browser_context_));
}

void VpnServiceForExtension::OnConfigureDialog(
    const std::string& configuration_name) {
  DispatchEvent(std::make_unique<extensions::Event>(
      extensions::events::HistogramValue::VPN_PROVIDER_ON_UI_EVENT,
      api_vpn::OnUIEvent::kEventName,
      api_vpn::OnUIEvent::Create(api_vpn::UIEvent::kShowConfigureDialog,
                                 configuration_name),
      browser_context_));
}

void VpnServiceForExtension::OnConfigRemoved(
    const std::string& configuration_name) {
  DispatchEvent(std::make_unique<extensions::Event>(
      extensions::events::HistogramValue::VPN_PROVIDER_ON_CONFIG_REMOVED,
      api_vpn::OnConfigRemoved::kEventName,
      api_vpn::OnConfigRemoved::Create(configuration_name), browser_context_));
}

void VpnServiceForExtension::OnPlatformMessage(
    const std::string& configuration_name,
    int32_t platform_message,
    const std::optional<std::string>& error) {
  DispatchEvent(std::make_unique<extensions::Event>(
      extensions::events::VPN_PROVIDER_ON_PLATFORM_MESSAGE,
      api_vpn::OnPlatformMessage::kEventName,
      api_vpn::OnPlatformMessage::Create(
          configuration_name,
          static_cast<api_vpn::PlatformMessage>(platform_message),
          error.value_or(std::string{})),
      browser_context_));
}

void VpnServiceForExtension::OnPacketReceived(
    const std::vector<uint8_t>& data) {
  DispatchEvent(std::make_unique<extensions::Event>(
      extensions::events::VPN_PROVIDER_ON_PACKET_RECEIVED,
      api_vpn::OnPacketReceived::kEventName,
      api_vpn::OnPacketReceived::Create(
          std::vector<uint8_t>(data.begin(), data.end())),
      browser_context_));
}

void VpnServiceForExtension::DispatchEvent(
    std::unique_ptr<extensions::Event> event) const {
  extensions::EventRouter::Get(browser_context_)
      ->DispatchEventToExtension(extension_id_, std::move(event));
}

VpnService::VpnService(content::BrowserContext* browser_context)
    : browser_context_(browser_context) {
  auto* registry = extensions::ExtensionRegistry::Get(browser_context);
  extension_registry_observer_.Observe(registry);

  auto* event_router = extensions::EventRouter::Get(browser_context);
  for (const char* event_name : kEventNames) {
    event_router->RegisterObserver(this, event_name);
  }
}

VpnService::~VpnService() = default;

void VpnService::SendShowAddDialogToExtension(const std::string& extension_id) {
  GetVpnServiceForExtension(extension_id)->DispatchAddDialogEvent();
}

void VpnService::SendShowConfigureDialogToExtension(
    const std::string& extension_id,
    const std::string& configuration_name) {
  GetVpnServiceForExtension(extension_id)
      ->DispatchConfigureDialogEvent(configuration_name);
}

void VpnService::CreateConfiguration(const std::string& extension_id,
                                     const std::string& configuration_name,
                                     SuccessCallback success,
                                     FailureCallback failure) {
  GetVpnServiceForExtension(extension_id)
      ->CreateConfiguration(
          configuration_name,
          AdaptCallback(std::move(success), std::move(failure)));
}

void VpnService::DestroyConfiguration(const std::string& extension_id,
                                      const std::string& configuration_name,
                                      SuccessCallback success,
                                      FailureCallback failure) {
  GetVpnServiceForExtension(extension_id)
      ->DestroyConfiguration(
          configuration_name,
          AdaptCallback(std::move(success), std::move(failure)));
}

void VpnService::SetParameters(const std::string& extension_id,
                               base::Value::Dict parameters,
                               SuccessCallback success,
                               FailureCallback failure) {
  GetVpnServiceForExtension(extension_id)
      ->SetParameters(std::move(parameters),
                      AdaptCallback(std::move(success), std::move(failure)));
}

void VpnService::SendPacket(const std::string& extension_id,
                            const std::vector<char>& data,
                            SuccessCallback success,
                            FailureCallback failure) {
  GetVpnServiceForExtension(extension_id)
      ->SendPacket(std::vector<uint8_t>(data.begin(), data.end()),
                   AdaptCallback(std::move(success), std::move(failure)));
}

void VpnService::NotifyConnectionStateChanged(const std::string& extension_id,
                                              bool connection_success,
                                              SuccessCallback success,
                                              FailureCallback failure) {
  GetVpnServiceForExtension(extension_id)
      ->NotifyConnectionStateChanged(
          connection_success,
          AdaptCallback(std::move(success), std::move(failure)));
}

std::unique_ptr<content::VpnServiceProxy> VpnService::GetVpnServiceProxy() {
  return std::make_unique<VpnServiceProxyImpl>(weak_factory_.GetWeakPtr());
}

void VpnService::Shutdown() {
  extensions::EventRouter::Get(browser_context_)->UnregisterObserver(this);
}

void VpnService::OnExtensionUninstalled(content::BrowserContext*,
                                        const extensions::Extension* extension,
                                        extensions::UninstallReason) {
  // Extension should have a vpnProvider permission in order to use the API;
  // therefore we can safely ignore all other extensions (because otherwise
  // we'll just make an unnecessary mojo call).
  if (!IsVpnProvider(extension)) {
    return;
  }
  GetVpnService()->MaybeFailActiveConnectionAndDestroyConfigurations(
      extension->id(), /*destroy_configurations=*/true);
  extension_id_to_service_.erase(extension->id());
}

void VpnService::OnExtensionUnloaded(
    content::BrowserContext*,
    const extensions::Extension* extension,
    extensions::UnloadedExtensionReason reason) {
  // Extension should have a vpnProvider permission in order to use the API;
  // therefore we can safely ignore all other extensions (because otherwise
  // we'll just make an unnecessary mojo call).
  if (!IsVpnProvider(extension)) {
    return;
  }
  bool destroy_configurations =
      reason == extensions::UnloadedExtensionReason::DISABLE ||
      reason == extensions::UnloadedExtensionReason::BLOCKLIST;
  GetVpnService()->MaybeFailActiveConnectionAndDestroyConfigurations(
      extension->id(), destroy_configurations);
  if (destroy_configurations) {
    extension_id_to_service_.erase(extension->id());
  }
}

void VpnService::OnListenerAdded(const extensions::EventListenerInfo& details) {
  // Ensures that the service is created for the extension, so that incoming VPN
  // events can be dispatched to the extension.
  GetVpnServiceForExtension(details.extension_id);
}

// static
crosapi::mojom::VpnService* VpnService::GetVpnService() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  // CrosapiManager may not be initialized.
  // TODO(crbug.com/40225953): Assert it's only happening in tests.
  if (!crosapi::CrosapiManager::IsInitialized()) {
    LOG(ERROR) << "CrosapiManager is not initialized.";
    return nullptr;
  }
  return crosapi::CrosapiManager::Get()->crosapi_ash()->vpn_service_ash();
#else
  auto* service = chromeos::LacrosService::Get();
  if (!service->IsAvailable<crosapi::mojom::VpnService>()) {
    LOG(ERROR) << "chrome.vpnProvider is not available in Lacros";
    return nullptr;
  }
  return service->GetRemote<crosapi::mojom::VpnService>().get();
#endif
}

mojo::Remote<crosapi::mojom::VpnServiceForExtension>&
VpnService::GetVpnServiceForExtension(const std::string& extension_id) {
  auto& service = extension_id_to_service_[extension_id];
  if (!service) {
    service = std::make_unique<VpnServiceForExtension>(extension_id,
                                                       browser_context_);
  }
  return service->Proxy();
}

void VpnService::BindPepperVpnProxy(
    const std::string& extension_id,
    const std::string& configuration_name,
    SuccessCallback success,
    FailureCallback failure,
    std::unique_ptr<content::PepperVpnProviderResourceHostProxy>
        pepper_vpn_provider_proxy) {
  // Here we create a PepperVpnProxyAdapter that will forward everything to
  // the underlying PepperVpnProviderResourceHostProxy and bind it via
  // crosapi. Note that the crosapi call might be unsuccessful if the active
  // vpn configuration is not owned by the given extension; therefore we don't
  // create the SelfOwnedReceiver right away, but instead do it in the
  // callback on success or reset the entangled pipe on failure.
  auto pepper_adapter = std::make_unique<PepperVpnProxyAdapter>(
      std::move(pepper_vpn_provider_proxy));
  mojo::PendingRemote<crosapi::mojom::PepperVpnProxyObserver> pepper_client;

  auto callback = base::BindOnce(
      &VpnService::OnBindPepperVpnProxy, weak_factory_.GetWeakPtr(),
      std::move(success), std::move(failure), std::move(pepper_adapter),
      pepper_client.InitWithNewPipeAndPassReceiver());

  GetVpnServiceForExtension(extension_id)
      ->BindPepperVpnProxyObserver(configuration_name, std::move(pepper_client),
                                   std::move(callback));
}

void VpnService::OnBindPepperVpnProxy(
    SuccessCallback success,
    FailureCallback failure,
    std::unique_ptr<PepperVpnProxyAdapter> pepper_adapter,
    mojo::PendingReceiver<crosapi::mojom::PepperVpnProxyObserver>
        pepper_receiver,
    crosapi::mojom::VpnErrorResponsePtr error) {
  if (error) {
    pepper_receiver.reset();
    RunFailureCallback(std::move(failure), error->name, error->message);
  } else {
    // Gets reset when the active configuration in ash gets destroyed.
    mojo::MakeSelfOwnedReceiver(std::move(pepper_adapter),
                                std::move(pepper_receiver));
    RunSuccessCallback(std::move(success));
  }
}

VpnService::PepperVpnProxyAdapter::PepperVpnProxyAdapter(
    std::unique_ptr<content::PepperVpnProviderResourceHostProxy>
        pepper_vpn_proxy)
    : pepper_vpn_proxy_(std::move(pepper_vpn_proxy)) {}

VpnService::PepperVpnProxyAdapter::~PepperVpnProxyAdapter() = default;

void VpnService::PepperVpnProxyAdapter::OnUnbind() {
  pepper_vpn_proxy_->SendOnUnbind();
  pepper_vpn_proxy_.reset();
}

void VpnService::PepperVpnProxyAdapter::OnPacketReceived(
    const std::vector<uint8_t>& data) {
  DCHECK(pepper_vpn_proxy_);
  pepper_vpn_proxy_->SendOnPacketReceived(
      std::vector<char>(data.begin(), data.end()));
}

}  // namespace chromeos