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