Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / components / policy / core / common / cloud / device_management_service.cc
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   ~DeviceManagementRequestJobImpl() override;
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   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