• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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|.
NextPowerOf2(int64 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.
UpdateDict(base::DictionaryValue * dict,const char * pref_path,bool set_or_clear,base::Value * value)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.
ConvertRestoreMode(em::DeviceStateRetrievalResponse::RestoreMode restore_mode)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   NOTREACHED() << "Bad restore mode " << restore_mode;
80   return std::string();
81 }
82 
83 }  // namespace
84 
AutoEnrollmentClient(const ProgressCallback & callback,DeviceManagementService * service,PrefService * local_state,scoped_refptr<net::URLRequestContextGetter> system_request_context,const std::string & server_backed_state_key,bool retrieve_device_state,int power_initial,int power_limit)85 AutoEnrollmentClient::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 
~AutoEnrollmentClient()117 AutoEnrollmentClient::~AutoEnrollmentClient() {
118   net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
119 }
120 
121 // static
RegisterPrefs(PrefRegistrySimple * registry)122 void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) {
123   registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
124   registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
125 }
126 
127 // static
CancelAutoEnrollment()128 void 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 
Start()135 void 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 
Retry()151 void AutoEnrollmentClient::Retry() {
152   RetryStep();
153 }
154 
CancelAndDeleteSoon()155 void 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 
OnNetworkChanged(net::NetworkChangeNotifier::ConnectionType type)168 void AutoEnrollmentClient::OnNetworkChanged(
169     net::NetworkChangeNotifier::ConnectionType type) {
170   if (type != net::NetworkChangeNotifier::CONNECTION_NONE &&
171       !progress_callback_.is_null()) {
172     RetryStep();
173   }
174 }
175 
GetCachedDecision()176 bool 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 
RetryStep()198 bool 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 
ReportProgress(AutoEnrollmentState state)221 void 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 
NextStep()230 void 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 
SendBucketDownloadRequest()251 bool 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 
SendDeviceStateRequest()282 bool 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 
HandleRequestCompletion(RequestCompletionHandler handler,DeviceManagementStatus status,int net_error,const em::DeviceManagementResponse & response)300 void 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 
OnBucketDownloadRequestCompletion(DeviceManagementStatus status,int net_error,const em::DeviceManagementResponse & response)331 bool 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 
OnDeviceStateRequestCompletion(DeviceManagementStatus status,int net_error,const enterprise_management::DeviceManagementResponse & response)389 bool 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 
IsIdHashInProtobuf(const google::protobuf::RepeatedPtrField<std::string> & hashes)421 bool 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 
UpdateBucketDownloadTimingHistograms()430 void AutoEnrollmentClient::UpdateBucketDownloadTimingHistograms() {
431   // The mininum 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