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