system-proxy: Request credentials from browser
When a ProxyConnectJob fails with "proxy auth required", the job will
ask for credentials from the ServerProxy object which owns it.
If the credentials are in the ServerProxy auth cache, the request will
be resolved with those credentials.
If not, the request will be forwarded to the parent process which will
send a dbus call to the browser to notify that it needs credentials for
a specific protection space (proxy_url, scheme, realm).
The browser will then send credentials along with the protection space
via dbus, with empty username and password if the user hasn't entered
them yet.
The dbus response is then forwarded to the ProxyConnectJob which made
the original request.
BUG=chromium:1042642
TEST=unittest
Change-Id: I1f5d43971d18df98aeb7c0b25642ceb29c761915
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2245699
Reviewed-by: Pavol Marko <pmarko@chromium.org>
Tested-by: Andreea-Elena Costinas <acostinas@google.com>
Commit-Queue: Andreea-Elena Costinas <acostinas@google.com>
diff --git a/system-proxy/dbus_bindings/org.chromium.SystemProxy.xml b/system-proxy/dbus_bindings/org.chromium.SystemProxy.xml
index c474b8f..e089e8f 100644
--- a/system-proxy/dbus_bindings/org.chromium.SystemProxy.xml
+++ b/system-proxy/dbus_bindings/org.chromium.SystemProxy.xml
@@ -56,5 +56,16 @@
</arg>
<annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
</signal>
+ <signal name="AuthenticationRequired">
+ <tp:docstring>
+ Signal emitted when a system proxy worker requires credentials for proxy authentication.
+ </tp:docstring>
+ <arg name="details" type="ay">
+ <tp:docstring>
+ Serialized AuthenticationRequiredDetails message.
+ </tp:docstring>
+ </arg>
+ <annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
+ </signal>
</interface>
</node>
diff --git a/system-proxy/proto/worker_common.proto b/system-proxy/proto/worker_common.proto
index 70bc986..8f8b097 100644
--- a/system-proxy/proto/worker_common.proto
+++ b/system-proxy/proto/worker_common.proto
@@ -4,9 +4,22 @@
package system_proxy.worker;
+// The protection space determines the domain over which credentials can
+// be automatically applied (defined in RFC7235 , section 2.2).
+message ProtectionSpace {
+ // The origin of the URL of the web proxy server issuing
+ // the challenge, formatted as scheme://url:port.
+ optional string origin = 1;
+ // The case-sensitive realm string of the challenge.
+ optional string realm = 2;
+ // The authentication scheme that can be basic, digest or NTLM.
+ optional string scheme = 3;
+}
+
message Credentials {
optional string username = 1;
optional string password = 2;
+ optional ProtectionSpace protection_space = 3;
}
message SocketAddress {
@@ -34,10 +47,15 @@
repeated string proxy_servers = 2;
}
+message AuthRequiredRequest {
+ optional ProtectionSpace protection_space = 1;
+}
+
message WorkerRequest {
oneof params {
LogRequest log_request = 1;
ProxyResolutionRequest proxy_resolution_request = 2;
+ AuthRequiredRequest auth_required_request = 3;
}
}
diff --git a/system-proxy/sandboxed_worker.cc b/system-proxy/sandboxed_worker.cc
index d6851bf..6a306b2 100644
--- a/system-proxy/sandboxed_worker.cc
+++ b/system-proxy/sandboxed_worker.cc
@@ -21,7 +21,6 @@
#include <chromeos/patchpanel/net_util.h>
#include <google/protobuf/repeated_field.h>
-#include "bindings/worker_common.pb.h"
#include "system-proxy/protobuf_util.h"
#include "system-proxy/system_proxy_adaptor.h"
@@ -93,11 +92,7 @@
return true;
}
-void SandboxedWorker::SetUsernameAndPassword(const std::string& username,
- const std::string& password) {
- worker::Credentials credentials;
- credentials.set_username(username);
- credentials.set_password(password);
+void SandboxedWorker::SetCredentials(const worker::Credentials& credentials) {
worker::WorkerConfigs configs;
*configs.mutable_credentials() = credentials;
if (!WriteProtobuf(stdin_pipe_.get(), configs)) {
@@ -187,6 +182,11 @@
weak_ptr_factory_.GetWeakPtr(),
proxy_request.target_url()));
}
+ if (request.has_auth_required_request()) {
+ const worker::AuthRequiredRequest& auth_request =
+ request.auth_required_request();
+ adaptor_->RequestAuthenticationCredentials(auth_request.protection_space());
+ }
}
void SandboxedWorker::SetNetNamespaceLifelineFd(
diff --git a/system-proxy/sandboxed_worker.h b/system-proxy/sandboxed_worker.h
index 530da54..b2714b3 100644
--- a/system-proxy/sandboxed_worker.h
+++ b/system-proxy/sandboxed_worker.h
@@ -17,6 +17,8 @@
#include <base/memory/weak_ptr.h>
#include <chromeos/scoped_minijail.h>
+#include "bindings/worker_common.pb.h"
+
namespace system_proxy {
class SystemProxyAdaptor;
@@ -30,9 +32,9 @@
// Starts a sandboxed worker with pipes.
virtual bool Start();
- // Sends the username and password to the worker via communication pipes.
- void SetUsernameAndPassword(const std::string& username,
- const std::string& password);
+ // Sends the credentials which include username, password and protection
+ // space (optional) to the worker via communication pipes.
+ void SetCredentials(const worker::Credentials& credentials);
// Sends the availability of kerberos auth to the worker via communication
// pipes.
bool SetKerberosEnabled(bool enabled,
@@ -63,6 +65,8 @@
FRIEND_TEST(SystemProxyAdaptorTest, SetAuthenticationDetails);
FRIEND_TEST(SystemProxyAdaptorTest, KerberosEnabled);
FRIEND_TEST(SystemProxyAdaptorTest, ProxyResolutionFilter);
+ FRIEND_TEST(SystemProxyAdaptorTest, ProtectionSpaceAuthenticationRequired);
+ FRIEND_TEST(SystemProxyAdaptorTest, ProtectionSpaceNoCredentials);
void OnMessageReceived();
void OnErrorReceived();
diff --git a/system-proxy/server_proxy.cc b/system-proxy/server_proxy.cc
index ea93796..6662c92 100644
--- a/system-proxy/server_proxy.cc
+++ b/system-proxy/server_proxy.cc
@@ -97,8 +97,45 @@
const std::string& scheme,
const std::string& realm,
OnAuthAcquiredCallback callback) {
- // TODO(acostinas): Request the credentials from the main process.
- std::move(callback).Run(std::string());
+ worker::ProtectionSpace protection_space;
+ protection_space.set_origin(proxy_url);
+ protection_space.set_realm(realm);
+ protection_space.set_scheme(scheme);
+
+ std::string auth_key = protection_space.SerializeAsString();
+ // Check the local cache.
+ auto it = auth_cache_.find(auth_key);
+ if (it != auth_cache_.end()) {
+ std::move(callback).Run(it->second);
+ return;
+ }
+
+ // Request the credentials from the main process.
+ worker::AuthRequiredRequest auth_request;
+ *auth_request.mutable_protection_space() = protection_space;
+
+ worker::WorkerRequest request;
+ *request.mutable_auth_required_request() = auth_request;
+
+ if (!WriteProtobuf(GetStdoutPipe(), request)) {
+ LOG(ERROR) << "Failed to send authentication required request";
+ std::move(callback).Run(/* credentials= */ std::string());
+ return;
+ }
+ pending_auth_required_requests_[auth_key].push_back(std::move(callback));
+}
+
+void ServerProxy::AuthCredentialsProvided(
+ const std::string& auth_credentials_key, const std::string& credentials) {
+ auto it = pending_auth_required_requests_.find(auth_credentials_key);
+ if (it == pending_auth_required_requests_.end()) {
+ LOG(WARNING) << "No pending requests found for credentials";
+ return;
+ }
+ for (auto& auth_acquired_callback : it->second) {
+ std::move(auth_acquired_callback).Run(credentials);
+ }
+ pending_auth_required_requests_.erase(auth_credentials_key);
}
void ServerProxy::HandleStdinReadable() {
@@ -109,10 +146,23 @@
}
if (config.has_credentials()) {
+ std::string credentials;
const std::string username = UrlEncode(config.credentials().username());
const std::string password = UrlEncode(config.credentials().password());
- system_credentials_ = base::JoinString({username.c_str(), password.c_str()},
- kCredentialsColonSeparator);
+ credentials = base::JoinString({username.c_str(), password.c_str()},
+ kCredentialsColonSeparator);
+ if (config.credentials().has_protection_space()) {
+ std::string auth_key =
+ config.credentials().protection_space().SerializeAsString();
+ if (!username.empty() && !password.empty()) {
+ auth_cache_[auth_key] = credentials;
+ AuthCredentialsProvided(auth_key, credentials);
+ } else {
+ AuthCredentialsProvided(auth_key, std::string());
+ }
+ } else {
+ system_credentials_ = credentials;
+ }
}
if (config.has_listening_address()) {
diff --git a/system-proxy/server_proxy.h b/system-proxy/server_proxy.h
index 578a19e..4b9a96d 100644
--- a/system-proxy/server_proxy.h
+++ b/system-proxy/server_proxy.h
@@ -74,6 +74,9 @@
FRIEND_TEST(ServerProxyTest, HandlePendingJobs);
FRIEND_TEST(ServerProxyTest, SetupConnection);
FRIEND_TEST(ServerProxyTest, HandleCanceledJobWhilePendingProxyResolution);
+ FRIEND_TEST(ServerProxyTest, HandlePendingAuthRequests);
+ FRIEND_TEST(ServerProxyTest, HandlePendingAuthRequestsCachedCredentials);
+ FRIEND_TEST(ServerProxyTest, HandlePendingAuthRequestsNoCredentials);
bool HandleSignal(const struct signalfd_siginfo& siginfo);
@@ -99,19 +102,28 @@
// Sets the environment variables for kerberos authentication.
void SetKerberosEnv(bool kerberos_enabled);
+ // Notifies proxy connect jobs which are pending authentication that
+ // credentials were provided for the protection space identified by
+ // |auth_credentials_key|. Called when the parent process sends credentials
+ // along with the associated protection space via the standard input.
+ void AuthCredentialsProvided(const std::string& auth_credentials_key,
+ const std::string& credentials);
+
// The proxy listening address in network-byte order.
uint32_t listening_addr_ = 0;
int listening_port_;
// The user name and password to use for proxy authentication in the format
// compatible with libcurl's CURLOPT_USERPWD: both user name and password URL
- // encoded and separated by colon.
+ // encoded and separated by colon. Only set for system traffic. If set, the
+ // credentials will be applied to any connection, regardless of the remote
+ // proxy it's connecting to or the challenge response.
std::string system_credentials_;
std::unique_ptr<patchpanel::Socket> listening_fd_;
// List of SocketForwarders that corresponds to the TCP tunnel between the
- // local client and the remote proxy, forwarding data between the TCP
+ // local client and the remote proxy, forwarding data between the TCP
// connection initiated by the local client to the local proxy and the TCP
// connection initiated by the local proxy to the remote proxy.
std::list<std::unique_ptr<patchpanel::SocketForwarder>> forwarders_;
@@ -125,6 +137,21 @@
std::map<std::string, std::list<OnProxyResolvedCallback>>
pending_proxy_resolution_requests_;
+ // Collection of ongoing authentication requests. The key represents the
+ // ProtectionSpace proto message (proxy url, scheme and realm) associated with
+ // the request, serialized as a string. The value is a list of callbaks to
+ // pending connect jobs that are awaiting authentication and have received a
+ // challenge with the same scheme and realm from the same proxy server.
+ std::map<std::string, std::list<OnAuthAcquiredCallback>>
+ pending_auth_required_requests_;
+
+ // Stores HTTP authentication identities acquired from the user and challenge
+ // info. The credentials are mapped by the protection space (origin, realm,
+ // scheme) and can only be used in response to challenges corresponding to
+ // this specific triple, as opposed to |system_credentials_| which, if set,
+ // can be used for any protection space.
+ std::map<std::string, std::string> auth_cache_;
+
base::OnceClosure quit_closure_;
std::unique_ptr<base::FileDescriptorWatcher::Controller> stdin_watcher_;
std::unique_ptr<base::FileDescriptorWatcher::Controller> fd_watcher_;
diff --git a/system-proxy/server_proxy_test.cc b/system-proxy/server_proxy_test.cc
index f6dbd3c..4da1972 100644
--- a/system-proxy/server_proxy_test.cc
+++ b/system-proxy/server_proxy_test.cc
@@ -304,4 +304,136 @@
EXPECT_EQ(0, server_proxy_->pending_proxy_resolution_requests_.size());
}
+// This test verifies that the athentication request is forwarded to the parent
+// process and that the pending authentication requests are resolved when the
+// parent sends the credentials associated with the protection space included in
+// the request.
+TEST_F(ServerProxyTest, HandlePendingAuthRequests) {
+ RedirectStdPipes();
+
+ worker::ProtectionSpace protection_space;
+ protection_space.set_origin(kFakeProxyAddress);
+ protection_space.set_scheme("Basic");
+ protection_space.set_realm("Proxy test realm");
+ std::string actual_credentials = "";
+
+ EXPECT_CALL(*server_proxy_, GetStdoutPipe())
+ .WillOnce(Return(stdout_write_fd_.get()));
+
+ server_proxy_->AuthenticationRequired(
+ protection_space.origin(), protection_space.scheme(),
+ protection_space.realm(),
+ base::Bind(
+ [](std::string* actual_credentials, const std::string& credentials) {
+ *actual_credentials = credentials;
+ },
+ &actual_credentials));
+
+ EXPECT_EQ(1, server_proxy_->pending_auth_required_requests_.size());
+ EXPECT_EQ(protection_space.SerializeAsString(),
+ server_proxy_->pending_auth_required_requests_.begin()->first);
+
+ brillo_loop_.RunOnce(false);
+
+ worker::WorkerRequest request;
+ // Read the request from the worker's stdout output.
+ ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
+ ASSERT_TRUE(request.has_auth_required_request());
+ ASSERT_TRUE(request.auth_required_request().has_protection_space());
+ EXPECT_EQ(
+ request.auth_required_request().protection_space().SerializeAsString(),
+ protection_space.SerializeAsString());
+
+ // Write reply with a fake credentials to the worker's standard input.
+ worker::Credentials credentials;
+ *credentials.mutable_protection_space() = protection_space;
+ credentials.set_username("test_user");
+ credentials.set_password("test_pwd");
+ worker::WorkerConfigs configs;
+ *configs.mutable_credentials() = credentials;
+
+ ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
+ brillo_loop_.RunOnce(false);
+ EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
+ EXPECT_EQ("test_user:test_pwd", actual_credentials);
+}
+
+// This test verifies that pending athentication requests are solved when the
+// parent returns empty credentials for the protection space.
+TEST_F(ServerProxyTest, HandlePendingAuthRequestsNoCredentials) {
+ RedirectStdPipes();
+
+ worker::ProtectionSpace protection_space;
+ protection_space.set_origin(kFakeProxyAddress);
+ protection_space.set_scheme("Basic");
+ protection_space.set_realm("Proxy test realm");
+ std::string actual_credentials = "";
+
+ EXPECT_CALL(*server_proxy_, GetStdoutPipe())
+ .WillOnce(Return(stdout_write_fd_.get()));
+
+ server_proxy_->AuthenticationRequired(
+ protection_space.origin(), protection_space.scheme(),
+ protection_space.realm(),
+ base::Bind(
+ [](std::string* actual_credentials, const std::string& credentials) {
+ *actual_credentials = credentials;
+ },
+ &actual_credentials));
+
+ EXPECT_EQ(1, server_proxy_->pending_auth_required_requests_.size());
+ EXPECT_EQ(protection_space.SerializeAsString(),
+ server_proxy_->pending_auth_required_requests_.begin()->first);
+
+ brillo_loop_.RunOnce(false);
+
+ worker::WorkerRequest request;
+ // Read the request from the worker's stdout output.
+ ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
+ ASSERT_TRUE(request.has_auth_required_request());
+ ASSERT_TRUE(request.auth_required_request().has_protection_space());
+ EXPECT_EQ(
+ request.auth_required_request().protection_space().SerializeAsString(),
+ protection_space.SerializeAsString());
+
+ // Write reply with a fake credentials to the worker's standard input.
+ worker::Credentials credentials;
+ *credentials.mutable_protection_space() = protection_space;
+ worker::WorkerConfigs configs;
+ *configs.mutable_credentials() = credentials;
+
+ ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
+ brillo_loop_.RunOnce(false);
+ EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
+ EXPECT_EQ("", actual_credentials);
+}
+
+// This test verifies that the athentication request is solved with cached
+// credentials.
+TEST_F(ServerProxyTest, HandlePendingAuthRequestsCachedCredentials) {
+ RedirectStdPipes();
+
+ worker::ProtectionSpace protection_space;
+ protection_space.set_origin(kFakeProxyAddress);
+ protection_space.set_scheme("Basic");
+ protection_space.set_realm("Proxy test realm");
+ std::string actual_credentials = "";
+
+ server_proxy_->auth_cache_[protection_space.SerializeAsString()] =
+ "test_user:test_pwd";
+
+ server_proxy_->AuthenticationRequired(
+ protection_space.origin(), protection_space.scheme(),
+ protection_space.realm(),
+ base::Bind(
+ [](std::string* actual_credentials, const std::string& credentials) {
+ *actual_credentials = credentials;
+ },
+ &actual_credentials));
+
+ brillo_loop_.RunOnce(false);
+ EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
+ EXPECT_EQ("test_user:test_pwd", actual_credentials);
+}
+
} // namespace system_proxy
diff --git a/system-proxy/system_proxy_adaptor.cc b/system-proxy/system_proxy_adaptor.cc
index 46f88b7..839cb31 100644
--- a/system-proxy/system_proxy_adaptor.cc
+++ b/system-proxy/system_proxy_adaptor.cc
@@ -82,11 +82,6 @@
return SerializeProto(response);
}
- if (!request.has_credentials() && !request.has_kerberos_enabled()) {
- response.set_error_message(kNoCredentialsSpecifiedError);
- return SerializeProto(response);
- }
-
if (request.traffic_type() != TrafficOrigin::SYSTEM) {
response.set_error_message(kOnlySystemTrafficSupportedError);
return SerializeProto(response);
@@ -98,17 +93,28 @@
}
if (request.has_credentials()) {
- if (!request.credentials().has_username() ||
- !request.credentials().has_password()) {
+ if (!((request.credentials().has_username() &&
+ request.credentials().has_password()) ||
+ request.has_protection_space())) {
response.set_error_message(kNoCredentialsSpecifiedError);
return SerializeProto(response);
}
+ worker::Credentials credentials;
+ if (request.has_protection_space()) {
+ worker::ProtectionSpace protection_space;
+ protection_space.set_origin(request.protection_space().origin());
+ protection_space.set_scheme(request.protection_space().scheme());
+ protection_space.set_realm(request.protection_space().realm());
+ *credentials.mutable_protection_space() = protection_space;
+ }
+ if (request.credentials().has_username()) {
+ credentials.set_username(request.credentials().username());
+ credentials.set_password(request.credentials().password());
+ }
brillo::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(&SystemProxyAdaptor::SetCredentialsTask,
weak_ptr_factory_.GetWeakPtr(),
- system_services_worker_.get(),
- request.credentials().username(),
- request.credentials().password()));
+ system_services_worker_.get(), credentials));
}
if (request.has_kerberos_enabled()) {
@@ -128,35 +134,8 @@
std::vector<uint8_t> SystemProxyAdaptor::SetSystemTrafficCredentials(
const std::vector<uint8_t>& request_blob) {
- LOG(INFO) << "Received set credentials request.";
-
- SetSystemTrafficCredentialsRequest request;
- const std::string error_message =
- DeserializeProto(FROM_HERE, &request, request_blob);
-
SetSystemTrafficCredentialsResponse response;
- if (!error_message.empty()) {
- response.set_error_message(error_message);
- return SerializeProto(response);
- }
-
- if (!request.has_system_services_username() ||
- !request.has_system_services_password()) {
- response.set_error_message(kNoCredentialsSpecifiedError);
- return SerializeProto(response);
- }
-
- if (!CreateWorkerIfNeeded(/* user_traffic */ false)) {
- response.set_error_message(kFailedToStartWorkerError);
- return SerializeProto(response);
- }
-
- brillo::MessageLoop::current()->PostTask(
- FROM_HERE,
- base::Bind(&SystemProxyAdaptor::SetCredentialsTask,
- weak_ptr_factory_.GetWeakPtr(), system_services_worker_.get(),
- request.system_services_username(),
- request.system_services_password()));
+ response.set_error_message("Deprecated. Please use SetAuthenticationDetails");
return SerializeProto(response);
}
@@ -223,11 +202,10 @@
return true;
}
-void SystemProxyAdaptor::SetCredentialsTask(SandboxedWorker* worker,
- const std::string& username,
- const std::string& password) {
+void SystemProxyAdaptor::SetCredentialsTask(
+ SandboxedWorker* worker, const worker::Credentials& credentials) {
DCHECK(worker);
- worker->SetUsernameAndPassword(username, password);
+ worker->SetCredentials(credentials);
}
void SystemProxyAdaptor::SetKerberosEnabledTask(
@@ -303,4 +281,15 @@
SendWorkerActiveSignal(SerializeProto(details));
}
+void SystemProxyAdaptor::RequestAuthenticationCredentials(
+ const worker::ProtectionSpace& protection_space) {
+ AuthenticationRequiredDetails details;
+ ProtectionSpace proxy_protection_space;
+ proxy_protection_space.set_origin(protection_space.origin());
+ proxy_protection_space.set_realm(protection_space.realm());
+ proxy_protection_space.set_scheme(protection_space.scheme());
+ *details.mutable_proxy_protection_space() = proxy_protection_space;
+ SendAuthenticationRequiredSignal(SerializeProto(details));
+}
+
} // namespace system_proxy
diff --git a/system-proxy/system_proxy_adaptor.h b/system-proxy/system_proxy_adaptor.h
index 1863f51..c8dec58 100644
--- a/system-proxy/system_proxy_adaptor.h
+++ b/system-proxy/system_proxy_adaptor.h
@@ -14,6 +14,7 @@
#include <gtest/gtest_prod.h> // for FRIEND_TEST
#include <patchpanel/proto_bindings/patchpanel_service.pb.h>
+#include "bindings/worker_common.pb.h"
#include "system_proxy/org.chromium.SystemProxy.h"
namespace brillo {
@@ -54,6 +55,9 @@
const std::string& target_url,
const brillo::http::GetChromeProxyServersCallback& callback);
+ void RequestAuthenticationCredentials(
+ const worker::ProtectionSpace& protection_space);
+
protected:
virtual std::unique_ptr<SandboxedWorker> CreateWorker();
virtual bool ConnectNamespace(SandboxedWorker* worker, bool user_traffic);
@@ -68,10 +72,11 @@
FRIEND_TEST(SystemProxyAdaptorTest, ShutDown);
FRIEND_TEST(SystemProxyAdaptorTest, ConnectNamespace);
FRIEND_TEST(SystemProxyAdaptorTest, ProxyResolutionFilter);
+ FRIEND_TEST(SystemProxyAdaptorTest, ProtectionSpaceAuthenticationRequired);
+ FRIEND_TEST(SystemProxyAdaptorTest, ProtectionSpaceNoCredentials);
void SetCredentialsTask(SandboxedWorker* worker,
- const std::string& username,
- const std::string& password);
+ const worker::Credentials& credentials);
void SetKerberosEnabledTask(SandboxedWorker* worker,
bool kerberos_enabled,
diff --git a/system-proxy/system_proxy_adaptor_test.cc b/system-proxy/system_proxy_adaptor_test.cc
index 7f6cb7c..c2c979c 100644
--- a/system-proxy/system_proxy_adaptor_test.cc
+++ b/system-proxy/system_proxy_adaptor_test.cc
@@ -7,6 +7,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <list>
+#include <map>
#include <utility>
#include <base/bind.h>
@@ -27,6 +28,7 @@
#include <dbus/mock_object_proxy.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/kerberos/dbus-constants.h>
+#include <dbus/system_proxy/dbus-constants.h>
#include "bindings/worker_common.pb.h"
#include "system-proxy/kerberos_client.h"
@@ -93,6 +95,8 @@
FRIEND_TEST(SystemProxyAdaptorTest, KerberosEnabled);
FRIEND_TEST(SystemProxyAdaptorTest, ConnectNamespace);
FRIEND_TEST(SystemProxyAdaptorTest, ProxyResolutionFilter);
+ FRIEND_TEST(SystemProxyAdaptorTest, ProtectionSpaceAuthenticationRequired);
+ FRIEND_TEST(SystemProxyAdaptorTest, ProtectionSpaceNoCredentials);
base::WeakPtrFactory<FakeSystemProxyAdaptor> weak_ptr_factory_;
};
@@ -131,6 +135,16 @@
SystemProxyAdaptorTest& operator=(const SystemProxyAdaptorTest&) = delete;
~SystemProxyAdaptorTest() override = default;
+ void AddCredentialsToAuthCache(
+ const worker::ProtectionSpace& protection_space,
+ const std::string& username,
+ const std::string& password) {
+ Credentials credentials;
+ credentials.set_username(username);
+ credentials.set_password(password);
+ mock_auth_cache_[protection_space.SerializeAsString()] = credentials;
+ }
+
void OnWorkerActive(dbus::Signal* signal) {
EXPECT_EQ(signal->GetInterface(), "org.chromium.SystemProxy");
EXPECT_EQ(signal->GetMember(), "WorkerActive");
@@ -142,8 +156,41 @@
EXPECT_EQ(kLocalProxyHostPort, details.local_proxy_url());
}
+ void OnAuthenticationRequired(dbus::Signal* signal) {
+ EXPECT_EQ(signal->GetInterface(), "org.chromium.SystemProxy");
+ EXPECT_EQ(signal->GetMember(), "AuthenticationRequired");
+
+ dbus::MessageReader signal_reader(signal);
+ system_proxy::AuthenticationRequiredDetails details;
+ EXPECT_TRUE(signal_reader.PopArrayOfBytesAsProto(&details));
+
+ Credentials credentials;
+
+ auto it = mock_auth_cache_.find(
+ details.proxy_protection_space().SerializeAsString());
+
+ if (it != mock_auth_cache_.end()) {
+ credentials = it->second;
+ } else {
+ credentials.set_username("");
+ credentials.set_password("");
+ }
+
+ SetAuthenticationDetailsRequest request;
+ *request.mutable_credentials() = credentials;
+ *request.mutable_protection_space() = details.proxy_protection_space();
+ request.set_traffic_type(TrafficOrigin::SYSTEM);
+
+ std::vector<uint8_t> proto_blob(request.ByteSizeLong());
+ request.SerializeToArray(proto_blob.data(), proto_blob.size());
+
+ adaptor_->SetAuthenticationDetails(proto_blob);
+ ASSERT_TRUE(brillo_loop_.RunOnce(/*may_block=*/false));
+ }
+
protected:
bool active_worker_signal_called_ = false;
+ std::map<std::string, Credentials> mock_auth_cache_;
scoped_refptr<dbus::MockBus> bus_ = new dbus::MockBus(dbus::Bus::Options());
scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
// SystemProxyAdaptor instance that creates fake worker processes.
@@ -156,40 +203,6 @@
brillo::BaseMessageLoop brillo_loop_{&loop_};
};
-TEST_F(SystemProxyAdaptorTest, SetSystemTrafficCredentials) {
- EXPECT_CALL(*bus_, GetObjectProxy(patchpanel::kPatchPanelServiceName, _))
- .WillOnce(Return(mock_patchpanel_proxy_.get()));
-
- EXPECT_FALSE(adaptor_->system_services_worker_.get());
- SetSystemTrafficCredentialsRequest request;
- request.set_system_services_username(kUser);
- request.set_system_services_password(kPassword);
- std::vector<uint8_t> proto_blob(request.ByteSizeLong());
- request.SerializeToArray(proto_blob.data(), proto_blob.size());
-
- // First create a worker object.
- adaptor_->SetSystemTrafficCredentials(proto_blob);
- brillo_loop_.RunOnce(false);
-
- EXPECT_TRUE(adaptor_->system_services_worker_.get());
- EXPECT_TRUE(adaptor_->system_services_worker_->IsRunning());
-
- int fds[2];
- EXPECT_TRUE(base::CreateLocalNonBlockingPipe(fds));
- base::ScopedFD read_scoped_fd(fds[0]);
- // Reset the worker stdin pipe to read the input from the other endpoint.
- adaptor_->system_services_worker_->stdin_pipe_.reset(fds[1]);
-
- adaptor_->SetSystemTrafficCredentials(proto_blob);
- brillo_loop_.RunOnce(false);
-
- worker::WorkerConfigs config;
- ASSERT_TRUE(ReadProtobuf(read_scoped_fd.get(), &config));
- EXPECT_TRUE(config.has_credentials());
- EXPECT_EQ(config.credentials().username(), kUser);
- EXPECT_EQ(config.credentials().password(), kPassword);
-}
-
TEST_F(SystemProxyAdaptorTest, SetAuthenticationDetails) {
EXPECT_CALL(*bus_, GetObjectProxy(patchpanel::kPatchPanelServiceName, _))
.WillOnce(Return(mock_patchpanel_proxy_.get()));
@@ -268,14 +281,17 @@
EXPECT_CALL(*bus_, GetObjectProxy(patchpanel::kPatchPanelServiceName, _))
.WillOnce(Return(mock_patchpanel_proxy_.get()));
EXPECT_FALSE(adaptor_->system_services_worker_.get());
- SetSystemTrafficCredentialsRequest request;
- request.set_system_services_username(kUser);
- request.set_system_services_password(kPassword);
+ SetAuthenticationDetailsRequest request;
+ Credentials credentials;
+ credentials.set_username(kUser);
+ credentials.set_password(kPassword);
+ *request.mutable_credentials() = credentials;
+ request.set_traffic_type(TrafficOrigin::SYSTEM);
std::vector<uint8_t> proto_blob(request.ByteSizeLong());
request.SerializeToArray(proto_blob.data(), proto_blob.size());
// First create a worker object.
- adaptor_->SetSystemTrafficCredentials(proto_blob);
+ adaptor_->SetAuthenticationDetails(proto_blob);
brillo_loop_.RunOnce(false);
EXPECT_TRUE(adaptor_->system_services_worker_.get());
@@ -325,4 +341,78 @@
EXPECT_EQ("http://test.proxy.com", proxies.front());
}
+// Test that verifies that authentication requests are result in sending a
+// signal to notify credentials are missing and credentials and protection space
+// if correctly forwarded to the worker processes.
+TEST_F(SystemProxyAdaptorTest, ProtectionSpaceAuthenticationRequired) {
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_))
+ .WillOnce(
+ Invoke(this, &SystemProxyAdaptorTest::OnAuthenticationRequired));
+
+ worker::ProtectionSpace protection_space;
+ protection_space.set_origin("http://test.proxy.com");
+ protection_space.set_realm("my realm");
+ protection_space.set_scheme("basic");
+ std::string msg;
+ protection_space.SerializeToString(&msg);
+ AddCredentialsToAuthCache(protection_space, kUser, kPassword);
+
+ adaptor_->system_services_worker_ = adaptor_->CreateWorker();
+ int fds[2];
+ ASSERT_TRUE(base::CreateLocalNonBlockingPipe(fds));
+ base::ScopedFD read_scoped_fd(fds[0]);
+ // Reset the worker stdin pipe to read the input from the other endpoint.
+ adaptor_->system_services_worker_->stdin_pipe_.reset(fds[1]);
+ adaptor_->RequestAuthenticationCredentials(protection_space);
+
+ brillo_loop_.RunOnce(false);
+
+ worker::WorkerConfigs config;
+ ASSERT_TRUE(ReadProtobuf(read_scoped_fd.get(), &config));
+ EXPECT_TRUE(config.has_credentials());
+
+ const worker::Credentials& reply = config.credentials();
+ EXPECT_TRUE(reply.has_protection_space());
+ EXPECT_EQ(reply.username(), kUser);
+ EXPECT_EQ(reply.password(), kPassword);
+ EXPECT_EQ(reply.protection_space().SerializeAsString(),
+ protection_space.SerializeAsString());
+}
+
+// Test that verifies that authentication requests that resolve to an empty
+// credentials set are forwarded to the worker processes.
+TEST_F(SystemProxyAdaptorTest, ProtectionSpaceNoCredentials) {
+ EXPECT_CALL(*mock_exported_object_, SendSignal(_))
+ .WillOnce(
+ Invoke(this, &SystemProxyAdaptorTest::OnAuthenticationRequired));
+
+ worker::ProtectionSpace protection_space;
+ protection_space.set_origin("http://test.proxy.com");
+ protection_space.set_realm("my realm");
+ protection_space.set_scheme("basic");
+ std::string msg;
+ protection_space.SerializeToString(&msg);
+
+ adaptor_->system_services_worker_ = adaptor_->CreateWorker();
+ int fds[2];
+ ASSERT_TRUE(base::CreateLocalNonBlockingPipe(fds));
+ base::ScopedFD read_scoped_fd(fds[0]);
+ // Reset the worker stdin pipe to read the input from the other endpoint.
+ adaptor_->system_services_worker_->stdin_pipe_.reset(fds[1]);
+ adaptor_->RequestAuthenticationCredentials(protection_space);
+
+ brillo_loop_.RunOnce(false);
+
+ worker::WorkerConfigs config;
+ ASSERT_TRUE(ReadProtobuf(read_scoped_fd.get(), &config));
+ EXPECT_TRUE(config.has_credentials());
+
+ const worker::Credentials& reply = config.credentials();
+ EXPECT_TRUE(reply.has_protection_space());
+ EXPECT_EQ(reply.username(), "");
+ EXPECT_EQ(reply.password(), "");
+ EXPECT_EQ(reply.protection_space().SerializeAsString(),
+ protection_space.SerializeAsString());
+}
+
} // namespace system_proxy