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.
5 #include "components/policy/core/common/cloud/device_management_service.h"
10 #include "base/compiler_specific.h"
11 #include "base/location.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "components/data_use_measurement/core/data_use_user_data.h"
15 #include "net/base/escape.h"
16 #include "net/base/load_flags.h"
17 #include "net/base/net_errors.h"
18 #include "net/http/http_response_headers.h"
19 #include "net/url_request/url_fetcher.h"
20 #include "net/url_request/url_request_context_getter.h"
21 #include "net/url_request/url_request_status.h"
24 namespace em
= enterprise_management
;
30 const char kPostContentType
[] = "application/protobuf";
32 const char kServiceTokenAuthHeader
[] = "Authorization: GoogleLogin auth=";
33 const char kDMTokenAuthHeader
[] = "Authorization: GoogleDMToken token=";
35 // Number of times to retry on ERR_NETWORK_CHANGED errors.
36 const int kMaxNetworkChangedRetries
= 3;
38 // HTTP Error Codes of the DM Server with their concrete meanings in the context
39 // of the DM Server communication.
40 const int kSuccess
= 200;
41 const int kInvalidArgument
= 400;
42 const int kInvalidAuthCookieOrDMToken
= 401;
43 const int kMissingLicenses
= 402;
44 const int kDeviceManagementNotAllowed
= 403;
45 const int kInvalidURL
= 404; // This error is not coming from the GFE.
46 const int kInvalidSerialNumber
= 405;
47 const int kDomainMismatch
= 406;
48 const int kDeviceIdConflict
= 409;
49 const int kDeviceNotFound
= 410;
50 const int kPendingApproval
= 412;
51 const int kInternalServerError
= 500;
52 const int kServiceUnavailable
= 503;
53 const int kPolicyNotFound
= 902;
54 const int kDeprovisioned
= 903;
56 bool IsProxyError(const net::URLRequestStatus status
) {
57 switch (status
.error()) {
58 case net::ERR_PROXY_CONNECTION_FAILED
:
59 case net::ERR_TUNNEL_CONNECTION_FAILED
:
60 case net::ERR_PROXY_AUTH_UNSUPPORTED
:
61 case net::ERR_HTTPS_PROXY_TUNNEL_RESPONSE
:
62 case net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED
:
63 case net::ERR_PROXY_CERTIFICATE_INVALID
:
64 case net::ERR_SOCKS_CONNECTION_FAILED
:
65 case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE
:
71 bool IsProtobufMimeType(const net::URLFetcher
* fetcher
) {
72 return fetcher
->GetResponseHeaders()->HasHeaderValue(
73 "content-type", "application/x-protobuffer");
76 bool FailedWithProxy(const net::URLFetcher
* fetcher
) {
77 if ((fetcher
->GetLoadFlags() & net::LOAD_BYPASS_PROXY
) != 0) {
78 // The request didn't use a proxy.
82 if (!fetcher
->GetStatus().is_success() &&
83 IsProxyError(fetcher
->GetStatus())) {
84 LOG(WARNING
) << "Proxy failed while contacting dmserver.";
88 if (fetcher
->GetStatus().is_success() &&
89 fetcher
->GetResponseCode() == kSuccess
&&
90 fetcher
->WasFetchedViaProxy() &&
91 !IsProtobufMimeType(fetcher
)) {
92 // The proxy server can be misconfigured but pointing to an existing
93 // server that replies to requests. Try to recover if a successful
94 // request that went through a proxy returns an unexpected mime type.
95 LOG(WARNING
) << "Got bad mime-type in response from dmserver that was "
96 << "fetched via a proxy.";
103 const char* JobTypeToRequestType(DeviceManagementRequestJob::JobType type
) {
105 case DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT
:
106 return dm_protocol::kValueRequestAutoEnrollment
;
107 case DeviceManagementRequestJob::TYPE_REGISTRATION
:
108 return dm_protocol::kValueRequestRegister
;
109 case DeviceManagementRequestJob::TYPE_POLICY_FETCH
:
110 return dm_protocol::kValueRequestPolicy
;
111 case DeviceManagementRequestJob::TYPE_API_AUTH_CODE_FETCH
:
112 return dm_protocol::kValueRequestApiAuthorization
;
113 case DeviceManagementRequestJob::TYPE_UNREGISTRATION
:
114 return dm_protocol::kValueRequestUnregister
;
115 case DeviceManagementRequestJob::TYPE_UPLOAD_CERTIFICATE
:
116 return dm_protocol::kValueRequestUploadCertificate
;
117 case DeviceManagementRequestJob::TYPE_DEVICE_STATE_RETRIEVAL
:
118 return dm_protocol::kValueRequestDeviceStateRetrieval
;
119 case DeviceManagementRequestJob::TYPE_UPLOAD_STATUS
:
120 return dm_protocol::kValueRequestUploadStatus
;
121 case DeviceManagementRequestJob::TYPE_REMOTE_COMMANDS
:
122 return dm_protocol::kValueRequestRemoteCommands
;
123 case DeviceManagementRequestJob::TYPE_ATTRIBUTE_UPDATE_PERMISSION
:
124 return dm_protocol::kValueRequestDeviceAttributeUpdatePermission
;
125 case DeviceManagementRequestJob::TYPE_ATTRIBUTE_UPDATE
:
126 return dm_protocol::kValueRequestDeviceAttributeUpdate
;
127 case DeviceManagementRequestJob::TYPE_GCM_ID_UPDATE
:
128 return dm_protocol::kValueRequestGcmIdUpdate
;
130 NOTREACHED() << "Invalid job type " << type
;
136 // Request job implementation used with DeviceManagementService.
137 class DeviceManagementRequestJobImpl
: public DeviceManagementRequestJob
{
139 DeviceManagementRequestJobImpl(
141 const std::string
& agent_parameter
,
142 const std::string
& platform_parameter
,
143 DeviceManagementService
* service
,
144 const scoped_refptr
<net::URLRequestContextGetter
>& request_context
);
145 ~DeviceManagementRequestJobImpl() override
;
147 // Handles the URL request response.
148 void HandleResponse(const net::URLRequestStatus
& status
,
150 const net::ResponseCookies
& cookies
,
151 const std::string
& data
);
153 // Gets the URL to contact.
154 GURL
GetURL(const std::string
& server_url
);
156 // Configures the fetcher, setting up payload and headers.
157 void ConfigureRequest(net::URLFetcher
* fetcher
);
159 // Returns true if this job should be retried. |fetcher| has just completed,
160 // and can be inspected to determine if the request failed and should be
162 bool ShouldRetry(const net::URLFetcher
* fetcher
);
164 // Invoked right before retrying this job.
168 // DeviceManagementRequestJob:
172 // Invokes the callback with the given error code.
173 void ReportError(DeviceManagementStatus code
);
175 // Pointer to the service this job is associated with.
176 DeviceManagementService
* service_
;
178 // Whether the BYPASS_PROXY flag should be set by ConfigureRequest().
181 // Number of times that this job has been retried due to ERR_NETWORK_CHANGED.
184 // The request context to use for this job.
185 scoped_refptr
<net::URLRequestContextGetter
> request_context_
;
187 DISALLOW_COPY_AND_ASSIGN(DeviceManagementRequestJobImpl
);
190 DeviceManagementRequestJobImpl::DeviceManagementRequestJobImpl(
192 const std::string
& agent_parameter
,
193 const std::string
& platform_parameter
,
194 DeviceManagementService
* service
,
195 const scoped_refptr
<net::URLRequestContextGetter
>& request_context
)
196 : DeviceManagementRequestJob(type
, agent_parameter
, platform_parameter
),
198 bypass_proxy_(false),
200 request_context_(request_context
) {
203 DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() {
204 service_
->RemoveJob(this);
207 void DeviceManagementRequestJobImpl::Run() {
208 service_
->AddJob(this);
211 void DeviceManagementRequestJobImpl::HandleResponse(
212 const net::URLRequestStatus
& status
,
214 const net::ResponseCookies
& cookies
,
215 const std::string
& data
) {
216 if (status
.status() != net::URLRequestStatus::SUCCESS
) {
217 LOG(WARNING
) << "DMServer request failed, status: " << status
.status()
218 << ", error: " << status
.error();
219 em::DeviceManagementResponse dummy_response
;
220 callback_
.Run(DM_STATUS_REQUEST_FAILED
, status
.error(), dummy_response
);
224 if (response_code
!= kSuccess
)
225 LOG(WARNING
) << "DMServer sent an error response: " << response_code
;
227 switch (response_code
) {
229 em::DeviceManagementResponse response
;
230 if (!response
.ParseFromString(data
)) {
231 ReportError(DM_STATUS_RESPONSE_DECODING_ERROR
);
234 callback_
.Run(DM_STATUS_SUCCESS
, net::OK
, response
);
237 case kInvalidArgument
:
238 ReportError(DM_STATUS_REQUEST_INVALID
);
240 case kInvalidAuthCookieOrDMToken
:
241 ReportError(DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID
);
243 case kMissingLicenses
:
244 ReportError(DM_STATUS_SERVICE_MISSING_LICENSES
);
246 case kDeviceManagementNotAllowed
:
247 ReportError(DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED
);
249 case kPendingApproval
:
250 ReportError(DM_STATUS_SERVICE_ACTIVATION_PENDING
);
253 case kInternalServerError
:
254 case kServiceUnavailable
:
255 ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE
);
257 case kDeviceNotFound
:
258 ReportError(DM_STATUS_SERVICE_DEVICE_NOT_FOUND
);
260 case kPolicyNotFound
:
261 ReportError(DM_STATUS_SERVICE_POLICY_NOT_FOUND
);
263 case kInvalidSerialNumber
:
264 ReportError(DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER
);
266 case kDomainMismatch
:
267 ReportError(DM_STATUS_SERVICE_DOMAIN_MISMATCH
);
270 ReportError(DM_STATUS_SERVICE_DEPROVISIONED
);
272 case kDeviceIdConflict
:
273 ReportError(DM_STATUS_SERVICE_DEVICE_ID_CONFLICT
);
276 // Handle all unknown 5xx HTTP error codes as temporary and any other
277 // unknown error as one that needs more time to recover.
278 if (response_code
>= 500 && response_code
<= 599)
279 ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE
);
281 ReportError(DM_STATUS_HTTP_STATUS_ERROR
);
286 GURL
DeviceManagementRequestJobImpl::GetURL(
287 const std::string
& server_url
) {
288 std::string
result(server_url
);
290 for (ParameterMap::const_iterator
entry(query_params_
.begin());
291 entry
!= query_params_
.end();
293 if (entry
!= query_params_
.begin())
295 result
+= net::EscapeQueryParamValue(entry
->first
, true);
297 result
+= net::EscapeQueryParamValue(entry
->second
, true);
302 void DeviceManagementRequestJobImpl::ConfigureRequest(
303 net::URLFetcher
* fetcher
) {
304 // TODO(dcheng): It might make sense to make this take a const
305 // scoped_refptr<T>& too eventually.
306 fetcher
->SetRequestContext(request_context_
.get());
307 fetcher
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
308 net::LOAD_DO_NOT_SAVE_COOKIES
|
309 net::LOAD_DISABLE_CACHE
|
310 (bypass_proxy_
? net::LOAD_BYPASS_PROXY
: 0));
312 CHECK(request_
.SerializeToString(&payload
));
313 fetcher
->SetUploadData(kPostContentType
, payload
);
314 std::string extra_headers
;
315 if (!gaia_token_
.empty())
316 extra_headers
+= kServiceTokenAuthHeader
+ gaia_token_
+ "\n";
317 if (!dm_token_
.empty())
318 extra_headers
+= kDMTokenAuthHeader
+ dm_token_
+ "\n";
319 fetcher
->SetExtraRequestHeaders(extra_headers
);
322 bool DeviceManagementRequestJobImpl::ShouldRetry(
323 const net::URLFetcher
* fetcher
) {
324 if (FailedWithProxy(fetcher
) && !bypass_proxy_
) {
325 // Retry the job if it failed due to a broken proxy, by bypassing the
326 // proxy on the next try.
327 bypass_proxy_
= true;
331 // Early device policy fetches on ChromeOS and Auto-Enrollment checks are
332 // often interrupted during ChromeOS startup when network change notifications
333 // are sent. Allowing the fetcher to retry once after that is enough to
334 // recover; allow it to retry up to 3 times just in case.
335 if (fetcher
->GetStatus().error() == net::ERR_NETWORK_CHANGED
&&
336 retries_count_
< kMaxNetworkChangedRetries
) {
341 // The request didn't fail, or the limit of retry attempts has been reached;
342 // forward the result to the job owner.
346 void DeviceManagementRequestJobImpl::PrepareRetry() {
347 if (!retry_callback_
.is_null())
348 retry_callback_
.Run(this);
351 void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code
) {
352 em::DeviceManagementResponse dummy_response
;
353 callback_
.Run(code
, net::OK
, dummy_response
);
356 DeviceManagementRequestJob::~DeviceManagementRequestJob() {}
358 void DeviceManagementRequestJob::SetGaiaToken(const std::string
& gaia_token
) {
359 gaia_token_
= gaia_token
;
362 void DeviceManagementRequestJob::SetOAuthToken(const std::string
& oauth_token
) {
363 AddParameter(dm_protocol::kParamOAuthToken
, oauth_token
);
366 void DeviceManagementRequestJob::SetDMToken(const std::string
& dm_token
) {
367 dm_token_
= dm_token
;
370 void DeviceManagementRequestJob::SetClientID(const std::string
& client_id
) {
371 AddParameter(dm_protocol::kParamDeviceID
, client_id
);
374 em::DeviceManagementRequest
* DeviceManagementRequestJob::GetRequest() {
378 DeviceManagementRequestJob::DeviceManagementRequestJob(
380 const std::string
& agent_parameter
,
381 const std::string
& platform_parameter
) {
382 AddParameter(dm_protocol::kParamRequest
, JobTypeToRequestType(type
));
383 AddParameter(dm_protocol::kParamDeviceType
, dm_protocol::kValueDeviceType
);
384 AddParameter(dm_protocol::kParamAppType
, dm_protocol::kValueAppType
);
385 AddParameter(dm_protocol::kParamAgent
, agent_parameter
);
386 AddParameter(dm_protocol::kParamPlatform
, platform_parameter
);
389 void DeviceManagementRequestJob::SetRetryCallback(
390 const RetryCallback
& retry_callback
) {
391 retry_callback_
= retry_callback
;
394 void DeviceManagementRequestJob::Start(const Callback
& callback
) {
395 callback_
= callback
;
399 void DeviceManagementRequestJob::AddParameter(const std::string
& name
,
400 const std::string
& value
) {
401 query_params_
.push_back(std::make_pair(name
, value
));
404 // A random value that other fetchers won't likely use.
405 const int DeviceManagementService::kURLFetcherID
= 0xde71ce1d;
407 DeviceManagementService::~DeviceManagementService() {
408 // All running jobs should have been cancelled by now.
409 DCHECK(pending_jobs_
.empty());
410 DCHECK(queued_jobs_
.empty());
413 DeviceManagementRequestJob
* DeviceManagementService::CreateJob(
414 DeviceManagementRequestJob::JobType type
,
415 const scoped_refptr
<net::URLRequestContextGetter
>& request_context
) {
416 return new DeviceManagementRequestJobImpl(
418 configuration_
->GetAgentParameter(),
419 configuration_
->GetPlatformParameter(),
424 void DeviceManagementService::ScheduleInitialization(int64 delay_milliseconds
) {
427 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
428 FROM_HERE
, base::Bind(&DeviceManagementService::Initialize
,
429 weak_ptr_factory_
.GetWeakPtr()),
430 base::TimeDelta::FromMilliseconds(delay_milliseconds
));
433 void DeviceManagementService::Initialize() {
438 while (!queued_jobs_
.empty()) {
439 StartJob(queued_jobs_
.front());
440 queued_jobs_
.pop_front();
444 void DeviceManagementService::Shutdown() {
445 for (JobFetcherMap::iterator
job(pending_jobs_
.begin());
446 job
!= pending_jobs_
.end();
449 queued_jobs_
.push_back(job
->second
);
451 pending_jobs_
.clear();
454 DeviceManagementService::DeviceManagementService(
455 scoped_ptr
<Configuration
> configuration
)
456 : configuration_(configuration
.Pass()),
458 weak_ptr_factory_(this) {
459 DCHECK(configuration_
);
462 void DeviceManagementService::StartJob(DeviceManagementRequestJobImpl
* job
) {
463 std::string server_url
= GetServerUrl();
464 net::URLFetcher
* fetcher
=
465 net::URLFetcher::Create(kURLFetcherID
, job
->GetURL(server_url
),
466 net::URLFetcher::POST
, this).release();
467 data_use_measurement::DataUseUserData::AttachToFetcher(
468 fetcher
, data_use_measurement::DataUseUserData::POLICY
);
469 job
->ConfigureRequest(fetcher
);
470 pending_jobs_
[fetcher
] = job
;
474 std::string
DeviceManagementService::GetServerUrl() {
475 return configuration_
->GetServerUrl();
478 void DeviceManagementService::OnURLFetchComplete(
479 const net::URLFetcher
* source
) {
480 JobFetcherMap::iterator
entry(pending_jobs_
.find(source
));
481 if (entry
== pending_jobs_
.end()) {
482 NOTREACHED() << "Callback from foreign URL fetcher";
486 DeviceManagementRequestJobImpl
* job
= entry
->second
;
487 pending_jobs_
.erase(entry
);
489 if (job
->ShouldRetry(source
)) {
490 VLOG(1) << "Retrying dmserver request.";
495 source
->GetResponseAsString(&data
);
496 job
->HandleResponse(source
->GetStatus(), source
->GetResponseCode(),
497 source
->GetCookies(), data
);
502 void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl
* job
) {
506 queued_jobs_
.push_back(job
);
509 void DeviceManagementService::RemoveJob(DeviceManagementRequestJobImpl
* job
) {
510 for (JobFetcherMap::iterator
entry(pending_jobs_
.begin());
511 entry
!= pending_jobs_
.end();
513 if (entry
->second
== job
) {
515 pending_jobs_
.erase(entry
);
520 const JobQueue::iterator elem
=
521 std::find(queued_jobs_
.begin(), queued_jobs_
.end(), job
);
522 if (elem
!= queued_jobs_
.end())
523 queued_jobs_
.erase(elem
);
526 } // namespace policy