blob: 0c352c2df031694d4d4ec8d147fbbf4ce815a8ac [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>
18#include <base/bind_helpers.h>
19#include <base/callback_helpers.h>
20#include <base/files/file_util.h>
21#include <base/files/scoped_file.h>
Andreea Costinascc4d54e2020-10-19 15:46:25 +020022#include <base/strings/stringprintf.h>
Qijiang Fan34014672020-07-20 16:05:38 +090023#include <base/task/single_thread_task_executor.h>
Andreea Costinas08a5d182020-04-29 22:12:47 +020024#include <base/test/test_mock_time_task_runner.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010025#include <brillo/message_loops/base_message_loop.h>
Andreea Costinascc4d54e2020-10-19 15:46:25 +020026#include <chromeos/patchpanel/net_util.h>
Garrick Evanscd8c2972020-04-14 14:35:52 +090027#include <chromeos/patchpanel/socket.h>
28#include <chromeos/patchpanel/socket_forwarder.h>
Andreea Costinase45d54b2020-03-10 09:21:14 +010029
30#include "bindings/worker_common.pb.h"
31#include "system-proxy/protobuf_util.h"
Andreea Costinas054fbb52020-06-12 20:46:22 +020032#include "system-proxy/test_http_server.h"
Andreea Costinase45d54b2020-03-10 09:21:14 +010033
34namespace {
Andreea Costinas054fbb52020-06-12 20:46:22 +020035
Andreea Costinasbb2aa022020-06-13 00:03:23 +020036constexpr char kCredentials[] = "username:pwd";
Andreea Costinas054fbb52020-06-12 20:46:22 +020037constexpr char kValidConnectRequest[] =
38 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\n";
Andreea Costinascc4d54e2020-10-19 15:46:25 +020039constexpr char kProxyAuthorizationHeaderToken[] = "Proxy-Authorization:";
Andreea Costinase45d54b2020-03-10 09:21:14 +010040} // namespace
41
42namespace system_proxy {
43
44using ::testing::_;
45using ::testing::Return;
46
47class ProxyConnectJobTest : public ::testing::Test {
48 public:
Andreea Costinasbb2aa022020-06-13 00:03:23 +020049 struct HttpAuthEntry {
50 HttpAuthEntry(const std::string& origin,
51 const std::string& scheme,
52 const std::string& realm,
53 const std::string& credentials)
54 : origin(origin),
55 scheme(scheme),
56 realm(realm),
57 credentials(credentials) {}
58 std::string origin;
59 std::string scheme;
60 std::string realm;
61 std::string credentials;
62 };
Andreea Costinase45d54b2020-03-10 09:21:14 +010063 ProxyConnectJobTest() = default;
64 ProxyConnectJobTest(const ProxyConnectJobTest&) = delete;
65 ProxyConnectJobTest& operator=(const ProxyConnectJobTest&) = delete;
66 ~ProxyConnectJobTest() = default;
67
68 void SetUp() override {
69 int fds[2];
70 ASSERT_NE(-1,
71 socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
72 0 /* protocol */, fds));
73 cros_client_socket_ =
Garrick Evans3388a032020-03-24 11:25:55 +090074 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[1]));
Andreea Costinase45d54b2020-03-10 09:21:14 +010075
Andreea-Elena Costinasfae5c152020-09-28 18:18:31 +000076 connect_job_ = std::make_unique<ProxyConnectJob>(
Garrick Evans3388a032020-03-24 11:25:55 +090077 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[0])), "",
Andreea Costinascc4d54e2020-10-19 15:46:25 +020078 CURLAUTH_ANY,
Andreea Costinase45d54b2020-03-10 09:21:14 +010079 base::BindOnce(&ProxyConnectJobTest::ResolveProxy,
80 base::Unretained(this)),
Andreea Costinased9e6122020-08-12 12:06:19 +020081 base::BindRepeating(&ProxyConnectJobTest::OnAuthCredentialsRequired,
82 base::Unretained(this)),
Andreea Costinase45d54b2020-03-10 09:21:14 +010083 base::BindOnce(&ProxyConnectJobTest::OnConnectionSetupFinished,
84 base::Unretained(this)));
Andreea Costinase45d54b2020-03-10 09:21:14 +010085 }
86
87 protected:
Andreea Costinasa8bc59c2020-09-18 13:49:13 +020088 virtual void OnConnectionSetupFinished(
89 std::unique_ptr<patchpanel::SocketForwarder> fwd,
90 ProxyConnectJob* connect_job) {}
91 virtual void ResolveProxy(
Andreea Costinase45d54b2020-03-10 09:21:14 +010092 const std::string& target_url,
93 base::OnceCallback<void(const std::list<std::string>&)> callback) {
Andreea Costinasa8bc59c2020-09-18 13:49:13 +020094 std::move(callback).Run({});
Andreea Costinase45d54b2020-03-10 09:21:14 +010095 }
Andreea Costinasa8bc59c2020-09-18 13:49:13 +020096 virtual void OnAuthCredentialsRequired(
Andreea Costinasbb2aa022020-06-13 00:03:23 +020097 const std::string& proxy_url,
98 const std::string& scheme,
99 const std::string& realm,
Andreea Costinased9e6122020-08-12 12:06:19 +0200100 const std::string& bad_credentials,
101 base::RepeatingCallback<void(const std::string&)> callback) {
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200102 std::move(callback).Run(/* credentials = */ "");
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200103 }
104
Andreea Costinase45d54b2020-03-10 09:21:14 +0100105 std::unique_ptr<ProxyConnectJob> connect_job_;
Qijiang Fan34014672020-07-20 16:05:38 +0900106 base::SingleThreadTaskExecutor task_executor_{base::MessagePumpType::IO};
107 std::unique_ptr<brillo::BaseMessageLoop> brillo_loop_{
108 std::make_unique<brillo::BaseMessageLoop>(task_executor_.task_runner())};
Garrick Evans3388a032020-03-24 11:25:55 +0900109 std::unique_ptr<patchpanel::Socket> cros_client_socket_;
Andreea Costinas08a5d182020-04-29 22:12:47 +0200110
Andreea Costinas08a5d182020-04-29 22:12:47 +0200111 FRIEND_TEST(ProxyConnectJobTest, ClientConnectTimeoutJobCanceled);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100112};
113
Andreea Costinase45d54b2020-03-10 09:21:14 +0100114TEST_F(ProxyConnectJobTest, BadHttpRequestWrongMethod) {
Andreea Costinas08a5d182020-04-29 22:12:47 +0200115 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100116 char badConnRequest[] = "GET www.example.server.com:443 HTTP/1.1\r\n\r\n";
117 cros_client_socket_->SendTo(badConnRequest, std::strlen(badConnRequest));
Qijiang Fan34014672020-07-20 16:05:38 +0900118 brillo_loop_->RunOnce(false);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100119
120 EXPECT_EQ("", connect_job_->target_url_);
121 EXPECT_EQ(0, connect_job_->proxy_servers_.size());
122 const std::string expected_http_response =
123 "HTTP/1.1 400 Bad Request - Origin: local proxy\r\n\r\n";
124 std::vector<char> buf(expected_http_response.size());
125 ASSERT_TRUE(
126 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
127 std::string actual_response(buf.data(), buf.size());
128 EXPECT_EQ(expected_http_response, actual_response);
129}
130
Andreea Costinas435851b2020-05-25 14:18:41 +0200131TEST_F(ProxyConnectJobTest, SlowlorisTimeout) {
132 // Add a TaskRunner where we can control time.
133 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
134 new base::TestMockTimeTaskRunner()};
135 brillo_loop_ = nullptr;
136 brillo_loop_ = std::make_unique<brillo::BaseMessageLoop>(task_runner);
137 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
138
Andreea Costinas08a5d182020-04-29 22:12:47 +0200139 connect_job_->Start();
Andreea Costinase45d54b2020-03-10 09:21:14 +0100140 // No empty line after http message.
141 char badConnRequest[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n";
142 cros_client_socket_->SendTo(badConnRequest, std::strlen(badConnRequest));
Andreea Costinas435851b2020-05-25 14:18:41 +0200143 task_runner->RunUntilIdle();
144
145 EXPECT_EQ(1, task_runner->GetPendingTaskCount());
146 constexpr base::TimeDelta kDoubleWaitClientConnectTimeout =
147 base::TimeDelta::FromSeconds(4);
148 // Move the time ahead so that the client connection timeout callback is
149 // triggered.
150 task_runner->FastForwardBy(kDoubleWaitClientConnectTimeout);
Andreea Costinase45d54b2020-03-10 09:21:14 +0100151
152 EXPECT_EQ("", connect_job_->target_url_);
153 EXPECT_EQ(0, connect_job_->proxy_servers_.size());
154 const std::string expected_http_response =
Andreea Costinas435851b2020-05-25 14:18:41 +0200155 "HTTP/1.1 408 Request Timeout - Origin: local proxy\r\n\r\n";
Andreea Costinase45d54b2020-03-10 09:21:14 +0100156 std::vector<char> buf(expected_http_response.size());
157 ASSERT_TRUE(
158 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
159 std::string actual_response(buf.data(), buf.size());
160 EXPECT_EQ(expected_http_response, actual_response);
161}
162
Andreea Costinas08a5d182020-04-29 22:12:47 +0200163TEST_F(ProxyConnectJobTest, WaitClientConnectTimeout) {
164 // Add a TaskRunner where we can control time.
165 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
166 new base::TestMockTimeTaskRunner()};
Qijiang Fan34014672020-07-20 16:05:38 +0900167 brillo_loop_ = nullptr;
168 brillo_loop_ = std::make_unique<brillo::BaseMessageLoop>(task_runner);
Andreea Costinas08a5d182020-04-29 22:12:47 +0200169 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
170
171 connect_job_->Start();
172
173 EXPECT_EQ(1, task_runner->GetPendingTaskCount());
174 // Move the time ahead so that the client connection timeout callback is
175 // triggered.
176 task_runner->FastForwardBy(task_runner->NextPendingTaskDelay());
177
178 const std::string expected_http_response =
179 "HTTP/1.1 408 Request Timeout - Origin: local proxy\r\n\r\n";
180 std::vector<char> buf(expected_http_response.size());
181 ASSERT_TRUE(
182 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
183 std::string actual_response(buf.data(), buf.size());
184
185 EXPECT_EQ(expected_http_response, actual_response);
186}
187
188// Check that the client connect timeout callback is not fired if the owning
189// proxy connect job is destroyed.
190TEST_F(ProxyConnectJobTest, ClientConnectTimeoutJobCanceled) {
191 // Add a TaskRunner where we can control time.
192 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
193 new base::TestMockTimeTaskRunner()};
Qijiang Fan34014672020-07-20 16:05:38 +0900194 brillo_loop_ = nullptr;
195 brillo_loop_ = std::make_unique<brillo::BaseMessageLoop>(task_runner);
Andreea Costinas08a5d182020-04-29 22:12:47 +0200196 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
197
198 // Create a proxy connect job and start the client connect timeout counter.
199 {
200 int fds[2];
201 ASSERT_NE(-1,
202 socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
203 0 /* protocol */, fds));
204 auto client_socket =
205 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[1]));
206
207 auto connect_job = std::make_unique<ProxyConnectJob>(
208 std::make_unique<patchpanel::Socket>(base::ScopedFD(fds[0])), "",
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200209 CURLAUTH_ANY,
Andreea Costinas08a5d182020-04-29 22:12:47 +0200210 base::BindOnce(&ProxyConnectJobTest::ResolveProxy,
211 base::Unretained(this)),
Andreea Costinased9e6122020-08-12 12:06:19 +0200212 base::BindRepeating(&ProxyConnectJobTest::OnAuthCredentialsRequired,
213 base::Unretained(this)),
Andreea Costinas08a5d182020-04-29 22:12:47 +0200214 base::BindOnce(&ProxyConnectJobTest::OnConnectionSetupFinished,
215 base::Unretained(this)));
216 // Post the timeout task.
217 connect_job->Start();
218 EXPECT_TRUE(task_runner->HasPendingTask());
219 }
220 // Check that the task was canceled.
221 EXPECT_FALSE(task_runner->HasPendingTask());
222}
223
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200224class HttpServerProxyConnectJobTest : public ProxyConnectJobTest {
225 public:
226 HttpServerProxyConnectJobTest() = default;
227 HttpServerProxyConnectJobTest(const HttpServerProxyConnectJobTest&) = delete;
228 HttpServerProxyConnectJobTest& operator=(
229 const HttpServerProxyConnectJobTest&) = delete;
230 ~HttpServerProxyConnectJobTest() = default;
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200231
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200232 protected:
233 HttpTestServer http_test_server_;
234
235 std::vector<HttpAuthEntry> http_auth_cache_;
236 bool auth_requested_ = false;
237
238 void AddHttpAuthEntry(const std::string& origin,
239 const std::string& scheme,
240 const std::string& realm,
241 const std::string& credentials) {
242 http_auth_cache_.push_back(
243 HttpAuthEntry(origin, scheme, realm, credentials));
244 }
245
246 bool AuthRequested() { return auth_requested_; }
247
248 void AddServerReply(HttpTestServer::HttpConnectReply reply) {
249 http_test_server_.AddHttpConnectReply(reply);
250 }
251
252 void ResolveProxy(const std::string& target_url,
253 base::OnceCallback<void(const std::list<std::string>&)>
254 callback) override {
255 // Return the URL of the test proxy.
256 std::move(callback).Run({http_test_server_.GetUrl()});
257 }
258
259 void OnAuthCredentialsRequired(
260 const std::string& proxy_url,
261 const std::string& scheme,
262 const std::string& realm,
263 const std::string& bad_credentials,
264 base::RepeatingCallback<void(const std::string&)> callback) override {
265 auth_requested_ = true;
266 for (const auto& auth_entry : http_auth_cache_) {
267 if (auth_entry.origin == proxy_url && auth_entry.realm == realm &&
268 auth_entry.scheme == scheme) {
269 std::move(callback).Run(auth_entry.credentials);
270 return;
271 }
272 }
273 if (invoke_authentication_callback_) {
274 std::move(callback).Run(/* credentials = */ "");
275 }
276 }
277 void OnConnectionSetupFinished(
278 std::unique_ptr<patchpanel::SocketForwarder> fwd,
279 ProxyConnectJob* connect_job) override {
280 ASSERT_EQ(connect_job, connect_job_.get());
281 if (fwd) {
282 forwarder_created_ = true;
283
284 brillo_loop_->RunOnce(false);
285
286 fwd.reset();
287 }
288 }
289
290 bool forwarder_created_ = false;
291 // Used to simulate time-outs while waiting for credentials from the Browser.
292 bool invoke_authentication_callback_ = true;
293};
294
295TEST_F(HttpServerProxyConnectJobTest, SuccessfulConnection) {
296 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
297 http_test_server_.Start();
298
299 connect_job_->Start();
300 cros_client_socket_->SendTo(kValidConnectRequest,
301 std::strlen(kValidConnectRequest));
302 brillo_loop_->RunOnce(false);
303 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
304 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
305 EXPECT_EQ(http_test_server_.GetUrl(), connect_job_->proxy_servers_.front());
306 EXPECT_TRUE(forwarder_created_);
307}
308
Andreea Costinas435851b2020-05-25 14:18:41 +0200309TEST_F(HttpServerProxyConnectJobTest, MultipleReadConnectRequest) {
310 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
311 http_test_server_.Start();
312 connect_job_->Start();
313 char part1[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n";
314 char part2[] = "\r\n";
315 cros_client_socket_->SendTo(part1, std::strlen(part1));
316 // Process the partial CONNECT request.
317 brillo_loop_->RunOnce(false);
318 cros_client_socket_->SendTo(part2, std::strlen(part2));
319 brillo_loop_->RunOnce(false);
320 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
321 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
322 EXPECT_EQ(http_test_server_.GetUrl(), connect_job_->proxy_servers_.front());
323 EXPECT_TRUE(forwarder_created_);
324}
325
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200326TEST_F(HttpServerProxyConnectJobTest, TunnelFailedBadGatewayFromRemote) {
327 AddServerReply(HttpTestServer::HttpConnectReply::kBadGateway);
328 http_test_server_.Start();
329
330 connect_job_->Start();
331 cros_client_socket_->SendTo(kValidConnectRequest,
332 std::strlen(kValidConnectRequest));
333 brillo_loop_->RunOnce(false);
334 EXPECT_FALSE(forwarder_created_);
335
336 std::string expected_server_reply =
337 "HTTP/1.1 502 Error creating tunnel - Origin: local proxy\r\n\r\n";
338 std::vector<char> buf(expected_server_reply.size());
339
340 ASSERT_TRUE(cros_client_socket_->RecvFrom(buf.data(), buf.size()));
341 std::string actual_server_reply(buf.data(), buf.size());
342 EXPECT_EQ(expected_server_reply, actual_server_reply);
343}
344
345TEST_F(HttpServerProxyConnectJobTest, SuccessfulConnectionAltEnding) {
346 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
347 http_test_server_.Start();
348
349 connect_job_->Start();
350 char validConnRequest[] = "CONNECT www.example.server.com:443 HTTP/1.1\r\n\n";
351 cros_client_socket_->SendTo(validConnRequest, std::strlen(validConnRequest));
352 brillo_loop_->RunOnce(false);
353
354 EXPECT_EQ("www.example.server.com:443", connect_job_->target_url_);
355 EXPECT_EQ(1, connect_job_->proxy_servers_.size());
356 EXPECT_EQ(http_test_server_.GetUrl(), connect_job_->proxy_servers_.front());
357 EXPECT_TRUE(forwarder_created_);
358 ASSERT_FALSE(AuthRequested());
359}
360
361// Test that the the CONNECT request is sent again after acquiring credentials.
362TEST_F(HttpServerProxyConnectJobTest, ResendWithCredentials) {
363 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
364 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
365 http_test_server_.Start();
366
367 AddHttpAuthEntry(http_test_server_.GetUrl(), "Basic", "\"My Proxy\"",
368 kCredentials);
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200369 connect_job_->Start();
370
371 cros_client_socket_->SendTo(kValidConnectRequest,
372 std::strlen(kValidConnectRequest));
Qijiang Fan34014672020-07-20 16:05:38 +0900373 brillo_loop_->RunOnce(false);
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200374
375 ASSERT_TRUE(AuthRequested());
376 EXPECT_TRUE(forwarder_created_);
377 EXPECT_EQ(kCredentials, connect_job_->credentials_);
378 EXPECT_EQ(200, connect_job_->http_response_code_);
379}
380
381// Test that the proxy auth required status is forwarded to the client if
382// credentials are missing.
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200383TEST_F(HttpServerProxyConnectJobTest, NoCredentials) {
384 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
385 http_test_server_.Start();
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200386 connect_job_->Start();
387
388 cros_client_socket_->SendTo(kValidConnectRequest,
389 std::strlen(kValidConnectRequest));
Qijiang Fan34014672020-07-20 16:05:38 +0900390 brillo_loop_->RunOnce(false);
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200391
392 ASSERT_TRUE(AuthRequested());
393 EXPECT_EQ("", connect_job_->credentials_);
394 EXPECT_EQ(407, connect_job_->http_response_code_);
395}
396
397// Test that the proxy auth required status is forwarded to the client if the
398// server chose Kerberos as an authentication method.
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200399TEST_F(HttpServerProxyConnectJobTest, KerberosAuth) {
400 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredKerberos);
401 http_test_server_.Start();
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200402
403 connect_job_->Start();
404
405 cros_client_socket_->SendTo(kValidConnectRequest,
406 std::strlen(kValidConnectRequest));
Qijiang Fan34014672020-07-20 16:05:38 +0900407 brillo_loop_->RunOnce(false);
Andreea Costinasbb2aa022020-06-13 00:03:23 +0200408
409 ASSERT_FALSE(AuthRequested());
410 EXPECT_EQ("", connect_job_->credentials_);
411 EXPECT_EQ(407, connect_job_->http_response_code_);
412}
413
Andreea Costinased9e6122020-08-12 12:06:19 +0200414// Test that the connection times out while waiting for credentials from the
415// Browser.
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200416TEST_F(HttpServerProxyConnectJobTest, AuthenticationTimeout) {
Andreea Costinased9e6122020-08-12 12:06:19 +0200417 // Add a TaskRunner where we can control time.
418 scoped_refptr<base::TestMockTimeTaskRunner> task_runner{
419 new base::TestMockTimeTaskRunner()};
420 brillo_loop_ = nullptr;
421 brillo_loop_ = std::make_unique<brillo::BaseMessageLoop>(task_runner);
422 base::TestMockTimeTaskRunner::ScopedContext scoped_context(task_runner.get());
423
424 invoke_authentication_callback_ = false;
425
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200426 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
427 http_test_server_.Start();
Andreea Costinased9e6122020-08-12 12:06:19 +0200428
429 connect_job_->Start();
430
431 cros_client_socket_->SendTo(kValidConnectRequest,
432 std::strlen(kValidConnectRequest));
433 task_runner->RunUntilIdle();
434 // We need to manually invoke the method which reads from the client socket
435 // because |task_runner| will not execute the FileDescriptorWatcher's tasks.
436 connect_job_->OnClientReadReady();
437
438 // Check that an authentication request was sent.
439 ASSERT_TRUE(AuthRequested());
440 EXPECT_EQ(1, task_runner->GetPendingTaskCount());
441 // Move the time ahead so that the client connection timeout callback is
442 // triggered.
443 task_runner->FastForwardBy(task_runner->NextPendingTaskDelay());
444
445 const std::string expected_http_response =
446 "HTTP/1.1 407 Credentials required - Origin: local proxy\r\n\r\n";
447 std::vector<char> buf(expected_http_response.size());
448 ASSERT_TRUE(
449 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
450 std::string actual_response(buf.data(), buf.size());
451
452 // Check that the auth failure was forwarded to the client.
453 EXPECT_EQ(expected_http_response, actual_response);
454}
455
456// Verifies that the receiving the same bad credentials twice will send an auth
457// failure to the Chrome OS local client.
Andreea Costinasa8bc59c2020-09-18 13:49:13 +0200458TEST_F(HttpServerProxyConnectJobTest, CancelIfBadCredentials) {
459 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
460 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
461 http_test_server_.Start();
462
463 AddHttpAuthEntry(http_test_server_.GetUrl(), "Basic", "\"My Proxy\"",
464 kCredentials);
Andreea Costinased9e6122020-08-12 12:06:19 +0200465
466 connect_job_->Start();
467
468 cros_client_socket_->SendTo(kValidConnectRequest,
469 std::strlen(kValidConnectRequest));
470 brillo_loop_->RunOnce(false);
471
472 ASSERT_TRUE(AuthRequested());
473
474 const std::string expected_http_response =
475 "HTTP/1.1 407 Credentials required - Origin: local proxy\r\n\r\n";
476 std::vector<char> buf(expected_http_response.size());
477 ASSERT_TRUE(
478 base::ReadFromFD(cros_client_socket_->fd(), buf.data(), buf.size()));
479 std::string actual_response(buf.data(), buf.size());
480
481 EXPECT_EQ(expected_http_response, actual_response);
482}
483
Andreea Costinas435851b2020-05-25 14:18:41 +0200484// This test verifies that any data sent by a client immediately after the end
485// of the HTTP CONNECT request is cached correctly.
486TEST_F(HttpServerProxyConnectJobTest, BufferedClientData) {
487 char connectRequestwithData[] =
488 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\r\nTest body";
489 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
490 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
491 http_test_server_.Start();
492
493 AddHttpAuthEntry(http_test_server_.GetUrl(), "Basic", "\"My Proxy\"",
494 kCredentials);
495 connect_job_->Start();
496
497 cros_client_socket_->SendTo(connectRequestwithData,
498 std::strlen(connectRequestwithData));
499 brillo_loop_->RunOnce(false);
500
501 const std::string expected = "Test body";
502 const std::string actual(connect_job_->connect_data_.data(),
503 connect_job_->connect_data_.size());
504}
505
506TEST_F(HttpServerProxyConnectJobTest, BufferedClientDataAltEnding) {
507 char connectRequestwithData[] =
508 "CONNECT www.example.server.com:443 HTTP/1.1\r\n\nTest body";
509 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
510 AddServerReply(HttpTestServer::HttpConnectReply::kOk);
511 http_test_server_.Start();
512
513 AddHttpAuthEntry(http_test_server_.GetUrl(), "Basic", "\"My Proxy\"",
514 kCredentials);
515 connect_job_->Start();
516
517 cros_client_socket_->SendTo(connectRequestwithData,
518 std::strlen(connectRequestwithData));
519 brillo_loop_->RunOnce(false);
520
521 const std::string expected = "Test body";
522 const std::string actual(connect_job_->connect_data_.data(),
523 connect_job_->connect_data_.size());
524}
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200525
526// Test that the policy auth scheme is respected by curl.
527TEST_F(HttpServerProxyConnectJobTest, PolicyAuthSchemeOk) {
528 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
529 http_test_server_.Start();
530 connect_job_->credentials_ = "a:b";
531 connect_job_->curl_auth_schemes_ = CURLAUTH_BASIC;
532 connect_job_->StoreRequestHeadersForTesting();
533 connect_job_->Start();
534
535 cros_client_socket_->SendTo(kValidConnectRequest,
536 std::strlen(kValidConnectRequest));
537 brillo_loop_->RunOnce(false);
538
539 std::size_t pos = connect_job_->GetRequestHeadersForTesting().find(
540 kProxyAuthorizationHeaderToken);
541 // Expect to find the proxy auth headers since CURLAUTH_BASIC is an allowed
542 // auth scheme.
543 EXPECT_NE(pos, std::string::npos);
544}
545
546// Test that proxy auth headers with credentials are not sent by curl if the
547// auth scheme used by the server is not allowed.
548TEST_F(HttpServerProxyConnectJobTest, PolicyAuthBadScheme) {
549 AddServerReply(HttpTestServer::HttpConnectReply::kAuthRequiredBasic);
550 http_test_server_.Start();
551 connect_job_->credentials_ = "a:b";
552 connect_job_->curl_auth_schemes_ = CURLAUTH_DIGEST;
553 connect_job_->StoreRequestHeadersForTesting();
554 connect_job_->Start();
555
556 cros_client_socket_->SendTo(kValidConnectRequest,
557 std::strlen(kValidConnectRequest));
558 brillo_loop_->RunOnce(false);
559
560 std::size_t pos = connect_job_->GetRequestHeadersForTesting().find(
561 kProxyAuthorizationHeaderToken);
562 // Expect not to find the proxy auth headers since CURLAUTH_BASIC is not an
563 // allowed auth scheme.
564 EXPECT_EQ(pos, std::string::npos);
565}
566
Andreea Costinase45d54b2020-03-10 09:21:14 +0100567} // namespace system_proxy