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