blob: 6352278bbd4f77f85d8cfc18743c110970bc8742 [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>
Andreea Costinas44cefa22020-03-09 09:07:39 +010018#include <base/callback_helpers.h>
Qijiang Fan713061e2021-03-08 15:45:12 +090019#include <base/check.h>
20#include <base/check_op.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010021#include <base/files/file_util.h>
22#include <base/files/scoped_file.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010023#include <base/strings/string_util.h>
Qijiang Fan34014672020-07-20 16:05:38 +090024#include <base/task/single_thread_task_executor.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010025#include <brillo/dbus/async_event_sequencer.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010026#include <brillo/message_loops/base_message_loop.h>
Garrick Evanscd8c2972020-04-14 14:35:52 +090027#include <chromeos/patchpanel/socket.h>
28#include <chromeos/patchpanel/socket_forwarder.h>
Andreea Costinas44cefa22020-03-09 09:07:39 +010029#include "bindings/worker_common.pb.h"
30#include "system-proxy/protobuf_util.h"
Andreea Costinase45d54b2020-03-10 09:21:14 +010031#include "system-proxy/proxy_connect_job.h"
Andreea Costinas44cefa22020-03-09 09:07:39 +010032
33namespace system_proxy {
34namespace {
Andreea Costinase45d54b2020-03-10 09:21:14 +010035constexpr char kUsername[] = "proxy:user";
36constexpr char kUsernameEncoded[] = "proxy%3Auser";
37constexpr char kPassword[] = "proxy password";
38constexpr char kPasswordEncoded[] = "proxy%20password";
Andreea Costinas44cefa22020-03-09 09:07:39 +010039constexpr int kTestPort = 3128;
Andreea Costinas5862b102020-03-19 14:45:36 +010040constexpr char kFakeProxyAddress[] = "http://127.0.0.1";
Andreea Costinase45d54b2020-03-10 09:21:14 +010041
Andreea Costinas44cefa22020-03-09 09:07:39 +010042} // namespace
43
Andreea Costinase45d54b2020-03-10 09:21:14 +010044using ::testing::_;
Andreea Costinas44cefa22020-03-09 09:07:39 +010045using ::testing::Return;
46
47class MockServerProxy : public ServerProxy {
48 public:
49 explicit MockServerProxy(base::OnceClosure quit_closure)
50 : ServerProxy(std::move(quit_closure)) {}
51 MockServerProxy(const MockServerProxy&) = delete;
52 MockServerProxy& operator=(const MockServerProxy&) = delete;
53 ~MockServerProxy() override = default;
54
Andreea Costinas82a604e2021-04-09 13:19:19 +020055 void RunAfterOnConnectionAccept(base::OnceClosure closure) {
56 conn_accept_closure_ = std::move(closure);
57 }
58
Andreea Costinas44cefa22020-03-09 09:07:39 +010059 MOCK_METHOD(int, GetStdinPipe, (), (override));
Andreea Costinas5862b102020-03-19 14:45:36 +010060 MOCK_METHOD(int, GetStdoutPipe, (), (override));
Andreea Costinas82a604e2021-04-09 13:19:19 +020061
62 private:
63 void OnConnectionAccept() override {
64 ServerProxy::OnConnectionAccept();
65 if (conn_accept_closure_)
66 std::move(conn_accept_closure_).Run();
67 }
68
69 base::OnceClosure conn_accept_closure_;
Andreea Costinas44cefa22020-03-09 09:07:39 +010070};
71
Andreea Costinase45d54b2020-03-10 09:21:14 +010072class MockProxyConnectJob : public ProxyConnectJob {
73 public:
Garrick Evans3388a032020-03-24 11:25:55 +090074 MockProxyConnectJob(std::unique_ptr<patchpanel::Socket> socket,
Andreea Costinase45d54b2020-03-10 09:21:14 +010075 const std::string& credentials,
76 ResolveProxyCallback resolve_proxy_callback,
Andreea Costinasbb2aa022020-06-13 00:03:23 +020077 AuthenticationRequiredCallback auth_required_callback,
Andreea Costinase45d54b2020-03-10 09:21:14 +010078 OnConnectionSetupFinishedCallback setup_finished_callback)
79 : ProxyConnectJob(std::move(socket),
80 credentials,
Andreea Costinascc4d54e2020-10-19 15:46:25 +020081 CURLAUTH_ANY,
Andreea Costinase45d54b2020-03-10 09:21:14 +010082 std::move(resolve_proxy_callback),
Andreea Costinasbb2aa022020-06-13 00:03:23 +020083 std::move(auth_required_callback),
Andreea Costinase45d54b2020-03-10 09:21:14 +010084 std::move(setup_finished_callback)) {}
85 MockProxyConnectJob(const MockProxyConnectJob&) = delete;
86 MockProxyConnectJob& operator=(const MockProxyConnectJob&) = delete;
87 ~MockProxyConnectJob() override = default;
88
89 MOCK_METHOD(bool, Start, (), (override));
90};
91
Andreea Costinas44cefa22020-03-09 09:07:39 +010092class ServerProxyTest : public ::testing::Test {
93 public:
94 ServerProxyTest() {
95 server_proxy_ =
96 std::make_unique<MockServerProxy>(brillo_loop_.QuitClosure());
97 }
98
99 ServerProxyTest(const ServerProxyTest&) = delete;
100 ServerProxyTest& operator=(const ServerProxyTest&) = delete;
101 ~ServerProxyTest() override {}
102
103 protected:
Andreea Costinas5862b102020-03-19 14:45:36 +0100104 // Redirects the standard streams of the worker so that the tests can write
105 // data in the worker's stdin input and read data from the worker's stdout
106 // output.
Andreea Costinas44cefa22020-03-09 09:07:39 +0100107 void RedirectStdPipes() {
108 int fds[2];
109 CHECK(base::CreateLocalNonBlockingPipe(fds));
Andreea Costinas5862b102020-03-19 14:45:36 +0100110 stdin_read_fd_.reset(fds[0]);
111 stdin_write_fd_.reset(fds[1]);
112 CHECK(base::CreateLocalNonBlockingPipe(fds));
113 stdout_read_fd_.reset(fds[0]);
114 stdout_write_fd_.reset(fds[1]);
Andreea Costinas44cefa22020-03-09 09:07:39 +0100115
Andreea Costinas5862b102020-03-19 14:45:36 +0100116 ON_CALL(*server_proxy_, GetStdinPipe())
117 .WillByDefault(Return(stdin_read_fd_.get()));
118 // Don't redirect all the calls to |stdout_write_fd_| or the test result
119 // will not be printed in the console. Instead, when wanting to read the
120 // standard output, set the expectation to once return |stdout_write_fd_|.
121 ON_CALL(*server_proxy_, GetStdoutPipe())
122 .WillByDefault(Return(STDOUT_FILENO));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100123 server_proxy_->Init();
124 }
125 // SystemProxyAdaptor instance that creates fake worker processes.
126 std::unique_ptr<MockServerProxy> server_proxy_;
Qijiang Fan34014672020-07-20 16:05:38 +0900127 base::SingleThreadTaskExecutor task_executor_{base::MessagePumpType::IO};
128 brillo::BaseMessageLoop brillo_loop_{task_executor_.task_runner()};
Andreea Costinas5862b102020-03-19 14:45:36 +0100129 base::ScopedFD stdin_read_fd_, stdin_write_fd_, stdout_read_fd_,
130 stdout_write_fd_;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100131};
132
133TEST_F(ServerProxyTest, FetchCredentials) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200134 worker::Credentials credentials;
Andreea Costinase45d54b2020-03-10 09:21:14 +0100135 credentials.set_username(kUsername);
Andreea Costinas44cefa22020-03-09 09:07:39 +0100136 credentials.set_password(kPassword);
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200137 credentials.add_policy_credentials_auth_schemes("basic");
138 credentials.add_policy_credentials_auth_schemes("digest");
139
Andreea Costinasaae97382020-05-05 13:31:58 +0200140 worker::WorkerConfigs configs;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100141 *configs.mutable_credentials() = credentials;
142 RedirectStdPipes();
143
Andreea Costinas5862b102020-03-19 14:45:36 +0100144 EXPECT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100145
146 brillo_loop_.RunOnce(false);
147
Andreea Costinase45d54b2020-03-10 09:21:14 +0100148 std::string expected_credentials =
149 base::JoinString({kUsernameEncoded, kPasswordEncoded}, ":");
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200150 EXPECT_EQ(server_proxy_->system_credentials_, expected_credentials);
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200151 EXPECT_EQ(server_proxy_->system_credentials_auth_schemes_,
152 CURLAUTH_BASIC | CURLAUTH_DIGEST | CURLAUTH_NEGOTIATE);
Andreea Costinas44cefa22020-03-09 09:07:39 +0100153}
154
155TEST_F(ServerProxyTest, FetchListeningAddress) {
Andreea Costinasaae97382020-05-05 13:31:58 +0200156 worker::SocketAddress address;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100157 address.set_addr(INADDR_ANY);
158 address.set_port(kTestPort);
Andreea Costinasaae97382020-05-05 13:31:58 +0200159 worker::WorkerConfigs configs;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100160 *configs.mutable_listening_address() = address;
Andreea Costinas44cefa22020-03-09 09:07:39 +0100161 // Redirect the worker stdin and stdout pipes.
Andreea Costinas5862b102020-03-19 14:45:36 +0100162 RedirectStdPipes();
163 // Send the config to the worker's stdin input.
164 EXPECT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
Andreea Costinas44cefa22020-03-09 09:07:39 +0100165 brillo_loop_.RunOnce(false);
166
167 EXPECT_EQ(server_proxy_->listening_addr_, INADDR_ANY);
168 EXPECT_EQ(server_proxy_->listening_port_, kTestPort);
169}
170
Andreea Costinas5862b102020-03-19 14:45:36 +0100171// Tests that ServerProxy handles the basic flow of a connect request:
172// - server accepts a connection a creates a job for it until the connection is
173// finished;
174// - the connect request from the client socket is read and parsed;
175// - proxy resolution request is correctly handled by the job and ServerProxy;
176// - client is sent an HTTP error code in case of failure;
177// - the failed connection job is removed from the queue.
Andreea Costinase45d54b2020-03-10 09:21:14 +0100178TEST_F(ServerProxyTest, HandleConnectRequest) {
179 server_proxy_->listening_addr_ = htonl(INADDR_LOOPBACK);
180 server_proxy_->listening_port_ = kTestPort;
181 // Redirect the worker stdin and stdout pipes.
182 RedirectStdPipes();
183 server_proxy_->CreateListeningSocket();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100184 CHECK_NE(-1, server_proxy_->listening_fd_->fd());
185 brillo_loop_.RunOnce(false);
186
187 struct sockaddr_in ipv4addr;
188 ipv4addr.sin_family = AF_INET;
189 ipv4addr.sin_port = htons(kTestPort);
190 ipv4addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
191
Andreea Costinas82a604e2021-04-09 13:19:19 +0200192 base::RunLoop run_loop;
193 server_proxy_->RunAfterOnConnectionAccept(run_loop.QuitClosure());
194
Andreea Costinase45d54b2020-03-10 09:21:14 +0100195 auto client_socket =
Garrick Evans3388a032020-03-24 11:25:55 +0900196 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100197 EXPECT_TRUE(client_socket->Connect((const struct sockaddr*)&ipv4addr,
198 sizeof(ipv4addr)));
Andreea Costinas82a604e2021-04-09 13:19:19 +0200199 // This loop will stop once a connection request is processed and added to the
200 // queue.
201 run_loop.Run();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100202
203 EXPECT_EQ(1, server_proxy_->pending_connect_jobs_.size());
Andreea Costinas5862b102020-03-19 14:45:36 +0100204 const std::string_view http_req =
205 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
206 client_socket->SendTo(http_req.data(), http_req.size());
207
208 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
209 .WillOnce(Return(stdout_write_fd_.get()));
210 brillo_loop_.RunOnce(false);
Andreea Costinasaae97382020-05-05 13:31:58 +0200211 worker::WorkerRequest request;
Andreea Costinas5862b102020-03-19 14:45:36 +0100212 // Read the request from the worker's stdout output.
213 ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
214 ASSERT_TRUE(request.has_proxy_resolution_request());
215
Andreea Costinasa89309d2020-05-08 15:51:12 +0200216 EXPECT_EQ("https://www.example.server.com:443",
Andreea Costinas5862b102020-03-19 14:45:36 +0100217 request.proxy_resolution_request().target_url());
218
219 EXPECT_EQ(1, server_proxy_->pending_proxy_resolution_requests_.size());
220
221 // Write reply with a fake proxy to the worker's standard input.
Andreea Costinasaae97382020-05-05 13:31:58 +0200222 worker::ProxyResolutionReply reply;
Andreea Costinas5862b102020-03-19 14:45:36 +0100223 reply.set_target_url(request.proxy_resolution_request().target_url());
224 reply.add_proxy_servers(kFakeProxyAddress);
Andreea Costinasaae97382020-05-05 13:31:58 +0200225 worker::WorkerConfigs configs;
Andreea Costinas5862b102020-03-19 14:45:36 +0100226 *configs.mutable_proxy_resolution_reply() = reply;
227
228 ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
229 brillo_loop_.RunOnce(false);
230
231 // Verify that the correct HTTP error code is sent to the client. Because
232 // curl_perform will fail, this will be reported as an internal server error.
233 const std::string expected_http_reply =
234 "HTTP/1.1 500 Internal Server Error - Origin: local proxy\r\n\r\n";
235 std::vector<char> buf(expected_http_reply.size());
236 ASSERT_TRUE(base::ReadFromFD(client_socket->fd(), buf.data(), buf.size()));
237 buf.push_back('\0');
238 const std::string actual_http_reply(buf.data());
239 EXPECT_EQ(expected_http_reply, actual_http_reply);
240 EXPECT_EQ(0, server_proxy_->pending_connect_jobs_.size());
Andreea Costinase45d54b2020-03-10 09:21:14 +0100241}
242
243// Tests the |OnConnectionSetupFinished| callback is handled correctly in case
244// of success or error.
245TEST_F(ServerProxyTest, HandlePendingJobs) {
246 int connection_count = 100;
247 int success_count = 51;
248 int failure_count = 49;
249 // Create |connection_count| connections.
250 for (int i = 0; i < connection_count; ++i) {
251 auto client_socket =
Garrick Evans3388a032020-03-24 11:25:55 +0900252 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100253 auto mock_connect_job = std::make_unique<MockProxyConnectJob>(
254 std::move(client_socket), "" /* credentials */,
255 base::BindOnce([](const std::string& target_url,
256 OnProxyResolvedCallback callback) {}),
Andreea Costinased9e6122020-08-12 12:06:19 +0200257 base::BindRepeating([](const std::string& proxy_url,
258 const std::string& realm,
259 const std::string& scheme,
260 const std::string& bad_cached_credentials,
261 OnAuthAcquiredCallback callback) {}),
Andreea Costinase45d54b2020-03-10 09:21:14 +0100262 base::BindOnce(&ServerProxy::OnConnectionSetupFinished,
263 base::Unretained(server_proxy_.get())));
264 server_proxy_->pending_connect_jobs_[mock_connect_job.get()] =
265 std::move(mock_connect_job);
266 }
267 // Resolve |failure_count| pending connections with error.
268 for (int i = 0; i < failure_count; ++i) {
269 auto job_iter = server_proxy_->pending_connect_jobs_.begin();
270 std::move(job_iter->second->setup_finished_callback_)
271 .Run(nullptr, job_iter->first);
272 }
273 // Expect failed requests have been cleared from the pending list and no
274 // forwarder.
275 EXPECT_EQ(success_count, server_proxy_->pending_connect_jobs_.size());
276 EXPECT_EQ(0, server_proxy_->forwarders_.size());
277
278 // Resolve |success_count| successful connections.
279 for (int i = 0; i < success_count; ++i) {
Garrick Evans3388a032020-03-24 11:25:55 +0900280 auto fwd = std::make_unique<patchpanel::SocketForwarder>(
Andreea Costinase45d54b2020-03-10 09:21:14 +0100281 "" /* thread name */,
Garrick Evans3388a032020-03-24 11:25:55 +0900282 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM),
283 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM));
Andreea Costinase45d54b2020-03-10 09:21:14 +0100284 fwd->Start();
285 auto job_iter = server_proxy_->pending_connect_jobs_.begin();
286 std::move(job_iter->second->setup_finished_callback_)
287 .Run(std::move(fwd), job_iter->first);
288 }
289
290 // Expect the successful requests to have been cleared and |success_count|
291 // active forwarders.
292 EXPECT_EQ(0, server_proxy_->pending_connect_jobs_.size());
293 EXPECT_EQ(success_count, server_proxy_->forwarders_.size());
294}
295
Andreea Costinas833eb7c2020-06-12 11:09:15 +0200296// Test to ensure proxy resolution requests are correctly handled if the
297// associated job is canceled before resolution.
298TEST_F(ServerProxyTest, HandleCanceledJobWhilePendingProxyResolution) {
299 server_proxy_->listening_addr_ = htonl(INADDR_LOOPBACK);
300 server_proxy_->listening_port_ = 3129;
301 // Redirect the worker stdin and stdout pipes.
302 RedirectStdPipes();
303 server_proxy_->CreateListeningSocket();
304 CHECK_NE(-1, server_proxy_->listening_fd_->fd());
305 brillo_loop_.RunOnce(false);
306
307 struct sockaddr_in ipv4addr;
308 ipv4addr.sin_family = AF_INET;
309 ipv4addr.sin_port = htons(3129);
310 ipv4addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
311
Andreea Costinas82a604e2021-04-09 13:19:19 +0200312 base::RunLoop run_loop;
313 server_proxy_->RunAfterOnConnectionAccept(run_loop.QuitClosure());
314
Andreea Costinas833eb7c2020-06-12 11:09:15 +0200315 auto client_socket =
316 std::make_unique<patchpanel::Socket>(AF_INET, SOCK_STREAM);
317 EXPECT_TRUE(client_socket->Connect((const struct sockaddr*)&ipv4addr,
318 sizeof(ipv4addr)));
Andreea Costinas82a604e2021-04-09 13:19:19 +0200319 // This loop will stop once a connection request is processed and added to the
320 // queue.
321 run_loop.Run();
Andreea Costinas833eb7c2020-06-12 11:09:15 +0200322
323 EXPECT_EQ(1, server_proxy_->pending_connect_jobs_.size());
324 const std::string_view http_req =
325 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
326 client_socket->SendTo(http_req.data(), http_req.size());
327
328 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
329 .WillOnce(Return(stdout_write_fd_.get()));
330 brillo_loop_.RunOnce(false);
331
332 EXPECT_EQ(1, server_proxy_->pending_connect_jobs_.size());
333 server_proxy_->pending_connect_jobs_.clear();
334
335 EXPECT_EQ(1, server_proxy_->pending_proxy_resolution_requests_.size());
336 server_proxy_->OnProxyResolved("https://www.example.server.com:443", {});
337
338 EXPECT_EQ(0, server_proxy_->pending_proxy_resolution_requests_.size());
339}
340
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200341// This test verifies that the athentication request is forwarded to the parent
342// process and that the pending authentication requests are resolved when the
343// parent sends the credentials associated with the protection space included in
344// the request.
345TEST_F(ServerProxyTest, HandlePendingAuthRequests) {
346 RedirectStdPipes();
347
348 worker::ProtectionSpace protection_space;
349 protection_space.set_origin(kFakeProxyAddress);
350 protection_space.set_scheme("Basic");
351 protection_space.set_realm("Proxy test realm");
352 std::string actual_credentials = "";
353
354 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
355 .WillOnce(Return(stdout_write_fd_.get()));
356
357 server_proxy_->AuthenticationRequired(
358 protection_space.origin(), protection_space.scheme(),
Andreea Costinased9e6122020-08-12 12:06:19 +0200359 protection_space.realm(), /* bad_cached_credentials = */ "",
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200360 base::Bind(
361 [](std::string* actual_credentials, const std::string& credentials) {
362 *actual_credentials = credentials;
363 },
364 &actual_credentials));
365
366 EXPECT_EQ(1, server_proxy_->pending_auth_required_requests_.size());
367 EXPECT_EQ(protection_space.SerializeAsString(),
368 server_proxy_->pending_auth_required_requests_.begin()->first);
369
370 brillo_loop_.RunOnce(false);
371
372 worker::WorkerRequest request;
373 // Read the request from the worker's stdout output.
374 ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
375 ASSERT_TRUE(request.has_auth_required_request());
376 ASSERT_TRUE(request.auth_required_request().has_protection_space());
377 EXPECT_EQ(
378 request.auth_required_request().protection_space().SerializeAsString(),
379 protection_space.SerializeAsString());
380
381 // Write reply with a fake credentials to the worker's standard input.
382 worker::Credentials credentials;
383 *credentials.mutable_protection_space() = protection_space;
384 credentials.set_username("test_user");
385 credentials.set_password("test_pwd");
386 worker::WorkerConfigs configs;
387 *configs.mutable_credentials() = credentials;
388
389 ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
390 brillo_loop_.RunOnce(false);
391 EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
392 EXPECT_EQ("test_user:test_pwd", actual_credentials);
393}
394
395// This test verifies that pending athentication requests are solved when the
396// parent returns empty credentials for the protection space.
397TEST_F(ServerProxyTest, HandlePendingAuthRequestsNoCredentials) {
398 RedirectStdPipes();
399
400 worker::ProtectionSpace protection_space;
401 protection_space.set_origin(kFakeProxyAddress);
402 protection_space.set_scheme("Basic");
403 protection_space.set_realm("Proxy test realm");
404 std::string actual_credentials = "";
405
406 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
407 .WillOnce(Return(stdout_write_fd_.get()));
408
409 server_proxy_->AuthenticationRequired(
410 protection_space.origin(), protection_space.scheme(),
Andreea Costinased9e6122020-08-12 12:06:19 +0200411 protection_space.realm(), /* bad_cached_credentials = */ "",
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200412 base::Bind(
413 [](std::string* actual_credentials, const std::string& credentials) {
414 *actual_credentials = credentials;
415 },
416 &actual_credentials));
417
418 EXPECT_EQ(1, server_proxy_->pending_auth_required_requests_.size());
419 EXPECT_EQ(protection_space.SerializeAsString(),
420 server_proxy_->pending_auth_required_requests_.begin()->first);
421
422 brillo_loop_.RunOnce(false);
423
424 worker::WorkerRequest request;
425 // Read the request from the worker's stdout output.
426 ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
427 ASSERT_TRUE(request.has_auth_required_request());
428 ASSERT_TRUE(request.auth_required_request().has_protection_space());
429 EXPECT_EQ(
430 request.auth_required_request().protection_space().SerializeAsString(),
431 protection_space.SerializeAsString());
432
433 // Write reply with a fake credentials to the worker's standard input.
434 worker::Credentials credentials;
435 *credentials.mutable_protection_space() = protection_space;
436 worker::WorkerConfigs configs;
437 *configs.mutable_credentials() = credentials;
438
439 ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
440 brillo_loop_.RunOnce(false);
441 EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
442 EXPECT_EQ("", actual_credentials);
443}
444
445// This test verifies that the athentication request is solved with cached
446// credentials.
447TEST_F(ServerProxyTest, HandlePendingAuthRequestsCachedCredentials) {
448 RedirectStdPipes();
449
450 worker::ProtectionSpace protection_space;
451 protection_space.set_origin(kFakeProxyAddress);
452 protection_space.set_scheme("Basic");
453 protection_space.set_realm("Proxy test realm");
454 std::string actual_credentials = "";
455
456 server_proxy_->auth_cache_[protection_space.SerializeAsString()] =
457 "test_user:test_pwd";
458
459 server_proxy_->AuthenticationRequired(
460 protection_space.origin(), protection_space.scheme(),
Andreea Costinased9e6122020-08-12 12:06:19 +0200461 protection_space.realm(), /* bad_cached_credentials = */ "",
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200462 base::Bind(
463 [](std::string* actual_credentials, const std::string& credentials) {
464 *actual_credentials = credentials;
465 },
466 &actual_credentials));
467
468 brillo_loop_.RunOnce(false);
469 EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
470 EXPECT_EQ("test_user:test_pwd", actual_credentials);
471}
472
Andreea Costinase9c73592020-07-17 15:27:54 +0200473// This test verifies that the stored credentials are removed when receiving a
474// |ClearUserCredentials| request.
475TEST_F(ServerProxyTest, ClearUserCredentials) {
476 worker::ProtectionSpace protection_space;
477 protection_space.set_origin(kFakeProxyAddress);
478 protection_space.set_scheme("Basic");
479 protection_space.set_realm("Proxy test realm");
480 // Add an entry in the cache.
481 server_proxy_->auth_cache_[protection_space.SerializeAsString()] =
482 "test_user:test_pwd";
483
484 worker::ClearUserCredentials clear_user_credentials;
485 worker::WorkerConfigs configs;
486 *configs.mutable_clear_user_credentials() = clear_user_credentials;
487 // Redirect the worker stdin and stdout pipes.
488 RedirectStdPipes();
489 // Send the config to the worker's stdin input.
490 EXPECT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
491 brillo_loop_.RunOnce(false);
492 // Expect that the credentials were cleared.
493 EXPECT_EQ(0, server_proxy_->auth_cache_.size());
494}
495
Andreea Costinased9e6122020-08-12 12:06:19 +0200496// Verifies that even if there are credentials in the cache for the remote
497// web-proxy, the ServerProxy sends a request to the parent web-proxy if the
498// credentials are flagged as bad.
499TEST_F(ServerProxyTest, AuthRequestsBadCachedCredentials) {
500 constexpr char kBadCachedCredetials[] = "bad_user:bad_pwd";
501 constexpr char kCredetials[] = "test_user:test_pwd";
502
503 RedirectStdPipes();
504 EXPECT_CALL(*server_proxy_, GetStdoutPipe())
505 .WillOnce(Return(stdout_write_fd_.get()));
506
507 // Add credentials to the cache for the proxy.
508 worker::ProtectionSpace protection_space;
509 protection_space.set_origin(kFakeProxyAddress);
510 protection_space.set_scheme("Basic");
511 protection_space.set_realm("Proxy test realm");
512 server_proxy_->auth_cache_[protection_space.SerializeAsString()] =
513 kBadCachedCredetials;
514
515 // Request credentials for the proxy.
516 std::string actual_credentials = "";
517 server_proxy_->AuthenticationRequired(
518 protection_space.origin(), protection_space.scheme(),
519 protection_space.realm(), kBadCachedCredetials,
520 base::Bind(
521 [](std::string* actual_credentials, const std::string& credentials) {
522 *actual_credentials = credentials;
523 },
524 &actual_credentials));
525
526 // Expect that the credentials are not served from the cache.
527 EXPECT_EQ(1, server_proxy_->pending_auth_required_requests_.size());
528 EXPECT_EQ(protection_space.SerializeAsString(),
529 server_proxy_->pending_auth_required_requests_.begin()->first);
530
531 brillo_loop_.RunOnce(false);
532
533 worker::WorkerRequest request;
534 // Read the request from the worker's stdout output.
535 ASSERT_TRUE(ReadProtobuf(stdout_read_fd_.get(), &request));
536 ASSERT_TRUE(request.has_auth_required_request());
537 ASSERT_TRUE(request.auth_required_request().has_protection_space());
538 EXPECT_EQ(
539 request.auth_required_request().protection_space().SerializeAsString(),
540 protection_space.SerializeAsString());
541
542 // Write reply with a fake credentials to the worker's standard input.
543 worker::Credentials credentials;
544 *credentials.mutable_protection_space() = protection_space;
545 credentials.set_username("test_user");
546 credentials.set_password("test_pwd");
547 worker::WorkerConfigs configs;
548 *configs.mutable_credentials() = credentials;
549
550 ASSERT_TRUE(WriteProtobuf(stdin_write_fd_.get(), configs));
551 brillo_loop_.RunOnce(false);
552 EXPECT_EQ(0, server_proxy_->pending_auth_required_requests_.size());
553 EXPECT_EQ(kCredetials, actual_credentials);
554}
555
Andreea Costinas44cefa22020-03-09 09:07:39 +0100556} // namespace system_proxy