chromium/chrome/browser/ash/dbus/proxy_resolution_service_provider_browsertest.cc

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

#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "chrome/browser/ash/dbus/proxy_resolution_service_provider.h"
#include "chrome/browser/ash/login/login_manager_test.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/ash/net/system_proxy_manager.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"

namespace ash {

namespace {

constexpr char kLocalProxyUrl[] = "localhost:3128";

// Encode the PAC script as a data: URL.
std::string GetPacUrl(const char* pac_data) {
  std::string b64_encoded = base::Base64Encode(pac_data);
  return "data:application/x-javascript-config;base64," + b64_encoded;
}
}  // namespace

// Helper for calling ProxyResolutionServiceProvider's |ResolveProxyInternal()|
// method. Unlike the unit-tests which mock the network setup, this uses the
// default dependencies from the running browser.
class ProxyResolutionServiceProviderTestWrapper {
 public:
  ProxyResolutionServiceProviderTestWrapper() {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  }

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

  ~ProxyResolutionServiceProviderTestWrapper() {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  }

  // Calls ResolveProxyInternal() and returns its result synchronously as a
  // single string (which may be prefixed by "ERROR: " if it is an error message
  // as opposed to a proxy result).
  std::string ResolveProxyAndWait(const std::string& url) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

    base::RunLoop run_loop;

    std::string result;
    impl_.ResolveProxyInternal(
        url,
        base::BindOnce(
            &ProxyResolutionServiceProviderTestWrapper::OnResolveProxyComplete,
            &result, run_loop.QuitClosure()),
        chromeos::SystemProxyOverride::kDefault);

    run_loop.Run();

    return result;
  }

 private:
  static void OnResolveProxyComplete(std::string* result,
                                     base::RepeatingClosure quit_closure,
                                     const std::string& error,
                                     const std::string& pac_string) {
    if (!error.empty()) {
      *result = "ERROR: " + error;
    } else {
      *result = pac_string;
    }

    std::move(quit_closure).Run();
  }

  ProxyResolutionServiceProvider impl_;
};

// Base test fixture that exposes a way to invoke ProxyResolutionServiceProvider
// synchronously from the UI thread.
class ProxyResolutionServiceProviderBaseBrowserTest
    : public MixinBasedInProcessBrowserTest {
 public:
  void SetUpOnMainThread() override {
    MixinBasedInProcessBrowserTest::SetUpOnMainThread();
    proxy_service_ =
        std::make_unique<ProxyResolutionServiceProviderTestWrapper>();
  }

  void TearDownOnMainThread() override {
    proxy_service_.reset();
    MixinBasedInProcessBrowserTest::TearDownOnMainThread();
  }

  std::string ResolveProxyAndWait(const std::string& source_url) {
    return proxy_service_->ResolveProxyAndWait(source_url);
  }

 private:
  std::unique_ptr<ProxyResolutionServiceProviderTestWrapper> proxy_service_;
};

// Fixture that launches the browser with --proxy-server="https://proxy.test".
class ProxyResolutionServiceProviderManualProxyBrowserTest
    : public ProxyResolutionServiceProviderBaseBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitchASCII(switches::kProxyServer,
                                    "https://proxy.test");
  }
};

// Tests that the D-Bus proxy resolver returns the correct result when using
// --proxy-server flag. These resolutions will happen synchronously at the //net
// layer.
IN_PROC_BROWSER_TEST_F(ProxyResolutionServiceProviderManualProxyBrowserTest,
                       ResolveProxy) {
  EXPECT_EQ("HTTPS proxy.test:443",
            ResolveProxyAndWait("http://www.google.com"));
}

// Simple PAC script that returns the same two proxies for all requests.
const char kPacData[] =
    "function FindProxyForURL(url, host) {\n"
    "  return 'PROXY foo1; PROXY foo2';\n"
    "}\n";

// Fixture that launches the browser with --proxy-pac-url="data:...".
class ProxyResolutionServiceProviderPacBrowserTest
    : public ProxyResolutionServiceProviderBaseBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitchASCII(switches::kProxyPacUrl,
                                    GetPacUrl(kPacData));
  }
};

// Tests that the D-Bus proxy resolver returns the correct result when using
// --proxy-pac-url flag. These resolutions will happen asynchronously at the
// //net layer, as they need to query a PAC script.
IN_PROC_BROWSER_TEST_F(ProxyResolutionServiceProviderPacBrowserTest,
                       ResolveProxy) {
  EXPECT_EQ("PROXY foo1:80;PROXY foo2:80",
            ResolveProxyAndWait("http://www.google.com"));
}

// PAC script that returns a proxy for all url except for a whitelisted domain.
const char kPacDataWithWhitelistedDomain[] =
    "function FindProxyForURL(url, host) {\n"
    "  if (dnsDomainIs(host, '.direct.com'))\n"
    "    return 'DIRECT';\n"
    "  return 'PROXY foo1';\n"
    "}\n";

// Fixture that launches the browser with --proxy-pac-url="data:..." and
// System-proxy enabled. With System-proxy enabled and configured, all system
// service connections going trough an http web proxy will be connected through
// a local proxy that will perform the proxy authentication and connection
// setup.
class ProxyResolutionServiceProviderSystemProxyPolicyTest
    : public ProxyResolutionServiceProviderBaseBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitchASCII(switches::kProxyPacUrl,
                                    GetPacUrl(kPacDataWithWhitelistedDomain));
  }

 protected:
  void SetLocalProxyAddress(const std::string& local_proxy_url) {
    SystemProxyManager::Get()->SetSystemProxyEnabledForTest(true);
    SystemProxyManager::Get()->SetSystemServicesProxyUrlForTest(
        local_proxy_url);
  }
};

// Tests that the proxy resolver returns the address of the local proxy when
// set.
IN_PROC_BROWSER_TEST_F(ProxyResolutionServiceProviderSystemProxyPolicyTest,
                       ResolveProxyLocalProxySet) {
  SetLocalProxyAddress(kLocalProxyUrl);
  EXPECT_EQ("PROXY localhost:3128; PROXY foo1:80",
            ResolveProxyAndWait("http://www.google.com"));
}

// Tests that the proxy list semicolon separator is not appended if the local
// proxy is not set.
IN_PROC_BROWSER_TEST_F(ProxyResolutionServiceProviderSystemProxyPolicyTest,
                       ResolveProxyNoSeparator) {
  SetLocalProxyAddress(/* local_proxy_url= */ std::string());
  EXPECT_EQ("PROXY foo1:80", ResolveProxyAndWait("http://www.google.com"));
}

// Tests that the proxy resolver doesn't return the local proxy address for
// DIRECT connections.
IN_PROC_BROWSER_TEST_F(ProxyResolutionServiceProviderSystemProxyPolicyTest,
                       ResolveProxyDirect) {
  SetLocalProxyAddress(kLocalProxyUrl);
  EXPECT_EQ("DIRECT", ResolveProxyAndWait("http://www.test.direct.com"));
}

class ProxyResolutionServiceAtLoginScreen
    : public ProxyResolutionServiceProviderBaseBrowserTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitchASCII(switches::kProxyPacUrl,
                                    GetPacUrl(kPacData));
  }

 protected:
  LoginManagerMixin login_manager_{&mixin_host_};
};

// Tests that the D-Bus proxy resolver returns the proxy configured at the
// sign-in screen.
IN_PROC_BROWSER_TEST_F(ProxyResolutionServiceAtLoginScreen, ResolveProxy) {
  EXPECT_EQ("PROXY foo1:80;PROXY foo2:80",
            ResolveProxyAndWait("http://www.google.com"));
}

}  // namespace ash