blob: ad19db162519927b4c1623969b5f71be18367fbf [file] [log] [blame]
Andreea Costinase45d54b2020-03-10 09:21:14 +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/proxy_connect_job.h"
6
7#include <netinet/in.h>
8#include <sys/socket.h>
9#include <sys/types.h>
10
11#include <gmock/gmock.h>
12#include <gtest/gtest.h>
13#include <utility>
14
Andreea Costinascc4d54e2020-10-19 15:46:25 +020015#include <curl/curl.h>
16
Andreea Costinase45d54b2020-03-10 09:21:14 +010017#include <base/bind.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010018#include <base/callback_helpers.h>
19#include <base/files/file_util.h>
20#include <base/files/scoped_file.h>
Andreea Costinascc4d54e2020-10-19 15:46:25 +020021#include <base/strings/stringprintf.h>
Qijiang Fan34014672020-07-20 16:05:38 +090022#include <base/task/single_thread_task_executor.h>
Andreea Costinas08a5d182020-04-29 22:12:47 +020023#include <base/test/test_mock_time_task_runner.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010024#include <brillo/message_loops/base_message_loop.h>
Andreea Costinascc4d54e2020-10-19 15:46:25 +020025#include <chromeos/patchpanel/net_util.h>
Garrick Evanscd8c2972020-04-14 14:35:52 +090026#include <chromeos/patchpanel/socket.h>
27#include <chromeos/patchpanel/socket_forwarder.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010028
29#include "bindings/worker_common.pb.h"
30#include "system-proxy/protobuf_util.h"
Andreea Costinas054fbb52020-06-12 20:46:22 +020031#include "system-proxy/test_http_server.h"
Andreea Costinase45d54b2020-03-10 09:21:14 +010032
33namespace {
Andreea Costinas054fbb52020-06-12 20:46:22 +020034
Andreea Costinasbb2aa022020-06-13 00:03:23 +020035constexpr char kCredentials[] = "username:pwd";
Andreea Costinas054fbb52020-06-12 20:46:22 +020036constexpr char kValidConnectRequest[] =
37 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
Andreea Costinascc4d54e2020-10-19 15:46:25 +020038constexpr char kProxyAuthorizationHeaderToken[] = "Proxy-Authorization:";
Andreea Costinase45d54b2020-03-10 09:21:14 +010039} // namespace
40
41namespace system_proxy {
42
43using ::testing::_;
44using ::testing::Return;
45
46class ProxyConnectJobTest : public ::testing::Test {
47 public:
Andreea Costinasbb2aa022020-06-13 00:03:23 +020048 struct HttpAuthEntry {
49 HttpAuthEntry(const std::string& origin,
50 const std::string& scheme,
51 const std::string& realm,
52 const std::string& credentials)
53 : origin(origin),
54 scheme(scheme),
55 realm(realm),
56 credentials(credentials) {}
57 std::string origin;
58 std::string scheme;
59 std::string realm;
60 std::string credentials;
61 };
Andreea Costinase45d54b2020-03-10 09:21:14 +010062 ProxyConnectJobTest() = default;
63 ProxyConnectJobTest(const ProxyConnectJobTest&) = delete;
64 ProxyConnectJobTest& operator=(const ProxyConnectJobTest&) = delete;
65 ~ProxyConnectJobTest() = default;
66
67 void SetUp() override {
68 int fds[2];
69 ASSERT_NE(-1,
70 socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
71 0 /* protocol */, fds));
72 cros_client_socket_ =
Garrick Evans3388a032020-03-24 11:25:55 +090073 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[1]));
Andreea Costinase45d54b2020-03-10 09:21:14 +010074
Andreea-Elena Costinasfae5c152020-09-28 18:18:31 +000075 connect_job_ = std::make_unique<ProxyConnectJob>(
Garrick Evans3388a032020-03-24 11:25:55 +090076 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[0])), "",
Andreea Costinascc4d54e2020-10-19 15:46:25 +020077 CURLAUTH_ANY,
Andreea Costinase45d54b2020-03-10 09:21:14 +010078 base::BindOnce(&ProxyConnectJobTest::ResolveProxy,
79 base::Unretained(this)),
Andreea Costinased9e6122020-08-12 12:06:19 +020080 base::BindRepeating(&ProxyConnectJobTest::OnAuthCredentialsRequired,
81 base::Unretained(this)),
Andreea Costinase45d54b2020-03-10 09:21:14 +010082 base::BindOnce(&ProxyConnectJobTest::OnConnectionSetupFinished,
83 base::Unretained(this)));
Andreea Costinase45d54b2020-03-10 09:21:14 +010084 }
85
86 protected:
Andreea Costinasa8bc59c2020-09-18 13:49:13 +020087 virtual void OnConnectionSetupFinished(
88 std::unique_ptr<patchpanel::SocketForwarder> fwd,
89 ProxyConnectJob* connect_job) {}
90 virtual void ResolveProxy(
Andreea Costinase45d54b2020-03-10 09:21:14 +010091 const std::string& target_url,
92 base::OnceCallback<void(const std::list<std::string>&)> callback) {
Andreea Costinasa8bc59c2020-09-18 13:49:13 +020093 std::move(callback).Run({});
Andreea Costinase45d54b2020-03-10 09:21:14 +010094 }
Andreea Costinasa8bc59c2020-09-18 13:49:13 +020095 virtual void OnAuthCredentialsRequired(
Andreea Costinasbb2aa022020-06-13 00:03:23 +020096 const std::string& proxy_url,
97 const std::string& scheme,
98 const std::string& realm,
Andreea Costinased9e6122020-08-12 12:06:19 +020099 const std::string& bad_credentials,
100 base::RepeatingCallback<void(const std::string&)> callback) {
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200101 std::move(callback).Run(/* credentials = */ "");
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200102 }
103
Andreea Costinase45d54b2020-03-10 09:21:14 +0100104 std::unique_ptr<ProxyConnectJob> connect_job_;
Qijiang Fan34014672020-07-20 16:05:38 +0900105 base::SingleThreadTaskExecutor task_executor_{base::MessagePumpType::IO};
106 std::unique_ptr<brillo::BaseMessageLoop> brillo_loop_{
107 std::make_unique<brillo::BaseMessageLoop>(task_executor_.task_runner())};
Garrick Evans3388a032020-03-24 11:25:55 +0900108 std::unique_ptr<patchpanel::Socket> cros_client_socket_;
Andreea Costinas08a5d182020-04-29 22:12:47 +0200109
Andreea Costinas08a5d182020-04-29 22:12:47 +0200110 FRIEND_TEST(ProxyConnectJobTest, ClientConnectTimeoutJobCanceled);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100111};
112
Andreea Costinase45d54b2020-03-10 09:21:14 +0100113TEST_F(ProxyConnectJobTest, BadHttpRequestWrongMethod) {
Andreea Costinas08a5d182020-04-29 22:12:47 +0200114 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100115 char badConnRequest[] = "GET www.example.server.com:443 HTTP/1.1\r\n\r\n";
116 cros_client_socket_->SendTo(badConnRequest, std::strlen(badConnRequest));
Qijiang Fan34014672020-07-20 16:05:38 +0900117 brillo_loop_->RunOnce(false);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100118
119 EXPECT_EQ("", connect_job_->target_url_);
120 EXPECT_EQ(0, connect_job_->proxy_servers_.size());
121 const std::string expected_http_response =
122 "HTTP/1.1 400 Bad Request - Origin: local proxy\r\n\r\n";
123 std::vector<char> buf(expected_http_response.size());
124 ASSERT_TRUE(
125 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
126 std::string actual_response(buf.data(), buf.size());
127 EXPECT_EQ(expected_http_response, actual_response);
128}
129
Andreea Costinas435851b2020-05-25 14:18:41 +0200130TEST_F(ProxyConnectJobTest, SlowlorisTimeout) {
131 // Add a TaskRunner where we can control time.
132 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
133 new base::TestMockTimeTaskRunner()};
134 brillo_loop_ = nullptr;
135 brillo_loop_ = std::make_unique<brillo::BaseMessageLoop>(task_runner);
136 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
137
Andreea Costinas08a5d182020-04-29 22:12:47 +0200138 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100139 // No empty line after http message.
140 char badConnRequest[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n";
141 cros_client_socket_->SendTo(badConnRequest, std::strlen(badConnRequest));
Andreea Costinas435851b2020-05-25 14:18:41 +0200142 task_runner->RunUntilIdle();
143
144 EXPECT_EQ(1, task_runner->GetPendingTaskCount());
145 constexpr base::TimeDelta kDoubleWaitClientConnectTimeout =
146 base::TimeDelta::FromSeconds(4);
147 // Move the time ahead so that the client connection timeout callback is
148 // triggered.
149 task_runner->FastForwardBy(kDoubleWaitClientConnectTimeout);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100150
151 EXPECT_EQ("", connect_job_->target_url_);
152 EXPECT_EQ(0, connect_job_->proxy_servers_.size());
153 const std::string expected_http_response =
Andreea Costinas435851b2020-05-25 14:18:41 +0200154 "HTTP/1.1 408 Request Timeout - Origin: local proxy\r\n\r\n";
Andreea Costinase45d54b2020-03-10 09:21:14 +0100155 std::vector<char> buf(expected_http_response.size());
156 ASSERT_TRUE(
157 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
158 std::string actual_response(buf.data(), buf.size());
159 EXPECT_EQ(expected_http_response, actual_response);
160}
161
Andreea Costinas08a5d182020-04-29 22:12:47 +0200162TEST_F(ProxyConnectJobTest, WaitClientConnectTimeout) {
163 // Add a TaskRunner where we can control time.
164 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
165 new base::TestMockTimeTaskRunner()};
Qijiang Fan34014672020-07-20 16:05:38 +0900166 brillo_loop_ = nullptr;
167 brillo_loop_ = std::make_unique<brillo::BaseMessageLoop>(task_runner);
Andreea Costinas08a5d182020-04-29 22:12:47 +0200168 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
169
170 connect_job_->Start();
171
172 EXPECT_EQ(1, task_runner->GetPendingTaskCount());
173 // Move the time ahead so that the client connection timeout callback is
174 // triggered.
175 task_runner->FastForwardBy(task_runner->NextPendingTaskDelay());
176
177 const std::string expected_http_response =
178 "HTTP/1.1 408 Request Timeout - Origin: local proxy\r\n\r\n";
179 std::vector<char> buf(expected_http_response.size());
180 ASSERT_TRUE(
181 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
182 std::string actual_response(buf.data(), buf.size());
183
184 EXPECT_EQ(expected_http_response, actual_response);
185}
186
187// Check that the client connect timeout callback is not fired if the owning
188// proxy connect job is destroyed.
189TEST_F(ProxyConnectJobTest, ClientConnectTimeoutJobCanceled) {
190 // Add a TaskRunner where we can control time.
191 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
192 new base::TestMockTimeTaskRunner()};
Qijiang Fan34014672020-07-20 16:05:38 +0900193 brillo_loop_ = nullptr;
194 brillo_loop_ = std::make_unique<brillo::BaseMessageLoop>(task_runner);
Andreea Costinas08a5d182020-04-29 22:12:47 +0200195 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
196
197 // Create a proxy connect job and start the client connect timeout counter.
198 {
199 int fds[2];
200 ASSERT_NE(-1,
201 socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
202 0 /* protocol */, fds));
203 auto client_socket =
204 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[1]));
205
206 auto connect_job = std::make_unique<ProxyConnectJob>(
207 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[0])), "",
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200208 CURLAUTH_ANY,
Andreea Costinas08a5d182020-04-29 22:12:47 +0200209 base::BindOnce(&ProxyConnectJobTest::ResolveProxy,
210 base::Unretained(this)),
Andreea Costinased9e6122020-08-12 12:06:19 +0200211 base::BindRepeating(&ProxyConnectJobTest::OnAuthCredentialsRequired,
212 base::Unretained(this)),
Andreea Costinas08a5d182020-04-29 22:12:47 +0200213 base::BindOnce(&ProxyConnectJobTest::OnConnectionSetupFinished,
214 base::Unretained(this)));
215 // Post the timeout task.
216 connect_job->Start();
217 EXPECT_TRUE(task_runner->HasPendingTask());
218 }
219 // Check that the task was canceled.
220 EXPECT_FALSE(task_runner->HasPendingTask());
221}
222
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200223class HttpServerProxyConnectJobTest : public ProxyConnectJobTest {
224 public:
225 HttpServerProxyConnectJobTest() = default;
226 HttpServerProxyConnectJobTest(const HttpServerProxyConnectJobTest&) = delete;
227 HttpServerProxyConnectJobTest& operator=(
228 const HttpServerProxyConnectJobTest&) = delete;
229 ~HttpServerProxyConnectJobTest() = default;
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200230
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200231 protected:
232 HttpTestServer http_test_server_;
233
234 std::vector<HttpAuthEntry> http_auth_cache_;
235 bool auth_requested_ = false;
236
237 void AddHttpAuthEntry(const std::string& origin,
238 const std::string& scheme,
239 const std::string& realm,
240 const std::string& credentials) {
241 http_auth_cache_.push_back(
242 HttpAuthEntry(origin, scheme, realm, credentials));
243 }
244
245 bool AuthRequested() { return auth_requested_; }
246
247 void AddServerReply(HttpTestServer::HttpConnectReply reply) {
248 http_test_server_.AddHttpConnectReply(reply);
249 }
250
251 void ResolveProxy(const std::string& target_url,
252 base::OnceCallback<void(const std::list<std::string>&)>
253 callback) override {
254 // Return the URL of the test proxy.
255 std::move(callback).Run({http_test_server_.GetUrl()});
256 }
257
258 void OnAuthCredentialsRequired(
259 const std::string& proxy_url,
260 const std::string& scheme,
261 const std::string& realm,
262 const std::string& bad_credentials,
263 base::RepeatingCallback<void(const std::string&)> callback) override {
264 auth_requested_ = true;
265 for (const auto& auth_entry : http_auth_cache_) {
266 if (auth_entry.origin == proxy_url && auth_entry.realm == realm &&
267 auth_entry.scheme == scheme) {
268 std::move(callback).Run(auth_entry.credentials);
269 return;
270 }
271 }
272 if (invoke_authentication_callback_) {
273 std::move(callback).Run(/* credentials = */ "");
274 }
275 }
276 void OnConnectionSetupFinished(
277 std::unique_ptr<patchpanel::SocketForwarder> fwd,
278 ProxyConnectJob* connect_job) override {
279 ASSERT_EQ(connect_job, connect_job_.get());
280 if (fwd) {
281 forwarder_created_ = true;
282
283 brillo_loop_->RunOnce(false);
284
285 fwd.reset();
286 }
287 }
288
289 bool forwarder_created_ = false;
290 // Used to simulate time-outs while waiting for credentials from the Browser.
291 bool invoke_authentication_callback_ = true;
292};
293
294TEST_F(HttpServerProxyConnectJobTest, SuccessfulConnection) {
295 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
296 http_test_server_.Start();
297
298 connect_job_->Start();
299 cros_client_socket_->SendTo(kValidConnectRequest,
300 std::strlen(kValidConnectRequest));
301 brillo_loop_->RunOnce(false);
302 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
303 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
304 EXPECT_EQ(http_test_server_.GetUrl(), connect_job_->proxy_servers_.front());
305 EXPECT_TRUE(forwarder_created_);
306}
307
Andreea Costinas435851b2020-05-25 14:18:41 +0200308TEST_F(HttpServerProxyConnectJobTest, MultipleReadConnectRequest) {
309 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
310 http_test_server_.Start();
311 connect_job_->Start();
312 char part1[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n";
313 char part2[] = "\r\n";
314 cros_client_socket_->SendTo(part1, std::strlen(part1));
315 // Process the partial CONNECT request.
316 brillo_loop_->RunOnce(false);
317 cros_client_socket_->SendTo(part2, std::strlen(part2));
318 brillo_loop_->RunOnce(false);
319 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
320 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
321 EXPECT_EQ(http_test_server_.GetUrl(), connect_job_->proxy_servers_.front());
322 EXPECT_TRUE(forwarder_created_);
323}
324
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200325TEST_F(HttpServerProxyConnectJobTest, TunnelFailedBadGatewayFromRemote) {
326 AddServerReply(HttpTestServer::HttpConnectReply::kBadGateway);
327 http_test_server_.Start();
328
329 connect_job_->Start();
330 cros_client_socket_->SendTo(kValidConnectRequest,
331 std::strlen(kValidConnectRequest));
332 brillo_loop_->RunOnce(false);
333 EXPECT_FALSE(forwarder_created_);
334
335 std::string expected_server_reply =
336 "HTTP/1.1 502 Error creating tunnel - Origin: local proxy\r\n\r\n";
337 std::vector<char> buf(expected_server_reply.size());
338
339 ASSERT_TRUE(cros_client_socket_->RecvFrom(buf.data(), buf.size()));
340 std::string actual_server_reply(buf.data(), buf.size());
341 EXPECT_EQ(expected_server_reply, actual_server_reply);
342}
343
344TEST_F(HttpServerProxyConnectJobTest, SuccessfulConnectionAltEnding) {
345 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
346 http_test_server_.Start();
347
348 connect_job_->Start();
349 char validConnRequest[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n\n";
350 cros_client_socket_->SendTo(validConnRequest, std::strlen(validConnRequest));
351 brillo_loop_->RunOnce(false);
352
353 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
354 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
355 EXPECT_EQ(http_test_server_.GetUrl(), connect_job_->proxy_servers_.front());
356 EXPECT_TRUE(forwarder_created_);
357 ASSERT_FALSE(AuthRequested());
358}
359
360// Test that the the CONNECT request is sent again after acquiring credentials.
361TEST_F(HttpServerProxyConnectJobTest, ResendWithCredentials) {
362 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
363 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
364 http_test_server_.Start();
365
366 AddHttpAuthEntry(http_test_server_.GetUrl(), "Basic", "\"My Proxy\"",
367 kCredentials);
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200368 connect_job_->Start();
369
370 cros_client_socket_->SendTo(kValidConnectRequest,
371 std::strlen(kValidConnectRequest));
Qijiang Fan34014672020-07-20 16:05:38 +0900372 brillo_loop_->RunOnce(false);
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200373
374 ASSERT_TRUE(AuthRequested());
375 EXPECT_TRUE(forwarder_created_);
376 EXPECT_EQ(kCredentials, connect_job_->credentials_);
377 EXPECT_EQ(200, connect_job_->http_response_code_);
378}
379
380// Test that the proxy auth required status is forwarded to the client if
381// credentials are missing.
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200382TEST_F(HttpServerProxyConnectJobTest, NoCredentials) {
383 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
384 http_test_server_.Start();
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200385 connect_job_->Start();
386
387 cros_client_socket_->SendTo(kValidConnectRequest,
388 std::strlen(kValidConnectRequest));
Qijiang Fan34014672020-07-20 16:05:38 +0900389 brillo_loop_->RunOnce(false);
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200390
391 ASSERT_TRUE(AuthRequested());
392 EXPECT_EQ("", connect_job_->credentials_);
393 EXPECT_EQ(407, connect_job_->http_response_code_);
394}
395
396// Test that the proxy auth required status is forwarded to the client if the
397// server chose Kerberos as an authentication method.
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200398TEST_F(HttpServerProxyConnectJobTest, KerberosAuth) {
399 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredKerberos);
400 http_test_server_.Start();
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200401
402 connect_job_->Start();
403
404 cros_client_socket_->SendTo(kValidConnectRequest,
405 std::strlen(kValidConnectRequest));
Qijiang Fan34014672020-07-20 16:05:38 +0900406 brillo_loop_->RunOnce(false);
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200407
408 ASSERT_FALSE(AuthRequested());
409 EXPECT_EQ("", connect_job_->credentials_);
410 EXPECT_EQ(407, connect_job_->http_response_code_);
411}
412
Andreea Costinased9e6122020-08-12 12:06:19 +0200413// Test that the connection times out while waiting for credentials from the
414// Browser.
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200415TEST_F(HttpServerProxyConnectJobTest, AuthenticationTimeout) {
Andreea Costinased9e6122020-08-12 12:06:19 +0200416 // Add a TaskRunner where we can control time.
417 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
418 new base::TestMockTimeTaskRunner()};
419 brillo_loop_ = nullptr;
420 brillo_loop_ = std::make_unique<brillo::BaseMessageLoop>(task_runner);
421 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
422
423 invoke_authentication_callback_ = false;
424
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200425 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
426 http_test_server_.Start();
Andreea Costinased9e6122020-08-12 12:06:19 +0200427
428 connect_job_->Start();
429
430 cros_client_socket_->SendTo(kValidConnectRequest,
431 std::strlen(kValidConnectRequest));
432 task_runner->RunUntilIdle();
433 // We need to manually invoke the method which reads from the client socket
434 // because |task_runner| will not execute the FileDescriptorWatcher's tasks.
435 connect_job_->OnClientReadReady();
436
437 // Check that an authentication request was sent.
438 ASSERT_TRUE(AuthRequested());
439 EXPECT_EQ(1, task_runner->GetPendingTaskCount());
440 // Move the time ahead so that the client connection timeout callback is
441 // triggered.
442 task_runner->FastForwardBy(task_runner->NextPendingTaskDelay());
443
444 const std::string expected_http_response =
445 "HTTP/1.1 407 Credentials required - Origin: local proxy\r\n\r\n";
446 std::vector<char> buf(expected_http_response.size());
447 ASSERT_TRUE(
448 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
449 std::string actual_response(buf.data(), buf.size());
450
451 // Check that the auth failure was forwarded to the client.
452 EXPECT_EQ(expected_http_response, actual_response);
453}
454
455// Verifies that the receiving the same bad credentials twice will send an auth
456// failure to the Chrome OS local client.
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200457TEST_F(HttpServerProxyConnectJobTest, CancelIfBadCredentials) {
458 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
459 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
460 http_test_server_.Start();
461
462 AddHttpAuthEntry(http_test_server_.GetUrl(), "Basic", "\"My Proxy\"",
463 kCredentials);
Andreea Costinased9e6122020-08-12 12:06:19 +0200464
465 connect_job_->Start();
466
467 cros_client_socket_->SendTo(kValidConnectRequest,
468 std::strlen(kValidConnectRequest));
469 brillo_loop_->RunOnce(false);
470
471 ASSERT_TRUE(AuthRequested());
472
473 const std::string expected_http_response =
474 "HTTP/1.1 407 Credentials required - Origin: local proxy\r\n\r\n";
475 std::vector<char> buf(expected_http_response.size());
476 ASSERT_TRUE(
477 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
478 std::string actual_response(buf.data(), buf.size());
479
480 EXPECT_EQ(expected_http_response, actual_response);
481}
482
Andreea Costinas435851b2020-05-25 14:18:41 +0200483// This test verifies that any data sent by a client immediately after the end
484// of the HTTP CONNECT request is cached correctly.
485TEST_F(HttpServerProxyConnectJobTest, BufferedClientData) {
486 char connectRequestwithData[] =
487 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\nTest body";
488 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
489 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
490 http_test_server_.Start();
491
492 AddHttpAuthEntry(http_test_server_.GetUrl(), "Basic", "\"My Proxy\"",
493 kCredentials);
494 connect_job_->Start();
495
496 cros_client_socket_->SendTo(connectRequestwithData,
497 std::strlen(connectRequestwithData));
498 brillo_loop_->RunOnce(false);
499
500 const std::string expected = "Test body";
501 const std::string actual(connect_job_->connect_data_.data(),
502 connect_job_->connect_data_.size());
503}
504
505TEST_F(HttpServerProxyConnectJobTest, BufferedClientDataAltEnding) {
506 char connectRequestwithData[] =
507 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\nTest body";
508 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
509 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
510 http_test_server_.Start();
511
512 AddHttpAuthEntry(http_test_server_.GetUrl(), "Basic", "\"My Proxy\"",
513 kCredentials);
514 connect_job_->Start();
515
516 cros_client_socket_->SendTo(connectRequestwithData,
517 std::strlen(connectRequestwithData));
518 brillo_loop_->RunOnce(false);
519
520 const std::string expected = "Test body";
521 const std::string actual(connect_job_->connect_data_.data(),
522 connect_job_->connect_data_.size());
523}
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200524
525// Test that the policy auth scheme is respected by curl.
526TEST_F(HttpServerProxyConnectJobTest, PolicyAuthSchemeOk) {
527 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
528 http_test_server_.Start();
529 connect_job_->credentials_ = "a:b";
530 connect_job_->curl_auth_schemes_ = CURLAUTH_BASIC;
531 connect_job_->StoreRequestHeadersForTesting();
532 connect_job_->Start();
533
534 cros_client_socket_->SendTo(kValidConnectRequest,
535 std::strlen(kValidConnectRequest));
536 brillo_loop_->RunOnce(false);
537
538 std::size_t pos = connect_job_->GetRequestHeadersForTesting().find(
539 kProxyAuthorizationHeaderToken);
540 // Expect to find the proxy auth headers since CURLAUTH_BASIC is an allowed
541 // auth scheme.
542 EXPECT_NE(pos, std::string::npos);
543}
544
545// Test that proxy auth headers with credentials are not sent by curl if the
546// auth scheme used by the server is not allowed.
547TEST_F(HttpServerProxyConnectJobTest, PolicyAuthBadScheme) {
548 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
549 http_test_server_.Start();
550 connect_job_->credentials_ = "a:b";
551 connect_job_->curl_auth_schemes_ = CURLAUTH_DIGEST;
552 connect_job_->StoreRequestHeadersForTesting();
553 connect_job_->Start();
554
555 cros_client_socket_->SendTo(kValidConnectRequest,
556 std::strlen(kValidConnectRequest));
557 brillo_loop_->RunOnce(false);
558
559 std::size_t pos = connect_job_->GetRequestHeadersForTesting().find(
560 kProxyAuthorizationHeaderToken);
561 // Expect not to find the proxy auth headers since CURLAUTH_BASIC is not an
562 // allowed auth scheme.
563 EXPECT_EQ(pos, std::string::npos);
564}
565
Andreea Costinase45d54b2020-03-10 09:21:14 +0100566} // namespace system_proxy