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