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