Home | History | Annotate | Download | only in policy
      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/chromeos/policy/auto_enrollment_client.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/guid.h"
      9 #include "base/location.h"
     10 #include "base/logging.h"
     11 #include "base/message_loop/message_loop_proxy.h"
     12 #include "base/metrics/histogram.h"
     13 #include "base/metrics/sparse_histogram.h"
     14 #include "base/prefs/pref_registry_simple.h"
     15 #include "base/prefs/pref_service.h"
     16 #include "base/prefs/scoped_user_pref_update.h"
     17 #include "chrome/browser/browser_process.h"
     18 #include "chrome/browser/chromeos/policy/server_backed_device_state.h"
     19 #include "chrome/common/chrome_content_client.h"
     20 #include "chrome/common/pref_names.h"
     21 #include "components/policy/core/common/cloud/device_management_service.h"
     22 #include "components/policy/core/common/cloud/system_policy_request_context.h"
     23 #include "content/public/browser/browser_thread.h"
     24 #include "crypto/sha2.h"
     25 #include "net/url_request/url_request_context_getter.h"
     26 #include "policy/proto/device_management_backend.pb.h"
     27 #include "url/gurl.h"
     28 
     29 using content::BrowserThread;
     30 
     31 namespace em = enterprise_management;
     32 
     33 namespace policy {
     34 
     35 namespace {
     36 
     37 // UMA histogram names.
     38 const char kUMAProtocolTime[] = "Enterprise.AutoEnrollmentProtocolTime";
     39 const char kUMAExtraTime[] = "Enterprise.AutoEnrollmentExtraTime";
     40 const char kUMARequestStatus[] = "Enterprise.AutoEnrollmentRequestStatus";
     41 const char kUMANetworkErrorCode[] =
     42     "Enterprise.AutoEnrollmentRequestNetworkErrorCode";
     43 
     44 // Returns the power of the next power-of-2 starting at |value|.
     45 int NextPowerOf2(int64 value) {
     46   for (int i = 0; i <= AutoEnrollmentClient::kMaximumPower; ++i) {
     47     if ((GG_INT64_C(1) << i) >= value)
     48       return i;
     49   }
     50   // No other value can be represented in an int64.
     51   return AutoEnrollmentClient::kMaximumPower + 1;
     52 }
     53 
     54 // Sets or clears a value in a dictionary.
     55 void UpdateDict(base::DictionaryValue* dict,
     56                 const char* pref_path,
     57                 bool set_or_clear,
     58                 base::Value* value) {
     59   scoped_ptr<base::Value> scoped_value(value);
     60   if (set_or_clear)
     61     dict->Set(pref_path, scoped_value.release());
     62   else
     63     dict->Remove(pref_path, NULL);
     64 }
     65 
     66 // Converts a restore mode enum value from the DM protocol into the
     67 // corresponding prefs string constant.
     68 std::string ConvertRestoreMode(
     69     em::DeviceStateRetrievalResponse::RestoreMode restore_mode) {
     70   switch (restore_mode) {
     71     case em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE:
     72       return std::string();
     73     case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_REQUESTED:
     74       return kDeviceStateRestoreModeReEnrollmentRequested;
     75     case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED:
     76       return kDeviceStateRestoreModeReEnrollmentEnforced;
     77   }
     78 
     79   // Return is required to avoid compiler warning.
     80   NOTREACHED() << "Bad restore mode " << restore_mode;
     81   return std::string();
     82 }
     83 
     84 }  // namespace
     85 
     86 AutoEnrollmentClient::AutoEnrollmentClient(
     87     const ProgressCallback& callback,
     88     DeviceManagementService* service,
     89     PrefService* local_state,
     90     scoped_refptr<net::URLRequestContextGetter> system_request_context,
     91     const std::string& server_backed_state_key,
     92     bool retrieve_device_state,
     93     int power_initial,
     94     int power_limit)
     95     : progress_callback_(callback),
     96       state_(AUTO_ENROLLMENT_STATE_IDLE),
     97       has_server_state_(false),
     98       device_state_available_(false),
     99       device_id_(base::GenerateGUID()),
    100       server_backed_state_key_(server_backed_state_key),
    101       retrieve_device_state_(retrieve_device_state),
    102       current_power_(power_initial),
    103       power_limit_(power_limit),
    104       modulus_updates_received_(0),
    105       device_management_service_(service),
    106       local_state_(local_state) {
    107   request_context_ = new SystemPolicyRequestContext(
    108       system_request_context, GetUserAgent());
    109 
    110   DCHECK_LE(current_power_, power_limit_);
    111   DCHECK(!progress_callback_.is_null());
    112   if (!server_backed_state_key_.empty()) {
    113     server_backed_state_key_hash_ =
    114         crypto::SHA256HashString(server_backed_state_key_);
    115   }
    116 }
    117 
    118 AutoEnrollmentClient::~AutoEnrollmentClient() {
    119   net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
    120 }
    121 
    122 // static
    123 void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) {
    124   registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
    125   registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
    126 }
    127 
    128 // static
    129 void AutoEnrollmentClient::CancelAutoEnrollment() {
    130   PrefService* local_state = g_browser_process->local_state();
    131   local_state->SetBoolean(prefs::kShouldAutoEnroll, false);
    132   local_state->ClearPref(prefs::kServerBackedDeviceState);
    133   local_state->CommitPendingWrite();
    134 }
    135 
    136 void AutoEnrollmentClient::Start() {
    137   // (Re-)register the network change observer.
    138   net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
    139   net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
    140 
    141   // Drop the previous job and reset state.
    142   request_job_.reset();
    143   state_ = AUTO_ENROLLMENT_STATE_PENDING;
    144   time_start_ = base::Time::Now();
    145   modulus_updates_received_ = 0;
    146   has_server_state_ = false;
    147   device_state_available_ = false;
    148 
    149   NextStep();
    150 }
    151 
    152 void AutoEnrollmentClient::Retry() {
    153   RetryStep();
    154 }
    155 
    156 void AutoEnrollmentClient::CancelAndDeleteSoon() {
    157   if (time_start_.is_null() || !request_job_) {
    158     // The client isn't running, just delete it.
    159     delete this;
    160   } else {
    161     // Client still running, but our owner isn't interested in the result
    162     // anymore. Wait until the protocol completes to measure the extra time
    163     // needed.
    164     time_extra_start_ = base::Time::Now();
    165     progress_callback_.Reset();
    166   }
    167 }
    168 
    169 void AutoEnrollmentClient::OnNetworkChanged(
    170     net::NetworkChangeNotifier::ConnectionType type) {
    171   if (type != net::NetworkChangeNotifier::CONNECTION_NONE &&
    172       !progress_callback_.is_null()) {
    173     RetryStep();
    174   }
    175 }
    176 
    177 bool AutoEnrollmentClient::GetCachedDecision() {
    178   const PrefService::Preference* has_server_state_pref =
    179       local_state_->FindPreference(prefs::kShouldAutoEnroll);
    180   const PrefService::Preference* previous_limit_pref =
    181       local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit);
    182   bool has_server_state = false;
    183   int previous_limit = -1;
    184 
    185   if (!has_server_state_pref ||
    186       has_server_state_pref->IsDefaultValue() ||
    187       !has_server_state_pref->GetValue()->GetAsBoolean(&has_server_state) ||
    188       !previous_limit_pref ||
    189       previous_limit_pref->IsDefaultValue() ||
    190       !previous_limit_pref->GetValue()->GetAsInteger(&previous_limit) ||
    191       power_limit_ > previous_limit) {
    192     return false;
    193   }
    194 
    195   has_server_state_ = has_server_state;
    196   return true;
    197 }
    198 
    199 bool AutoEnrollmentClient::RetryStep() {
    200   // If there is a pending request job, let it finish.
    201   if (request_job_)
    202     return true;
    203 
    204   if (GetCachedDecision()) {
    205     // The bucket download check has completed already. If it came back
    206     // positive, then device state should be (re-)downloaded.
    207     if (has_server_state_) {
    208       if (retrieve_device_state_ && !device_state_available_ &&
    209           SendDeviceStateRequest()) {
    210         return true;
    211       }
    212     }
    213   } else {
    214     // Start bucket download.
    215     if (SendBucketDownloadRequest())
    216       return true;
    217   }
    218 
    219   return false;
    220 }
    221 
    222 void AutoEnrollmentClient::ReportProgress(AutoEnrollmentState state) {
    223   state_ = state;
    224   if (progress_callback_.is_null()) {
    225     base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this);
    226   } else {
    227     progress_callback_.Run(state_);
    228   }
    229 }
    230 
    231 void AutoEnrollmentClient::NextStep() {
    232   if (!RetryStep()) {
    233     // Protocol finished successfully, report result.
    234     bool trigger_enrollment = false;
    235     if (retrieve_device_state_) {
    236       const base::DictionaryValue* device_state_dict =
    237           local_state_->GetDictionary(prefs::kServerBackedDeviceState);
    238       std::string restore_mode;
    239       device_state_dict->GetString(kDeviceStateRestoreMode, &restore_mode);
    240       trigger_enrollment =
    241           (restore_mode == kDeviceStateRestoreModeReEnrollmentRequested ||
    242            restore_mode == kDeviceStateRestoreModeReEnrollmentEnforced);
    243     } else {
    244       trigger_enrollment = has_server_state_;
    245     }
    246 
    247     ReportProgress(trigger_enrollment ? AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT
    248                                       : AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
    249   }
    250 }
    251 
    252 bool AutoEnrollmentClient::SendBucketDownloadRequest() {
    253   if (server_backed_state_key_hash_.empty())
    254     return false;
    255 
    256   // Only power-of-2 moduli are supported for now. These are computed by taking
    257   // the lower |current_power_| bits of the hash.
    258   uint64 remainder = 0;
    259   for (int i = 0; 8 * i < current_power_; ++i) {
    260     uint64 byte = server_backed_state_key_hash_[31 - i] & 0xff;
    261     remainder = remainder | (byte << (8 * i));
    262   }
    263   remainder = remainder & ((GG_UINT64_C(1) << current_power_) - 1);
    264 
    265   ReportProgress(AUTO_ENROLLMENT_STATE_PENDING);
    266 
    267   request_job_.reset(
    268       device_management_service_->CreateJob(
    269           DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT,
    270           request_context_.get()));
    271   request_job_->SetClientID(device_id_);
    272   em::DeviceAutoEnrollmentRequest* request =
    273       request_job_->GetRequest()->mutable_auto_enrollment_request();
    274   request->set_remainder(remainder);
    275   request->set_modulus(GG_INT64_C(1) << current_power_);
    276   request_job_->Start(
    277       base::Bind(&AutoEnrollmentClient::HandleRequestCompletion,
    278                  base::Unretained(this),
    279                  &AutoEnrollmentClient::OnBucketDownloadRequestCompletion));
    280   return true;
    281 }
    282 
    283 bool AutoEnrollmentClient::SendDeviceStateRequest() {
    284   ReportProgress(AUTO_ENROLLMENT_STATE_PENDING);
    285 
    286   request_job_.reset(
    287       device_management_service_->CreateJob(
    288           DeviceManagementRequestJob::TYPE_DEVICE_STATE_RETRIEVAL,
    289           request_context_.get()));
    290   request_job_->SetClientID(device_id_);
    291   em::DeviceStateRetrievalRequest* request =
    292       request_job_->GetRequest()->mutable_device_state_retrieval_request();
    293   request->set_server_backed_state_key(server_backed_state_key_);
    294   request_job_->Start(
    295       base::Bind(&AutoEnrollmentClient::HandleRequestCompletion,
    296                  base::Unretained(this),
    297                  &AutoEnrollmentClient::OnDeviceStateRequestCompletion));
    298   return true;
    299 }
    300 
    301 void AutoEnrollmentClient::HandleRequestCompletion(
    302     RequestCompletionHandler handler,
    303     DeviceManagementStatus status,
    304     int net_error,
    305     const em::DeviceManagementResponse& response) {
    306   UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, status);
    307   if (status != DM_STATUS_SUCCESS) {
    308     LOG(ERROR) << "Auto enrollment error: " << status;
    309     if (status == DM_STATUS_REQUEST_FAILED)
    310       UMA_HISTOGRAM_SPARSE_SLOWLY(kUMANetworkErrorCode, -net_error);
    311     request_job_.reset();
    312 
    313     // Abort if CancelAndDeleteSoon has been called meanwhile.
    314     if (progress_callback_.is_null()) {
    315       base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this);
    316     } else {
    317       ReportProgress(status == DM_STATUS_REQUEST_FAILED
    318                          ? AUTO_ENROLLMENT_STATE_CONNECTION_ERROR
    319                          : AUTO_ENROLLMENT_STATE_SERVER_ERROR);
    320     }
    321     return;
    322   }
    323 
    324   bool progress = (this->*handler)(status, net_error, response);
    325   request_job_.reset();
    326   if (progress)
    327     NextStep();
    328   else
    329     ReportProgress(AUTO_ENROLLMENT_STATE_SERVER_ERROR);
    330 }
    331 
    332 bool AutoEnrollmentClient::OnBucketDownloadRequestCompletion(
    333     DeviceManagementStatus status,
    334     int net_error,
    335     const em::DeviceManagementResponse& response) {
    336   bool progress = false;
    337   const em::DeviceAutoEnrollmentResponse& enrollment_response =
    338       response.auto_enrollment_response();
    339   if (!response.has_auto_enrollment_response()) {
    340     LOG(ERROR) << "Server failed to provide auto-enrollment response.";
    341   } else if (enrollment_response.has_expected_modulus()) {
    342     // Server is asking us to retry with a different modulus.
    343     modulus_updates_received_++;
    344 
    345     int64 modulus = enrollment_response.expected_modulus();
    346     int power = NextPowerOf2(modulus);
    347     if ((GG_INT64_C(1) << power) != modulus) {
    348       LOG(WARNING) << "Auto enrollment: the server didn't ask for a power-of-2 "
    349                    << "modulus. Using the closest power-of-2 instead "
    350                    << "(" << modulus << " vs 2^" << power << ")";
    351     }
    352     if (modulus_updates_received_ >= 2) {
    353       LOG(ERROR) << "Auto enrollment error: already retried with an updated "
    354                  << "modulus but the server asked for a new one again: "
    355                  << power;
    356     } else if (power > power_limit_) {
    357       LOG(ERROR) << "Auto enrollment error: the server asked for a larger "
    358                  << "modulus than the client accepts (" << power << " vs "
    359                  << power_limit_ << ").";
    360     } else {
    361       // Retry at most once with the modulus that the server requested.
    362       if (power <= current_power_) {
    363         LOG(WARNING) << "Auto enrollment: the server asked to use a modulus ("
    364                      << power << ") that isn't larger than the first used ("
    365                      << current_power_ << "). Retrying anyway.";
    366       }
    367       // Remember this value, so that eventual retries start with the correct
    368       // modulus.
    369       current_power_ = power;
    370       return true;
    371     }
    372   } else {
    373     // Server should have sent down a list of hashes to try.
    374     has_server_state_ = IsIdHashInProtobuf(enrollment_response.hash());
    375     // Cache the current decision in local_state, so that it is reused in case
    376     // the device reboots before enrolling.
    377     local_state_->SetBoolean(prefs::kShouldAutoEnroll, has_server_state_);
    378     local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_);
    379     local_state_->CommitPendingWrite();
    380     VLOG(1) << "Auto enrollment check complete, has_server_state_ = "
    381             << has_server_state_;
    382     progress = true;
    383   }
    384 
    385   // Bucket download done, update UMA.
    386   UpdateBucketDownloadTimingHistograms();
    387   return progress;
    388 }
    389 
    390 bool AutoEnrollmentClient::OnDeviceStateRequestCompletion(
    391     DeviceManagementStatus status,
    392     int net_error,
    393     const enterprise_management::DeviceManagementResponse& response) {
    394   bool progress = false;
    395   if (!response.has_device_state_retrieval_response()) {
    396     LOG(ERROR) << "Server failed to provide auto-enrollment response.";
    397   } else {
    398     const em::DeviceStateRetrievalResponse& state_response =
    399         response.device_state_retrieval_response();
    400     {
    401       DictionaryPrefUpdate dict(local_state_, prefs::kServerBackedDeviceState);
    402       UpdateDict(dict.Get(),
    403                  kDeviceStateManagementDomain,
    404                  state_response.has_management_domain(),
    405                  new base::StringValue(state_response.management_domain()));
    406 
    407       std::string restore_mode =
    408           ConvertRestoreMode(state_response.restore_mode());
    409       UpdateDict(dict.Get(),
    410                  kDeviceStateRestoreMode,
    411                  !restore_mode.empty(),
    412                  new base::StringValue(restore_mode));
    413     }
    414     local_state_->CommitPendingWrite();
    415     device_state_available_ = true;
    416     progress = true;
    417   }
    418 
    419   return progress;
    420 }
    421 
    422 bool AutoEnrollmentClient::IsIdHashInProtobuf(
    423       const google::protobuf::RepeatedPtrField<std::string>& hashes) {
    424   for (int i = 0; i < hashes.size(); ++i) {
    425     if (hashes.Get(i) == server_backed_state_key_hash_)
    426       return true;
    427   }
    428   return false;
    429 }
    430 
    431 void AutoEnrollmentClient::UpdateBucketDownloadTimingHistograms() {
    432   // The minimum time can't be 0, must be at least 1.
    433   static const base::TimeDelta kMin = base::TimeDelta::FromMilliseconds(1);
    434   static const base::TimeDelta kMax = base::TimeDelta::FromMinutes(5);
    435   // However, 0 can still be sampled.
    436   static const base::TimeDelta kZero = base::TimeDelta::FromMilliseconds(0);
    437   static const int kBuckets = 50;
    438 
    439   base::Time now = base::Time::Now();
    440   if (!time_start_.is_null()) {
    441     base::TimeDelta delta = now - time_start_;
    442     UMA_HISTOGRAM_CUSTOM_TIMES(kUMAProtocolTime, delta, kMin, kMax, kBuckets);
    443   }
    444   base::TimeDelta delta = kZero;
    445   if (!time_extra_start_.is_null())
    446     delta = now - time_extra_start_;
    447   // This samples |kZero| when there was no need for extra time, so that we can
    448   // measure the ratio of users that succeeded without needing a delay to the
    449   // total users going through OOBE.
    450   UMA_HISTOGRAM_CUSTOM_TIMES(kUMAExtraTime, delta, kMin, kMax, kBuckets);
    451 }
    452 
    453 }  // namespace policy
    454