chromium/chrome/browser/lacros/net/lacros_extension_proxy_tracker_browsertest.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/lacros/net/lacros_extension_proxy_tracker.h"

#include "base/files/file_util.h"
#include "base/test/test_future.h"
#include "chrome/browser/extensions/component_loader.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chromeos/crosapi/mojom/network_settings_service.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "content/public/test/browser_test.h"
#include "mojo/public/cpp/bindings/receiver_set.h"

namespace {
// Fakes the NetworkSettingsService in Ash-Chrome so we can send proxy updates
// from the tests via the mojo API.
class FakeNetworkSettingsService
    : public crosapi::mojom::NetworkSettingsService {
 public:
  FakeNetworkSettingsService() {
    // Replace the production network settings service with a fake for testing.
    mojo::Remote<crosapi::mojom::NetworkSettingsService>& remote =
        chromeos::LacrosService::Get()
            ->GetRemote<crosapi::mojom::NetworkSettingsService>();
    remote.reset();
    receiver_.Bind(remote.BindNewPipeAndPassReceiver());
  }
  FakeNetworkSettingsService(const FakeNetworkSettingsService&) = delete;
  FakeNetworkSettingsService& operator=(const FakeNetworkSettingsService&) =
      delete;
  ~FakeNetworkSettingsService() override = default;

  // crosapi::mojom::AshNetworkService:
  void AddNetworkSettingsObserver(
      mojo::PendingRemote<crosapi::mojom::NetworkSettingsObserver> observer)
      override {}

  void SetExtensionProxy(crosapi::mojom::ProxyConfigPtr proxy_config) override {
    // When enabling an extension, the same "ExtensionReady" ExtensionRegistry
    // event is triggered twice resulting in the same extension metadata being
    // sent twice. We only care for the latest update.
    set_extension_proxy_future_.Clear();
    set_extension_proxy_future_.SetValue(std::move(proxy_config));
  }
  void ClearExtensionProxy() override {
    clear_extension_proxy_future_.Clear();
    clear_extension_proxy_future_.SetValue(true);
  }
  void SetExtensionControllingProxyMetadata(
      crosapi::mojom::ExtensionControllingProxyPtr extension) override {
    // When enabling an extension, the same "ExtensionReady" ExtensionRegistry
    // event is triggered twice resulting in the same extension metadata being
    // sent twice. We only care for the latest update.
    set_extension_metadata_future_.Clear();
    set_extension_metadata_future_.SetValue(std::move(extension));
  }
  void ClearExtensionControllingProxyMetadata() override {
    clear_extension_metadata_future_.Clear();
    clear_extension_metadata_future_.SetValue(true);
  }

  void IsAlwaysOnVpnPreConnectUrlAllowlistEnforced(
      IsAlwaysOnVpnPreConnectUrlAllowlistEnforcedCallback callback) override {}

  crosapi::mojom::ProxyConfigPtr WaitSetExtensionProxy() {
    return set_extension_proxy_future_.Take();
  }
  bool WaitClearExtensionProxy() {
    return clear_extension_proxy_future_.Take();
  }
  crosapi::mojom::ExtensionControllingProxyPtr
  WaitSetExtensionControllingProxyMetadata() {
    return set_extension_metadata_future_.Take();
  }
  bool WaitClearExtensionControllingProxyMetadata() {
    return clear_extension_metadata_future_.Take();
  }

 private:
  base::test::TestFuture<crosapi::mojom::ProxyConfigPtr>
      set_extension_proxy_future_;
  base::test::TestFuture<bool> clear_extension_proxy_future_;
  base::test::TestFuture<crosapi::mojom::ExtensionControllingProxyPtr>
      set_extension_metadata_future_;
  base::test::TestFuture<bool> clear_extension_metadata_future_;
  mojo::Receiver<crosapi::mojom::NetworkSettingsService> receiver_{this};
};

}  // namespace

namespace lacros {
namespace net {

class LacrosExtensionProxyTrackerTest
    : public extensions::ExtensionBrowserTest {
 public:
  LacrosExtensionProxyTrackerTest() = default;
  ~LacrosExtensionProxyTrackerTest() override = default;

  void SetUpOnMainThread() override {
    extensions::ExtensionBrowserTest::SetUpOnMainThread();
    service_ = std::make_unique<FakeNetworkSettingsService>();
  }

 protected:
  // Returns false if the network settings service interface is not available or
  // doesn't support extension set proxies on this version of Ash-Chrome.
  bool IsExtensionMetadataSupported() {
    auto* lacros_service = chromeos::LacrosService::Get();
    if (!lacros_service) {
      return false;
    }
    return lacros_service
               ->IsAvailable<crosapi::mojom::NetworkSettingsService>() &&
           LacrosExtensionProxyTracker::AshVersionSupportsExtensionMetadata();
  }

  void VerifyExtensionMetadataSent(const std::string& extension_id,
                                   const std::string& extension_name) {
    if (IsExtensionMetadataSupported()) {
      auto extension = service_->WaitSetExtensionControllingProxyMetadata();
      ASSERT_TRUE(extension);
      EXPECT_EQ(extension->id, extension_id);
      EXPECT_EQ(extension->name, extension_name);
      return;
    }
    auto proxy_config = service_->WaitSetExtensionProxy();
    ASSERT_TRUE(proxy_config);
    ASSERT_TRUE(proxy_config->extension);
    EXPECT_EQ(proxy_config->extension->id, extension_id);
    EXPECT_EQ(proxy_config->extension->name, extension_name);
    // Verify that the proxy config set by the extension hosted at
    // //chrome/test/data/extensions/api_test/proxy/pac is sent to Ash.
    ASSERT_TRUE(proxy_config->proxy_settings->is_pac());
    EXPECT_EQ(proxy_config->proxy_settings->get_pac()->pac_url,
              GURL("http://wpad/windows.pac"));
  }

  void VerifyExtensionClearRequestSent() {
    if (IsExtensionMetadataSupported()) {
      EXPECT_TRUE(service_->WaitClearExtensionControllingProxyMetadata());
      return;
    }
    EXPECT_TRUE(service_->WaitClearExtensionProxy());
  }
  std::unique_ptr<FakeNetworkSettingsService> service_;
};

// Test that verifies that proxies set via an extension in Lacros are propagated
// to Ash. This test also verifies that uninstalling the extension results in a
// mojo request to Ash to clear the extension set proxy.
IN_PROC_BROWSER_TEST_F(LacrosExtensionProxyTrackerTest, ExtensionSetProxy) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  base::FilePath extension_path = base::MakeAbsoluteFilePath(
      test_data_dir_.AppendASCII("api_test/proxy/pac"));
  if (extension_path.empty()) {
    return;
  }

  std::string extension_id;
  // The test extension code is hosted at
  // //chrome/test/data/extensions/api_test/proxy/pac/.
  extension_id = LoadExtension(extension_path)->id();

  VerifyExtensionMetadataSent(extension_id, "chrome.proxy");

  UninstallExtension(extension_id);

  VerifyExtensionClearRequestSent();
}

// Test that the extension metadata is sent when the extension is loaded, by
// reacting to ExtensionRegistry events. Specifically, this test checks that the
// mextension data is sent after disabling and re-enabling the extension. In
// this case, the proxy pref is available before the extensio is loaded.
IN_PROC_BROWSER_TEST_F(LacrosExtensionProxyTrackerTest,
                       SendUpdatesOnExtensionLoaded) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  base::FilePath extension_path = base::MakeAbsoluteFilePath(
      test_data_dir_.AppendASCII("api_test/proxy/pac"));
  if (extension_path.empty()) {
    return;
  }
  std::string extension_id;
  // The test extension code is hosted at
  // //chrome/test/data/extensions/api_test/proxy/pac/.
  extension_id = LoadExtension(extension_path)->id();
  VerifyExtensionMetadataSent(extension_id, "chrome.proxy");

  DisableExtension(extension_id);
  VerifyExtensionClearRequestSent();

  EnableExtension(extension_id);
  VerifyExtensionMetadataSent(extension_id, "chrome.proxy");
}

}  // namespace net
}  // namespace lacros