blob: cb4cf183707d0a676a2f735aedfa05f0a5fc05aa [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/chromeos/dbus/proxy_resolution_service_provider.h"
#include <memory>
#include "ash/constants/ash_features.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chrome/browser/chromeos/net/system_proxy_manager.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/dbus/services/service_provider_test_helper.h"
#include "chromeos/dbus/shill/shill_clients.h"
#include "chromeos/dbus/system_proxy/system_proxy_client.h"
#include "chromeos/network/network_handler.h"
#include "chromeos/tpm/stub_install_attributes.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/net_errors.h"
#include "net/proxy_resolution/proxy_info.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/test/test_network_context.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace chromeos {
namespace {
// The parsed result from ProxyResolutionServiceProvider's D-Bus result (error
// is a string).
struct ResolveProxyResult {
std::string error;
std::string proxy_info;
};
// A mock result to configure what the NetworkContext should return (error is an
// integer).
struct LookupProxyForURLMockResult {
net::Error error = net::ERR_UNEXPECTED;
std::string proxy_info_pac_string;
};
// Mock NetworkContext that allows controlling the result of
// LookUpProxyForURL().
class MockNetworkContext : public network::TestNetworkContext {
public:
MockNetworkContext() {}
~MockNetworkContext() override {}
// network::mojom::NetworkContext implementation:
void LookUpProxyForURL(
const GURL& url,
const net::NetworkIsolationKey& network_isolation_key,
mojo::PendingRemote<::network::mojom::ProxyLookupClient>
proxy_lookup_client) override {
last_url_ = url;
last_network_isolation_key_ = network_isolation_key;
mojo::Remote<::network::mojom::ProxyLookupClient>
proxy_lookup_client_remote(std::move(proxy_lookup_client));
if (lookup_proxy_result_.error == net::OK) {
net::ProxyInfo proxy_info;
proxy_info.UsePacString(lookup_proxy_result_.proxy_info_pac_string);
proxy_lookup_client_remote->OnProxyLookupComplete(net::OK, proxy_info);
} else {
proxy_lookup_client_remote->OnProxyLookupComplete(
lookup_proxy_result_.error, base::nullopt);
}
}
void SetNextProxyResult(LookupProxyForURLMockResult mock_result) {
lookup_proxy_result_ = mock_result;
}
const GURL& last_url() const { return last_url_; }
const net::NetworkIsolationKey& last_network_isolation_key() const {
return last_network_isolation_key_;
}
private:
GURL last_url_;
net::NetworkIsolationKey last_network_isolation_key_;
LookupProxyForURLMockResult lookup_proxy_result_;
chromeos::ScopedStubInstallAttributes test_install_attributes_{
chromeos::StubInstallAttributes::CreateCloudManaged("fake-domain",
"fake-id")};
DISALLOW_COPY_AND_ASSIGN(MockNetworkContext);
};
} // namespace
class ProxyResolutionServiceProviderTest : public testing::Test {
public:
ProxyResolutionServiceProviderTest() {
service_provider_ = std::make_unique<ProxyResolutionServiceProvider>();
service_provider_->set_network_context_for_test(&mock_network_context_);
test_helper_.SetUp(
kNetworkProxyServiceName, dbus::ObjectPath(kNetworkProxyServicePath),
kNetworkProxyServiceInterface, kNetworkProxyServiceResolveProxyMethod,
service_provider_.get());
}
~ProxyResolutionServiceProviderTest() override {
test_helper_.TearDown();
}
protected:
// Makes a D-Bus call to |service_provider_|'s ResolveProxy method and sets
// the parsed response in |result|.
void CallMethod(const std::string& source_url, ResolveProxyResult* result) {
dbus::MethodCall method_call(kNetworkProxyServiceInterface,
kNetworkProxyServiceResolveProxyMethod);
dbus::MessageWriter writer(&method_call);
writer.AppendString(source_url);
std::unique_ptr<dbus::Response> response =
test_helper_.CallMethod(&method_call);
// Parse the |dbus::Response|.
ASSERT_TRUE(response);
dbus::MessageReader reader(response.get());
std::string proxy_info, error;
EXPECT_TRUE(reader.PopString(&result->proxy_info));
EXPECT_TRUE(reader.PopString(&result->error));
}
base::test::SingleThreadTaskEnvironment task_environment_;
MockNetworkContext mock_network_context_;
std::unique_ptr<ProxyResolutionServiceProvider> service_provider_;
ServiceProviderTestHelper test_helper_;
DISALLOW_COPY_AND_ASSIGN(ProxyResolutionServiceProviderTest);
};
// Tests the normal success case. The proxy resolver returns a single proxy.
TEST_F(ProxyResolutionServiceProviderTest, Success) {
mock_network_context_.SetNextProxyResult({net::OK, "PROXY localhost:8080"});
ResolveProxyResult result;
CallMethod("http://www.gmail.com/", &result);
// The response should contain the proxy info and an empty error.
EXPECT_EQ("PROXY localhost:8080", result.proxy_info);
EXPECT_EQ("", result.error);
}
// Tests the case where the proxy resolver fails with
// ERR_MANDATORY_PROXY_CONFIGURATION_FAILED.
TEST_F(ProxyResolutionServiceProviderTest, ResolverFailed) {
mock_network_context_.SetNextProxyResult(
{net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, "PROXY localhost:8080"});
ResolveProxyResult result;
CallMethod("http://www.gmail.com/", &result);
// The response should contain empty proxy info and a "mandatory proxy config
// failed" error (which the error from the resolver will be mapped to).
EXPECT_EQ("DIRECT", result.proxy_info);
EXPECT_EQ(net::ErrorToString(net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED),
result.error);
}
// Tests calling the proxy resolution provider with an invalid URL.
TEST_F(ProxyResolutionServiceProviderTest, BadURL) {
ResolveProxyResult result;
CallMethod(":bad-url", &result);
// The response should contain empty proxy info and a "mandatory proxy config
// failed" error (which the error from the resolver will be mapped to).
EXPECT_EQ("DIRECT", result.proxy_info);
EXPECT_EQ("Invalid URL", result.error);
}
// Tests the failure case where a NetworkContext cannot be retrieved. This could
// happen at certain points during startup/shutdown while the primary profile is
// null.
TEST_F(ProxyResolutionServiceProviderTest, NullNetworkContext) {
service_provider_->set_network_context_for_test(nullptr);
ResolveProxyResult result;
CallMethod("http://www.gmail.com/", &result);
// The response should contain a failure.
EXPECT_EQ("DIRECT", result.proxy_info);
EXPECT_EQ("No NetworkContext", result.error);
}
// Make sure requests use an opaque transient NetworkIsolationKey.
TEST_F(ProxyResolutionServiceProviderTest,
UniqueTransientNetworkIsolationKeys) {
const GURL kUrl("https://foo.test/food");
const char kProxyResult[] = "PROXY proxy.test:8080";
mock_network_context_.SetNextProxyResult({net::OK, kProxyResult});
ResolveProxyResult result;
CallMethod(kUrl.spec(), &result);
EXPECT_EQ(kProxyResult, result.proxy_info);
EXPECT_EQ("", result.error);
EXPECT_EQ(kUrl, mock_network_context_.last_url());
EXPECT_TRUE(mock_network_context_.last_network_isolation_key().IsTransient());
}
// Tests the behaviour of system-proxy when enabled via the feature flag
// `ash::features::kSystemProxyForSystemServices` and via the device policy
// SystemProxySettings.
class ProxyResolutionServiceWithSystemProxyTest
: public ProxyResolutionServiceProviderTest {
public:
ProxyResolutionServiceWithSystemProxyTest()
: ProxyResolutionServiceProviderTest(),
local_state_(TestingBrowserProcess::GetGlobal()) {}
~ProxyResolutionServiceWithSystemProxyTest() override = default;
// testing::Test
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
ash::features::kSystemProxyForSystemServices);
ProxyResolutionServiceProviderTest::SetUp();
shill_clients::InitializeFakes();
NetworkHandler::Initialize();
SystemProxyClient::InitializeFake();
SystemProxyManager::Initialize(local_state_.Get());
SystemProxyManager::Get()->SetSystemServicesProxyUrlForTest(
"system-proxy:3128");
}
void TearDown() override {
SystemProxyManager::Shutdown();
NetworkHandler::Shutdown();
shill_clients::Shutdown();
SystemProxyClient::Shutdown();
}
protected:
// Makes a D-Bus call to |service_provider_|'s ResolveProxy method and sets
// the parsed response in |result|.
void CallMethod(const std::string& source_url,
ResolveProxyResult* result,
SystemProxyOverride system_proxy_override) {
dbus::MethodCall method_call(kNetworkProxyServiceInterface,
kNetworkProxyServiceResolveProxyMethod);
dbus::MessageWriter writer(&method_call);
writer.AppendString(source_url);
writer.AppendInt32(system_proxy_override);
std::unique_ptr<dbus::Response> response =
test_helper_.CallMethod(&method_call);
// Parse the |dbus::Response|.
ASSERT_TRUE(response);
dbus::MessageReader reader(response.get());
std::string proxy_info, error;
EXPECT_TRUE(reader.PopString(&result->proxy_info));
EXPECT_TRUE(reader.PopString(&result->error));
}
protected:
ScopedTestingLocalState local_state_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// When system-proxy is enabled via the flag and the caller explicitly opt into
// using system-proxy for authentication, the network address of system-proxy
// should be added first in the PAC style list of proxies.
TEST_F(ProxyResolutionServiceWithSystemProxyTest, FlagOptIn) {
mock_network_context_.SetNextProxyResult({net::OK, "PROXY localhost:8080"});
ResolveProxyResult result;
CallMethod("http://www.gmail.com/", &result, SystemProxyOverride::kOptIn);
// The response should contain the system-proxy address and an empty error.
EXPECT_EQ("PROXY system-proxy:3128; PROXY localhost:8080", result.proxy_info);
EXPECT_EQ("", result.error);
}
// If there's no proxy configured in Chrome, the address of system-proxy should
// not be returned by the proxy resolution service.
TEST_F(ProxyResolutionServiceWithSystemProxyTest, DirectProxy) {
mock_network_context_.SetNextProxyResult({net::OK, "DIRECT"});
ResolveProxyResult result;
CallMethod("http://www.gmail.com/", &result, SystemProxyOverride::kOptIn);
EXPECT_EQ("DIRECT", result.proxy_info);
EXPECT_EQ("", result.error);
}
// When system-proxy is enabled via the flag and the caller doesn't explicitly
// opt into using system-proxy for authentication, the address of system-proxy
// should not be returned by the proxy resolution service.
TEST_F(ProxyResolutionServiceWithSystemProxyTest, FlagDefault) {
mock_network_context_.SetNextProxyResult({net::OK, "PROXY localhost:8080"});
ResolveProxyResult result;
CallMethod("http://www.gmail.com/", &result, SystemProxyOverride::kDefault);
EXPECT_EQ("PROXY localhost:8080", result.proxy_info);
EXPECT_EQ("", result.error);
}
// When system-proxy is enabled via policy and the caller doesn't
// explicitly opt into using system-proxy for authentication, the network
// address of system-proxy should still be added first in the PAC style list of
// proxies.
TEST_F(ProxyResolutionServiceWithSystemProxyTest, PolicyDefault) {
mock_network_context_.SetNextProxyResult({net::OK, "PROXY localhost:8080"});
// Enable system-proxy via policy.
SystemProxyManager::Get()->SetSystemProxyEnabledForTest(true);
ResolveProxyResult result;
CallMethod("http://www.gmail.com/", &result, SystemProxyOverride::kDefault);
EXPECT_EQ("PROXY system-proxy:3128; PROXY localhost:8080", result.proxy_info);
EXPECT_EQ("", result.error);
}
// When system-proxy is enabled via policy and the caller explicitly opts out of
// using system-proxy for authentication, the network address of system-proxy
// should be not be returned.
TEST_F(ProxyResolutionServiceWithSystemProxyTest, PolicyOptOut) {
mock_network_context_.SetNextProxyResult({net::OK, "PROXY localhost:8080"});
// Enable system-proxy via policy.
SystemProxyManager::Get()->SetSystemProxyEnabledForTest(true);
ResolveProxyResult result;
CallMethod("http://www.gmail.com/", &result, SystemProxyOverride::kOptOut);
EXPECT_EQ("PROXY localhost:8080", result.proxy_info);
EXPECT_EQ("", result.error);
}
} // namespace chromeos