1 // Copyright (c) 2012 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 "components/policy/core/common/cloud/device_management_service.h" 6 7 #include <utility> 8 9 #include "base/bind.h" 10 #include "base/compiler_specific.h" 11 #include "base/message_loop/message_loop.h" 12 #include "base/message_loop/message_loop_proxy.h" 13 #include "net/base/escape.h" 14 #include "net/base/load_flags.h" 15 #include "net/base/net_errors.h" 16 #include "net/http/http_response_headers.h" 17 #include "net/url_request/url_fetcher.h" 18 #include "net/url_request/url_request_status.h" 19 #include "url/gurl.h" 20 21 namespace em = enterprise_management; 22 23 namespace policy { 24 25 namespace { 26 27 const char kPostContentType[] = "application/protobuf"; 28 29 const char kServiceTokenAuthHeader[] = "Authorization: GoogleLogin auth="; 30 const char kDMTokenAuthHeader[] = "Authorization: GoogleDMToken token="; 31 32 // Number of times to retry on ERR_NETWORK_CHANGED errors. 33 const int kMaxNetworkChangedRetries = 3; 34 35 // HTTP Error Codes of the DM Server with their concrete meanings in the context 36 // of the DM Server communication. 37 const int kSuccess = 200; 38 const int kInvalidArgument = 400; 39 const int kInvalidAuthCookieOrDMToken = 401; 40 const int kMissingLicenses = 402; 41 const int kDeviceManagementNotAllowed = 403; 42 const int kInvalidURL = 404; // This error is not coming from the GFE. 43 const int kInvalidSerialNumber = 405; 44 const int kDeviceIdConflict = 409; 45 const int kDeviceNotFound = 410; 46 const int kPendingApproval = 412; 47 const int kInternalServerError = 500; 48 const int kServiceUnavailable = 503; 49 const int kPolicyNotFound = 902; 50 const int kDeprovisioned = 903; 51 52 bool IsProxyError(const net::URLRequestStatus status) { 53 switch (status.error()) { 54 case net::ERR_PROXY_CONNECTION_FAILED: 55 case net::ERR_TUNNEL_CONNECTION_FAILED: 56 case net::ERR_PROXY_AUTH_UNSUPPORTED: 57 case net::ERR_HTTPS_PROXY_TUNNEL_RESPONSE: 58 case net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED: 59 case net::ERR_PROXY_CERTIFICATE_INVALID: 60 case net::ERR_SOCKS_CONNECTION_FAILED: 61 case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE: 62 return true; 63 } 64 return false; 65 } 66 67 bool IsProtobufMimeType(const net::URLFetcher* fetcher) { 68 return fetcher->GetResponseHeaders()->HasHeaderValue( 69 "content-type", "application/x-protobuffer"); 70 } 71 72 bool FailedWithProxy(const net::URLFetcher* fetcher) { 73 if ((fetcher->GetLoadFlags() & net::LOAD_BYPASS_PROXY) != 0) { 74 // The request didn't use a proxy. 75 return false; 76 } 77 78 if (!fetcher->GetStatus().is_success() && 79 IsProxyError(fetcher->GetStatus())) { 80 LOG(WARNING) << "Proxy failed while contacting dmserver."; 81 return true; 82 } 83 84 if (fetcher->GetStatus().is_success() && 85 fetcher->GetResponseCode() == kSuccess && 86 fetcher->WasFetchedViaProxy() && 87 !IsProtobufMimeType(fetcher)) { 88 // The proxy server can be misconfigured but pointing to an existing 89 // server that replies to requests. Try to recover if a successful 90 // request that went through a proxy returns an unexpected mime type. 91 LOG(WARNING) << "Got bad mime-type in response from dmserver that was " 92 << "fetched via a proxy."; 93 return true; 94 } 95 96 return false; 97 } 98 99 const char* UserAffiliationToString(UserAffiliation affiliation) { 100 switch (affiliation) { 101 case USER_AFFILIATION_MANAGED: 102 return dm_protocol::kValueUserAffiliationManaged; 103 case USER_AFFILIATION_NONE: 104 return dm_protocol::kValueUserAffiliationNone; 105 } 106 NOTREACHED() << "Invalid user affiliation " << affiliation; 107 return dm_protocol::kValueUserAffiliationNone; 108 } 109 110 const char* JobTypeToRequestType(DeviceManagementRequestJob::JobType type) { 111 switch (type) { 112 case DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT: 113 return dm_protocol::kValueRequestAutoEnrollment; 114 case DeviceManagementRequestJob::TYPE_REGISTRATION: 115 return dm_protocol::kValueRequestRegister; 116 case DeviceManagementRequestJob::TYPE_POLICY_FETCH: 117 return dm_protocol::kValueRequestPolicy; 118 case DeviceManagementRequestJob::TYPE_API_AUTH_CODE_FETCH: 119 return dm_protocol::kValueRequestApiAuthorization; 120 case DeviceManagementRequestJob::TYPE_UNREGISTRATION: 121 return dm_protocol::kValueRequestUnregister; 122 case DeviceManagementRequestJob::TYPE_UPLOAD_CERTIFICATE: 123 return dm_protocol::kValueRequestUploadCertificate; 124 } 125 NOTREACHED() << "Invalid job type " << type; 126 return ""; 127 } 128 129 } // namespace 130 131 // Request job implementation used with DeviceManagementService. 132 class DeviceManagementRequestJobImpl : public DeviceManagementRequestJob { 133 public: 134 DeviceManagementRequestJobImpl( 135 JobType type, 136 const std::string& agent_parameter, 137 const std::string& platform_parameter, 138 DeviceManagementService* service, 139 net::URLRequestContextGetter* request_context); 140 virtual ~DeviceManagementRequestJobImpl(); 141 142 // Handles the URL request response. 143 void HandleResponse(const net::URLRequestStatus& status, 144 int response_code, 145 const net::ResponseCookies& cookies, 146 const std::string& data); 147 148 // Gets the URL to contact. 149 GURL GetURL(const std::string& server_url); 150 151 // Configures the fetcher, setting up payload and headers. 152 void ConfigureRequest(net::URLFetcher* fetcher); 153 154 // Returns true if this job should be retried. |fetcher| has just completed, 155 // and can be inspected to determine if the request failed and should be 156 // retried. 157 bool ShouldRetry(const net::URLFetcher* fetcher); 158 159 // Invoked right before retrying this job. 160 void PrepareRetry(); 161 162 protected: 163 // DeviceManagementRequestJob: 164 virtual void Run() OVERRIDE; 165 166 private: 167 // Invokes the callback with the given error code. 168 void ReportError(DeviceManagementStatus code); 169 170 // Pointer to the service this job is associated with. 171 DeviceManagementService* service_; 172 173 // Whether the BYPASS_PROXY flag should be set by ConfigureRequest(). 174 bool bypass_proxy_; 175 176 // Number of times that this job has been retried due to ERR_NETWORK_CHANGED. 177 int retries_count_; 178 179 // The request context to use for this job. 180 net::URLRequestContextGetter* request_context_; 181 182 DISALLOW_COPY_AND_ASSIGN(DeviceManagementRequestJobImpl); 183 }; 184 185 DeviceManagementRequestJobImpl::DeviceManagementRequestJobImpl( 186 JobType type, 187 const std::string& agent_parameter, 188 const std::string& platform_parameter, 189 DeviceManagementService* service, 190 net::URLRequestContextGetter* request_context) 191 : DeviceManagementRequestJob(type, agent_parameter, platform_parameter), 192 service_(service), 193 bypass_proxy_(false), 194 retries_count_(0), 195 request_context_(request_context) {} 196 197 DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() { 198 service_->RemoveJob(this); 199 } 200 201 void DeviceManagementRequestJobImpl::Run() { 202 service_->AddJob(this); 203 } 204 205 void DeviceManagementRequestJobImpl::HandleResponse( 206 const net::URLRequestStatus& status, 207 int response_code, 208 const net::ResponseCookies& cookies, 209 const std::string& data) { 210 if (status.status() != net::URLRequestStatus::SUCCESS) { 211 LOG(WARNING) << "DMServer request failed, status: " << status.status() 212 << ", error: " << status.error(); 213 em::DeviceManagementResponse dummy_response; 214 callback_.Run(DM_STATUS_REQUEST_FAILED, status.error(), dummy_response); 215 return; 216 } 217 218 if (response_code != kSuccess) 219 LOG(WARNING) << "DMServer sent an error response: " << response_code; 220 221 switch (response_code) { 222 case kSuccess: { 223 em::DeviceManagementResponse response; 224 if (!response.ParseFromString(data)) { 225 ReportError(DM_STATUS_RESPONSE_DECODING_ERROR); 226 return; 227 } 228 callback_.Run(DM_STATUS_SUCCESS, net::OK, response); 229 return; 230 } 231 case kInvalidArgument: 232 ReportError(DM_STATUS_REQUEST_INVALID); 233 return; 234 case kInvalidAuthCookieOrDMToken: 235 ReportError(DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID); 236 return; 237 case kMissingLicenses: 238 ReportError(DM_STATUS_SERVICE_MISSING_LICENSES); 239 return; 240 case kDeviceManagementNotAllowed: 241 ReportError(DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED); 242 return; 243 case kPendingApproval: 244 ReportError(DM_STATUS_SERVICE_ACTIVATION_PENDING); 245 return; 246 case kInvalidURL: 247 case kInternalServerError: 248 case kServiceUnavailable: 249 ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE); 250 return; 251 case kDeviceNotFound: 252 ReportError(DM_STATUS_SERVICE_DEVICE_NOT_FOUND); 253 return; 254 case kPolicyNotFound: 255 ReportError(DM_STATUS_SERVICE_POLICY_NOT_FOUND); 256 return; 257 case kInvalidSerialNumber: 258 ReportError(DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER); 259 return; 260 case kDeprovisioned: 261 ReportError(DM_STATUS_SERVICE_DEPROVISIONED); 262 return; 263 case kDeviceIdConflict: 264 ReportError(DM_STATUS_SERVICE_DEVICE_ID_CONFLICT); 265 return; 266 default: 267 // Handle all unknown 5xx HTTP error codes as temporary and any other 268 // unknown error as one that needs more time to recover. 269 if (response_code >= 500 && response_code <= 599) 270 ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE); 271 else 272 ReportError(DM_STATUS_HTTP_STATUS_ERROR); 273 return; 274 } 275 } 276 277 GURL DeviceManagementRequestJobImpl::GetURL( 278 const std::string& server_url) { 279 std::string result(server_url); 280 result += '?'; 281 for (ParameterMap::const_iterator entry(query_params_.begin()); 282 entry != query_params_.end(); 283 ++entry) { 284 if (entry != query_params_.begin()) 285 result += '&'; 286 result += net::EscapeQueryParamValue(entry->first, true); 287 result += '='; 288 result += net::EscapeQueryParamValue(entry->second, true); 289 } 290 return GURL(result); 291 } 292 293 void DeviceManagementRequestJobImpl::ConfigureRequest( 294 net::URLFetcher* fetcher) { 295 fetcher->SetRequestContext(request_context_); 296 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 297 net::LOAD_DO_NOT_SAVE_COOKIES | 298 net::LOAD_DISABLE_CACHE | 299 (bypass_proxy_ ? net::LOAD_BYPASS_PROXY : 0)); 300 std::string payload; 301 CHECK(request_.SerializeToString(&payload)); 302 fetcher->SetUploadData(kPostContentType, payload); 303 std::string extra_headers; 304 if (!gaia_token_.empty()) 305 extra_headers += kServiceTokenAuthHeader + gaia_token_ + "\n"; 306 if (!dm_token_.empty()) 307 extra_headers += kDMTokenAuthHeader + dm_token_ + "\n"; 308 fetcher->SetExtraRequestHeaders(extra_headers); 309 } 310 311 bool DeviceManagementRequestJobImpl::ShouldRetry( 312 const net::URLFetcher* fetcher) { 313 if (FailedWithProxy(fetcher) && !bypass_proxy_) { 314 // Retry the job if it failed due to a broken proxy, by bypassing the 315 // proxy on the next try. 316 bypass_proxy_ = true; 317 return true; 318 } 319 320 // Early device policy fetches on ChromeOS and Auto-Enrollment checks are 321 // often interrupted during ChromeOS startup when network change notifications 322 // are sent. Allowing the fetcher to retry once after that is enough to 323 // recover; allow it to retry up to 3 times just in case. 324 if (fetcher->GetStatus().error() == net::ERR_NETWORK_CHANGED && 325 retries_count_ < kMaxNetworkChangedRetries) { 326 ++retries_count_; 327 return true; 328 } 329 330 // The request didn't fail, or the limit of retry attempts has been reached; 331 // forward the result to the job owner. 332 return false; 333 } 334 335 void DeviceManagementRequestJobImpl::PrepareRetry() { 336 if (!retry_callback_.is_null()) 337 retry_callback_.Run(this); 338 } 339 340 void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code) { 341 em::DeviceManagementResponse dummy_response; 342 callback_.Run(code, net::OK, dummy_response); 343 } 344 345 DeviceManagementRequestJob::~DeviceManagementRequestJob() {} 346 347 void DeviceManagementRequestJob::SetGaiaToken(const std::string& gaia_token) { 348 gaia_token_ = gaia_token; 349 } 350 351 void DeviceManagementRequestJob::SetOAuthToken(const std::string& oauth_token) { 352 AddParameter(dm_protocol::kParamOAuthToken, oauth_token); 353 } 354 355 void DeviceManagementRequestJob::SetUserAffiliation( 356 UserAffiliation user_affiliation) { 357 AddParameter(dm_protocol::kParamUserAffiliation, 358 UserAffiliationToString(user_affiliation)); 359 } 360 361 void DeviceManagementRequestJob::SetDMToken(const std::string& dm_token) { 362 dm_token_ = dm_token; 363 } 364 365 void DeviceManagementRequestJob::SetClientID(const std::string& client_id) { 366 AddParameter(dm_protocol::kParamDeviceID, client_id); 367 } 368 369 em::DeviceManagementRequest* DeviceManagementRequestJob::GetRequest() { 370 return &request_; 371 } 372 373 DeviceManagementRequestJob::DeviceManagementRequestJob( 374 JobType type, 375 const std::string& agent_parameter, 376 const std::string& platform_parameter) { 377 AddParameter(dm_protocol::kParamRequest, JobTypeToRequestType(type)); 378 AddParameter(dm_protocol::kParamDeviceType, dm_protocol::kValueDeviceType); 379 AddParameter(dm_protocol::kParamAppType, dm_protocol::kValueAppType); 380 AddParameter(dm_protocol::kParamAgent, agent_parameter); 381 AddParameter(dm_protocol::kParamPlatform, platform_parameter); 382 } 383 384 void DeviceManagementRequestJob::SetRetryCallback( 385 const RetryCallback& retry_callback) { 386 retry_callback_ = retry_callback; 387 } 388 389 void DeviceManagementRequestJob::Start(const Callback& callback) { 390 callback_ = callback; 391 Run(); 392 } 393 394 void DeviceManagementRequestJob::AddParameter(const std::string& name, 395 const std::string& value) { 396 query_params_.push_back(std::make_pair(name, value)); 397 } 398 399 // A random value that other fetchers won't likely use. 400 const int DeviceManagementService::kURLFetcherID = 0xde71ce1d; 401 402 DeviceManagementService::~DeviceManagementService() { 403 // All running jobs should have been cancelled by now. 404 DCHECK(pending_jobs_.empty()); 405 DCHECK(queued_jobs_.empty()); 406 } 407 408 DeviceManagementRequestJob* DeviceManagementService::CreateJob( 409 DeviceManagementRequestJob::JobType type, 410 net::URLRequestContextGetter* request_context) { 411 return new DeviceManagementRequestJobImpl( 412 type, 413 configuration_->GetAgentParameter(), 414 configuration_->GetPlatformParameter(), 415 this, 416 request_context); 417 } 418 419 void DeviceManagementService::ScheduleInitialization(int64 delay_milliseconds) { 420 if (initialized_) 421 return; 422 base::MessageLoop::current()->PostDelayedTask( 423 FROM_HERE, 424 base::Bind(&DeviceManagementService::Initialize, 425 weak_ptr_factory_.GetWeakPtr()), 426 base::TimeDelta::FromMilliseconds(delay_milliseconds)); 427 } 428 429 void DeviceManagementService::Initialize() { 430 if (initialized_) 431 return; 432 initialized_ = true; 433 434 while (!queued_jobs_.empty()) { 435 StartJob(queued_jobs_.front()); 436 queued_jobs_.pop_front(); 437 } 438 } 439 440 void DeviceManagementService::Shutdown() { 441 for (JobFetcherMap::iterator job(pending_jobs_.begin()); 442 job != pending_jobs_.end(); 443 ++job) { 444 delete job->first; 445 queued_jobs_.push_back(job->second); 446 } 447 pending_jobs_.clear(); 448 } 449 450 DeviceManagementService::DeviceManagementService( 451 scoped_ptr<Configuration> configuration) 452 : configuration_(configuration.Pass()), 453 initialized_(false), 454 weak_ptr_factory_(this) { 455 DCHECK(configuration_); 456 } 457 458 void DeviceManagementService::StartJob(DeviceManagementRequestJobImpl* job) { 459 std::string server_url = GetServerUrl(); 460 net::URLFetcher* fetcher = net::URLFetcher::Create( 461 kURLFetcherID, job->GetURL(server_url), net::URLFetcher::POST, this); 462 job->ConfigureRequest(fetcher); 463 pending_jobs_[fetcher] = job; 464 fetcher->Start(); 465 } 466 467 std::string DeviceManagementService::GetServerUrl() { 468 return configuration_->GetServerUrl(); 469 } 470 471 void DeviceManagementService::OnURLFetchComplete( 472 const net::URLFetcher* source) { 473 JobFetcherMap::iterator entry(pending_jobs_.find(source)); 474 if (entry == pending_jobs_.end()) { 475 NOTREACHED() << "Callback from foreign URL fetcher"; 476 return; 477 } 478 479 DeviceManagementRequestJobImpl* job = entry->second; 480 pending_jobs_.erase(entry); 481 482 if (job->ShouldRetry(source)) { 483 VLOG(1) << "Retrying dmserver request."; 484 job->PrepareRetry(); 485 StartJob(job); 486 } else { 487 std::string data; 488 source->GetResponseAsString(&data); 489 job->HandleResponse(source->GetStatus(), source->GetResponseCode(), 490 source->GetCookies(), data); 491 } 492 delete source; 493 } 494 495 void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl* job) { 496 if (initialized_) 497 StartJob(job); 498 else 499 queued_jobs_.push_back(job); 500 } 501 502 void DeviceManagementService::RemoveJob(DeviceManagementRequestJobImpl* job) { 503 for (JobFetcherMap::iterator entry(pending_jobs_.begin()); 504 entry != pending_jobs_.end(); 505 ++entry) { 506 if (entry->second == job) { 507 delete entry->first; 508 pending_jobs_.erase(entry); 509 return; 510 } 511 } 512 513 const JobQueue::iterator elem = 514 std::find(queued_jobs_.begin(), queued_jobs_.end(), job); 515 if (elem != queued_jobs_.end()) 516 queued_jobs_.erase(elem); 517 } 518 519 } // namespace policy 520