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 // Return is required to avoid compiler warning. 80 NOTREACHED() << "Bad restore mode " << restore_mode; 81 return std::string(); 82} 83 84} // namespace 85 86AutoEnrollmentClient::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 118AutoEnrollmentClient::~AutoEnrollmentClient() { 119 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); 120} 121 122// static 123void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) { 124 registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false); 125 registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1); 126} 127 128// static 129void 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 136void 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 152void AutoEnrollmentClient::Retry() { 153 RetryStep(); 154} 155 156void 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 169void AutoEnrollmentClient::OnNetworkChanged( 170 net::NetworkChangeNotifier::ConnectionType type) { 171 if (type != net::NetworkChangeNotifier::CONNECTION_NONE && 172 !progress_callback_.is_null()) { 173 RetryStep(); 174 } 175} 176 177bool 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 199bool 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 222void 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 231void 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 252bool 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 283bool 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 301void 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 332bool 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 390bool 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 422bool 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 431void 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