OLD | NEW |
| (Empty) |
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 "chrome/browser/policy/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 kDeviceIdConflict = 409; | |
45 const int kDeviceNotFound = 410; | |
46 const int kPendingApproval = 412; | |
47 const int kInternalServerError = 500; | |
48 const int kServiceUnavailable = 503; | |
49 const int kPolicyNotFound = 902; // This error is not sent as HTTP status code. | |
50 | |
51 bool IsProxyError(const net::URLRequestStatus status) { | |
52 switch (status.error()) { | |
53 case net::ERR_PROXY_CONNECTION_FAILED: | |
54 case net::ERR_TUNNEL_CONNECTION_FAILED: | |
55 case net::ERR_PROXY_AUTH_UNSUPPORTED: | |
56 case net::ERR_HTTPS_PROXY_TUNNEL_RESPONSE: | |
57 case net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED: | |
58 case net::ERR_PROXY_CERTIFICATE_INVALID: | |
59 case net::ERR_SOCKS_CONNECTION_FAILED: | |
60 case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE: | |
61 return true; | |
62 } | |
63 return false; | |
64 } | |
65 | |
66 bool IsProtobufMimeType(const net::URLFetcher* fetcher) { | |
67 return fetcher->GetResponseHeaders()->HasHeaderValue( | |
68 "content-type", "application/x-protobuffer"); | |
69 } | |
70 | |
71 bool FailedWithProxy(const net::URLFetcher* fetcher) { | |
72 if ((fetcher->GetLoadFlags() & net::LOAD_BYPASS_PROXY) != 0) { | |
73 // The request didn't use a proxy. | |
74 return false; | |
75 } | |
76 | |
77 if (!fetcher->GetStatus().is_success() && | |
78 IsProxyError(fetcher->GetStatus())) { | |
79 LOG(WARNING) << "Proxy failed while contacting dmserver."; | |
80 return true; | |
81 } | |
82 | |
83 if (fetcher->GetStatus().is_success() && | |
84 fetcher->GetResponseCode() == kSuccess && | |
85 fetcher->WasFetchedViaProxy() && | |
86 !IsProtobufMimeType(fetcher)) { | |
87 // The proxy server can be misconfigured but pointing to an existing | |
88 // server that replies to requests. Try to recover if a successful | |
89 // request that went through a proxy returns an unexpected mime type. | |
90 LOG(WARNING) << "Got bad mime-type in response from dmserver that was " | |
91 << "fetched via a proxy."; | |
92 return true; | |
93 } | |
94 | |
95 return false; | |
96 } | |
97 | |
98 const char* UserAffiliationToString(UserAffiliation affiliation) { | |
99 switch (affiliation) { | |
100 case USER_AFFILIATION_MANAGED: | |
101 return dm_protocol::kValueUserAffiliationManaged; | |
102 case USER_AFFILIATION_NONE: | |
103 return dm_protocol::kValueUserAffiliationNone; | |
104 } | |
105 NOTREACHED() << "Invalid user affiliation " << affiliation; | |
106 return dm_protocol::kValueUserAffiliationNone; | |
107 } | |
108 | |
109 const char* JobTypeToRequestType(DeviceManagementRequestJob::JobType type) { | |
110 switch (type) { | |
111 case DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT: | |
112 return dm_protocol::kValueRequestAutoEnrollment; | |
113 case DeviceManagementRequestJob::TYPE_REGISTRATION: | |
114 return dm_protocol::kValueRequestRegister; | |
115 case DeviceManagementRequestJob::TYPE_POLICY_FETCH: | |
116 return dm_protocol::kValueRequestPolicy; | |
117 case DeviceManagementRequestJob::TYPE_API_AUTH_CODE_FETCH: | |
118 return dm_protocol::kValueRequestApiAuthorization; | |
119 case DeviceManagementRequestJob::TYPE_UNREGISTRATION: | |
120 return dm_protocol::kValueRequestUnregister; | |
121 case DeviceManagementRequestJob::TYPE_UPLOAD_CERTIFICATE: | |
122 return dm_protocol::kValueRequestUploadCertificate; | |
123 } | |
124 NOTREACHED() << "Invalid job type " << type; | |
125 return ""; | |
126 } | |
127 | |
128 } // namespace | |
129 | |
130 // Request job implementation used with DeviceManagementService. | |
131 class DeviceManagementRequestJobImpl : public DeviceManagementRequestJob { | |
132 public: | |
133 DeviceManagementRequestJobImpl( | |
134 JobType type, | |
135 const std::string& agent_parameter, | |
136 const std::string& platform_parameter, | |
137 DeviceManagementService* service, | |
138 net::URLRequestContextGetter* request_context); | |
139 virtual ~DeviceManagementRequestJobImpl(); | |
140 | |
141 // Handles the URL request response. | |
142 void HandleResponse(const net::URLRequestStatus& status, | |
143 int response_code, | |
144 const net::ResponseCookies& cookies, | |
145 const std::string& data); | |
146 | |
147 // Gets the URL to contact. | |
148 GURL GetURL(const std::string& server_url); | |
149 | |
150 // Configures the fetcher, setting up payload and headers. | |
151 void ConfigureRequest(net::URLFetcher* fetcher); | |
152 | |
153 // Returns true if this job should be retried. |fetcher| has just completed, | |
154 // and can be inspected to determine if the request failed and should be | |
155 // retried. | |
156 bool ShouldRetry(const net::URLFetcher* fetcher); | |
157 | |
158 // Invoked right before retrying this job. | |
159 void PrepareRetry(); | |
160 | |
161 protected: | |
162 // DeviceManagementRequestJob: | |
163 virtual void Run() OVERRIDE; | |
164 | |
165 private: | |
166 // Invokes the callback with the given error code. | |
167 void ReportError(DeviceManagementStatus code); | |
168 | |
169 // Pointer to the service this job is associated with. | |
170 DeviceManagementService* service_; | |
171 | |
172 // Whether the BYPASS_PROXY flag should be set by ConfigureRequest(). | |
173 bool bypass_proxy_; | |
174 | |
175 // Number of times that this job has been retried due to ERR_NETWORK_CHANGED. | |
176 int retries_count_; | |
177 | |
178 // The request context to use for this job. | |
179 net::URLRequestContextGetter* request_context_; | |
180 | |
181 DISALLOW_COPY_AND_ASSIGN(DeviceManagementRequestJobImpl); | |
182 }; | |
183 | |
184 DeviceManagementRequestJobImpl::DeviceManagementRequestJobImpl( | |
185 JobType type, | |
186 const std::string& agent_parameter, | |
187 const std::string& platform_parameter, | |
188 DeviceManagementService* service, | |
189 net::URLRequestContextGetter* request_context) | |
190 : DeviceManagementRequestJob(type, agent_parameter, platform_parameter), | |
191 service_(service), | |
192 bypass_proxy_(false), | |
193 retries_count_(0), | |
194 request_context_(request_context) {} | |
195 | |
196 DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() { | |
197 service_->RemoveJob(this); | |
198 } | |
199 | |
200 void DeviceManagementRequestJobImpl::Run() { | |
201 service_->AddJob(this); | |
202 } | |
203 | |
204 void DeviceManagementRequestJobImpl::HandleResponse( | |
205 const net::URLRequestStatus& status, | |
206 int response_code, | |
207 const net::ResponseCookies& cookies, | |
208 const std::string& data) { | |
209 if (status.status() != net::URLRequestStatus::SUCCESS) { | |
210 LOG(WARNING) << "DMServer request failed, status: " << status.status() | |
211 << ", error: " << status.error(); | |
212 em::DeviceManagementResponse dummy_response; | |
213 callback_.Run(DM_STATUS_REQUEST_FAILED, status.error(), dummy_response); | |
214 return; | |
215 } | |
216 | |
217 if (response_code != kSuccess) | |
218 LOG(WARNING) << "DMServer sent an error response: " << response_code; | |
219 | |
220 switch (response_code) { | |
221 case kSuccess: { | |
222 em::DeviceManagementResponse response; | |
223 if (!response.ParseFromString(data)) { | |
224 ReportError(DM_STATUS_RESPONSE_DECODING_ERROR); | |
225 return; | |
226 } | |
227 callback_.Run(DM_STATUS_SUCCESS, net::OK, response); | |
228 return; | |
229 } | |
230 case kInvalidArgument: | |
231 ReportError(DM_STATUS_REQUEST_INVALID); | |
232 return; | |
233 case kInvalidAuthCookieOrDMToken: | |
234 ReportError(DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID); | |
235 return; | |
236 case kMissingLicenses: | |
237 ReportError(DM_STATUS_SERVICE_MISSING_LICENSES); | |
238 return; | |
239 case kDeviceManagementNotAllowed: | |
240 ReportError(DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED); | |
241 return; | |
242 case kPendingApproval: | |
243 ReportError(DM_STATUS_SERVICE_ACTIVATION_PENDING); | |
244 return; | |
245 case kInvalidURL: | |
246 case kInternalServerError: | |
247 case kServiceUnavailable: | |
248 ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE); | |
249 return; | |
250 case kDeviceNotFound: | |
251 ReportError(DM_STATUS_SERVICE_DEVICE_NOT_FOUND); | |
252 return; | |
253 case kPolicyNotFound: | |
254 ReportError(DM_STATUS_SERVICE_POLICY_NOT_FOUND); | |
255 return; | |
256 case kInvalidSerialNumber: | |
257 ReportError(DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER); | |
258 return; | |
259 case kDeviceIdConflict: | |
260 ReportError(DM_STATUS_SERVICE_DEVICE_ID_CONFLICT); | |
261 return; | |
262 default: | |
263 // Handle all unknown 5xx HTTP error codes as temporary and any other | |
264 // unknown error as one that needs more time to recover. | |
265 if (response_code >= 500 && response_code <= 599) | |
266 ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE); | |
267 else | |
268 ReportError(DM_STATUS_HTTP_STATUS_ERROR); | |
269 return; | |
270 } | |
271 } | |
272 | |
273 GURL DeviceManagementRequestJobImpl::GetURL( | |
274 const std::string& server_url) { | |
275 std::string result(server_url); | |
276 result += '?'; | |
277 for (ParameterMap::const_iterator entry(query_params_.begin()); | |
278 entry != query_params_.end(); | |
279 ++entry) { | |
280 if (entry != query_params_.begin()) | |
281 result += '&'; | |
282 result += net::EscapeQueryParamValue(entry->first, true); | |
283 result += '='; | |
284 result += net::EscapeQueryParamValue(entry->second, true); | |
285 } | |
286 return GURL(result); | |
287 } | |
288 | |
289 void DeviceManagementRequestJobImpl::ConfigureRequest( | |
290 net::URLFetcher* fetcher) { | |
291 fetcher->SetRequestContext(request_context_); | |
292 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | | |
293 net::LOAD_DO_NOT_SAVE_COOKIES | | |
294 net::LOAD_DISABLE_CACHE | | |
295 (bypass_proxy_ ? net::LOAD_BYPASS_PROXY : 0)); | |
296 std::string payload; | |
297 CHECK(request_.SerializeToString(&payload)); | |
298 fetcher->SetUploadData(kPostContentType, payload); | |
299 std::string extra_headers; | |
300 if (!gaia_token_.empty()) | |
301 extra_headers += kServiceTokenAuthHeader + gaia_token_ + "\n"; | |
302 if (!dm_token_.empty()) | |
303 extra_headers += kDMTokenAuthHeader + dm_token_ + "\n"; | |
304 fetcher->SetExtraRequestHeaders(extra_headers); | |
305 } | |
306 | |
307 bool DeviceManagementRequestJobImpl::ShouldRetry( | |
308 const net::URLFetcher* fetcher) { | |
309 if (FailedWithProxy(fetcher) && !bypass_proxy_) { | |
310 // Retry the job if it failed due to a broken proxy, by bypassing the | |
311 // proxy on the next try. | |
312 bypass_proxy_ = true; | |
313 return true; | |
314 } | |
315 | |
316 // Early device policy fetches on ChromeOS and Auto-Enrollment checks are | |
317 // often interrupted during ChromeOS startup when network change notifications | |
318 // are sent. Allowing the fetcher to retry once after that is enough to | |
319 // recover; allow it to retry up to 3 times just in case. | |
320 if (fetcher->GetStatus().error() == net::ERR_NETWORK_CHANGED && | |
321 retries_count_ < kMaxNetworkChangedRetries) { | |
322 ++retries_count_; | |
323 return true; | |
324 } | |
325 | |
326 // The request didn't fail, or the limit of retry attempts has been reached; | |
327 // forward the result to the job owner. | |
328 return false; | |
329 } | |
330 | |
331 void DeviceManagementRequestJobImpl::PrepareRetry() { | |
332 if (!retry_callback_.is_null()) | |
333 retry_callback_.Run(this); | |
334 } | |
335 | |
336 void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code) { | |
337 em::DeviceManagementResponse dummy_response; | |
338 callback_.Run(code, net::OK, dummy_response); | |
339 } | |
340 | |
341 DeviceManagementRequestJob::~DeviceManagementRequestJob() {} | |
342 | |
343 void DeviceManagementRequestJob::SetGaiaToken(const std::string& gaia_token) { | |
344 gaia_token_ = gaia_token; | |
345 } | |
346 | |
347 void DeviceManagementRequestJob::SetOAuthToken(const std::string& oauth_token) { | |
348 AddParameter(dm_protocol::kParamOAuthToken, oauth_token); | |
349 } | |
350 | |
351 void DeviceManagementRequestJob::SetUserAffiliation( | |
352 UserAffiliation user_affiliation) { | |
353 AddParameter(dm_protocol::kParamUserAffiliation, | |
354 UserAffiliationToString(user_affiliation)); | |
355 } | |
356 | |
357 void DeviceManagementRequestJob::SetDMToken(const std::string& dm_token) { | |
358 dm_token_ = dm_token; | |
359 } | |
360 | |
361 void DeviceManagementRequestJob::SetClientID(const std::string& client_id) { | |
362 AddParameter(dm_protocol::kParamDeviceID, client_id); | |
363 } | |
364 | |
365 em::DeviceManagementRequest* DeviceManagementRequestJob::GetRequest() { | |
366 return &request_; | |
367 } | |
368 | |
369 DeviceManagementRequestJob::DeviceManagementRequestJob( | |
370 JobType type, | |
371 const std::string& agent_parameter, | |
372 const std::string& platform_parameter) { | |
373 AddParameter(dm_protocol::kParamRequest, JobTypeToRequestType(type)); | |
374 AddParameter(dm_protocol::kParamDeviceType, dm_protocol::kValueDeviceType); | |
375 AddParameter(dm_protocol::kParamAppType, dm_protocol::kValueAppType); | |
376 AddParameter(dm_protocol::kParamAgent, agent_parameter); | |
377 AddParameter(dm_protocol::kParamPlatform, platform_parameter); | |
378 } | |
379 | |
380 void DeviceManagementRequestJob::SetRetryCallback( | |
381 const RetryCallback& retry_callback) { | |
382 retry_callback_ = retry_callback; | |
383 } | |
384 | |
385 void DeviceManagementRequestJob::Start(const Callback& callback) { | |
386 callback_ = callback; | |
387 Run(); | |
388 } | |
389 | |
390 void DeviceManagementRequestJob::AddParameter(const std::string& name, | |
391 const std::string& value) { | |
392 query_params_.push_back(std::make_pair(name, value)); | |
393 } | |
394 | |
395 // A random value that other fetchers won't likely use. | |
396 const int DeviceManagementService::kURLFetcherID = 0xde71ce1d; | |
397 | |
398 DeviceManagementService::~DeviceManagementService() { | |
399 // All running jobs should have been cancelled by now. | |
400 DCHECK(pending_jobs_.empty()); | |
401 DCHECK(queued_jobs_.empty()); | |
402 } | |
403 | |
404 DeviceManagementRequestJob* DeviceManagementService::CreateJob( | |
405 DeviceManagementRequestJob::JobType type, | |
406 net::URLRequestContextGetter* request_context) { | |
407 return new DeviceManagementRequestJobImpl( | |
408 type, | |
409 configuration_->GetAgentParameter(), | |
410 configuration_->GetPlatformParameter(), | |
411 this, | |
412 request_context); | |
413 } | |
414 | |
415 void DeviceManagementService::ScheduleInitialization(int64 delay_milliseconds) { | |
416 if (initialized_) | |
417 return; | |
418 base::MessageLoop::current()->PostDelayedTask( | |
419 FROM_HERE, | |
420 base::Bind(&DeviceManagementService::Initialize, | |
421 weak_ptr_factory_.GetWeakPtr()), | |
422 base::TimeDelta::FromMilliseconds(delay_milliseconds)); | |
423 } | |
424 | |
425 void DeviceManagementService::Initialize() { | |
426 if (initialized_) | |
427 return; | |
428 initialized_ = true; | |
429 | |
430 while (!queued_jobs_.empty()) { | |
431 StartJob(queued_jobs_.front()); | |
432 queued_jobs_.pop_front(); | |
433 } | |
434 } | |
435 | |
436 void DeviceManagementService::Shutdown() { | |
437 for (JobFetcherMap::iterator job(pending_jobs_.begin()); | |
438 job != pending_jobs_.end(); | |
439 ++job) { | |
440 delete job->first; | |
441 queued_jobs_.push_back(job->second); | |
442 } | |
443 pending_jobs_.clear(); | |
444 } | |
445 | |
446 DeviceManagementService::DeviceManagementService( | |
447 scoped_ptr<Configuration> configuration) | |
448 : configuration_(configuration.Pass()), | |
449 initialized_(false), | |
450 weak_ptr_factory_(this) { | |
451 DCHECK(configuration_); | |
452 } | |
453 | |
454 void DeviceManagementService::StartJob(DeviceManagementRequestJobImpl* job) { | |
455 std::string server_url = GetServerURL(); | |
456 net::URLFetcher* fetcher = net::URLFetcher::Create( | |
457 kURLFetcherID, job->GetURL(server_url), net::URLFetcher::POST, this); | |
458 job->ConfigureRequest(fetcher); | |
459 pending_jobs_[fetcher] = job; | |
460 fetcher->Start(); | |
461 } | |
462 | |
463 std::string DeviceManagementService::GetServerURL() { | |
464 return configuration_->GetServerUrl(); | |
465 } | |
466 | |
467 void DeviceManagementService::OnURLFetchComplete( | |
468 const net::URLFetcher* source) { | |
469 JobFetcherMap::iterator entry(pending_jobs_.find(source)); | |
470 if (entry == pending_jobs_.end()) { | |
471 NOTREACHED() << "Callback from foreign URL fetcher"; | |
472 return; | |
473 } | |
474 | |
475 DeviceManagementRequestJobImpl* job = entry->second; | |
476 pending_jobs_.erase(entry); | |
477 | |
478 if (job->ShouldRetry(source)) { | |
479 VLOG(1) << "Retrying dmserver request."; | |
480 job->PrepareRetry(); | |
481 StartJob(job); | |
482 } else { | |
483 std::string data; | |
484 source->GetResponseAsString(&data); | |
485 job->HandleResponse(source->GetStatus(), source->GetResponseCode(), | |
486 source->GetCookies(), data); | |
487 } | |
488 delete source; | |
489 } | |
490 | |
491 void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl* job) { | |
492 if (initialized_) | |
493 StartJob(job); | |
494 else | |
495 queued_jobs_.push_back(job); | |
496 } | |
497 | |
498 void DeviceManagementService::RemoveJob(DeviceManagementRequestJobImpl* job) { | |
499 for (JobFetcherMap::iterator entry(pending_jobs_.begin()); | |
500 entry != pending_jobs_.end(); | |
501 ++entry) { | |
502 if (entry->second == job) { | |
503 delete entry->first; | |
504 pending_jobs_.erase(entry); | |
505 return; | |
506 } | |
507 } | |
508 | |
509 const JobQueue::iterator elem = | |
510 std::find(queued_jobs_.begin(), queued_jobs_.end(), job); | |
511 if (elem != queued_jobs_.end()) | |
512 queued_jobs_.erase(elem); | |
513 } | |
514 | |
515 } // namespace policy | |
OLD | NEW |