blob: 93426e568bb1af18f3f9028634eec7d593bcc3da [file] [log] [blame]
Andreea Costinas44cefa22020-03-09 09:07:39 +01001// Copyright 2020 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "system-proxy/server_proxy.h"
6
7#include <netinet/in.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +01008#include <sys/socket.h>
9#include <sys/types.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010010
Andreea Costinascc4d54e2020-10-19 15:46:25 +020011#include <curl/curl.h>
12
Andreea Costinas44cefa22020-03-09 09:07:39 +010013#include <gmock/gmock.h>
14#include <gtest/gtest.h>
15#include <utility>
16
17#include <base/bind.h>
18#include <base/bind_helpers.h>
19#include <base/callback_helpers.h>
Qijiang Fan713061e2021-03-08 15:45:12 +090020#include <base/check.h>
21#include <base/check_op.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010022#include <base/files/file_util.h>
23#include <base/files/scoped_file.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010024#include <base/strings/string_util.h>
Qijiang Fan34014672020-07-20 16:05:38 +090025#include <base/task/single_thread_task_executor.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010026#include <brillo/dbus/async_event_sequencer.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010027#include <brillo/message_loops/base_message_loop.h>
Garrick Evanscd8c2972020-04-14 14:35:52 +090028#include <chromeos/patchpanel/socket.h>
29#include <chromeos/patchpanel/socket_forwarder.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010030#include "bindings/worker_common.pb.h"
31#include "system-proxy/protobuf_util.h"
Andreea Costinase45d54b2020-03-10 09:21:14 +010032#include "system-proxy/proxy_connect_job.h"
Andreea Costinas44cefa22020-03-09 09:07:39 +010033
34namespace system_proxy {
35namespace {
Andreea Costinase45d54b2020-03-10 09:21:14 +010036constexpr char kUsername[] = "proxy:user";
37constexpr char kUsernameEncoded[] = "proxy%3Auser";
38constexpr char kPassword[] = "proxy password";
39constexpr char kPasswordEncoded[] = "proxy%20password";
Andreea Costinas44cefa22020-03-09 09:07:39 +010040constexpr int kTestPort = 3128;
Andreea Costinas5862b102020-03-19 14:45:36 +010041constexpr char kFakeProxyAddress[] = "http://127.0.0.1";
Andreea Costinase45d54b2020-03-10 09:21:14 +010042
Andreea Costinas44cefa22020-03-09 09:07:39 +010043} // namespace
44
Andreea Costinase45d54b2020-03-10 09:21:14 +010045using ::testing::_;
Andreea Costinas44cefa22020-03-09 09:07:39 +010046using ::testing::Return;
47
48class MockServerProxy : public ServerProxy {
49 public:
50 explicit MockServerProxy(base::OnceClosure quit_closure)
51 : ServerProxy(std::move(quit_closure)) {}
52 MockServerProxy(const MockServerProxy&) = delete;
53 MockServerProxy& operator=(const MockServerProxy&) = delete;
54 ~MockServerProxy() override = default;
55
56 MOCK_METHOD(int, GetStdinPipe, (), (override));
Andreea Costinas5862b102020-03-19 14:45:36 +010057 MOCK_METHOD(int, GetStdoutPipe, (), (override));
Andreea Costinas44cefa22020-03-09 09:07:39 +010058};
59
Andreea Costinase45d54b2020-03-10 09:21:14 +010060class MockProxyConnectJob : public ProxyConnectJob {
61 public:
Garrick Evans3388a032020-03-24 11:25:55 +090062 MockProxyConnectJob(std::unique_ptr<patchpanel::Socket> socket,
Andreea Costinase45d54b2020-03-10 09:21:14 +010063 const std::string& credentials,
64 ResolveProxyCallback resolve_proxy_callback,
Andreea Costinasbb2aa022020-06-13 00:03:23 +020065 AuthenticationRequiredCallback auth_required_callback,
Andreea Costinase45d54b2020-03-10 09:21:14 +010066 OnConnectionSetupFinishedCallback setup_finished_callback)
67 : ProxyConnectJob(std::move(socket),
68 credentials,
Andreea Costinascc4d54e2020-10-19 15:46:25 +020069 CURLAUTH_ANY,
Andreea Costinase45d54b2020-03-10 09:21:14 +010070 std::move(resolve_proxy_callback),
Andreea Costinasbb2aa022020-06-13 00:03:23 +020071 std::move(auth_required_callback),
Andreea Costinase45d54b2020-03-10 09:21:14 +010072 std::move(setup_finished_callback)) {}
73 MockProxyConnectJob(const MockProxyConnectJob&) = delete;
74 MockProxyConnectJob& operator=(const MockProxyConnectJob&) = delete;
75 ~MockProxyConnectJob() override = default;
76
77 MOCK_METHOD(bool, Start, (), (override));
78};
79
Andreea Costinas44cefa22020-03-09 09:07:39 +010080class ServerProxyTest : public ::testing::Test {
81 public:
82 ServerProxyTest() {
83 server_proxy_ =
84 std::make_unique<MockServerProxy>(brillo_loop_.QuitClosure());
85 }
86
87 ServerProxyTest(const ServerProxyTest&) = delete;
88 ServerProxyTest& operator=(const ServerProxyTest&) = delete;
89 ~ServerProxyTest() override {}
90
91 protected:
Andreea Costinas5862b102020-03-19 14:45:36 +010092 // Redirects the standard streams of the worker so that the tests can write
93 // data in the worker's stdin input and read data from the worker's stdout
94 // output.
Andreea Costinas44cefa22020-03-09 09:07:39 +010095 void RedirectStdPipes() {
96 int fds[2];
97 CHECK(base::CreateLocalNonBlockingPipe(fds));
Andreea Costinas5862b102020-03-19 14:45:36 +010098 stdin_read_fd_.reset(fds[0]);
99 stdin_write_fd_.reset(fds[1]);
100 CHECK(base::CreateLocalNonBlockingPipe(fds));
101 stdout_read_fd_.reset(fds[0]);
102 stdout_write_fd_.reset(fds[1]);
Andreea Costinas44cefa22020-03-09 09:07:39 +0100103
Andreea Costinas5862b102020-03-19 14:45:36 +0100104 ON_CALL(*server_proxy_, GetStdinPipe())
105 .WillByDefault(Return(stdin_read_fd_.get()));
106 // Don't redirect all the calls to |stdout_write_fd_| or the test result
107 // will not be printed in the console. Instead, when wanting to read the
108 // standard output, set the expectation to once return |stdout_write_fd_|.
109 ON_CALL(*server_proxy_, GetStdoutPipe())
110 .WillByDefault(Return(STDOUT_FILENO));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100111 server_proxy_->Init();
112 }
113 // SystemProxyAdaptor instance that creates fake worker processes.
114 std::unique_ptr<MockServerProxy> server_proxy_;
Qijiang Fan34014672020-07-20 16:05:38 +0900115 base::SingleThreadTaskExecutor task_executor_{base::MessagePumpType::IO};
116 brillo::BaseMessageLoop brillo_loop_{task_executor_.task_runner()};
Andreea Costinas5862b102020-03-19 14:45:36 +0100117 base::ScopedFD stdin_read_fd_, stdin_write_fd_, stdout_read_fd_,
118 stdout_write_fd_;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100119};
120
121TEST_F(ServerProxyTest, FetchCredentials) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200122 worker::Credentials credentials;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100123 credentials.set_username(kUsername);
Andreea Costinas44cefa22020-03-09 09:07:39 +0100124 credentials.set_password(kPassword);
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200125 credentials.add_policy_credentials_auth_schemes("basic");
126 credentials.add_policy_credentials_auth_schemes("digest");
127
Andreea Costinasaae97382020-05-05 13:31:58 +0200128 worker::WorkerConfigs configs;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100129 *configs.mutable_credentials() = credentials;
130 RedirectStdPipes();
131
Andreea Costinas5862b102020-03-19 14:45:36 +0100132 EXPECT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100133
134 brillo_loop_.RunOnce(false);
135
Andreea Costinase45d54b2020-03-10 09:21:14 +0100136 std::string expected_credentials =
137 base::JoinString({kUsernameEncoded, kPasswordEncoded}, ":");
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200138 EXPECT_EQ(server_proxy_->system_credentials_, expected_credentials);
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200139 EXPECT_EQ(server_proxy_->system_credentials_auth_schemes_,
140 CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_NEGOTIATE);
Andreea Costinas44cefa22020-03-09 09:07:39 +0100141}
142
143TEST_F(ServerProxyTest, FetchListeningAddress) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200144 worker::SocketAddress address;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100145 address.set_addr(INADDR_ANY);
146 address.set_port(kTestPort);
Andreea Costinasaae97382020-05-05 13:31:58 +0200147 worker::WorkerConfigs configs;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100148 *configs.mutable_listening_address() = address;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100149 // Redirect the worker stdin and stdout pipes.
Andreea Costinas5862b102020-03-19 14:45:36 +0100150 RedirectStdPipes();
151 // Send the config to the worker's stdin input.
152 EXPECT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100153 brillo_loop_.RunOnce(false);
154
155 EXPECT_EQ(server_proxy_->listening_addr_, INADDR_ANY);
156 EXPECT_EQ(server_proxy_->listening_port_, kTestPort);
157}
158
Andreea Costinas5862b102020-03-19 14:45:36 +0100159// Tests that ServerProxy handles the basic flow of a connect request:
160// - server accepts a connection a creates a job for it until the connection is
161// finished;
162// - the connect request from the client socket is read and parsed;
163// - proxy resolution request is correctly handled by the job and ServerProxy;
164// - client is sent an HTTP error code in case of failure;
165// - the failed connection job is removed from the queue.
Andreea Costinase45d54b2020-03-10 09:21:14 +0100166TEST_F(ServerProxyTest, HandleConnectRequest) {
167 server_proxy_->listening_addr_ = htonl(INADDR_LOOPBACK);
168 server_proxy_->listening_port_ = kTestPort;
169 // Redirect the worker stdin and stdout pipes.
170 RedirectStdPipes();
171 server_proxy_->CreateListeningSocket();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100172 CHECK_NE(-1, server_proxy_->listening_fd_->fd());
173 brillo_loop_.RunOnce(false);
174
175 struct sockaddr_in ipv4addr;
176 ipv4addr.sin_family = AF_INET;
177 ipv4addr.sin_port = htons(kTestPort);
178 ipv4addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
179
180 auto client_socket =
Garrick Evans3388a032020-03-24 11:25:55 +0900181 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100182 EXPECT_TRUE(client_socket->Connect((const struct sockaddr*)&ipv4addr,
183 sizeof(ipv4addr)));
184 brillo_loop_.RunOnce(false);
185
186 EXPECT_EQ(1, server_proxy_->pending_connect_jobs_.size());
Andreea Costinas5862b102020-03-19 14:45:36 +0100187 const std::string_view http_req =
188 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
189 client_socket->SendTo(http_req.data(), http_req.size());
190
191 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
192 .WillOnce(Return(stdout_write_fd_.get()));
193 brillo_loop_.RunOnce(false);
Andreea Costinasaae97382020-05-05 13:31:58 +0200194 worker::WorkerRequest request;
Andreea Costinas5862b102020-03-19 14:45:36 +0100195 // Read the request from the worker's stdout output.
196 ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
197 ASSERT_TRUE(request.has_proxy_resolution_request());
198
Andreea Costinasa89309d2020-05-08 15:51:12 +0200199 EXPECT_EQ("https://www.example.server.com:443",
Andreea Costinas5862b102020-03-19 14:45:36 +0100200 request.proxy_resolution_request().target_url());
201
202 EXPECT_EQ(1, server_proxy_->pending_proxy_resolution_requests_.size());
203
204 // Write reply with a fake proxy to the worker's standard input.
Andreea Costinasaae97382020-05-05 13:31:58 +0200205 worker::ProxyResolutionReply reply;
Andreea Costinas5862b102020-03-19 14:45:36 +0100206 reply.set_target_url(request.proxy_resolution_request().target_url());
207 reply.add_proxy_servers(kFakeProxyAddress);
Andreea Costinasaae97382020-05-05 13:31:58 +0200208 worker::WorkerConfigs configs;
Andreea Costinas5862b102020-03-19 14:45:36 +0100209 *configs.mutable_proxy_resolution_reply() = reply;
210
211 ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
212 brillo_loop_.RunOnce(false);
213
214 // Verify that the correct HTTP error code is sent to the client. Because
215 // curl_perform will fail, this will be reported as an internal server error.
216 const std::string expected_http_reply =
217 "HTTP/1.1 500 Internal Server Error - Origin: local proxy\r\n\r\n";
218 std::vector<char> buf(expected_http_reply.size());
219 ASSERT_TRUE(base::ReadFromFD(client_socket->fd(), buf.data(), buf.size()));
220 buf.push_back('\0');
221 const std::string actual_http_reply(buf.data());
222 EXPECT_EQ(expected_http_reply, actual_http_reply);
223 EXPECT_EQ(0, server_proxy_->pending_connect_jobs_.size());
Andreea Costinase45d54b2020-03-10 09:21:14 +0100224}
225
226// Tests the |OnConnectionSetupFinished| callback is handled correctly in case
227// of success or error.
228TEST_F(ServerProxyTest, HandlePendingJobs) {
229 int connection_count = 100;
230 int success_count = 51;
231 int failure_count = 49;
232 // Create |connection_count| connections.
233 for (int i = 0; i < connection_count; ++i) {
234 auto client_socket =
Garrick Evans3388a032020-03-24 11:25:55 +0900235 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100236 auto mock_connect_job = std::make_unique<MockProxyConnectJob>(
237 std::move(client_socket), "" /* credentials */,
238 base::BindOnce([](const std::string& target_url,
239 OnProxyResolvedCallback callback) {}),
Andreea Costinased9e6122020-08-12 12:06:19 +0200240 base::BindRepeating([](const std::string& proxy_url,
241 const std::string& realm,
242 const std::string& scheme,
243 const std::string& bad_cached_credentials,
244 OnAuthAcquiredCallback callback) {}),
Andreea Costinase45d54b2020-03-10 09:21:14 +0100245 base::BindOnce(&ServerProxy::OnConnectionSetupFinished,
246 base::Unretained(server_proxy_.get())));
247 server_proxy_->pending_connect_jobs_[mock_connect_job.get()] =
248 std::move(mock_connect_job);
249 }
250 // Resolve |failure_count| pending connections with error.
251 for (int i = 0; i < failure_count; ++i) {
252 auto job_iter = server_proxy_->pending_connect_jobs_.begin();
253 std::move(job_iter->second->setup_finished_callback_)
254 .Run(nullptr, job_iter->first);
255 }
256 // Expect failed requests have been cleared from the pending list and no
257 // forwarder.
258 EXPECT_EQ(success_count, server_proxy_->pending_connect_jobs_.size());
259 EXPECT_EQ(0, server_proxy_->forwarders_.size());
260
261 // Resolve |success_count| successful connections.
262 for (int i = 0; i < success_count; ++i) {
Garrick Evans3388a032020-03-24 11:25:55 +0900263 auto fwd = std::make_unique<patchpanel::SocketForwarder>(
Andreea Costinase45d54b2020-03-10 09:21:14 +0100264 "" /* thread name */,
Garrick Evans3388a032020-03-24 11:25:55 +0900265 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM),
266 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM));
Andreea Costinase45d54b2020-03-10 09:21:14 +0100267 fwd->Start();
268 auto job_iter = server_proxy_->pending_connect_jobs_.begin();
269 std::move(job_iter->second->setup_finished_callback_)
270 .Run(std::move(fwd), job_iter->first);
271 }
272
273 // Expect the successful requests to have been cleared and |success_count|
274 // active forwarders.
275 EXPECT_EQ(0, server_proxy_->pending_connect_jobs_.size());
276 EXPECT_EQ(success_count, server_proxy_->forwarders_.size());
277}
278
Andreea Costinas833eb7c2020-06-12 11:09:15 +0200279// Test to ensure proxy resolution requests are correctly handled if the
280// associated job is canceled before resolution.
281TEST_F(ServerProxyTest, HandleCanceledJobWhilePendingProxyResolution) {
282 server_proxy_->listening_addr_ = htonl(INADDR_LOOPBACK);
283 server_proxy_->listening_port_ = 3129;
284 // Redirect the worker stdin and stdout pipes.
285 RedirectStdPipes();
286 server_proxy_->CreateListeningSocket();
287 CHECK_NE(-1, server_proxy_->listening_fd_->fd());
288 brillo_loop_.RunOnce(false);
289
290 struct sockaddr_in ipv4addr;
291 ipv4addr.sin_family = AF_INET;
292 ipv4addr.sin_port = htons(3129);
293 ipv4addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
294
295 auto client_socket =
296 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM);
297 EXPECT_TRUE(client_socket->Connect((const struct sockaddr*)&ipv4addr,
298 sizeof(ipv4addr)));
299 brillo_loop_.RunOnce(false);
300
301 EXPECT_EQ(1, server_proxy_->pending_connect_jobs_.size());
302 const std::string_view http_req =
303 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
304 client_socket->SendTo(http_req.data(), http_req.size());
305
306 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
307 .WillOnce(Return(stdout_write_fd_.get()));
308 brillo_loop_.RunOnce(false);
309
310 EXPECT_EQ(1, server_proxy_->pending_connect_jobs_.size());
311 server_proxy_->pending_connect_jobs_.clear();
312
313 EXPECT_EQ(1, server_proxy_->pending_proxy_resolution_requests_.size());
314 server_proxy_->OnProxyResolved("https://www.example.server.com:443", {});
315
316 EXPECT_EQ(0, server_proxy_->pending_proxy_resolution_requests_.size());
317}
318
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200319// This test verifies that the athentication request is forwarded to the parent
320// process and that the pending authentication requests are resolved when the
321// parent sends the credentials associated with the protection space included in
322// the request.
323TEST_F(ServerProxyTest, HandlePendingAuthRequests) {
324 RedirectStdPipes();
325
326 worker::ProtectionSpace protection_space;
327 protection_space.set_origin(kFakeProxyAddress);
328 protection_space.set_scheme("Basic");
329 protection_space.set_realm("Proxy test realm");
330 std::string actual_credentials = "";
331
332 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
333 .WillOnce(Return(stdout_write_fd_.get()));
334
335 server_proxy_->AuthenticationRequired(
336 protection_space.origin(), protection_space.scheme(),
Andreea Costinased9e6122020-08-12 12:06:19 +0200337 protection_space.realm(), /* bad_cached_credentials = */ "",
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200338 base::Bind(
339 [](std::string* actual_credentials, const std::string& credentials) {
340 *actual_credentials = credentials;
341 },
342 &actual_credentials));
343
344 EXPECT_EQ(1, server_proxy_->pending_auth_required_requests_.size());
345 EXPECT_EQ(protection_space.SerializeAsString(),
346 server_proxy_->pending_auth_required_requests_.begin()->first);
347
348 brillo_loop_.RunOnce(false);
349
350 worker::WorkerRequest request;
351 // Read the request from the worker's stdout output.
352 ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
353 ASSERT_TRUE(request.has_auth_required_request());
354 ASSERT_TRUE(request.auth_required_request().has_protection_space());
355 EXPECT_EQ(
356 request.auth_required_request().protection_space().SerializeAsString(),
357 protection_space.SerializeAsString());
358
359 // Write reply with a fake credentials to the worker's standard input.
360 worker::Credentials credentials;
361 *credentials.mutable_protection_space() = protection_space;
362 credentials.set_username("test_user");
363 credentials.set_password("test_pwd");
364 worker::WorkerConfigs configs;
365 *configs.mutable_credentials() = credentials;
366
367 ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
368 brillo_loop_.RunOnce(false);
369 EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
370 EXPECT_EQ("test_user:test_pwd", actual_credentials);
371}
372
373// This test verifies that pending athentication requests are solved when the
374// parent returns empty credentials for the protection space.
375TEST_F(ServerProxyTest, HandlePendingAuthRequestsNoCredentials) {
376 RedirectStdPipes();
377
378 worker::ProtectionSpace protection_space;
379 protection_space.set_origin(kFakeProxyAddress);
380 protection_space.set_scheme("Basic");
381 protection_space.set_realm("Proxy test realm");
382 std::string actual_credentials = "";
383
384 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
385 .WillOnce(Return(stdout_write_fd_.get()));
386
387 server_proxy_->AuthenticationRequired(
388 protection_space.origin(), protection_space.scheme(),
Andreea Costinased9e6122020-08-12 12:06:19 +0200389 protection_space.realm(), /* bad_cached_credentials = */ "",
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200390 base::Bind(
391 [](std::string* actual_credentials, const std::string& credentials) {
392 *actual_credentials = credentials;
393 },
394 &actual_credentials));
395
396 EXPECT_EQ(1, server_proxy_->pending_auth_required_requests_.size());
397 EXPECT_EQ(protection_space.SerializeAsString(),
398 server_proxy_->pending_auth_required_requests_.begin()->first);
399
400 brillo_loop_.RunOnce(false);
401
402 worker::WorkerRequest request;
403 // Read the request from the worker's stdout output.
404 ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
405 ASSERT_TRUE(request.has_auth_required_request());
406 ASSERT_TRUE(request.auth_required_request().has_protection_space());
407 EXPECT_EQ(
408 request.auth_required_request().protection_space().SerializeAsString(),
409 protection_space.SerializeAsString());
410
411 // Write reply with a fake credentials to the worker's standard input.
412 worker::Credentials credentials;
413 *credentials.mutable_protection_space() = protection_space;
414 worker::WorkerConfigs configs;
415 *configs.mutable_credentials() = credentials;
416
417 ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
418 brillo_loop_.RunOnce(false);
419 EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
420 EXPECT_EQ("", actual_credentials);
421}
422
423// This test verifies that the athentication request is solved with cached
424// credentials.
425TEST_F(ServerProxyTest, HandlePendingAuthRequestsCachedCredentials) {
426 RedirectStdPipes();
427
428 worker::ProtectionSpace protection_space;
429 protection_space.set_origin(kFakeProxyAddress);
430 protection_space.set_scheme("Basic");
431 protection_space.set_realm("Proxy test realm");
432 std::string actual_credentials = "";
433
434 server_proxy_->auth_cache_[protection_space.SerializeAsString()] =
435 "test_user:test_pwd";
436
437 server_proxy_->AuthenticationRequired(
438 protection_space.origin(), protection_space.scheme(),
Andreea Costinased9e6122020-08-12 12:06:19 +0200439 protection_space.realm(), /* bad_cached_credentials = */ "",
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200440 base::Bind(
441 [](std::string* actual_credentials, const std::string& credentials) {
442 *actual_credentials = credentials;
443 },
444 &actual_credentials));
445
446 brillo_loop_.RunOnce(false);
447 EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
448 EXPECT_EQ("test_user:test_pwd", actual_credentials);
449}
450
Andreea Costinase9c73592020-07-17 15:27:54 +0200451// This test verifies that the stored credentials are removed when receiving a
452// |ClearUserCredentials| request.
453TEST_F(ServerProxyTest, ClearUserCredentials) {
454 worker::ProtectionSpace protection_space;
455 protection_space.set_origin(kFakeProxyAddress);
456 protection_space.set_scheme("Basic");
457 protection_space.set_realm("Proxy test realm");
458 // Add an entry in the cache.
459 server_proxy_->auth_cache_[protection_space.SerializeAsString()] =
460 "test_user:test_pwd";
461
462 worker::ClearUserCredentials clear_user_credentials;
463 worker::WorkerConfigs configs;
464 *configs.mutable_clear_user_credentials() = clear_user_credentials;
465 // Redirect the worker stdin and stdout pipes.
466 RedirectStdPipes();
467 // Send the config to the worker's stdin input.
468 EXPECT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
469 brillo_loop_.RunOnce(false);
470 // Expect that the credentials were cleared.
471 EXPECT_EQ(0, server_proxy_->auth_cache_.size());
472}
473
Andreea Costinased9e6122020-08-12 12:06:19 +0200474// Verifies that even if there are credentials in the cache for the remote
475// web-proxy, the ServerProxy sends a request to the parent web-proxy if the
476// credentials are flagged as bad.
477TEST_F(ServerProxyTest, AuthRequestsBadCachedCredentials) {
478 constexpr char kBadCachedCredetials[] = "bad_user:bad_pwd";
479 constexpr char kCredetials[] = "test_user:test_pwd";
480
481 RedirectStdPipes();
482 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
483 .WillOnce(Return(stdout_write_fd_.get()));
484
485 // Add credentials to the cache for the proxy.
486 worker::ProtectionSpace protection_space;
487 protection_space.set_origin(kFakeProxyAddress);
488 protection_space.set_scheme("Basic");
489 protection_space.set_realm("Proxy test realm");
490 server_proxy_->auth_cache_[protection_space.SerializeAsString()] =
491 kBadCachedCredetials;
492
493 // Request credentials for the proxy.
494 std::string actual_credentials = "";
495 server_proxy_->AuthenticationRequired(
496 protection_space.origin(), protection_space.scheme(),
497 protection_space.realm(), kBadCachedCredetials,
498 base::Bind(
499 [](std::string* actual_credentials, const std::string& credentials) {
500 *actual_credentials = credentials;
501 },
502 &actual_credentials));
503
504 // Expect that the credentials are not served from the cache.
505 EXPECT_EQ(1, server_proxy_->pending_auth_required_requests_.size());
506 EXPECT_EQ(protection_space.SerializeAsString(),
507 server_proxy_->pending_auth_required_requests_.begin()->first);
508
509 brillo_loop_.RunOnce(false);
510
511 worker::WorkerRequest request;
512 // Read the request from the worker's stdout output.
513 ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
514 ASSERT_TRUE(request.has_auth_required_request());
515 ASSERT_TRUE(request.auth_required_request().has_protection_space());
516 EXPECT_EQ(
517 request.auth_required_request().protection_space().SerializeAsString(),
518 protection_space.SerializeAsString());
519
520 // Write reply with a fake credentials to the worker's standard input.
521 worker::Credentials credentials;
522 *credentials.mutable_protection_space() = protection_space;
523 credentials.set_username("test_user");
524 credentials.set_password("test_pwd");
525 worker::WorkerConfigs configs;
526 *configs.mutable_credentials() = credentials;
527
528 ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
529 brillo_loop_.RunOnce(false);
530 EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
531 EXPECT_EQ(kCredetials, actual_credentials);
532}
533
Andreea Costinas44cefa22020-03-09 09:07:39 +0100534} // namespace system_proxy