OLD | NEW |
| (Empty) |
1 // Copyright 2016 The Chromium 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 "blimp/client/core/session/assignment_source.h" | |
6 | |
7 #include <memory> | |
8 #include <utility> | |
9 | |
10 #include "base/bind.h" | |
11 #include "base/callback_helpers.h" | |
12 #include "base/command_line.h" | |
13 #include "base/files/file_util.h" | |
14 #include "base/json/json_reader.h" | |
15 #include "base/json/json_writer.h" | |
16 #include "base/location.h" | |
17 #include "base/memory/ref_counted.h" | |
18 #include "base/numerics/safe_conversions.h" | |
19 #include "base/single_thread_task_runner.h" | |
20 #include "base/strings/string_number_conversions.h" | |
21 #include "base/task_runner_util.h" | |
22 #include "base/threading/thread_restrictions.h" | |
23 #include "base/values.h" | |
24 #include "blimp/client/core/switches/blimp_client_switches.h" | |
25 #include "blimp/common/get_client_auth_token.h" | |
26 #include "blimp/common/protocol_version.h" | |
27 #include "components/safe_json/safe_json_parser.h" | |
28 #include "net/base/ip_address.h" | |
29 #include "net/base/ip_endpoint.h" | |
30 #include "net/base/load_flags.h" | |
31 #include "net/base/net_errors.h" | |
32 #include "net/http/http_status_code.h" | |
33 #include "net/proxy/proxy_config_service.h" | |
34 #include "net/proxy/proxy_service.h" | |
35 #include "net/url_request/url_fetcher.h" | |
36 #include "net/url_request/url_request_context.h" | |
37 #include "net/url_request/url_request_context_builder.h" | |
38 #include "net/url_request/url_request_context_getter.h" | |
39 | |
40 namespace blimp { | |
41 namespace client { | |
42 | |
43 namespace { | |
44 | |
45 // Assignment request JSON keys. | |
46 const char kProtocolVersionKey[] = "protocol_version"; | |
47 | |
48 // Assignment response JSON keys. | |
49 const char kClientAuthTokenKey[] = "clientToken"; | |
50 const char kHostKey[] = "host"; | |
51 const char kPortKey[] = "port"; | |
52 const char kCertificateKey[] = "certificate"; | |
53 | |
54 // Possible arguments for the "--engine-transport" command line parameter. | |
55 const char kGrpcTransportValue[] = "grpc"; | |
56 const char kSSLTransportValue[] = "ssl"; | |
57 const char kTCPTransportValue[] = "tcp"; | |
58 | |
59 class SimpleURLRequestContextGetter : public net::URLRequestContextGetter { | |
60 public: | |
61 SimpleURLRequestContextGetter( | |
62 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, | |
63 const scoped_refptr<base::SingleThreadTaskRunner>& file_task_runner) | |
64 : io_task_runner_(io_task_runner), file_task_runner_(file_task_runner) {} | |
65 | |
66 // net::URLRequestContextGetter implementation. | |
67 net::URLRequestContext* GetURLRequestContext() override { | |
68 DCHECK(io_task_runner_->BelongsToCurrentThread()); | |
69 | |
70 if (!proxy_config_service_) { | |
71 proxy_config_service_ = net::ProxyService::CreateSystemProxyConfigService( | |
72 io_task_runner_, file_task_runner_); | |
73 } | |
74 | |
75 if (!url_request_context_) { | |
76 net::URLRequestContextBuilder builder; | |
77 builder.set_proxy_config_service(std::move(proxy_config_service_)); | |
78 builder.DisableHttpCache(); | |
79 url_request_context_ = builder.Build(); | |
80 } | |
81 | |
82 return url_request_context_.get(); | |
83 } | |
84 | |
85 scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner() | |
86 const override { | |
87 return io_task_runner_; | |
88 } | |
89 | |
90 private: | |
91 ~SimpleURLRequestContextGetter() override { | |
92 DCHECK(io_task_runner_->BelongsToCurrentThread()); | |
93 } | |
94 | |
95 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; | |
96 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_; | |
97 std::unique_ptr<net::URLRequestContext> url_request_context_; | |
98 | |
99 // Constructed lazily on the IO thread by GetURLRequestContext(). | |
100 std::unique_ptr<net::ProxyConfigService> proxy_config_service_; | |
101 | |
102 DISALLOW_COPY_AND_ASSIGN(SimpleURLRequestContextGetter); | |
103 }; | |
104 | |
105 bool IsValidIpPortNumber(unsigned port) { | |
106 return port > 0 && port <= 65535; | |
107 } | |
108 | |
109 // Populates an Assignment using command-line parameters, if provided. | |
110 // Returns a null Assignment if no parameters were set. | |
111 // Must be called on a thread suitable for file IO. | |
112 Assignment GetAssignmentFromCommandLine() { | |
113 Assignment assignment; | |
114 | |
115 const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | |
116 assignment.client_auth_token = GetClientAuthToken(*cmd_line); | |
117 CHECK(!assignment.client_auth_token.empty()) | |
118 << "No client auth token provided."; | |
119 | |
120 unsigned port_parsed = 0; | |
121 if (!base::StringToUint( | |
122 cmd_line->GetSwitchValueASCII(switches::kEnginePort), | |
123 &port_parsed) || !IsValidIpPortNumber(port_parsed)) { | |
124 DLOG(FATAL) << "--engine-port must be a value between 1 and 65535."; | |
125 return Assignment(); | |
126 } | |
127 | |
128 net::IPAddress ip_address; | |
129 std::string ip_str = cmd_line->GetSwitchValueASCII(switches::kEngineIP); | |
130 if (!ip_address.AssignFromIPLiteral(ip_str)) { | |
131 DLOG(FATAL) << "Invalid engine IP " << ip_str; | |
132 return Assignment(); | |
133 } | |
134 assignment.engine_endpoint = | |
135 net::IPEndPoint(ip_address, base::checked_cast<uint16_t>(port_parsed)); | |
136 | |
137 std::string transport_str = | |
138 cmd_line->GetSwitchValueASCII(switches::kEngineTransport); | |
139 if (transport_str == kSSLTransportValue) { | |
140 assignment.transport_protocol = Assignment::TransportProtocol::SSL; | |
141 } else if (transport_str == kTCPTransportValue) { | |
142 assignment.transport_protocol = Assignment::TransportProtocol::TCP; | |
143 } else if (transport_str == kGrpcTransportValue) { | |
144 assignment.transport_protocol = Assignment::TransportProtocol::GRPC; | |
145 } else { | |
146 DLOG(FATAL) << "Invalid engine transport " << transport_str; | |
147 return Assignment(); | |
148 } | |
149 | |
150 scoped_refptr<net::X509Certificate> cert; | |
151 if (assignment.transport_protocol == Assignment::TransportProtocol::SSL) { | |
152 base::FilePath cert_path = | |
153 cmd_line->GetSwitchValuePath(switches::kEngineCertPath); | |
154 if (cert_path.empty()) { | |
155 DLOG(FATAL) << "Missing required parameter --" | |
156 << switches::kEngineCertPath << "."; | |
157 return Assignment(); | |
158 } | |
159 std::string cert_str; | |
160 if (!base::ReadFileToString(cert_path, &cert_str)) { | |
161 DLOG(FATAL) << "Couldn't read from file: " | |
162 << cert_path.LossyDisplayName(); | |
163 return Assignment(); | |
164 } | |
165 net::CertificateList cert_list = | |
166 net::X509Certificate::CreateCertificateListFromBytes( | |
167 cert_str.data(), cert_str.size(), | |
168 net::X509Certificate::FORMAT_PEM_CERT_SEQUENCE); | |
169 DLOG_IF(FATAL, (cert_list.size() != 1u)) | |
170 << "Only one cert is allowed in PEM cert list."; | |
171 assignment.cert = std::move(cert_list[0]); | |
172 } | |
173 | |
174 if (!assignment.IsValid()) { | |
175 DLOG(FATAL) << "Invalid command-line assignment."; | |
176 return Assignment(); | |
177 } | |
178 | |
179 return assignment; | |
180 } | |
181 | |
182 } // namespace | |
183 | |
184 AssignmentSource::AssignmentSource( | |
185 const GURL& assigner_endpoint, | |
186 const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner, | |
187 const scoped_refptr<base::SingleThreadTaskRunner>& file_task_runner) | |
188 : assigner_endpoint_(assigner_endpoint), | |
189 file_task_runner_(file_task_runner), | |
190 url_request_context_( | |
191 new SimpleURLRequestContextGetter(network_task_runner, | |
192 file_task_runner)), | |
193 weak_factory_(this) { | |
194 DCHECK(assigner_endpoint_.is_valid()); | |
195 } | |
196 | |
197 AssignmentSource::~AssignmentSource() {} | |
198 | |
199 void AssignmentSource::GetAssignment(const std::string& client_auth_token, | |
200 const AssignmentCallback& callback) { | |
201 DCHECK(callback_.is_null()); | |
202 callback_ = AssignmentCallback(callback); | |
203 | |
204 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kEngineIP)) { | |
205 base::PostTaskAndReplyWithResult( | |
206 file_task_runner_.get(), FROM_HERE, | |
207 base::Bind(&GetAssignmentFromCommandLine), | |
208 base::Bind(&AssignmentSource::OnGetAssignmentFromCommandLineDone, | |
209 weak_factory_.GetWeakPtr(), client_auth_token)); | |
210 } else { | |
211 QueryAssigner(client_auth_token); | |
212 } | |
213 } | |
214 | |
215 void AssignmentSource::OnGetAssignmentFromCommandLineDone( | |
216 const std::string& client_auth_token, | |
217 Assignment parsed_assignment) { | |
218 // If GetAssignmentFromCommandLine succeeded, then return its output. | |
219 if (parsed_assignment.IsValid()) { | |
220 base::ResetAndReturn(&callback_) | |
221 .Run(ASSIGNMENT_REQUEST_RESULT_OK, parsed_assignment); | |
222 return; | |
223 } | |
224 | |
225 // If no assignment was passed via the command line, then fall back on | |
226 // querying the Assigner service. | |
227 QueryAssigner(client_auth_token); | |
228 } | |
229 | |
230 void AssignmentSource::QueryAssigner(const std::string& client_auth_token) { | |
231 // Call out to the network for a real assignment. Build the network request | |
232 // to hit the assigner. | |
233 url_fetcher_ = | |
234 net::URLFetcher::Create(assigner_endpoint_, net::URLFetcher::POST, this); | |
235 url_fetcher_->SetRequestContext(url_request_context_.get()); | |
236 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES | | |
237 net::LOAD_DO_NOT_SEND_COOKIES); | |
238 url_fetcher_->AddExtraRequestHeader("Authorization: Bearer " + | |
239 client_auth_token); | |
240 | |
241 // Write the JSON for the request data. | |
242 base::DictionaryValue dictionary; | |
243 dictionary.SetString(kProtocolVersionKey, | |
244 base::IntToString(kProtocolVersion)); | |
245 std::string json; | |
246 base::JSONWriter::Write(dictionary, &json); | |
247 url_fetcher_->SetUploadData("application/json", json); | |
248 url_fetcher_->Start(); | |
249 } | |
250 | |
251 void AssignmentSource::OnURLFetchComplete(const net::URLFetcher* source) { | |
252 DCHECK(!callback_.is_null()); | |
253 DCHECK_EQ(url_fetcher_.get(), source); | |
254 | |
255 if (!source->GetStatus().is_success()) { | |
256 DVLOG(1) << "Assignment request failed due to network error: " | |
257 << net::ErrorToString(source->GetStatus().error()); | |
258 base::ResetAndReturn(&callback_) | |
259 .Run(ASSIGNMENT_REQUEST_RESULT_NETWORK_FAILURE, Assignment()); | |
260 return; | |
261 } | |
262 | |
263 switch (source->GetResponseCode()) { | |
264 case net::HTTP_OK: | |
265 ParseAssignerResponse(); | |
266 break; | |
267 case net::HTTP_BAD_REQUEST: | |
268 base::ResetAndReturn(&callback_) | |
269 .Run(ASSIGNMENT_REQUEST_RESULT_BAD_REQUEST, Assignment()); | |
270 break; | |
271 case net::HTTP_UNAUTHORIZED: | |
272 base::ResetAndReturn(&callback_) | |
273 .Run(ASSIGNMENT_REQUEST_RESULT_EXPIRED_ACCESS_TOKEN, Assignment()); | |
274 break; | |
275 case net::HTTP_FORBIDDEN: | |
276 base::ResetAndReturn(&callback_) | |
277 .Run(ASSIGNMENT_REQUEST_RESULT_USER_INVALID, Assignment()); | |
278 break; | |
279 case 429: // Too Many Requests | |
280 base::ResetAndReturn(&callback_) | |
281 .Run(ASSIGNMENT_REQUEST_RESULT_OUT_OF_VMS, Assignment()); | |
282 break; | |
283 case net::HTTP_INTERNAL_SERVER_ERROR: | |
284 base::ResetAndReturn(&callback_) | |
285 .Run(ASSIGNMENT_REQUEST_RESULT_SERVER_ERROR, Assignment()); | |
286 break; | |
287 default: | |
288 LOG(WARNING) << "Defaulting to BAD_RESPONSE for HTTP response code " | |
289 << source->GetResponseCode(); | |
290 base::ResetAndReturn(&callback_) | |
291 .Run(ASSIGNMENT_REQUEST_RESULT_BAD_RESPONSE, Assignment()); | |
292 break; | |
293 } | |
294 } | |
295 | |
296 void AssignmentSource::ParseAssignerResponse() { | |
297 DCHECK(url_fetcher_.get()); | |
298 DCHECK(url_fetcher_->GetStatus().is_success()); | |
299 DCHECK_EQ(net::HTTP_OK, url_fetcher_->GetResponseCode()); | |
300 | |
301 // Grab the response from the assigner request. | |
302 std::string response; | |
303 if (!url_fetcher_->GetResponseAsString(&response)) { | |
304 LOG(WARNING) << "Unable to retrieve response as string from URLFetcher."; | |
305 base::ResetAndReturn(&callback_) | |
306 .Run(ASSIGNMENT_REQUEST_RESULT_BAD_RESPONSE, Assignment()); | |
307 return; | |
308 } | |
309 | |
310 safe_json::SafeJsonParser::Parse( | |
311 response, | |
312 base::Bind(&AssignmentSource::OnJsonParsed, weak_factory_.GetWeakPtr()), | |
313 base::Bind(&AssignmentSource::OnJsonParseError, | |
314 weak_factory_.GetWeakPtr())); | |
315 } | |
316 | |
317 void AssignmentSource::OnJsonParsed(std::unique_ptr<base::Value> json) { | |
318 const base::DictionaryValue* dict; | |
319 if (!json->GetAsDictionary(&dict)) { | |
320 LOG(WARNING) << "Unable to parse JSON data as a dictionary."; | |
321 base::ResetAndReturn(&callback_) | |
322 .Run(ASSIGNMENT_REQUEST_RESULT_BAD_RESPONSE, Assignment()); | |
323 return; | |
324 } | |
325 | |
326 // Validate that all the expected fields are present. | |
327 std::string client_auth_token; | |
328 std::string host; | |
329 int port; | |
330 std::string cert_str; | |
331 if (!(dict->GetString(kClientAuthTokenKey, &client_auth_token) && | |
332 dict->GetString(kHostKey, &host) && dict->GetInteger(kPortKey, &port) && | |
333 dict->GetString(kCertificateKey, &cert_str))) { | |
334 LOG(WARNING) << "Not all fields present in assignment JSON data."; | |
335 base::ResetAndReturn(&callback_) | |
336 .Run(ASSIGNMENT_REQUEST_RESULT_BAD_RESPONSE, Assignment()); | |
337 return; | |
338 } | |
339 | |
340 net::IPAddress ip_address; | |
341 if (!ip_address.AssignFromIPLiteral(host)) { | |
342 LOG(WARNING) << "Unable to assign IP address from string literal " << host; | |
343 base::ResetAndReturn(&callback_) | |
344 .Run(ASSIGNMENT_REQUEST_RESULT_BAD_RESPONSE, Assignment()); | |
345 return; | |
346 } | |
347 | |
348 if (!base::IsValueInRangeForNumericType<uint16_t>(port)) { | |
349 LOG(WARNING) << "Assignment port number not in range for uint16_t"; | |
350 base::ResetAndReturn(&callback_) | |
351 .Run(ASSIGNMENT_REQUEST_RESULT_BAD_RESPONSE, Assignment()); | |
352 return; | |
353 } | |
354 | |
355 net::CertificateList cert_list = | |
356 net::X509Certificate::CreateCertificateListFromBytes( | |
357 cert_str.data(), cert_str.size(), | |
358 net::X509Certificate::FORMAT_PEM_CERT_SEQUENCE); | |
359 if (cert_list.size() != 1) { | |
360 base::ResetAndReturn(&callback_) | |
361 .Run(ASSIGNMENT_REQUEST_RESULT_INVALID_CERT, Assignment()); | |
362 return; | |
363 } | |
364 | |
365 // The assigner assumes SSL-only and all engines it assigns only communicate | |
366 // over SSL. | |
367 Assignment assignment; | |
368 assignment.transport_protocol = Assignment::TransportProtocol::SSL; | |
369 assignment.engine_endpoint = net::IPEndPoint(ip_address, port); | |
370 assignment.client_auth_token = client_auth_token; | |
371 assignment.cert = std::move(cert_list[0]); | |
372 | |
373 base::ResetAndReturn(&callback_) | |
374 .Run(ASSIGNMENT_REQUEST_RESULT_OK, assignment); | |
375 } | |
376 | |
377 void AssignmentSource::OnJsonParseError(const std::string& error) { | |
378 DLOG(ERROR) << "Error while parsing assigner JSON: " << error; | |
379 base::ResetAndReturn(&callback_) | |
380 .Run(ASSIGNMENT_REQUEST_RESULT_BAD_RESPONSE, Assignment()); | |
381 } | |
382 | |
383 } // namespace client | |
384 } // namespace blimp | |
OLD | NEW |