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