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