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