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