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
IsProxyError(const net::URLRequestStatus status)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
IsProtobufMimeType(const net::URLFetcher * fetcher)69 bool IsProtobufMimeType(const net::URLFetcher* fetcher) {
70 return fetcher->GetResponseHeaders()->HasHeaderValue(
71 "content-type", "application/x-protobuffer");
72 }
73
FailedWithProxy(const net::URLFetcher * fetcher)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
UserAffiliationToString(UserAffiliation affiliation)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
JobTypeToRequestType(DeviceManagementRequestJob::JobType type)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
DeviceManagementRequestJobImpl(JobType type,const std::string & agent_parameter,const std::string & platform_parameter,DeviceManagementService * service,const scoped_refptr<net::URLRequestContextGetter> & request_context)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
~DeviceManagementRequestJobImpl()202 DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() {
203 service_->RemoveJob(this);
204 }
205
Run()206 void DeviceManagementRequestJobImpl::Run() {
207 service_->AddJob(this);
208 }
209
HandleResponse(const net::URLRequestStatus & status,int response_code,const net::ResponseCookies & cookies,const std::string & data)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
GetURL(const std::string & server_url)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
ConfigureRequest(net::URLFetcher * fetcher)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
ShouldRetry(const net::URLFetcher * fetcher)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
PrepareRetry()345 void DeviceManagementRequestJobImpl::PrepareRetry() {
346 if (!retry_callback_.is_null())
347 retry_callback_.Run(this);
348 }
349
ReportError(DeviceManagementStatus code)350 void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code) {
351 em::DeviceManagementResponse dummy_response;
352 callback_.Run(code, net::OK, dummy_response);
353 }
354
~DeviceManagementRequestJob()355 DeviceManagementRequestJob::~DeviceManagementRequestJob() {}
356
SetGaiaToken(const std::string & gaia_token)357 void DeviceManagementRequestJob::SetGaiaToken(const std::string& gaia_token) {
358 gaia_token_ = gaia_token;
359 }
360
SetOAuthToken(const std::string & oauth_token)361 void DeviceManagementRequestJob::SetOAuthToken(const std::string& oauth_token) {
362 AddParameter(dm_protocol::kParamOAuthToken, oauth_token);
363 }
364
SetUserAffiliation(UserAffiliation user_affiliation)365 void DeviceManagementRequestJob::SetUserAffiliation(
366 UserAffiliation user_affiliation) {
367 AddParameter(dm_protocol::kParamUserAffiliation,
368 UserAffiliationToString(user_affiliation));
369 }
370
SetDMToken(const std::string & dm_token)371 void DeviceManagementRequestJob::SetDMToken(const std::string& dm_token) {
372 dm_token_ = dm_token;
373 }
374
SetClientID(const std::string & client_id)375 void DeviceManagementRequestJob::SetClientID(const std::string& client_id) {
376 AddParameter(dm_protocol::kParamDeviceID, client_id);
377 }
378
GetRequest()379 em::DeviceManagementRequest* DeviceManagementRequestJob::GetRequest() {
380 return &request_;
381 }
382
DeviceManagementRequestJob(JobType type,const std::string & agent_parameter,const std::string & platform_parameter)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
SetRetryCallback(const RetryCallback & retry_callback)394 void DeviceManagementRequestJob::SetRetryCallback(
395 const RetryCallback& retry_callback) {
396 retry_callback_ = retry_callback;
397 }
398
Start(const Callback & callback)399 void DeviceManagementRequestJob::Start(const Callback& callback) {
400 callback_ = callback;
401 Run();
402 }
403
AddParameter(const std::string & name,const std::string & value)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
~DeviceManagementService()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
CreateJob(DeviceManagementRequestJob::JobType type,const scoped_refptr<net::URLRequestContextGetter> & request_context)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
ScheduleInitialization(int64 delay_milliseconds)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
Initialize()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
Shutdown()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
DeviceManagementService(scoped_ptr<Configuration> configuration)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
StartJob(DeviceManagementRequestJobImpl * job)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
GetServerUrl()477 std::string DeviceManagementService::GetServerUrl() {
478 return configuration_->GetServerUrl();
479 }
480
OnURLFetchComplete(const net::URLFetcher * source)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
AddJob(DeviceManagementRequestJobImpl * job)505 void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl* job) {
506 if (initialized_)
507 StartJob(job);
508 else
509 queued_jobs_.push_back(job);
510 }
511
RemoveJob(DeviceManagementRequestJobImpl * job)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