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 // Return is required to avoid compiler warning.
80 NOTREACHED() << "Bad restore mode " << restore_mode;
81 return std::string();
82 }
83
84 } // namespace
85
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)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
~AutoEnrollmentClient()118 AutoEnrollmentClient::~AutoEnrollmentClient() {
119 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
120 }
121
122 // static
RegisterPrefs(PrefRegistrySimple * registry)123 void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) {
124 registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
125 registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
126 }
127
128 // static
CancelAutoEnrollment()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
Start()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
Retry()152 void AutoEnrollmentClient::Retry() {
153 RetryStep();
154 }
155
CancelAndDeleteSoon()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
OnNetworkChanged(net::NetworkChangeNotifier::ConnectionType type)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
GetCachedDecision()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
RetryStep()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
ReportProgress(AutoEnrollmentState state)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
NextStep()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
SendBucketDownloadRequest()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
SendDeviceStateRequest()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
HandleRequestCompletion(RequestCompletionHandler handler,DeviceManagementStatus status,int net_error,const em::DeviceManagementResponse & response)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
OnBucketDownloadRequestCompletion(DeviceManagementStatus status,int net_error,const em::DeviceManagementResponse & response)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
OnDeviceStateRequestCompletion(DeviceManagementStatus status,int net_error,const enterprise_management::DeviceManagementResponse & response)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
IsIdHashInProtobuf(const google::protobuf::RepeatedPtrField<std::string> & hashes)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
UpdateBucketDownloadTimingHistograms()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