| 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 |