Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/chromeos/policy/upload_job_impl.h" | 5 #include "chrome/browser/chromeos/policy/upload_job_impl.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 #include <set> | 8 #include <set> |
| 9 #include <utility> | 9 #include <utility> |
| 10 | 10 |
| 11 #include "base/location.h" | |
| 11 #include "base/logging.h" | 12 #include "base/logging.h" |
| 12 #include "base/macros.h" | 13 #include "base/macros.h" |
| 13 #include "base/strings/stringprintf.h" | 14 #include "base/strings/stringprintf.h" |
| 14 #include "google_apis/gaia/gaia_constants.h" | 15 #include "google_apis/gaia/gaia_constants.h" |
| 15 #include "google_apis/gaia/google_service_auth_error.h" | 16 #include "google_apis/gaia/google_service_auth_error.h" |
| 16 #include "net/base/mime_util.h" | 17 #include "net/base/mime_util.h" |
| 17 #include "net/http/http_status_code.h" | 18 #include "net/http/http_status_code.h" |
| 18 #include "net/url_request/url_request_status.h" | 19 #include "net/url_request/url_request_status.h" |
| 19 | 20 |
| 20 namespace policy { | 21 namespace policy { |
| 21 | 22 |
| 22 namespace { | 23 namespace { |
| 23 | 24 |
| 24 // Format for bearer tokens in HTTP requests to access OAuth 2.0 protected | 25 // Format for bearer tokens in HTTP requests to access OAuth 2.0 protected |
| 25 // resources. | 26 // resources. |
| 26 const char kAuthorizationHeaderFormat[] = "Authorization: Bearer %s"; | 27 const char kAuthorizationHeaderFormat[] = "Authorization: Bearer %s"; |
| 27 | 28 |
| 28 // Value the "Content-Type" field will be set to in the POST request. | 29 // Value the "Content-Type" field will be set to in the POST request. |
| 29 const char kUploadContentType[] = "multipart/form-data"; | 30 const char kUploadContentType[] = "multipart/form-data"; |
| 30 | 31 |
| 31 // Number of upload retries. | 32 // Number of upload attempts. |
| 32 const int kMaxRetries = 1; | 33 const int kMaxAttempts = 4; |
| 33 | 34 |
| 34 // Max size of MIME boundary according to RFC 1341, section 7.2.1. | 35 // Max size of MIME boundary according to RFC 1341, section 7.2.1. |
| 35 const size_t kMaxMimeBoundarySize = 70; | 36 const size_t kMaxMimeBoundarySize = 70; |
| 36 | 37 |
| 38 // Delay after first unsuccessful upload attempt. After each additional failure, | |
| 39 // the delay increases with this value. | |
|
Andrew T Wilson (Slow)
2016/05/02 09:15:49
Consider just having a simple fixed 25s delay - th
Marton Hunyady
2016/05/04 11:26:41
Done.
| |
| 40 // Value chosen because the server is waiting 2 minutes for a screenshot. | |
| 41 // 1st attempt is 0s after request, 2nd: 18s, 3rd: 54s, 4th: 108s. | |
| 42 const long kRetryDelayMs = 18000; | |
| 43 | |
| 37 } // namespace | 44 } // namespace |
| 38 | 45 |
| 39 UploadJobImpl::Delegate::~Delegate() { | 46 UploadJobImpl::Delegate::~Delegate() { |
| 40 } | 47 } |
| 41 | 48 |
| 42 UploadJobImpl::MimeBoundaryGenerator::~MimeBoundaryGenerator() { | 49 UploadJobImpl::MimeBoundaryGenerator::~MimeBoundaryGenerator() { |
| 43 } | 50 } |
| 44 | 51 |
| 45 UploadJobImpl::RandomMimeBoundaryGenerator::~RandomMimeBoundaryGenerator() { | 52 UploadJobImpl::RandomMimeBoundaryGenerator::~RandomMimeBoundaryGenerator() { |
| 46 } | 53 } |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 120 const { | 127 const { |
| 121 return net::GenerateMimeMultipartBoundary(); | 128 return net::GenerateMimeMultipartBoundary(); |
| 122 } | 129 } |
| 123 | 130 |
| 124 UploadJobImpl::UploadJobImpl( | 131 UploadJobImpl::UploadJobImpl( |
| 125 const GURL& upload_url, | 132 const GURL& upload_url, |
| 126 const std::string& account_id, | 133 const std::string& account_id, |
| 127 OAuth2TokenService* token_service, | 134 OAuth2TokenService* token_service, |
| 128 scoped_refptr<net::URLRequestContextGetter> url_context_getter, | 135 scoped_refptr<net::URLRequestContextGetter> url_context_getter, |
| 129 Delegate* delegate, | 136 Delegate* delegate, |
| 130 std::unique_ptr<MimeBoundaryGenerator> boundary_generator) | 137 std::unique_ptr<MimeBoundaryGenerator> boundary_generator, |
| 138 const scoped_refptr<base::SequencedTaskRunner> task_runner) | |
|
Andrew T Wilson (Slow)
2016/05/02 09:15:49
pass as a reference
Marton Hunyady
2016/05/04 11:26:41
Removed const, passing as value.
| |
| 131 : OAuth2TokenService::Consumer("cros_upload_job"), | 139 : OAuth2TokenService::Consumer("cros_upload_job"), |
| 132 upload_url_(upload_url), | 140 upload_url_(upload_url), |
| 133 account_id_(account_id), | 141 account_id_(account_id), |
| 134 token_service_(token_service), | 142 token_service_(token_service), |
| 135 url_context_getter_(url_context_getter), | 143 url_context_getter_(url_context_getter), |
| 136 delegate_(delegate), | 144 delegate_(delegate), |
| 137 boundary_generator_(std::move(boundary_generator)), | 145 boundary_generator_(std::move(boundary_generator)), |
| 138 state_(IDLE), | 146 state_(IDLE), |
| 139 retry_(0) { | 147 retry_(0), |
| 148 task_runner_(task_runner), | |
|
Andrew T Wilson (Slow)
2016/05/02 09:15:49
Now that we have a task_runner, we need to make su
Marton Hunyady
2016/05/04 11:26:41
Done.
| |
| 149 weak_factory_(this) { | |
| 140 DCHECK(token_service_); | 150 DCHECK(token_service_); |
| 141 DCHECK(url_context_getter_); | 151 DCHECK(url_context_getter_); |
| 142 DCHECK(delegate_); | 152 DCHECK(delegate_); |
| 143 if (!upload_url_.is_valid()) { | 153 if (!upload_url_.is_valid()) { |
| 144 state_ = ERROR; | 154 state_ = ERROR; |
| 145 NOTREACHED() << upload_url_ << " is not a valid URL."; | 155 NOTREACHED() << upload_url_ << " is not a valid URL."; |
| 146 } | 156 } |
| 147 } | 157 } |
| 148 | 158 |
| 149 UploadJobImpl::~UploadJobImpl() { | 159 UploadJobImpl::~UploadJobImpl() { |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 162 std::unique_ptr<DataSegment> data_segment( | 172 std::unique_ptr<DataSegment> data_segment( |
| 163 new DataSegment(name, filename, std::move(data), header_entries)); | 173 new DataSegment(name, filename, std::move(data), header_entries)); |
| 164 data_segments_.push_back(std::move(data_segment)); | 174 data_segments_.push_back(std::move(data_segment)); |
| 165 } | 175 } |
| 166 | 176 |
| 167 void UploadJobImpl::Start() { | 177 void UploadJobImpl::Start() { |
| 168 // Cannot start an upload on a busy or failed instance. | 178 // Cannot start an upload on a busy or failed instance. |
| 169 DCHECK_EQ(IDLE, state_); | 179 DCHECK_EQ(IDLE, state_); |
| 170 if (state_ != IDLE) | 180 if (state_ != IDLE) |
| 171 return; | 181 return; |
| 182 retry_ = 0; | |
|
Andrew T Wilson (Slow)
2016/05/02 09:15:49
Why is this necessary? You can only get to IDLE st
Marton Hunyady
2016/05/04 11:26:41
Done.
| |
| 172 RequestAccessToken(); | 183 RequestAccessToken(); |
| 173 } | 184 } |
| 174 | 185 |
| 175 void UploadJobImpl::RequestAccessToken() { | 186 void UploadJobImpl::RequestAccessToken() { |
| 176 state_ = ACQUIRING_TOKEN; | 187 state_ = ACQUIRING_TOKEN; |
| 177 | 188 |
| 178 OAuth2TokenService::ScopeSet scope_set; | 189 OAuth2TokenService::ScopeSet scope_set; |
| 179 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); | 190 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); |
| 180 access_token_request_ = | 191 access_token_request_ = |
| 181 token_service_->StartRequest(account_id_, scope_set, this); | 192 token_service_->StartRequest(account_id_, scope_set, this); |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 257 | 268 |
| 258 upload_fetcher_ = | 269 upload_fetcher_ = |
| 259 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this); | 270 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this); |
| 260 upload_fetcher_->SetRequestContext(url_context_getter_.get()); | 271 upload_fetcher_->SetRequestContext(url_context_getter_.get()); |
| 261 upload_fetcher_->SetUploadData(content_type, *post_data_); | 272 upload_fetcher_->SetUploadData(content_type, *post_data_); |
| 262 upload_fetcher_->AddExtraRequestHeader( | 273 upload_fetcher_->AddExtraRequestHeader( |
| 263 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); | 274 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); |
| 264 upload_fetcher_->Start(); | 275 upload_fetcher_->Start(); |
| 265 } | 276 } |
| 266 | 277 |
| 267 void UploadJobImpl::StartUpload(const std::string& access_token) { | 278 void UploadJobImpl::StartUpload() { |
| 268 if (!SetUpMultipart()) { | 279 if (!SetUpMultipart()) { |
| 269 LOG(ERROR) << "Multipart message assembly failed."; | 280 LOG(ERROR) << "Multipart message assembly failed."; |
| 270 state_ = ERROR; | 281 state_ = ERROR; |
| 271 return; | 282 return; |
| 272 } | 283 } |
| 273 CreateAndStartURLFetcher(access_token); | 284 CreateAndStartURLFetcher(access_token_); |
| 274 state_ = UPLOADING; | 285 state_ = UPLOADING; |
| 275 } | 286 } |
| 276 | 287 |
| 277 void UploadJobImpl::OnGetTokenSuccess( | 288 void UploadJobImpl::OnGetTokenSuccess( |
| 278 const OAuth2TokenService::Request* request, | 289 const OAuth2TokenService::Request* request, |
| 279 const std::string& access_token, | 290 const std::string& access_token, |
| 280 const base::Time& expiration_time) { | 291 const base::Time& expiration_time) { |
| 281 DCHECK_EQ(ACQUIRING_TOKEN, state_); | 292 DCHECK_EQ(ACQUIRING_TOKEN, state_); |
| 282 DCHECK_EQ(access_token_request_.get(), request); | 293 DCHECK_EQ(access_token_request_.get(), request); |
| 283 access_token_request_.reset(); | 294 access_token_request_.reset(); |
| 284 | 295 |
| 285 // Also cache the token locally, so that we can revoke it later if necessary. | 296 // Also cache the token locally, so that we can revoke it later if necessary. |
| 286 access_token_ = access_token; | 297 access_token_ = access_token; |
| 287 StartUpload(access_token); | 298 StartUpload(); |
| 288 } | 299 } |
| 289 | 300 |
| 290 void UploadJobImpl::OnGetTokenFailure( | 301 void UploadJobImpl::OnGetTokenFailure( |
| 291 const OAuth2TokenService::Request* request, | 302 const OAuth2TokenService::Request* request, |
| 292 const GoogleServiceAuthError& error) { | 303 const GoogleServiceAuthError& error) { |
| 293 DCHECK_EQ(ACQUIRING_TOKEN, state_); | 304 DCHECK_EQ(ACQUIRING_TOKEN, state_); |
| 294 DCHECK_EQ(access_token_request_.get(), request); | 305 DCHECK_EQ(access_token_request_.get(), request); |
| 295 access_token_request_.reset(); | |
|
Andrew T Wilson (Slow)
2016/05/02 09:15:49
Why did you remove this? I think you still want to
Marton Hunyady
2016/05/04 11:26:41
Done.
| |
| 296 LOG(ERROR) << "Token request failed: " << error.ToString(); | 306 LOG(ERROR) << "Token request failed: " << error.ToString(); |
| 297 state_ = ERROR; | 307 HandleError(AUTHENTICATION_ERROR); |
| 298 delegate_->OnFailure(AUTHENTICATION_ERROR); | 308 } |
| 309 | |
| 310 void UploadJobImpl::HandleError(ErrorCode errorCode) { | |
| 311 retry_++; | |
|
Andrew T Wilson (Slow)
2016/05/02 09:15:49
Should log the errorCode here.
Marton Hunyady
2016/05/04 11:26:41
Done.
| |
| 312 upload_fetcher_.reset(); | |
| 313 if (retry_ >= kMaxAttempts) { | |
| 314 // Maximum number of attempts reached, failure. | |
| 315 LOG(ERROR) << "Upload failed, maximum number of attempts reached."; | |
| 316 access_token_.clear(); | |
| 317 post_data_.reset(); | |
| 318 state_ = ERROR; | |
| 319 delegate_->OnFailure(errorCode); | |
| 320 } else { | |
| 321 if (errorCode == AUTHENTICATION_ERROR) { | |
| 322 LOG(ERROR) << "Upload failed, retrying upload with a new token."; | |
| 323 // Request new token and retry. | |
| 324 OAuth2TokenService::ScopeSet scope_set; | |
| 325 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); | |
| 326 token_service_->InvalidateAccessToken(account_id_, scope_set, | |
| 327 access_token_); | |
| 328 access_token_.clear(); | |
| 329 task_runner_->PostDelayedTask( | |
| 330 FROM_HERE, base::Bind(&UploadJobImpl::RequestAccessToken, | |
| 331 weak_factory_.GetWeakPtr()), | |
| 332 base::TimeDelta::FromMilliseconds(kRetryDelayMs * retry_)); | |
| 333 } else { | |
| 334 // Retry without a new token. | |
| 335 state_ = ACQUIRING_TOKEN; | |
| 336 LOG(WARNING) << "Upload failed, retrying upload with the same token."; | |
| 337 task_runner_->PostDelayedTask( | |
| 338 FROM_HERE, | |
| 339 base::Bind(&UploadJobImpl::StartUpload, weak_factory_.GetWeakPtr()), | |
| 340 base::TimeDelta::FromMilliseconds(kRetryDelayMs * retry_)); | |
| 341 } | |
| 342 } | |
| 299 } | 343 } |
| 300 | 344 |
| 301 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) { | 345 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) { |
| 302 DCHECK_EQ(upload_fetcher_.get(), source); | 346 DCHECK_EQ(upload_fetcher_.get(), source); |
| 347 DCHECK_EQ(UPLOADING, state_); | |
| 303 const net::URLRequestStatus& status = source->GetStatus(); | 348 const net::URLRequestStatus& status = source->GetStatus(); |
| 304 if (!status.is_success()) { | 349 if (!status.is_success()) { |
| 305 LOG(ERROR) << "URLRequestStatus error " << status.error(); | 350 LOG(ERROR) << "URLRequestStatus error " << status.error(); |
| 306 upload_fetcher_.reset(); | 351 HandleError(NETWORK_ERROR); |
| 307 state_ = ERROR; | 352 } else { |
| 308 post_data_.reset(); | 353 const int response_code = source->GetResponseCode(); |
| 309 delegate_->OnFailure(NETWORK_ERROR); | 354 if (response_code == net::HTTP_OK) { |
| 310 return; | 355 // Successful uploading |
|
Andrew T Wilson (Slow)
2016/05/02 09:15:49
nit: uploading -> upload.
Marton Hunyady
2016/05/04 11:26:41
Done.
| |
| 311 } | |
| 312 | |
| 313 const int response_code = source->GetResponseCode(); | |
| 314 const bool success = response_code == net::HTTP_OK; | |
| 315 if (!success) | |
| 316 LOG(ERROR) << "POST request failed with HTTP status code " << response_code; | |
| 317 | |
| 318 if (response_code == net::HTTP_UNAUTHORIZED) { | |
| 319 if (retry_ >= kMaxRetries) { | |
| 320 upload_fetcher_.reset(); | 356 upload_fetcher_.reset(); |
| 357 access_token_.clear(); | |
| 358 post_data_.reset(); | |
| 359 state_ = SUCCESS; | |
| 360 delegate_->OnSuccess(); | |
| 361 } else if (response_code == net::HTTP_UNAUTHORIZED) { | |
| 321 LOG(ERROR) << "Unauthorized request."; | 362 LOG(ERROR) << "Unauthorized request."; |
| 322 state_ = ERROR; | 363 HandleError(AUTHENTICATION_ERROR); |
| 323 post_data_.reset(); | 364 } else { |
| 324 delegate_->OnFailure(AUTHENTICATION_ERROR); | 365 LOG(ERROR) << "POST request failed with HTTP status code " |
| 325 return; | 366 << response_code << "."; |
| 367 HandleError(SERVER_ERROR); | |
| 326 } | 368 } |
| 327 retry_++; | |
| 328 upload_fetcher_.reset(); | |
| 329 OAuth2TokenService::ScopeSet scope_set; | |
| 330 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); | |
| 331 token_service_->InvalidateAccessToken(account_id_, scope_set, | |
| 332 access_token_); | |
| 333 access_token_.clear(); | |
| 334 RequestAccessToken(); | |
| 335 return; | |
| 336 } | |
| 337 | |
| 338 upload_fetcher_.reset(); | |
| 339 access_token_.clear(); | |
| 340 upload_fetcher_.reset(); | |
| 341 post_data_.reset(); | |
| 342 if (success) { | |
| 343 state_ = SUCCESS; | |
| 344 delegate_->OnSuccess(); | |
| 345 } else { | |
| 346 state_ = ERROR; | |
| 347 delegate_->OnFailure(SERVER_ERROR); | |
| 348 } | 369 } |
| 349 } | 370 } |
| 350 | 371 |
| 351 } // namespace policy | 372 } // namespace policy |
| OLD | NEW |