| 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 long g_retry_delay_ms = 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) { |
| 164 DCHECK(thread_checker_.CalledOnValidThread()); |
| 157 // Cannot add data to busy or failed instance. | 165 // Cannot add data to busy or failed instance. |
| 158 DCHECK_EQ(IDLE, state_); | 166 DCHECK_EQ(IDLE, state_); |
| 159 if (state_ != IDLE) | 167 if (state_ != IDLE) |
| 160 return; | 168 return; |
| 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() { |
| 176 DCHECK(thread_checker_.CalledOnValidThread()); |
| 168 // Cannot start an upload on a busy or failed instance. | 177 // Cannot start an upload on a busy or failed instance. |
| 169 DCHECK_EQ(IDLE, state_); | 178 DCHECK_EQ(IDLE, state_); |
| 170 if (state_ != IDLE) | 179 if (state_ != IDLE) |
| 171 return; | 180 return; |
| 181 DCHECK_EQ(0, retry_); |
| 182 |
| 172 RequestAccessToken(); | 183 RequestAccessToken(); |
| 173 } | 184 } |
| 174 | 185 |
| 186 // static |
| 187 void UploadJobImpl::SetRetryDelayForTesting(long retry_delay_ms) { |
| 188 CHECK_GE(retry_delay_ms, 0); |
| 189 g_retry_delay_ms = retry_delay_ms; |
| 190 } |
| 191 |
| 175 void UploadJobImpl::RequestAccessToken() { | 192 void UploadJobImpl::RequestAccessToken() { |
| 193 DCHECK(thread_checker_.CalledOnValidThread()); |
| 194 DCHECK(!access_token_request_); |
| 195 |
| 176 state_ = ACQUIRING_TOKEN; | 196 state_ = ACQUIRING_TOKEN; |
| 177 | 197 |
| 178 OAuth2TokenService::ScopeSet scope_set; | 198 OAuth2TokenService::ScopeSet scope_set; |
| 179 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); | 199 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); |
| 180 access_token_request_ = | 200 access_token_request_ = |
| 181 token_service_->StartRequest(account_id_, scope_set, this); | 201 token_service_->StartRequest(account_id_, scope_set, this); |
| 182 } | 202 } |
| 183 | 203 |
| 184 bool UploadJobImpl::SetUpMultipart() { | 204 bool UploadJobImpl::SetUpMultipart() { |
| 185 DCHECK_EQ(ACQUIRING_TOKEN, state_); | 205 DCHECK_EQ(ACQUIRING_TOKEN, state_); |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 257 | 277 |
| 258 upload_fetcher_ = | 278 upload_fetcher_ = |
| 259 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this); | 279 net::URLFetcher::Create(upload_url_, net::URLFetcher::POST, this); |
| 260 upload_fetcher_->SetRequestContext(url_context_getter_.get()); | 280 upload_fetcher_->SetRequestContext(url_context_getter_.get()); |
| 261 upload_fetcher_->SetUploadData(content_type, *post_data_); | 281 upload_fetcher_->SetUploadData(content_type, *post_data_); |
| 262 upload_fetcher_->AddExtraRequestHeader( | 282 upload_fetcher_->AddExtraRequestHeader( |
| 263 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); | 283 base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); |
| 264 upload_fetcher_->Start(); | 284 upload_fetcher_->Start(); |
| 265 } | 285 } |
| 266 | 286 |
| 267 void UploadJobImpl::StartUpload(const std::string& access_token) { | 287 void UploadJobImpl::StartUpload() { |
| 288 DCHECK(thread_checker_.CalledOnValidThread()); |
| 289 |
| 268 if (!SetUpMultipart()) { | 290 if (!SetUpMultipart()) { |
| 269 LOG(ERROR) << "Multipart message assembly failed."; | 291 LOG(ERROR) << "Multipart message assembly failed."; |
| 270 state_ = ERROR; | 292 state_ = ERROR; |
| 271 return; | 293 return; |
| 272 } | 294 } |
| 273 CreateAndStartURLFetcher(access_token); | 295 CreateAndStartURLFetcher(access_token_); |
| 274 state_ = UPLOADING; | 296 state_ = UPLOADING; |
| 275 } | 297 } |
| 276 | 298 |
| 277 void UploadJobImpl::OnGetTokenSuccess( | 299 void UploadJobImpl::OnGetTokenSuccess( |
| 278 const OAuth2TokenService::Request* request, | 300 const OAuth2TokenService::Request* request, |
| 279 const std::string& access_token, | 301 const std::string& access_token, |
| 280 const base::Time& expiration_time) { | 302 const base::Time& expiration_time) { |
| 281 DCHECK_EQ(ACQUIRING_TOKEN, state_); | 303 DCHECK_EQ(ACQUIRING_TOKEN, state_); |
| 282 DCHECK_EQ(access_token_request_.get(), request); | 304 DCHECK_EQ(access_token_request_.get(), request); |
| 283 access_token_request_.reset(); | 305 access_token_request_.reset(); |
| 284 | 306 |
| 285 // Also cache the token locally, so that we can revoke it later if necessary. | 307 // Also cache the token locally, so that we can revoke it later if necessary. |
| 286 access_token_ = access_token; | 308 access_token_ = access_token; |
| 287 StartUpload(access_token); | 309 StartUpload(); |
| 288 } | 310 } |
| 289 | 311 |
| 290 void UploadJobImpl::OnGetTokenFailure( | 312 void UploadJobImpl::OnGetTokenFailure( |
| 291 const OAuth2TokenService::Request* request, | 313 const OAuth2TokenService::Request* request, |
| 292 const GoogleServiceAuthError& error) { | 314 const GoogleServiceAuthError& error) { |
| 293 DCHECK_EQ(ACQUIRING_TOKEN, state_); | 315 DCHECK_EQ(ACQUIRING_TOKEN, state_); |
| 294 DCHECK_EQ(access_token_request_.get(), request); | 316 DCHECK_EQ(access_token_request_.get(), request); |
| 295 access_token_request_.reset(); | 317 access_token_request_.reset(); |
| 296 LOG(ERROR) << "Token request failed: " << error.ToString(); | 318 LOG(ERROR) << "Token request failed: " << error.ToString(); |
| 297 state_ = ERROR; | 319 HandleError(AUTHENTICATION_ERROR); |
| 298 delegate_->OnFailure(AUTHENTICATION_ERROR); | 320 } |
| 321 |
| 322 void UploadJobImpl::HandleError(ErrorCode error_code) { |
| 323 retry_++; |
| 324 upload_fetcher_.reset(); |
| 325 |
| 326 LOG(ERROR) << "Upload failed, error code: " << error_code; |
| 327 |
| 328 if (retry_ >= kMaxAttempts) { |
| 329 // Maximum number of attempts reached, failure. |
| 330 LOG(ERROR) << "Maximum number of attempts reached."; |
| 331 access_token_.clear(); |
| 332 post_data_.reset(); |
| 333 state_ = ERROR; |
| 334 delegate_->OnFailure(error_code); |
| 335 } else { |
| 336 if (error_code == AUTHENTICATION_ERROR) { |
| 337 LOG(ERROR) << "Retrying upload with a new token."; |
| 338 // Request new token and retry. |
| 339 OAuth2TokenService::ScopeSet scope_set; |
| 340 scope_set.insert(GaiaConstants::kDeviceManagementServiceOAuth); |
| 341 token_service_->InvalidateAccessToken(account_id_, scope_set, |
| 342 access_token_); |
| 343 access_token_.clear(); |
| 344 task_runner_->PostDelayedTask( |
| 345 FROM_HERE, base::Bind(&UploadJobImpl::RequestAccessToken, |
| 346 weak_factory_.GetWeakPtr()), |
| 347 base::TimeDelta::FromMilliseconds(g_retry_delay_ms)); |
| 348 } else { |
| 349 // Retry without a new token. |
| 350 state_ = ACQUIRING_TOKEN; |
| 351 LOG(WARNING) << "Retrying upload with the same token."; |
| 352 task_runner_->PostDelayedTask( |
| 353 FROM_HERE, |
| 354 base::Bind(&UploadJobImpl::StartUpload, weak_factory_.GetWeakPtr()), |
| 355 base::TimeDelta::FromMilliseconds(g_retry_delay_ms)); |
| 356 } |
| 357 } |
| 299 } | 358 } |
| 300 | 359 |
| 301 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) { | 360 void UploadJobImpl::OnURLFetchComplete(const net::URLFetcher* source) { |
| 302 DCHECK_EQ(upload_fetcher_.get(), source); | 361 DCHECK_EQ(upload_fetcher_.get(), source); |
| 362 DCHECK_EQ(UPLOADING, state_); |
| 303 const net::URLRequestStatus& status = source->GetStatus(); | 363 const net::URLRequestStatus& status = source->GetStatus(); |
| 304 if (!status.is_success()) { | 364 if (!status.is_success()) { |
| 305 LOG(ERROR) << "URLRequestStatus error " << status.error(); | 365 LOG(ERROR) << "URLRequestStatus error " << status.error(); |
| 306 upload_fetcher_.reset(); | 366 HandleError(NETWORK_ERROR); |
| 307 state_ = ERROR; | 367 } else { |
| 308 post_data_.reset(); | 368 const int response_code = source->GetResponseCode(); |
| 309 delegate_->OnFailure(NETWORK_ERROR); | 369 if (response_code == net::HTTP_OK) { |
| 310 return; | 370 // 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(); | 371 upload_fetcher_.reset(); |
| 372 access_token_.clear(); |
| 373 post_data_.reset(); |
| 374 state_ = SUCCESS; |
| 375 delegate_->OnSuccess(); |
| 376 } else if (response_code == net::HTTP_UNAUTHORIZED) { |
| 321 LOG(ERROR) << "Unauthorized request."; | 377 LOG(ERROR) << "Unauthorized request."; |
| 322 state_ = ERROR; | 378 HandleError(AUTHENTICATION_ERROR); |
| 323 post_data_.reset(); | 379 } else { |
| 324 delegate_->OnFailure(AUTHENTICATION_ERROR); | 380 LOG(ERROR) << "POST request failed with HTTP status code " |
| 325 return; | 381 << response_code << "."; |
| 382 HandleError(SERVER_ERROR); |
| 326 } | 383 } |
| 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 } | 384 } |
| 349 } | 385 } |
| 350 | 386 |
| 351 } // namespace policy | 387 } // namespace policy |
| OLD | NEW |