blob: 741002e3c72704a515606413e11e8f3ed3765ae5 [file] [log] [blame]
// Copyright (c) 2020 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/policy/system_proxy_manager.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/task_environment.h"
#include "chrome/browser/chromeos/settings/device_settings_test_helper.h"
#include "chrome/browser/chromeos/settings/scoped_testing_cros_settings.h"
#include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/shill/shill_clients.h"
#include "chromeos/dbus/system_proxy/system_proxy_client.h"
#include "chromeos/dbus/system_proxy/system_proxy_service.pb.h"
#include "chromeos/network/network_handler.h"
#include "components/arc/arc_prefs.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/http/http_auth.h"
#include "net/http/http_auth_cache.h"
#include "net/http/http_network_session.h"
#include "net/http/http_transaction_factory.h"
#include "net/url_request/url_request_context.h"
#include "services/network/network_context.h"
#include "services/network/network_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Invoke;
using testing::WithArg;
namespace {
constexpr char kBrowserUsername[] = "browser_username";
constexpr char kBrowserPassword[] = "browser_password";
constexpr char kKerberosActivePrincipalName[] = "kerberos_princ_name";
constexpr char kProxyAuthUrl[] = "http://example.com:3128";
constexpr char kProxyAuthEmptyPath[] = "http://example.com:3128/";
constexpr char kRealm[] = "My proxy";
constexpr char kScheme[] = "dIgEsT";
constexpr char kProxyAuthChallenge[] = "challenge";
std::unique_ptr<network::NetworkContext>
CreateNetworkContextForDefaultStoragePartition(
network::NetworkService* network_service,
content::BrowserContext* browser_context) {
mojo::PendingRemote<network::mojom::NetworkContext> network_context_remote;
auto params = network::mojom::NetworkContextParams::New();
params->cert_verifier_params = content::GetCertVerifierParams(
network::mojom::CertVerifierCreationParams::New());
auto network_context = std::make_unique<network::NetworkContext>(
network_service, network_context_remote.InitWithNewPipeAndPassReceiver(),
std::move(params));
content::BrowserContext::GetDefaultStoragePartition(browser_context)
->SetNetworkContextForTesting(std::move(network_context_remote));
return network_context;
}
network::NetworkService* GetNetworkService() {
content::GetNetworkService();
// Wait for the Network Service to initialize on the IO thread.
content::RunAllPendingInMessageLoop(content::BrowserThread::IO);
return network::NetworkService::GetNetworkServiceForTesting();
}
} // namespace
namespace policy {
// TODO(acostinas, https://crbug.com/1102351) Replace RunUntilIdle() in tests
// with RunLoop::Run() with explicit RunLoop::QuitClosure().
class SystemProxyManagerTest : public testing::Test {
public:
SystemProxyManagerTest() : local_state_(TestingBrowserProcess::GetGlobal()) {}
~SystemProxyManagerTest() override = default;
// testing::Test
void SetUp() override {
testing::Test::SetUp();
chromeos::shill_clients::InitializeFakes();
chromeos::NetworkHandler::Initialize();
profile_ = std::make_unique<TestingProfile>();
chromeos::SystemProxyClient::InitializeFake();
system_proxy_manager_ = std::make_unique<SystemProxyManager>(
chromeos::CrosSettings::Get(), local_state_.Get());
// Listen for pref changes for the primary profile.
system_proxy_manager_->StartObservingPrimaryProfilePrefs(profile_.get());
}
void TearDown() override {
system_proxy_manager_->StopObservingPrimaryProfilePrefs();
system_proxy_manager_.reset();
chromeos::SystemProxyClient::Shutdown();
chromeos::NetworkHandler::Shutdown();
chromeos::shill_clients::Shutdown();
}
protected:
void SetPolicy(bool system_proxy_enabled,
const std::string& system_services_username,
const std::string& system_services_password) {
base::DictionaryValue dict;
dict.SetKey("system_proxy_enabled", base::Value(system_proxy_enabled));
dict.SetKey("system_services_username",
base::Value(system_services_username));
dict.SetKey("system_services_password",
base::Value(system_services_password));
scoped_testing_cros_settings_.device_settings()->Set(
chromeos::kSystemProxySettings, dict);
task_environment_.RunUntilIdle();
}
chromeos::SystemProxyClient::TestInterface* client_test_interface() {
return chromeos::SystemProxyClient::Get()->GetTestInterface();
}
content::BrowserTaskEnvironment task_environment_;
ScopedTestingLocalState local_state_;
chromeos::ScopedTestingCrosSettings scoped_testing_cros_settings_;
std::unique_ptr<SystemProxyManager> system_proxy_manager_;
std::unique_ptr<TestingProfile> profile_;
chromeos::ScopedDeviceSettingsTestHelper device_settings_test_helper_;
chromeos::ScopedStubInstallAttributes test_install_attributes_;
};
// Verifies requests to shut down are sent to System-proxy according to the
// |kSystemProxySettings| policy.
TEST_F(SystemProxyManagerTest, ShutDownDaemon) {
EXPECT_EQ(0, client_test_interface()->GetShutDownCallCount());
SetPolicy(false /* system_proxy_enabled */, "" /* system_services_username */,
"" /* system_services_password */);
// Don't send empty credentials.
EXPECT_EQ(1, client_test_interface()->GetShutDownCallCount());
}
// Tests that |SystemProxyManager| sends the correct Kerberos details and
// updates to System-proxy.
TEST_F(SystemProxyManagerTest, KerberosConfig) {
int expected_set_auth_details_call_count = 0;
SetPolicy(true /* system_proxy_enabled */, "" /* system_services_username */,
"" /* system_services_password */);
EXPECT_EQ(++expected_set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
local_state_.Get()->SetBoolean(prefs::kKerberosEnabled, true);
EXPECT_EQ(++expected_set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
system_proxy::SetAuthenticationDetailsRequest request =
client_test_interface()->GetLastAuthenticationDetailsRequest();
EXPECT_FALSE(request.has_credentials());
EXPECT_TRUE(request.kerberos_enabled());
EXPECT_EQ(request.traffic_type(), system_proxy::TrafficOrigin::SYSTEM);
// Set an active principal name.
profile_->GetPrefs()->SetString(prefs::kKerberosActivePrincipalName,
kKerberosActivePrincipalName);
EXPECT_EQ(++expected_set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
profile_->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, true);
EXPECT_EQ(++expected_set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
request = client_test_interface()->GetLastAuthenticationDetailsRequest();
EXPECT_EQ(kKerberosActivePrincipalName, request.active_principal_name());
EXPECT_EQ(request.traffic_type(), system_proxy::TrafficOrigin::ALL);
// Remove the active principal name.
profile_->GetPrefs()->SetString(prefs::kKerberosActivePrincipalName, "");
request = client_test_interface()->GetLastAuthenticationDetailsRequest();
EXPECT_EQ("", request.active_principal_name());
EXPECT_TRUE(request.kerberos_enabled());
// Disable kerberos.
local_state_.Get()->SetBoolean(prefs::kKerberosEnabled, false);
request = client_test_interface()->GetLastAuthenticationDetailsRequest();
EXPECT_FALSE(request.kerberos_enabled());
}
// Tests that when no user is signed in, credential requests are resolved to a
// D-Bus call which sends back to System-proxy empty credentials for the
// specified protection space.
TEST_F(SystemProxyManagerTest, UserCredentialsRequiredNoUser) {
SetPolicy(true /* system_proxy_enabled */, "" /* system_services_username */,
"" /* system_services_password */);
system_proxy_manager_->StopObservingPrimaryProfilePrefs();
system_proxy::ProtectionSpace protection_space;
protection_space.set_origin(kProxyAuthUrl);
protection_space.set_scheme(kScheme);
protection_space.set_realm(kRealm);
system_proxy::AuthenticationRequiredDetails details;
details.set_bad_cached_credentials(false);
*details.mutable_proxy_protection_space() = protection_space;
client_test_interface()->SendAuthenticationRequiredSignal(details);
task_environment_.RunUntilIdle();
EXPECT_EQ(2, client_test_interface()->GetSetAuthenticationDetailsCallCount());
system_proxy::SetAuthenticationDetailsRequest request =
client_test_interface()->GetLastAuthenticationDetailsRequest();
ASSERT_TRUE(request.has_protection_space());
ASSERT_EQ(protection_space.SerializeAsString(),
request.protection_space().SerializeAsString());
ASSERT_TRUE(request.has_credentials());
EXPECT_EQ("", request.credentials().username());
EXPECT_EQ("", request.credentials().password());
system_proxy_manager_->StartObservingPrimaryProfilePrefs(profile_.get());
}
// Tests that credential requests are resolved to a D-Bus call which sends back
// to System-proxy credentials acquired from the NetworkService.
TEST_F(SystemProxyManagerTest, UserCredentialsRequestedFromNetworkService) {
SetPolicy(true /* system_proxy_enabled */, "" /* system_services_username */,
"" /* system_services_password */);
// Setup the NetworkContext with credentials.
std::unique_ptr<network::NetworkContext> network_context =
CreateNetworkContextForDefaultStoragePartition(GetNetworkService(),
profile_.get());
network_context->url_request_context()
->http_transaction_factory()
->GetSession()
->http_auth_cache()
->Add(GURL(kProxyAuthEmptyPath), net::HttpAuth::AUTH_PROXY, kRealm,
net::HttpAuth::AUTH_SCHEME_DIGEST, net::NetworkIsolationKey(),
kProxyAuthChallenge,
net::AuthCredentials(base::ASCIIToUTF16(kBrowserUsername),
base::ASCIIToUTF16(kBrowserPassword)),
std::string() /* path */);
system_proxy::ProtectionSpace protection_space;
protection_space.set_origin(kProxyAuthUrl);
protection_space.set_scheme(kScheme);
protection_space.set_realm(kRealm);
system_proxy::AuthenticationRequiredDetails details;
*details.mutable_proxy_protection_space() = protection_space;
EXPECT_EQ(1, client_test_interface()->GetSetAuthenticationDetailsCallCount());
client_test_interface()->SendAuthenticationRequiredSignal(details);
task_environment_.RunUntilIdle();
EXPECT_EQ(2, client_test_interface()->GetSetAuthenticationDetailsCallCount());
system_proxy::SetAuthenticationDetailsRequest request =
client_test_interface()->GetLastAuthenticationDetailsRequest();
ASSERT_TRUE(request.has_protection_space());
EXPECT_EQ(protection_space.SerializeAsString(),
request.protection_space().SerializeAsString());
ASSERT_TRUE(request.has_credentials());
EXPECT_EQ(kBrowserUsername, request.credentials().username());
EXPECT_EQ(kBrowserPassword, request.credentials().password());
EXPECT_EQ(request.traffic_type(), system_proxy::TrafficOrigin::SYSTEM);
// Enable ARC and verify that the credentials are sent both for user and
// system traffic.
profile_->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, true);
task_environment_.RunUntilIdle();
client_test_interface()->SendAuthenticationRequiredSignal(details);
task_environment_.RunUntilIdle();
request = client_test_interface()->GetLastAuthenticationDetailsRequest();
EXPECT_EQ(request.traffic_type(), system_proxy::TrafficOrigin::ALL);
}
// Tests that |SystemProxyManager| sends requests to start and shut down the
// worker which tunnels ARC++ traffic according to policy.
TEST_F(SystemProxyManagerTest, EnableArcWorker) {
int expected_set_auth_details_call_count = 0;
SetPolicy(true /* system_proxy_enabled */, "" /* system_services_username */,
"" /* system_services_password */);
EXPECT_EQ(++expected_set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
profile_->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, true);
task_environment_.RunUntilIdle();
EXPECT_EQ(++expected_set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
profile_->GetPrefs()->SetBoolean(arc::prefs::kArcEnabled, false);
EXPECT_EQ(1, client_test_interface()->GetShutDownCallCount());
}
// Tests that the user preference used by ARC++ to point to the local proxy is
// kept in sync.
TEST_F(SystemProxyManagerTest, ArcWorkerAddressPrefSynced) {
const char kLocalProxyAddress[] = "local address";
SetPolicy(true /* system_proxy_enabled */, "" /* system_services_username */,
"" /* system_services_password */);
system_proxy::WorkerActiveSignalDetails details;
details.set_traffic_origin(system_proxy::TrafficOrigin::USER);
details.set_local_proxy_url(kLocalProxyAddress);
client_test_interface()->SendWorkerActiveSignal(details);
task_environment_.RunUntilIdle();
EXPECT_EQ(kLocalProxyAddress,
profile_->GetPrefs()->GetString(
::prefs::kSystemProxyUserTrafficHostAndPort));
// The preference shouldn't be updated if the signal is send for system
// traffic.
details.set_traffic_origin(system_proxy::TrafficOrigin::SYSTEM);
details.set_local_proxy_url("other address");
client_test_interface()->SendWorkerActiveSignal(details);
task_environment_.RunUntilIdle();
EXPECT_EQ(kLocalProxyAddress,
profile_->GetPrefs()->GetString(
::prefs::kSystemProxyUserTrafficHostAndPort));
SetPolicy(false /* system_proxy_enabled */, "" /* system_services_username */,
"" /* system_services_password */);
EXPECT_TRUE(profile_->GetPrefs()
->GetString(::prefs::kSystemProxyUserTrafficHostAndPort)
.empty());
}
} // namespace policy