Home | History | Annotate | Download | only in cloud
      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 
     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 
     69 bool IsProtobufMimeType(const net::URLFetcher* fetcher) {
     70   return fetcher->GetResponseHeaders()->HasHeaderValue(
     71       "content-type", "application/x-protobuffer");
     72 }
     73 
     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 
    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 
    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 
    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 
    202 DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() {
    203   service_->RemoveJob(this);
    204 }
    205 
    206 void DeviceManagementRequestJobImpl::Run() {
    207   service_->AddJob(this);
    208 }
    209 
    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 
    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 
    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 
    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 
    345 void DeviceManagementRequestJobImpl::PrepareRetry() {
    346   if (!retry_callback_.is_null())
    347     retry_callback_.Run(this);
    348 }
    349 
    350 void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code) {
    351   em::DeviceManagementResponse dummy_response;
    352   callback_.Run(code, net::OK, dummy_response);
    353 }
    354 
    355 DeviceManagementRequestJob::~DeviceManagementRequestJob() {}
    356 
    357 void DeviceManagementRequestJob::SetGaiaToken(const std::string& gaia_token) {
    358   gaia_token_ = gaia_token;
    359 }
    360 
    361 void DeviceManagementRequestJob::SetOAuthToken(const std::string& oauth_token) {
    362   AddParameter(dm_protocol::kParamOAuthToken, oauth_token);
    363 }
    364 
    365 void DeviceManagementRequestJob::SetUserAffiliation(
    366     UserAffiliation user_affiliation) {
    367   AddParameter(dm_protocol::kParamUserAffiliation,
    368                UserAffiliationToString(user_affiliation));
    369 }
    370 
    371 void DeviceManagementRequestJob::SetDMToken(const std::string& dm_token) {
    372   dm_token_ = dm_token;
    373 }
    374 
    375 void DeviceManagementRequestJob::SetClientID(const std::string& client_id) {
    376   AddParameter(dm_protocol::kParamDeviceID, client_id);
    377 }
    378 
    379 em::DeviceManagementRequest* DeviceManagementRequestJob::GetRequest() {
    380   return &request_;
    381 }
    382 
    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 
    394 void DeviceManagementRequestJob::SetRetryCallback(
    395     const RetryCallback& retry_callback) {
    396   retry_callback_ = retry_callback;
    397 }
    398 
    399 void DeviceManagementRequestJob::Start(const Callback& callback) {
    400   callback_ = callback;
    401   Run();
    402 }
    403 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    477 std::string DeviceManagementService::GetServerUrl() {
    478   return configuration_->GetServerUrl();
    479 }
    480 
    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 
    505 void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl* job) {
    506   if (initialized_)
    507     StartJob(job);
    508   else
    509     queued_jobs_.push_back(job);
    510 }
    511 
    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