// 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/ash/net/system_proxy_manager.h"
#include <array>
#include <string>
#include <string_view>
#include <vector>
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task/current_thread.h"
#include "base/test/bind.h"
#include "base/test/gtest_tags.h"
#include "base/test/run_until.h"
#include "chrome/browser/ash/login/test/device_state_mixin.h"
#include "chrome/browser/ash/notifications/request_system_proxy_credentials_view.h"
#include "chrome/browser/ash/policy/affiliation/affiliation_test_helper.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/login/login_handler.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
#include "chromeos/ash/components/dbus/shill/shill_profile_client.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/dbus/system_proxy/system_proxy_client.h"
#include "chromeos/ash/components/dbus/system_proxy/system_proxy_service.pb.h"
#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/proxy/proxy_config_handler.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/prefs/pref_service.h"
#include "components/proxy_config/proxy_config_dictionary.h"
#include "components/proxy_config/proxy_config_pref_names.h"
#include "components/proxy_config/proxy_prefs.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/base/proxy_server.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_auth_cache.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
#include "net/url_request/url_request_context.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/textfield/textfield.h"
namespace ash {
namespace {
constexpr char kRealm[] = "My proxy";
constexpr char kScheme[] = "dIgEsT";
constexpr char kProxyAuthUrl[] = "http://example.com:3128";
constexpr char kSystemProxyNotificationId[] = "system-proxy.auth_required";
constexpr char kUsername[] = "testuser";
constexpr char16_t kUsername16[] = u"testuser";
constexpr char kPassword[] = "testpwd";
constexpr char16_t kPassword16[] = u"testpwd";
constexpr char kUserProfilePath[] = "user_profile";
constexpr char kDefaultServicePath[] = "default_wifi";
constexpr char kDefaultServiceSsid[] = "default_wifi_guid";
constexpr char kDefaultServiceGuid[] = "eth0";
constexpr char kWifiServicePath[] = "stub_wifi";
constexpr char kWifiSsid[] = "wifi0";
constexpr char kWifiGuid[] = "{wifi0_guid}";
constexpr char kONCPolicyWifi0Proxy[] =
R"({
"NetworkConfigurations": [ {
"GUID": "{wifi0_guid}",
"Name": "wifi0",
"ProxySettings": {
"Manual": {
"HTTPProxy": {
"Host": "proxyhost",
"Port": 3128
},
"SecureHTTPProxy": {
"Host": "proxyhost",
"Port": 3128
}
},
"Type": "Manual"
},
"Type": "WiFi",
"WiFi": {
"AutoConnect": true,
"HiddenSSID": false,
"SSID": "wifi0",
"Security": "None"
}
} ]
})";
constexpr char kONCPolicyWifi0AltProxy[] =
R"({
"NetworkConfigurations": [ {
"GUID": "{wifi0_guid}",
"Name": "wifi0",
"ProxySettings": {
"Manual": {
"HTTPProxy": {
"Host": "proxyhostalt",
"Port": 3129
},
"SecureHTTPProxy": {
"Host": "proxyhostalt",
"Port": 3129
}
},
"Type": "Manual"
},
"Type": "WiFi",
"WiFi": {
"AutoConnect": true,
"HiddenSSID": false,
"SSID": "wifi0",
"Security": "None"
}
} ]
})";
constexpr char kSystemProxyPolicyJson[] =
R"({
"system_proxy_enabled": true,
"system_services_username": "%s",
"system_services_password": "%s",
%s
})";
constexpr char kAuthSchemesPolicyEntry[] =
R"("policy_credentials_auth_schemes": [%s],)";
void RunUntilIdle() {
DCHECK(base::CurrentThread::Get());
base::RunLoop loop;
loop.RunUntilIdle();
}
} // namespace
class SystemProxyManagerBrowserTest : public InProcessBrowserTest {
public:
SystemProxyManagerBrowserTest() = default;
SystemProxyManagerBrowserTest(const SystemProxyManagerBrowserTest&) = delete;
SystemProxyManagerBrowserTest& operator=(
const SystemProxyManagerBrowserTest&) = delete;
~SystemProxyManagerBrowserTest() override = default;
void SetUpOnMainThread() override {
GetSystemProxyManager()->StartObservingPrimaryProfilePrefs(
browser()->profile());
display_service_tester_ =
std::make_unique<NotificationDisplayServiceTester>(/*profile=*/nullptr);
GetSystemProxyManager()->SetSystemProxyEnabledForTest(true);
}
void TearDownOnMainThread() override {
GetSystemProxyManager()->SetSystemProxyEnabledForTest(false);
}
protected:
SystemProxyManager* GetSystemProxyManager() {
return SystemProxyManager::Get();
}
RequestSystemProxyCredentialsView* dialog() {
return GetSystemProxyManager()->GetActiveAuthDialogForTest();
}
SystemProxyClient::TestInterface* client_test_interface() {
return SystemProxyClient::Get()->GetTestInterface();
}
void SendAuthenticationRequest(bool bad_cached_credentials) {
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(bad_cached_credentials);
*details.mutable_proxy_protection_space() = protection_space;
client_test_interface()->SendAuthenticationRequiredSignal(details);
}
void WaitForNotification() {
base::RunLoop run_loop;
display_service_tester_->SetNotificationAddedClosure(
run_loop.QuitClosure());
run_loop.Run();
}
std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
};
// Tests the flow for setting user credentials for System-proxy:
// - Receiving an authentication request prompts a notification;
// - Clicking on the notification opens a dialog;
// - Credentials introduced in the dialog are sent via D-Bus to System-proxy.
IN_PROC_BROWSER_TEST_F(SystemProxyManagerBrowserTest, AuthenticationDialog) {
base::RunLoop run_loop;
GetSystemProxyManager()->SetSendAuthDetailsClosureForTest(
run_loop.QuitClosure());
EXPECT_FALSE(
display_service_tester_->GetNotification(kSystemProxyNotificationId));
SendAuthenticationRequest(/* bad_cached_credentials = */ false);
WaitForNotification();
EXPECT_TRUE(
display_service_tester_->GetNotification(kSystemProxyNotificationId));
display_service_tester_->SimulateClick(
NotificationHandler::Type::TRANSIENT, kSystemProxyNotificationId,
/*action_index=*/std::nullopt, /*reply=*/std::nullopt);
// Dialog is created.
ASSERT_TRUE(dialog());
// Expect warning is not shown.
ASSERT_FALSE(dialog()->error_label_for_testing()->GetVisible());
dialog()->username_textfield_for_testing()->SetText(kUsername16);
dialog()->password_textfield_for_testing()->SetText(kPassword16);
// Simulate clicking on "OK" button.
dialog()->Accept();
// Wait for the callback set via |SetSendAuthDetailsClosureForTest| to be
// called. The callback will be called when SystemProxyManager calls the D-Bus
// method |SetAuthenticationDetails|
run_loop.Run();
system_proxy::SetAuthenticationDetailsRequest request =
client_test_interface()->GetLastAuthenticationDetailsRequest();
ASSERT_TRUE(request.has_credentials());
EXPECT_EQ(request.credentials().username(), kUsername);
EXPECT_EQ(request.credentials().password(), kPassword);
// Verify that the UI elements are reset.
GetSystemProxyManager()->CloseAuthDialogForTest();
EXPECT_FALSE(
display_service_tester_->GetNotification(kSystemProxyNotificationId));
EXPECT_FALSE(dialog());
}
// Tests that canceling the authentication dialog sends empty credentials to
// System-proxy.
IN_PROC_BROWSER_TEST_F(SystemProxyManagerBrowserTest,
CancelAuthenticationDialog) {
EXPECT_FALSE(
display_service_tester_->GetNotification(kSystemProxyNotificationId));
SendAuthenticationRequest(/* bad_cached_credentials = */ false);
WaitForNotification();
EXPECT_TRUE(
display_service_tester_->GetNotification(kSystemProxyNotificationId));
display_service_tester_->SimulateClick(
NotificationHandler::Type::TRANSIENT, kSystemProxyNotificationId,
/*action_index=*/std::nullopt, /*reply=*/std::nullopt);
// Dialog is created.
ASSERT_TRUE(dialog());
// Expect warning is not shown.
ASSERT_FALSE(dialog()->error_label_for_testing()->GetVisible());
dialog()->username_textfield_for_testing()->SetText(kUsername16);
dialog()->password_textfield_for_testing()->SetText(kPassword16);
base::RunLoop run_loop;
GetSystemProxyManager()->SetSendAuthDetailsClosureForTest(
run_loop.QuitClosure());
// Simulate clicking on "Cancel" button.
dialog()->Cancel();
run_loop.Run();
system_proxy::SetAuthenticationDetailsRequest request =
client_test_interface()->GetLastAuthenticationDetailsRequest();
ASSERT_TRUE(request.has_credentials());
EXPECT_EQ(request.credentials().username(), "");
EXPECT_EQ(request.credentials().password(), "");
// Verify that the UI elements are reset.
GetSystemProxyManager()->CloseAuthDialogForTest();
EXPECT_FALSE(
display_service_tester_->GetNotification(kSystemProxyNotificationId));
EXPECT_FALSE(dialog());
}
// Tests that the warning informing the user that the previous credentials are
// incorrect is shown in the UI.
IN_PROC_BROWSER_TEST_F(SystemProxyManagerBrowserTest,
BadCachedCredentialsWarning) {
EXPECT_FALSE(
display_service_tester_->GetNotification(kSystemProxyNotificationId));
SendAuthenticationRequest(/* bad_cached_credentials = */ true);
WaitForNotification();
EXPECT_TRUE(
display_service_tester_->GetNotification(kSystemProxyNotificationId));
display_service_tester_->SimulateClick(
NotificationHandler::Type::TRANSIENT, kSystemProxyNotificationId,
/*action_index=*/std::nullopt, /*reply=*/std::nullopt);
ASSERT_TRUE(dialog());
// Expect warning is shown.
EXPECT_TRUE(dialog()->error_label_for_testing()->GetVisible());
}
class SystemProxyManagerPolicyCredentialsBrowserTest
: public MixinBasedInProcessBrowserTest {
public:
SystemProxyManagerPolicyCredentialsBrowserTest() {
device_state_.SetState(
DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED);
device_state_.set_skip_initial_policy_setup(true);
}
SystemProxyManagerPolicyCredentialsBrowserTest(
const SystemProxyManagerPolicyCredentialsBrowserTest&) = delete;
SystemProxyManagerPolicyCredentialsBrowserTest& operator=(
const SystemProxyManagerPolicyCredentialsBrowserTest&) = delete;
~SystemProxyManagerPolicyCredentialsBrowserTest() override = default;
void SetUpInProcessBrowserTestFixture() override {
SessionManagerClient::InitializeFakeInMemory();
MixinBasedInProcessBrowserTest::SetUpInProcessBrowserTestFixture();
constexpr std::string_view kAffiliationID = "id";
// Initialize device policy.
auto affiliation_helper = policy::AffiliationTestHelper::CreateForCloud(
FakeSessionManagerClient::Get());
ASSERT_NO_FATAL_FAILURE(affiliation_helper.SetDeviceAffiliationIDs(
&policy_helper_, std::array{kAffiliationID}));
provider_.SetDefaultReturns(
/*is_initialization_complete_return=*/true,
/*is_first_policy_load_complete_return=*/true);
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
}
void SetUpOnMainThread() override { SetupNetworkEnvironment(); }
protected:
void SetPolicyCredentials(const std::string& username,
const std::string& password,
const std::string& auth_schemes = "") {
const std::string schemes_json_entry =
auth_schemes.empty()
? ""
: base::StringPrintf(kAuthSchemesPolicyEntry, auth_schemes.c_str());
const std::string policy_value =
base::StringPrintf(kSystemProxyPolicyJson, username.c_str(),
password.c_str(), schemes_json_entry.c_str());
enterprise_management::ChromeDeviceSettingsProto& proto(
policy_helper_.device_policy()->payload());
proto.mutable_system_proxy_settings()->set_system_proxy_settings(
policy_value);
policy_helper_.RefreshPolicyAndWaitUntilDeviceSettingsUpdated(
{kSystemProxySettings});
RunUntilIdle();
}
void SetOncPolicy(const std::string& policy_json, policy::PolicyScope scope) {
policy::PolicyMap policy;
policy.Set(policy::key::kOpenNetworkConfiguration,
policy::POLICY_LEVEL_MANDATORY, scope,
policy::POLICY_SOURCE_CLOUD, base::Value(policy_json), nullptr);
provider_.UpdateChromePolicy(policy);
RunUntilIdle();
}
void DisconnectNetworkService(const std::string& service_path) {
ShillServiceClient::TestInterface* service_test =
ShillServiceClient::Get()->GetTestInterface();
base::Value value(shill::kStateIdle);
service_test->SetServiceProperty(service_path, shill::kStateProperty,
value);
RunUntilIdle();
}
void ConnectWifiNetworkService(const std::string& service_path,
const std::string& guid,
const std::string& ssid) {
ShillServiceClient::TestInterface* service_test =
ShillServiceClient::Get()->GetTestInterface();
service_test->AddService(service_path, guid, ssid, shill::kTypeWifi,
shill::kStateOnline, true /* add_to_visible */);
service_test->SetServiceProperty(service_path, shill::kProfileProperty,
base::Value(kUserProfilePath));
RunUntilIdle();
}
void SetProxyConfigForNetworkService(const std::string& service_path,
base::Value::Dict proxy_config) {
ProxyConfigDictionary proxy_config_dict(std::move(proxy_config));
DCHECK(NetworkHandler::IsInitialized());
const NetworkState* network =
NetworkHandler::Get()->network_state_handler()->GetNetworkState(
service_path);
ASSERT_TRUE(network);
proxy_config::SetProxyConfigForNetwork(proxy_config_dict, *network);
}
void ExpectSystemCredentialsSent(
const std::string& username,
const std::string& password,
const std::vector<std::string>& auth_schemes = {}) {
system_proxy::SetAuthenticationDetailsRequest request =
client_test_interface()->GetLastAuthenticationDetailsRequest();
EXPECT_EQ(username, request.credentials().username());
EXPECT_EQ(password, request.credentials().password());
ASSERT_EQ(
auth_schemes.size(),
static_cast<size_t>(
request.credentials().policy_credentials_auth_schemes().size()));
for (size_t i = 0; i < auth_schemes.size(); ++i) {
EXPECT_EQ(request.credentials().policy_credentials_auth_schemes()[i],
auth_schemes[i]);
}
EXPECT_EQ(system_proxy::TrafficOrigin::SYSTEM, request.traffic_type());
}
SystemProxyClient::TestInterface* client_test_interface() {
return SystemProxyClient::Get()->GetTestInterface();
}
private:
void SetupNetworkEnvironment() {
ShillProfileClient::TestInterface* profile_test =
ShillProfileClient::Get()->GetTestInterface();
ShillServiceClient::TestInterface* service_test =
ShillServiceClient::Get()->GetTestInterface();
profile_test->AddProfile(kUserProfilePath, "user");
service_test->ClearServices();
ConnectWifiNetworkService(kDefaultServicePath, kDefaultServiceSsid,
kDefaultServiceGuid);
}
DeviceStateMixin device_state_{
&mixin_host_, DeviceStateMixin::State::OOBE_COMPLETED_UNOWNED};
ScopedStubInstallAttributes test_install_attributes_;
testing::NiceMock<policy::MockConfigurationPolicyProvider> provider_;
policy::DevicePolicyCrosTestHelper policy_helper_;
};
// Tests that the SystemProxyManager syncs credentials correctly for managed
// proxies configured via device ONC policy. It also tests the overall policy
// credentials syncing behaviour, more specifically that SystemProxyManager:
// - sends a D-Bus request when enabled by policy;
// - doesn't send redundant requests to set credentials, i.e. when the default
// network is updated;
// - sends requests to clear credentials if the network is not managed.
IN_PROC_BROWSER_TEST_F(SystemProxyManagerPolicyCredentialsBrowserTest,
DeviceONCPolicyUpdates) {
int set_auth_details_call_count = 0;
SetPolicyCredentials(/*username=*/"", /*password=*/"");
// Expect that if System-proxy policy is enabled, one initial call to set
// empty credentials is sent, regardless of network configuration.
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent("", "");
DisconnectNetworkService(kDefaultServicePath);
ConnectWifiNetworkService(kWifiServicePath, kWifiGuid, kWifiSsid);
// Set an ONC policy with proxy configuration and expect that no D-Bus call to
// update credentials is sent, since the credentials were not changed.
SetOncPolicy(kONCPolicyWifi0Proxy, policy::POLICY_SCOPE_MACHINE);
EXPECT_EQ(set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
// Set policy credentials for the remote proxy and expect that a D-Bus request
// to set policy credentials was sent.
SetPolicyCredentials(kUsername, kPassword);
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent(kUsername, kPassword);
// Connect to a different managed proxy and expect that no additional request
// is sent to System-proxy since credentials were not changed.
SetOncPolicy(kONCPolicyWifi0AltProxy, policy::POLICY_SCOPE_MACHINE);
EXPECT_EQ(set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
// Change credentials and expect that a D-Bus request to set the new
// credentials is sent to System-proxy.
SetPolicyCredentials("test_user_alt", "test_pwd_alt");
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent("test_user_alt", "test_pwd_alt");
DisconnectNetworkService(kWifiServicePath);
// Expect credentials are cleared on non-managed networks.
ConnectWifiNetworkService(kDefaultServicePath, kDefaultServiceSsid,
kDefaultServiceGuid);
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent("", "");
}
// Tests that the SystemProxyManager syncs credentials correctly for managed
// proxies configured via user ONC policy.
IN_PROC_BROWSER_TEST_F(SystemProxyManagerPolicyCredentialsBrowserTest,
UserONCPolicy) {
int set_auth_details_call_count = 0;
SetPolicyCredentials(kUsername, kPassword);
// Expect that credentials were not sent, since there's no managed proxy
// configured.
ExpectSystemCredentialsSent("", "");
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
DisconnectNetworkService(kDefaultServicePath);
// Configure a proxy via user ONC policy and expect that credentials were
// forwarded to System-proxy.
SetOncPolicy(kONCPolicyWifi0Proxy, policy::POLICY_SCOPE_USER);
ConnectWifiNetworkService(kWifiServicePath, kWifiGuid, kWifiSsid);
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent(kUsername, kPassword);
}
// Tests that the SystemProxyManager syncs credentials correctly for managed
// proxies configured via the user policy which sets the kProxy pref.
IN_PROC_BROWSER_TEST_F(SystemProxyManagerPolicyCredentialsBrowserTest,
UserPolicy) {
int set_auth_details_call_count = 0;
SetPolicyCredentials(kUsername, kPassword);
// Expect that credentials were not sent, since there's no managed proxy
// configured.
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent("", "");
// Configure a proxy via user policy.
auto proxy_config = base::Value::Dict()
.Set("mode", ProxyPrefs::kPacScriptProxyModeName)
.Set("pac_url", "http://proxy");
browser()->profile()->GetPrefs()->SetDict(::proxy_config::prefs::kProxy,
std::move(proxy_config));
RunUntilIdle();
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent(kUsername, kPassword);
}
// Tests that the SystemProxyManager doesn't send credentials for user
// configured proxies.
IN_PROC_BROWSER_TEST_F(SystemProxyManagerPolicyCredentialsBrowserTest,
UserSetProxy) {
SetPolicyCredentials(kUsername, kPassword);
auto proxy_config = base::Value::Dict()
.Set("mode", ProxyPrefs::kFixedServersProxyModeName)
.Set("server", "proxy:8080");
SetProxyConfigForNetworkService(kDefaultServicePath, std::move(proxy_config));
RunUntilIdle();
int set_auth_details_call_count = 0;
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent("", "");
}
// Tests that the SystemProxyManager forwards the authentication schemes set by
// policy to System-proxy via D-Bus.
IN_PROC_BROWSER_TEST_F(SystemProxyManagerPolicyCredentialsBrowserTest,
PolicySetAuthSchemes) {
int set_auth_details_call_count = 0;
SetPolicyCredentials(kUsername, kPassword, R"("basic","digest")");
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent("", "", {"basic", "digest"});
DisconnectNetworkService(kDefaultServicePath);
// Configure a proxy via user ONC policy and expect that credentials were
// forwarded to System-proxy.
SetOncPolicy(kONCPolicyWifi0Proxy, policy::POLICY_SCOPE_USER);
ConnectWifiNetworkService(kWifiServicePath, kWifiGuid, kWifiSsid);
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent(kUsername, kPassword, {"basic", "digest"});
SetPolicyCredentials(kUsername, kPassword, R"("ntlm")");
EXPECT_EQ(++set_auth_details_call_count,
client_test_interface()->GetSetAuthenticationDetailsCallCount());
ExpectSystemCredentialsSent(kUsername, kPassword, {"ntlm"});
}
namespace {
constexpr char kProxyUsername[] = "foo";
constexpr char16_t kProxyUsername16[] = u"foo";
constexpr char kProxyPassword[] = "bar";
constexpr char16_t kProxyPassword16[] = u"bar";
constexpr char kBadUsername[] = "bad-username";
constexpr char kBadPassword[] = "bad-pwd";
constexpr char kOriginHostname[] = "a.test";
} // namespace
class SystemProxyCredentialsReuseBrowserTest
: public SystemProxyManagerPolicyCredentialsBrowserTest {
public:
SystemProxyCredentialsReuseBrowserTest()
: proxy_server_(std::make_unique<net::SpawnedTestServer>(
net::SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
base::FilePath())) {}
SystemProxyCredentialsReuseBrowserTest(
const SystemProxyCredentialsReuseBrowserTest&) = delete;
SystemProxyCredentialsReuseBrowserTest& operator=(
const SystemProxyCredentialsReuseBrowserTest&) = delete;
~SystemProxyCredentialsReuseBrowserTest() override = default;
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule(kOriginHostname, "127.0.0.1");
proxy_server_->set_redirect_connect_to_localhost(true);
ASSERT_TRUE(proxy_server_->Start());
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
https_server_->ServeFilesFromSourceDirectory("chrome/test/data");
net::test_server::RegisterDefaultHandlers(https_server_.get());
ASSERT_TRUE(https_server_->Start());
}
protected:
content::WebContents* GetWebContents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
void SetManagedProxy() {
// Configure a proxy via user policy.
auto proxy_config =
base::Value::Dict()
.Set("mode", ProxyPrefs::kFixedServersProxyModeName)
.Set("server", proxy_server_->host_port_pair().ToString());
browser()->profile()->GetPrefs()->SetDict(::proxy_config::prefs::kProxy,
std::move(proxy_config));
RunUntilIdle();
}
GURL GetServerUrl(const std::string& page) {
return https_server_->GetURL(kOriginHostname, page);
}
// Navigates to the test page "/simple.html" and authenticates in the proxy
// login dialog with `username` and `password`.
void LoginWithDialog(const std::u16string& username,
const std::u16string& password) {
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GetServerUrl("/simple.html")));
ASSERT_TRUE(base::test::RunUntil([]() {
return LoginHandler::GetAllLoginHandlersForTest().size() == 1;
}));
LoginHandler* login_handler =
LoginHandler::GetAllLoginHandlersForTest().front();
login_handler->SetAuth(username, password);
}
void CheckEntryInHttpAuthCache(const std::string& auth_scheme,
const std::string& expected_username,
const std::string& expected_password) {
network::mojom::NetworkContext* network_context =
browser()->profile()->GetDefaultStoragePartition()->GetNetworkContext();
std::string username;
std::string password;
base::RunLoop loop;
network_context->LookupProxyAuthCredentials(
net::ProxyServer(net::ProxyServer::SCHEME_HTTP,
proxy_server_->host_port_pair()),
auth_scheme, "MyRealm1",
base::BindOnce(
[](std::string* username, std::string* password,
base::OnceClosure closure,
const std::optional<net::AuthCredentials>& credentials) {
if (credentials) {
*username = base::UTF16ToUTF8(credentials->username());
*password = base::UTF16ToUTF8(credentials->password());
}
std::move(closure).Run();
},
&username, &password, loop.QuitClosure()));
loop.Run();
EXPECT_EQ(username, expected_username);
EXPECT_EQ(password, expected_password);
}
SystemProxyManager* GetSystemProxyManager() {
return SystemProxyManager::Get();
}
std::unique_ptr<net::EmbeddedTestServer> https_server_;
// A proxy server which requires authentication using the 'Basic'
// authentication method.
std::unique_ptr<net::SpawnedTestServer> proxy_server_;
};
// Verifies that the policy provided credentials are not used for regular users.
IN_PROC_BROWSER_TEST_F(SystemProxyCredentialsReuseBrowserTest, RegularUser) {
base::AddTagToTestResult("feature_id",
"screenplay-04b4c463-f720-4cd4-9e50-7ee009d9a241");
SetManagedProxy();
SetPolicyCredentials(kProxyUsername, kProxyPassword);
LoginWithDialog(kProxyUsername16, kProxyPassword16);
CheckEntryInHttpAuthCache("Basic", kProxyUsername, kProxyPassword);
}
// Verifies that the policy provided credentials are used for MGS.
IN_PROC_BROWSER_TEST_F(SystemProxyCredentialsReuseBrowserTest,
PolicyCredentialsUsed) {
SetManagedProxy();
LoginState::Get()->SetLoggedInState(
LoginState::LOGGED_IN_ACTIVE, LoginState::LOGGED_IN_USER_PUBLIC_ACCOUNT);
SetPolicyCredentials(kProxyUsername, kProxyPassword);
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GetServerUrl("/simple.html")));
CheckEntryInHttpAuthCache("Basic", kProxyUsername, kProxyPassword);
}
// Verifies that if the policy provided proxy credentials are not correct in a
// MGS, then the user is prompted for credentials.
IN_PROC_BROWSER_TEST_F(SystemProxyCredentialsReuseBrowserTest,
BadPolicyCredentials) {
SetManagedProxy();
LoginState::Get()->SetLoggedInState(
LoginState::LOGGED_IN_ACTIVE, LoginState::LOGGED_IN_USER_PUBLIC_ACCOUNT);
SetPolicyCredentials(kBadUsername, kBadPassword);
LoginWithDialog(kProxyUsername16, kProxyPassword16);
CheckEntryInHttpAuthCache("Basic", kProxyUsername, kProxyPassword);
}
// Verifies that the policy provided proxy credentials are only used for
// authentication schemes allowed by the SystemProxySettings policy.
IN_PROC_BROWSER_TEST_F(SystemProxyCredentialsReuseBrowserTest,
RestrictedPolicyCredentials) {
SetManagedProxy();
LoginState::Get()->SetLoggedInState(
LoginState::LOGGED_IN_ACTIVE, LoginState::LOGGED_IN_USER_PUBLIC_ACCOUNT);
SetPolicyCredentials(kProxyUsername, kProxyPassword, R"("ntlm","digest")");
LoginWithDialog(kProxyUsername16, kProxyPassword16);
CheckEntryInHttpAuthCache("Basic", kProxyUsername, kProxyPassword);
}
} // namespace ash