• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
IsProxyError(const net::URLRequestStatus status)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 
IsProtobufMimeType(const net::URLFetcher * fetcher)68 bool IsProtobufMimeType(const net::URLFetcher* fetcher) {
69   return fetcher->GetResponseHeaders()->HasHeaderValue(
70       "content-type", "application/x-protobuffer");
71 }
72 
FailedWithProxy(const net::URLFetcher * fetcher)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 
UserAffiliationToString(UserAffiliation affiliation)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 
JobTypeToRequestType(DeviceManagementRequestJob::JobType type)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 
DeviceManagementRequestJobImpl(JobType type,const std::string & agent_parameter,const std::string & platform_parameter,DeviceManagementService * service,net::URLRequestContextGetter * request_context)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 
~DeviceManagementRequestJobImpl()200 DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() {
201   service_->RemoveJob(this);
202 }
203 
Run()204 void DeviceManagementRequestJobImpl::Run() {
205   service_->AddJob(this);
206 }
207 
HandleResponse(const net::URLRequestStatus & status,int response_code,const net::ResponseCookies & cookies,const std::string & data)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 
GetURL(const std::string & server_url)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 
ConfigureRequest(net::URLFetcher * fetcher)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 
ShouldRetry(const net::URLFetcher * fetcher)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 
PrepareRetry()341 void DeviceManagementRequestJobImpl::PrepareRetry() {
342   if (!retry_callback_.is_null())
343     retry_callback_.Run(this);
344 }
345 
ReportError(DeviceManagementStatus code)346 void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code) {
347   em::DeviceManagementResponse dummy_response;
348   callback_.Run(code, net::OK, dummy_response);
349 }
350 
~DeviceManagementRequestJob()351 DeviceManagementRequestJob::~DeviceManagementRequestJob() {}
352 
SetGaiaToken(const std::string & gaia_token)353 void DeviceManagementRequestJob::SetGaiaToken(const std::string& gaia_token) {
354   gaia_token_ = gaia_token;
355 }
356 
SetOAuthToken(const std::string & oauth_token)357 void DeviceManagementRequestJob::SetOAuthToken(const std::string& oauth_token) {
358   AddParameter(dm_protocol::kParamOAuthToken, oauth_token);
359 }
360 
SetUserAffiliation(UserAffiliation user_affiliation)361 void DeviceManagementRequestJob::SetUserAffiliation(
362     UserAffiliation user_affiliation) {
363   AddParameter(dm_protocol::kParamUserAffiliation,
364                UserAffiliationToString(user_affiliation));
365 }
366 
SetDMToken(const std::string & dm_token)367 void DeviceManagementRequestJob::SetDMToken(const std::string& dm_token) {
368   dm_token_ = dm_token;
369 }
370 
SetClientID(const std::string & client_id)371 void DeviceManagementRequestJob::SetClientID(const std::string& client_id) {
372   AddParameter(dm_protocol::kParamDeviceID, client_id);
373 }
374 
GetRequest()375 em::DeviceManagementRequest* DeviceManagementRequestJob::GetRequest() {
376   return &request_;
377 }
378 
DeviceManagementRequestJob(JobType type,const std::string & agent_parameter,const std::string & platform_parameter)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 
SetRetryCallback(const RetryCallback & retry_callback)390 void DeviceManagementRequestJob::SetRetryCallback(
391     const RetryCallback& retry_callback) {
392   retry_callback_ = retry_callback;
393 }
394 
Start(const Callback & callback)395 void DeviceManagementRequestJob::Start(const Callback& callback) {
396   callback_ = callback;
397   Run();
398 }
399 
AddParameter(const std::string & name,const std::string & value)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 
~DeviceManagementService()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 
CreateJob(DeviceManagementRequestJob::JobType type,net::URLRequestContextGetter * request_context)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 
ScheduleInitialization(int64 delay_milliseconds)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 
Initialize()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 
Shutdown()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 
DeviceManagementService(scoped_ptr<Configuration> configuration)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 
StartJob(DeviceManagementRequestJobImpl * job)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 
GetServerUrl()473 std::string DeviceManagementService::GetServerUrl() {
474   return configuration_->GetServerUrl();
475 }
476 
OnURLFetchComplete(const net::URLFetcher * source)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 
AddJob(DeviceManagementRequestJobImpl * job)501 void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl* job) {
502   if (initialized_)
503     StartJob(job);
504   else
505     queued_jobs_.push_back(job);
506 }
507 
RemoveJob(DeviceManagementRequestJobImpl * job)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