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