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 each unsuccessful upload attempt. | |
| 39 const long kRetryDelayMs = 25000; | |
| 40 | |
| 37 } // namespace | 41 } // namespace |
| 38 | 42 |
| 39 UploadJobImpl::Delegate::~Delegate() { | 43 UploadJobImpl::Delegate::~Delegate() { |
| 40 } | 44 } |
| 41 | 45 |
| 42 UploadJobImpl::MimeBoundaryGenerator::~MimeBoundaryGenerator() { | 46 UploadJobImpl::MimeBoundaryGenerator::~MimeBoundaryGenerator() { |
| 43 } | 47 } |
| 44 | 48 |
| 45 UploadJobImpl::RandomMimeBoundaryGenerator::~RandomMimeBoundaryGenerator() { | 49 UploadJobImpl::RandomMimeBoundaryGenerator::~RandomMimeBoundaryGenerator() { |
| 46 } | 50 } |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 120 const { | 124 const { |
| 121 return net::GenerateMimeMultipartBoundary(); | 125 return net::GenerateMimeMultipartBoundary(); |
| 122 } | 126 } |
| 123 | 127 |
| 124 UploadJobImpl::UploadJobImpl( | 128 UploadJobImpl::UploadJobImpl( |
| 125 const GURL& upload_url, | 129 const GURL& upload_url, |
| 126 const std::string& account_id, | 130 const std::string& account_id, |
| 127 OAuth2TokenService* token_service, | 131 OAuth2TokenService* token_service, |
| 128 scoped_refptr<net::URLRequestContextGetter> url_context_getter, | 132 scoped_refptr<net::URLRequestContextGetter> url_context_getter, |
| 129 Delegate* delegate, | 133 Delegate* delegate, |
| 130 std::unique_ptr<MimeBoundaryGenerator> boundary_generator) | 134 std::unique_ptr<MimeBoundaryGenerator> boundary_generator, |
| 135 scoped_refptr<base::SequencedTaskRunner> task_runner) | |
| 131 : OAuth2TokenService::Consumer("cros_upload_job"), | 136 : OAuth2TokenService::Consumer("cros_upload_job"), |
| 132 upload_url_(upload_url), | 137 upload_url_(upload_url), |
| 133 account_id_(account_id), | 138 account_id_(account_id), |
| 134 token_service_(token_service), | 139 token_service_(token_service), |
| 135 url_context_getter_(url_context_getter), | 140 url_context_getter_(url_context_getter), |
| 136 delegate_(delegate), | 141 delegate_(delegate), |
| 137 boundary_generator_(std::move(boundary_generator)), | 142 boundary_generator_(std::move(boundary_generator)), |
| 138 state_(IDLE), | 143 state_(IDLE), |
| 139 retry_(0) { | 144 retry_(0), |
| 145 task_runner_(task_runner), | |
| 146 weak_factory_(this) { | |
| 140 DCHECK(token_service_); | 147 DCHECK(token_service_); |
| 141 DCHECK(url_context_getter_); | 148 DCHECK(url_context_getter_); |
| 142 DCHECK(delegate_); | 149 DCHECK(delegate_); |
| 143 if (!upload_url_.is_valid()) { | 150 if (!upload_url_.is_valid()) { |
| 144 state_ = ERROR; | 151 state_ = ERROR; |
| 145 NOTREACHED() << upload_url_ << " is not a valid URL."; | 152 NOTREACHED() << upload_url_ << " is not a valid URL."; |
| 146 } | 153 } |
| 147 } | 154 } |
| 148 | 155 |
| 149 UploadJobImpl::~UploadJobImpl() { | 156 UploadJobImpl::~UploadJobImpl() { |
| 150 } | 157 } |
| 151 | 158 |
| 152 void UploadJobImpl::AddDataSegment( | 159 void UploadJobImpl::AddDataSegment( |
| 153 const std::string& name, | 160 const std::string& name, |
| 154 const std::string& filename, | 161 const std::string& filename, |
| 155 const std::map<std::string, std::string>& header_entries, | 162 const std::map<std::string, std::string>& header_entries, |
| 156 std::unique_ptr<std::string> data) { | 163 std::unique_ptr<std::string> data) { |
| 157 // Cannot add data to busy or failed instance. | 164 // Cannot add data to busy or failed instance. |
| 158 DCHECK_EQ(IDLE, state_); | 165 DCHECK_EQ(IDLE, state_); |
| 159 if (state_ != IDLE) | 166 if (state_ != IDLE) |
| 160 return; | 167 return; |
| 168 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 161 | 169 |
| 162 std::unique_ptr<DataSegment> data_segment( | 170 std::unique_ptr<DataSegment> data_segment( |
| 163 new DataSegment(name, filename, std::move(data), header_entries)); | 171 new DataSegment(name, filename, std::move(data), header_entries)); |
| 164 data_segments_.push_back(std::move(data_segment)); | 172 data_segments_.push_back(std::move(data_segment)); |
| 165 } | 173 } |
| 166 | 174 |
| 167 void UploadJobImpl::Start() { | 175 void UploadJobImpl::Start() { |
| 168 // Cannot start an upload on a busy or failed instance. | 176 // Cannot start an upload on a busy or failed instance. |
| 169 DCHECK_EQ(IDLE, state_); | 177 DCHECK_EQ(IDLE, state_); |
| 170 if (state_ != IDLE) | 178 if (state_ != IDLE) |
| 171 return; | 179 return; |
| 180 DCHECK_EQ(0, retry_); | |
| 181 DCHECK(thread_checker_.CalledOnValidThread()); | |
|
Andrew T Wilson (Slow)
2016/05/10 22:00:42
Move call to CalledOnValidThread() to the top of t
Marton Hunyady
2016/05/11 12:55:30
Done.
| |
| 182 | |
| 172 RequestAccessToken(); | 183 RequestAccessToken(); |
| 173 } | 184 } |
| 174 | 185 |
| 175 void UploadJobImpl::RequestAccessToken() { | 186 void UploadJobImpl::RequestAccessToken() { |
| 187 DCHECK(!access_token_request_); | |
| 188 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 189 | |
| 176 state_ = ACQUIRING_TOKEN; | 190 state_ = ACQUIRING_TOKEN; |
| 177 | 191 |
| 178 OAuth2TokenService::ScopeSet scope_set; | 192 OAuth2TokenService::ScopeSet scope_set; |
| 179 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); | 193 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); |
| 180 access_token_request_ = | 194 access_token_request_ = |
| 181 token_service_->StartRequest(account_id_, scope_set, this); | 195 token_service_->StartRequest(account_id_, scope_set, this); |
| 182 } | 196 } |
| 183 | 197 |
| 184 bool UploadJobImpl::SetUpMultipart() { | 198 bool UploadJobImpl::SetUpMultipart() { |
| 185 DCHECK_EQ(ACQUIRING_TOKEN, state_); | 199 DCHECK_EQ(ACQUIRING_TOKEN, state_); |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 257 | 271 |
| 258 upload_fetcher_ = | 272 upload_fetcher_ = |
| 259 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this); | 273 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this); |
| 260 upload_fetcher_->SetRequestContext(url_context_getter_.get()); | 274 upload_fetcher_->SetRequestContext(url_context_getter_.get()); |
| 261 upload_fetcher_->SetUploadData(content_type, *post_data_); | 275 upload_fetcher_->SetUploadData(content_type, *post_data_); |
| 262 upload_fetcher_->AddExtraRequestHeader( | 276 upload_fetcher_->AddExtraRequestHeader( |
| 263 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); | 277 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); |
| 264 upload_fetcher_->Start(); | 278 upload_fetcher_->Start(); |
| 265 } | 279 } |
| 266 | 280 |
| 267 void UploadJobImpl::StartUpload(const std::string& access_token) { | 281 void UploadJobImpl::StartUpload() { |
| 282 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 283 | |
| 268 if (!SetUpMultipart()) { | 284 if (!SetUpMultipart()) { |
| 269 LOG(ERROR) << "Multipart message assembly failed."; | 285 LOG(ERROR) << "Multipart message assembly failed."; |
| 270 state_ = ERROR; | 286 state_ = ERROR; |
| 271 return; | 287 return; |
| 272 } | 288 } |
| 273 CreateAndStartURLFetcher(access_token); | 289 CreateAndStartURLFetcher(access_token_); |
| 274 state_ = UPLOADING; | 290 state_ = UPLOADING; |
| 275 } | 291 } |
| 276 | 292 |
| 277 void UploadJobImpl::OnGetTokenSuccess( | 293 void UploadJobImpl::OnGetTokenSuccess( |
| 278 const OAuth2TokenService::Request* request, | 294 const OAuth2TokenService::Request* request, |
| 279 const std::string& access_token, | 295 const std::string& access_token, |
| 280 const base::Time& expiration_time) { | 296 const base::Time& expiration_time) { |
| 281 DCHECK_EQ(ACQUIRING_TOKEN, state_); | 297 DCHECK_EQ(ACQUIRING_TOKEN, state_); |
| 282 DCHECK_EQ(access_token_request_.get(), request); | 298 DCHECK_EQ(access_token_request_.get(), request); |
| 283 access_token_request_.reset(); | 299 access_token_request_.reset(); |
| 284 | 300 |
| 285 // Also cache the token locally, so that we can revoke it later if necessary. | 301 // Also cache the token locally, so that we can revoke it later if necessary. |
| 286 access_token_ = access_token; | 302 access_token_ = access_token; |
| 287 StartUpload(access_token); | 303 StartUpload(); |
| 288 } | 304 } |
| 289 | 305 |
| 290 void UploadJobImpl::OnGetTokenFailure( | 306 void UploadJobImpl::OnGetTokenFailure( |
| 291 const OAuth2TokenService::Request* request, | 307 const OAuth2TokenService::Request* request, |
| 292 const GoogleServiceAuthError& error) { | 308 const GoogleServiceAuthError& error) { |
| 293 DCHECK_EQ(ACQUIRING_TOKEN, state_); | 309 DCHECK_EQ(ACQUIRING_TOKEN, state_); |
| 294 DCHECK_EQ(access_token_request_.get(), request); | 310 DCHECK_EQ(access_token_request_.get(), request); |
| 295 access_token_request_.reset(); | 311 access_token_request_.reset(); |
| 296 LOG(ERROR) << "Token request failed: " << error.ToString(); | 312 LOG(ERROR) << "Token request failed: " << error.ToString(); |
| 297 state_ = ERROR; | 313 HandleError(AUTHENTICATION_ERROR); |
| 298 delegate_->OnFailure(AUTHENTICATION_ERROR); | 314 } |
| 315 | |
| 316 void UploadJobImpl::HandleError(ErrorCode errorCode) { | |
|
Andrew T Wilson (Slow)
2016/05/10 22:00:42
errorCode -> error_code
Marton Hunyady
2016/05/11 12:55:30
Done.
| |
| 317 retry_++; | |
| 318 upload_fetcher_.reset(); | |
| 319 | |
| 320 std::string errorString; | |
|
Andrew T Wilson (Slow)
2016/05/10 22:00:42
errorString->error_string
Marton Hunyady
2016/05/11 12:55:30
Done.
| |
| 321 switch (errorCode) { | |
| 322 case NETWORK_ERROR: | |
| 323 errorString = "NETWORK_ERROR"; | |
| 324 break; | |
| 325 case AUTHENTICATION_ERROR: | |
| 326 errorString = "AUTHENTICATION_ERROR"; | |
| 327 break; | |
| 328 case SERVER_ERROR: | |
| 329 errorString = "SERVER_ERROR"; | |
| 330 break; | |
| 331 } | |
| 332 LOG(ERROR) << "Upload failed: " << errorString; | |
|
Andrew T Wilson (Slow)
2016/05/10 22:00:42
This is OK, but I think it's superior to just log
Marton Hunyady
2016/05/11 12:55:30
Done.
| |
| 333 | |
| 334 if (retry_ >= kMaxAttempts) { | |
| 335 // Maximum number of attempts reached, failure. | |
| 336 LOG(ERROR) << "Maximum number of attempts reached."; | |
| 337 access_token_.clear(); | |
| 338 post_data_.reset(); | |
| 339 state_ = ERROR; | |
| 340 delegate_->OnFailure(errorCode); | |
| 341 } else { | |
| 342 if (errorCode == AUTHENTICATION_ERROR) { | |
| 343 LOG(ERROR) << "Retrying upload with a new token."; | |
| 344 // Request new token and retry. | |
| 345 OAuth2TokenService::ScopeSet scope_set; | |
| 346 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); | |
| 347 token_service_->InvalidateAccessToken(account_id_, scope_set, | |
| 348 access_token_); | |
| 349 access_token_.clear(); | |
| 350 task_runner_->PostDelayedTask( | |
| 351 FROM_HERE, base::Bind(&UploadJobImpl::RequestAccessToken, | |
| 352 weak_factory_.GetWeakPtr()), | |
| 353 base::TimeDelta::FromMilliseconds(kRetryDelayMs)); | |
| 354 } else { | |
| 355 // Retry without a new token. | |
| 356 state_ = ACQUIRING_TOKEN; | |
| 357 LOG(WARNING) << "Retrying upload with the same token."; | |
| 358 task_runner_->PostDelayedTask( | |
| 359 FROM_HERE, | |
| 360 base::Bind(&UploadJobImpl::StartUpload, weak_factory_.GetWeakPtr()), | |
| 361 base::TimeDelta::FromMilliseconds(kRetryDelayMs)); | |
| 362 } | |
| 363 } | |
| 299 } | 364 } |
| 300 | 365 |
| 301 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) { | 366 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) { |
| 302 DCHECK_EQ(upload_fetcher_.get(), source); | 367 DCHECK_EQ(upload_fetcher_.get(), source); |
| 368 DCHECK_EQ(UPLOADING, state_); | |
| 303 const net::URLRequestStatus& status = source->GetStatus(); | 369 const net::URLRequestStatus& status = source->GetStatus(); |
| 304 if (!status.is_success()) { | 370 if (!status.is_success()) { |
| 305 LOG(ERROR) << "URLRequestStatus error " << status.error(); | 371 LOG(ERROR) << "URLRequestStatus error " << status.error(); |
| 306 upload_fetcher_.reset(); | 372 HandleError(NETWORK_ERROR); |
| 307 state_ = ERROR; | 373 } else { |
| 308 post_data_.reset(); | 374 const int response_code = source->GetResponseCode(); |
| 309 delegate_->OnFailure(NETWORK_ERROR); | 375 if (response_code == net::HTTP_OK) { |
| 310 return; | 376 // Successful upload |
| 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(); | 377 upload_fetcher_.reset(); |
| 378 access_token_.clear(); | |
| 379 post_data_.reset(); | |
| 380 state_ = SUCCESS; | |
| 381 delegate_->OnSuccess(); | |
| 382 } else if (response_code == net::HTTP_UNAUTHORIZED) { | |
| 321 LOG(ERROR) << "Unauthorized request."; | 383 LOG(ERROR) << "Unauthorized request."; |
| 322 state_ = ERROR; | 384 HandleError(AUTHENTICATION_ERROR); |
| 323 post_data_.reset(); | 385 } else { |
| 324 delegate_->OnFailure(AUTHENTICATION_ERROR); | 386 LOG(ERROR) << "POST request failed with HTTP status code " |
| 325 return; | 387 << response_code << "."; |
| 388 HandleError(SERVER_ERROR); | |
| 326 } | 389 } |
| 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 } | 390 } |
| 349 } | 391 } |
| 350 | 392 |
| 351 } // namespace policy | 393 } // namespace policy |
| OLD | NEW |