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 "chrome/browser/policy/cloud/device_management_service.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/cookies/cookie_monster.h"
17 #include "net/dns/host_resolver.h"
18 #include "net/http/http_network_layer.h"
19 #include "net/http/http_response_headers.h"
20 #include "net/proxy/proxy_service.h"
21 #include "net/ssl/ssl_config_service_defaults.h"
22 #include "net/url_request/static_http_user_agent_settings.h"
23 #include "net/url_request/url_fetcher.h"
24 #include "net/url_request/url_request_context.h"
25 #include "net/url_request/url_request_context_getter.h"
26 #include "net/url_request/url_request_status.h"
29 namespace em = enterprise_management;
35 const char kPostContentType[] = "application/protobuf";
37 const char kServiceTokenAuthHeader[] = "Authorization: GoogleLogin auth=";
38 const char kDMTokenAuthHeader[] = "Authorization: GoogleDMToken token=";
40 // Number of times to retry on ERR_NETWORK_CHANGED errors.
41 const int kMaxNetworkChangedRetries = 3;
43 // HTTP Error Codes of the DM Server with their concrete meanings in the context
44 // of the DM Server communication.
45 const int kSuccess = 200;
46 const int kInvalidArgument = 400;
47 const int kInvalidAuthCookieOrDMToken = 401;
48 const int kMissingLicenses = 402;
49 const int kDeviceManagementNotAllowed = 403;
50 const int kInvalidURL = 404; // This error is not coming from the GFE.
51 const int kInvalidSerialNumber = 405;
52 const int kDeviceIdConflict = 409;
53 const int kDeviceNotFound = 410;
54 const int kPendingApproval = 412;
55 const int kInternalServerError = 500;
56 const int kServiceUnavailable = 503;
57 const int kPolicyNotFound = 902; // This error is not sent as HTTP status code.
59 bool IsProxyError(const net::URLRequestStatus status) {
60 switch (status.error()) {
61 case net::ERR_PROXY_CONNECTION_FAILED:
62 case net::ERR_TUNNEL_CONNECTION_FAILED:
63 case net::ERR_PROXY_AUTH_UNSUPPORTED:
64 case net::ERR_HTTPS_PROXY_TUNNEL_RESPONSE:
65 case net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED:
66 case net::ERR_PROXY_CERTIFICATE_INVALID:
67 case net::ERR_SOCKS_CONNECTION_FAILED:
68 case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE:
74 bool IsProtobufMimeType(const net::URLFetcher* fetcher) {
75 return fetcher->GetResponseHeaders()->HasHeaderValue(
76 "content-type", "application/x-protobuffer");
79 bool FailedWithProxy(const net::URLFetcher* fetcher) {
80 if ((fetcher->GetLoadFlags() & net::LOAD_BYPASS_PROXY) != 0) {
81 // The request didn't use a proxy.
85 if (!fetcher->GetStatus().is_success() &&
86 IsProxyError(fetcher->GetStatus())) {
87 LOG(WARNING) << "Proxy failed while contacting dmserver.";
91 if (fetcher->GetStatus().is_success() &&
92 fetcher->GetResponseCode() == kSuccess &&
93 fetcher->WasFetchedViaProxy() &&
94 !IsProtobufMimeType(fetcher)) {
95 // The proxy server can be misconfigured but pointing to an existing
96 // server that replies to requests. Try to recover if a successful
97 // request that went through a proxy returns an unexpected mime type.
98 LOG(WARNING) << "Got bad mime-type in response from dmserver that was "
99 << "fetched via a proxy.";
106 const char* UserAffiliationToString(UserAffiliation affiliation) {
107 switch (affiliation) {
108 case USER_AFFILIATION_MANAGED:
109 return dm_protocol::kValueUserAffiliationManaged;
110 case USER_AFFILIATION_NONE:
111 return dm_protocol::kValueUserAffiliationNone;
113 NOTREACHED() << "Invalid user affiliation " << affiliation;
114 return dm_protocol::kValueUserAffiliationNone;
117 const char* JobTypeToRequestType(DeviceManagementRequestJob::JobType type) {
119 case DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT:
120 return dm_protocol::kValueRequestAutoEnrollment;
121 case DeviceManagementRequestJob::TYPE_REGISTRATION:
122 return dm_protocol::kValueRequestRegister;
123 case DeviceManagementRequestJob::TYPE_POLICY_FETCH:
124 return dm_protocol::kValueRequestPolicy;
125 case DeviceManagementRequestJob::TYPE_API_AUTH_CODE_FETCH:
126 return dm_protocol::kValueRequestApiAuthorization;
127 case DeviceManagementRequestJob::TYPE_UNREGISTRATION:
128 return dm_protocol::kValueRequestUnregister;
129 case DeviceManagementRequestJob::TYPE_UPLOAD_CERTIFICATE:
130 return dm_protocol::kValueRequestUploadCertificate;
132 NOTREACHED() << "Invalid job type " << type;
136 // Custom request context implementation that allows to override the user agent,
137 // amongst others. Wraps a baseline request context from which we reuse the
138 // networking components.
139 class DeviceManagementRequestContext : public net::URLRequestContext {
141 DeviceManagementRequestContext(net::URLRequestContext* base_context,
142 const std::string& user_agent);
143 virtual ~DeviceManagementRequestContext();
146 net::StaticHttpUserAgentSettings http_user_agent_settings_;
149 DeviceManagementRequestContext::DeviceManagementRequestContext(
150 net::URLRequestContext* base_context,
151 const std::string& user_agent)
152 : http_user_agent_settings_("*", user_agent) {
153 // Share resolver, proxy service and ssl bits with the baseline context. This
154 // is important so we don't make redundant requests (e.g. when resolving proxy
155 // auto configuration).
156 set_net_log(base_context->net_log());
157 set_host_resolver(base_context->host_resolver());
158 set_proxy_service(base_context->proxy_service());
159 set_ssl_config_service(base_context->ssl_config_service());
161 // Share the http session.
162 set_http_transaction_factory(
163 new net::HttpNetworkLayer(
164 base_context->http_transaction_factory()->GetSession()));
166 // No cookies, please.
167 set_cookie_store(new net::CookieMonster(NULL, NULL));
169 set_http_user_agent_settings(&http_user_agent_settings_);
172 DeviceManagementRequestContext::~DeviceManagementRequestContext() {
173 delete http_transaction_factory();
176 // Request context holder.
177 class DeviceManagementRequestContextGetter
178 : public net::URLRequestContextGetter {
180 DeviceManagementRequestContextGetter(
181 scoped_refptr<net::URLRequestContextGetter> base_context_getter,
182 const std::string& user_agent)
183 : base_context_getter_(base_context_getter),
184 user_agent_(user_agent) {}
186 // Overridden from net::URLRequestContextGetter:
187 virtual net::URLRequestContext* GetURLRequestContext() OVERRIDE;
188 virtual scoped_refptr<base::SingleThreadTaskRunner>
189 GetNetworkTaskRunner() const OVERRIDE;
192 virtual ~DeviceManagementRequestContextGetter() {}
195 scoped_refptr<net::URLRequestContextGetter> base_context_getter_;
196 scoped_ptr<net::URLRequestContext> context_;
197 const std::string user_agent_;
201 net::URLRequestContext*
202 DeviceManagementRequestContextGetter::GetURLRequestContext() {
203 DCHECK(GetNetworkTaskRunner()->RunsTasksOnCurrentThread());
204 if (!context_.get()) {
205 context_.reset(new DeviceManagementRequestContext(
206 base_context_getter_->GetURLRequestContext(), user_agent_));
209 return context_.get();
212 scoped_refptr<base::SingleThreadTaskRunner>
213 DeviceManagementRequestContextGetter::GetNetworkTaskRunner() const {
214 return base_context_getter_->GetNetworkTaskRunner();
219 // Request job implementation used with DeviceManagementService.
220 class DeviceManagementRequestJobImpl : public DeviceManagementRequestJob {
222 DeviceManagementRequestJobImpl(JobType type,
223 const std::string& agent_parameter,
224 const std::string& platform_parameter,
225 DeviceManagementService* service);
226 virtual ~DeviceManagementRequestJobImpl();
228 // Handles the URL request response.
229 void HandleResponse(const net::URLRequestStatus& status,
231 const net::ResponseCookies& cookies,
232 const std::string& data);
234 // Gets the URL to contact.
235 GURL GetURL(const std::string& server_url);
237 // Configures the fetcher, setting up payload and headers.
238 void ConfigureRequest(net::URLFetcher* fetcher);
240 // Returns true if this job should be retried. |fetcher| has just completed,
241 // and can be inspected to determine if the request failed and should be
243 bool ShouldRetry(const net::URLFetcher* fetcher);
245 // Invoked right before retrying this job.
249 // DeviceManagementRequestJob:
250 virtual void Run() OVERRIDE;
253 // Invokes the callback with the given error code.
254 void ReportError(DeviceManagementStatus code);
256 // Pointer to the service this job is associated with.
257 DeviceManagementService* service_;
259 // Whether the BYPASS_PROXY flag should be set by ConfigureRequest().
262 // Number of times that this job has been retried due to ERR_NETWORK_CHANGED.
265 DISALLOW_COPY_AND_ASSIGN(DeviceManagementRequestJobImpl);
268 DeviceManagementRequestJobImpl::DeviceManagementRequestJobImpl(
270 const std::string& agent_parameter,
271 const std::string& platform_parameter,
272 DeviceManagementService* service)
273 : DeviceManagementRequestJob(type, agent_parameter, platform_parameter),
275 bypass_proxy_(false),
278 DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() {
279 service_->RemoveJob(this);
282 void DeviceManagementRequestJobImpl::Run() {
283 service_->AddJob(this);
286 void DeviceManagementRequestJobImpl::HandleResponse(
287 const net::URLRequestStatus& status,
289 const net::ResponseCookies& cookies,
290 const std::string& data) {
291 if (status.status() != net::URLRequestStatus::SUCCESS) {
292 LOG(WARNING) << "DMServer request failed, status: " << status.status()
293 << ", error: " << status.error();
294 em::DeviceManagementResponse dummy_response;
295 callback_.Run(DM_STATUS_REQUEST_FAILED, status.error(), dummy_response);
299 if (response_code != kSuccess)
300 LOG(WARNING) << "DMServer sent an error response: " << response_code;
302 switch (response_code) {
304 em::DeviceManagementResponse response;
305 if (!response.ParseFromString(data)) {
306 ReportError(DM_STATUS_RESPONSE_DECODING_ERROR);
309 callback_.Run(DM_STATUS_SUCCESS, net::OK, response);
312 case kInvalidArgument:
313 ReportError(DM_STATUS_REQUEST_INVALID);
315 case kInvalidAuthCookieOrDMToken:
316 ReportError(DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID);
318 case kMissingLicenses:
319 ReportError(DM_STATUS_SERVICE_MISSING_LICENSES);
321 case kDeviceManagementNotAllowed:
322 ReportError(DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED);
324 case kPendingApproval:
325 ReportError(DM_STATUS_SERVICE_ACTIVATION_PENDING);
328 case kInternalServerError:
329 case kServiceUnavailable:
330 ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE);
332 case kDeviceNotFound:
333 ReportError(DM_STATUS_SERVICE_DEVICE_NOT_FOUND);
335 case kPolicyNotFound:
336 ReportError(DM_STATUS_SERVICE_POLICY_NOT_FOUND);
338 case kInvalidSerialNumber:
339 ReportError(DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER);
341 case kDeviceIdConflict:
342 ReportError(DM_STATUS_SERVICE_DEVICE_ID_CONFLICT);
345 // Handle all unknown 5xx HTTP error codes as temporary and any other
346 // unknown error as one that needs more time to recover.
347 if (response_code >= 500 && response_code <= 599)
348 ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE);
350 ReportError(DM_STATUS_HTTP_STATUS_ERROR);
355 GURL DeviceManagementRequestJobImpl::GetURL(
356 const std::string& server_url) {
357 std::string result(server_url);
359 for (ParameterMap::const_iterator entry(query_params_.begin());
360 entry != query_params_.end();
362 if (entry != query_params_.begin())
364 result += net::EscapeQueryParamValue(entry->first, true);
366 result += net::EscapeQueryParamValue(entry->second, true);
371 void DeviceManagementRequestJobImpl::ConfigureRequest(
372 net::URLFetcher* fetcher) {
373 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
374 net::LOAD_DO_NOT_SAVE_COOKIES |
375 net::LOAD_DISABLE_CACHE |
376 (bypass_proxy_ ? net::LOAD_BYPASS_PROXY : 0));
378 CHECK(request_.SerializeToString(&payload));
379 fetcher->SetUploadData(kPostContentType, payload);
380 std::string extra_headers;
381 if (!gaia_token_.empty())
382 extra_headers += kServiceTokenAuthHeader + gaia_token_ + "\n";
383 if (!dm_token_.empty())
384 extra_headers += kDMTokenAuthHeader + dm_token_ + "\n";
385 fetcher->SetExtraRequestHeaders(extra_headers);
388 bool DeviceManagementRequestJobImpl::ShouldRetry(
389 const net::URLFetcher* fetcher) {
390 if (FailedWithProxy(fetcher) && !bypass_proxy_) {
391 // Retry the job if it failed due to a broken proxy, by bypassing the
392 // proxy on the next try.
393 bypass_proxy_ = true;
397 // Early device policy fetches on ChromeOS and Auto-Enrollment checks are
398 // often interrupted during ChromeOS startup when network change notifications
399 // are sent. Allowing the fetcher to retry once after that is enough to
400 // recover; allow it to retry up to 3 times just in case.
401 if (fetcher->GetStatus().error() == net::ERR_NETWORK_CHANGED &&
402 retries_count_ < kMaxNetworkChangedRetries) {
407 // The request didn't fail, or the limit of retry attempts has been reached;
408 // forward the result to the job owner.
412 void DeviceManagementRequestJobImpl::PrepareRetry() {
413 if (!retry_callback_.is_null())
414 retry_callback_.Run(this);
417 void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code) {
418 em::DeviceManagementResponse dummy_response;
419 callback_.Run(code, net::OK, dummy_response);
422 DeviceManagementRequestJob::~DeviceManagementRequestJob() {}
424 void DeviceManagementRequestJob::SetGaiaToken(const std::string& gaia_token) {
425 gaia_token_ = gaia_token;
428 void DeviceManagementRequestJob::SetOAuthToken(const std::string& oauth_token) {
429 AddParameter(dm_protocol::kParamOAuthToken, oauth_token);
432 void DeviceManagementRequestJob::SetUserAffiliation(
433 UserAffiliation user_affiliation) {
434 AddParameter(dm_protocol::kParamUserAffiliation,
435 UserAffiliationToString(user_affiliation));
438 void DeviceManagementRequestJob::SetDMToken(const std::string& dm_token) {
439 dm_token_ = dm_token;
442 void DeviceManagementRequestJob::SetClientID(const std::string& client_id) {
443 AddParameter(dm_protocol::kParamDeviceID, client_id);
446 em::DeviceManagementRequest* DeviceManagementRequestJob::GetRequest() {
450 DeviceManagementRequestJob::DeviceManagementRequestJob(
452 const std::string& agent_parameter,
453 const std::string& platform_parameter) {
454 AddParameter(dm_protocol::kParamRequest, JobTypeToRequestType(type));
455 AddParameter(dm_protocol::kParamDeviceType, dm_protocol::kValueDeviceType);
456 AddParameter(dm_protocol::kParamAppType, dm_protocol::kValueAppType);
457 AddParameter(dm_protocol::kParamAgent, agent_parameter);
458 AddParameter(dm_protocol::kParamPlatform, platform_parameter);
461 void DeviceManagementRequestJob::SetRetryCallback(
462 const RetryCallback& retry_callback) {
463 retry_callback_ = retry_callback;
466 void DeviceManagementRequestJob::Start(const Callback& callback) {
467 callback_ = callback;
471 void DeviceManagementRequestJob::AddParameter(const std::string& name,
472 const std::string& value) {
473 query_params_.push_back(std::make_pair(name, value));
476 // A random value that other fetchers won't likely use.
477 const int DeviceManagementService::kURLFetcherID = 0xde71ce1d;
479 DeviceManagementService::~DeviceManagementService() {
480 // All running jobs should have been cancelled by now.
481 DCHECK(pending_jobs_.empty());
482 DCHECK(queued_jobs_.empty());
485 DeviceManagementRequestJob* DeviceManagementService::CreateJob(
486 DeviceManagementRequestJob::JobType type) {
487 return new DeviceManagementRequestJobImpl(
489 configuration_->GetAgentParameter(),
490 configuration_->GetPlatformParameter(),
494 void DeviceManagementService::ScheduleInitialization(int64 delay_milliseconds) {
497 base::MessageLoop::current()->PostDelayedTask(
499 base::Bind(&DeviceManagementService::Initialize,
500 weak_ptr_factory_.GetWeakPtr()),
501 base::TimeDelta::FromMilliseconds(delay_milliseconds));
504 void DeviceManagementService::Initialize() {
507 DCHECK(!request_context_getter_.get());
508 request_context_getter_ = new DeviceManagementRequestContextGetter(
509 request_context_, configuration_->GetUserAgent());
512 while (!queued_jobs_.empty()) {
513 StartJob(queued_jobs_.front());
514 queued_jobs_.pop_front();
518 void DeviceManagementService::Shutdown() {
519 for (JobFetcherMap::iterator job(pending_jobs_.begin());
520 job != pending_jobs_.end();
523 queued_jobs_.push_back(job->second);
525 pending_jobs_.clear();
528 DeviceManagementService::DeviceManagementService(
529 scoped_ptr<Configuration> configuration,
530 scoped_refptr<net::URLRequestContextGetter> request_context)
531 : configuration_(configuration.Pass()),
532 request_context_(request_context),
534 weak_ptr_factory_(this) {
535 DCHECK(configuration_);
538 void DeviceManagementService::StartJob(DeviceManagementRequestJobImpl* job) {
539 std::string server_url = configuration_->GetServerUrl();
540 net::URLFetcher* fetcher = net::URLFetcher::Create(
541 kURLFetcherID, job->GetURL(server_url), net::URLFetcher::POST, this);
542 fetcher->SetRequestContext(request_context_getter_.get());
543 job->ConfigureRequest(fetcher);
544 pending_jobs_[fetcher] = job;
548 void DeviceManagementService::OnURLFetchComplete(
549 const net::URLFetcher* source) {
550 JobFetcherMap::iterator entry(pending_jobs_.find(source));
551 if (entry == pending_jobs_.end()) {
552 NOTREACHED() << "Callback from foreign URL fetcher";
556 DeviceManagementRequestJobImpl* job = entry->second;
557 pending_jobs_.erase(entry);
559 if (job->ShouldRetry(source)) {
560 VLOG(1) << "Retrying dmserver request.";
565 source->GetResponseAsString(&data);
566 job->HandleResponse(source->GetStatus(), source->GetResponseCode(),
567 source->GetCookies(), data);
572 void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl* job) {
576 queued_jobs_.push_back(job);
579 void DeviceManagementService::RemoveJob(DeviceManagementRequestJobImpl* job) {
580 for (JobFetcherMap::iterator entry(pending_jobs_.begin());
581 entry != pending_jobs_.end();
583 if (entry->second == job) {
585 pending_jobs_.erase(entry);
590 const JobQueue::iterator elem =
591 std::find(queued_jobs_.begin(), queued_jobs_.end(), job);
592 if (elem != queued_jobs_.end())
593 queued_jobs_.erase(elem);
596 } // namespace policy