auto_enrollment_client.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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/command_line.h" 9#include "base/guid.h" 10#include "base/location.h" 11#include "base/logging.h" 12#include "base/message_loop/message_loop_proxy.h" 13#include "base/metrics/histogram.h" 14#include "base/metrics/sparse_histogram.h" 15#include "base/prefs/pref_registry_simple.h" 16#include "base/prefs/pref_service.h" 17#include "base/prefs/scoped_user_pref_update.h" 18#include "base/strings/string_number_conversions.h" 19#include "chrome/browser/browser_process.h" 20#include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h" 21#include "chrome/browser/chromeos/policy/server_backed_device_state.h" 22#include "chrome/common/chrome_content_client.h" 23#include "chrome/common/pref_names.h" 24#include "chromeos/chromeos_switches.h" 25#include "components/policy/core/browser/browser_policy_connector.h" 26#include "components/policy/core/common/cloud/device_management_service.h" 27#include "components/policy/core/common/cloud/system_policy_request_context.h" 28#include "content/public/browser/browser_thread.h" 29#include "crypto/sha2.h" 30#include "net/url_request/url_request_context_getter.h" 31#include "policy/proto/device_management_backend.pb.h" 32#include "url/gurl.h" 33 34using content::BrowserThread; 35 36namespace em = enterprise_management; 37 38namespace policy { 39 40namespace { 41 42// UMA histogram names. 43const char kUMAProtocolTime[] = "Enterprise.AutoEnrollmentProtocolTime"; 44const char kUMAExtraTime[] = "Enterprise.AutoEnrollmentExtraTime"; 45const char kUMARequestStatus[] = "Enterprise.AutoEnrollmentRequestStatus"; 46const char kUMANetworkErrorCode[] = 47 "Enterprise.AutoEnrollmentRequestNetworkErrorCode"; 48 49// The modulus value is sent in an int64 field in the protobuf, whose maximum 50// value is 2^63-1. So 2^64 and 2^63 can't be represented as moduli and the 51// max is 2^62 (when the moduli are restricted to powers-of-2). 52const int kMaximumPower = 62; 53 54// Returns the int value of the |switch_name| argument, clamped to the [0, 62] 55// interval. Returns 0 if the argument doesn't exist or isn't an int value. 56int GetSanitizedArg(const std::string& switch_name) { 57 CommandLine* command_line = CommandLine::ForCurrentProcess(); 58 if (!command_line->HasSwitch(switch_name)) 59 return 0; 60 std::string value = command_line->GetSwitchValueASCII(switch_name); 61 int int_value; 62 if (!base::StringToInt(value, &int_value)) { 63 LOG(ERROR) << "Switch \"" << switch_name << "\" is not a valid int. " 64 << "Defaulting to 0."; 65 return 0; 66 } 67 if (int_value < 0) { 68 LOG(ERROR) << "Switch \"" << switch_name << "\" can't be negative. " 69 << "Using 0"; 70 return 0; 71 } 72 if (int_value > kMaximumPower) { 73 LOG(ERROR) << "Switch \"" << switch_name << "\" can't be greater than " 74 << kMaximumPower << ". Using " << kMaximumPower; 75 return kMaximumPower; 76 } 77 return int_value; 78} 79 80// Returns the power of the next power-of-2 starting at |value|. 81int NextPowerOf2(int64 value) { 82 for (int i = 0; i <= kMaximumPower; ++i) { 83 if ((GG_INT64_C(1) << i) >= value) 84 return i; 85 } 86 // No other value can be represented in an int64. 87 return kMaximumPower + 1; 88} 89 90// Sets or clears a value in a dictionary. 91void UpdateDict(base::DictionaryValue* dict, 92 const char* pref_path, 93 bool set_or_clear, 94 base::Value* value) { 95 scoped_ptr<base::Value> scoped_value(value); 96 if (set_or_clear) 97 dict->Set(pref_path, scoped_value.release()); 98 else 99 dict->Remove(pref_path, NULL); 100} 101 102// Converts a restore mode enum value from the DM protocol into the 103// corresponding prefs string constant. 104std::string ConvertRestoreMode( 105 em::DeviceStateRetrievalResponse::RestoreMode restore_mode) { 106 switch (restore_mode) { 107 case em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE: 108 return std::string(); 109 case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_REQUESTED: 110 return kDeviceStateRestoreModeReEnrollmentRequested; 111 case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED: 112 return kDeviceStateRestoreModeReEnrollmentEnforced; 113 } 114 115 NOTREACHED() << "Bad restore mode " << restore_mode; 116 return std::string(); 117} 118 119} // namespace 120 121AutoEnrollmentClient::AutoEnrollmentClient( 122 const ProgressCallback& callback, 123 DeviceManagementService* service, 124 PrefService* local_state, 125 scoped_refptr<net::URLRequestContextGetter> system_request_context, 126 const std::string& server_backed_state_key, 127 bool retrieve_device_state, 128 int power_initial, 129 int power_limit) 130 : progress_callback_(callback), 131 state_(STATE_PENDING), 132 has_server_state_(false), 133 device_state_available_(false), 134 device_id_(base::GenerateGUID()), 135 server_backed_state_key_(server_backed_state_key), 136 retrieve_device_state_(retrieve_device_state), 137 current_power_(power_initial), 138 power_limit_(power_limit), 139 modulus_updates_received_(0), 140 device_management_service_(service), 141 local_state_(local_state) { 142 request_context_ = new SystemPolicyRequestContext( 143 system_request_context, GetUserAgent()); 144 145 DCHECK_LE(current_power_, power_limit_); 146 DCHECK(!progress_callback_.is_null()); 147 if (!server_backed_state_key_.empty()) { 148 server_backed_state_key_hash_ = 149 crypto::SHA256HashString(server_backed_state_key_); 150 } 151 net::NetworkChangeNotifier::AddNetworkChangeObserver(this); 152} 153 154AutoEnrollmentClient::~AutoEnrollmentClient() { 155 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this); 156} 157 158// static 159void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) { 160 registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false); 161 registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1); 162} 163 164// static 165bool AutoEnrollmentClient::IsDisabled() { 166 CommandLine* command_line = CommandLine::ForCurrentProcess(); 167 // Do not communicate auto-enrollment data to the server if 168 // 1. we are running integration or perf tests with telemetry. 169 // 2. modulus configuration is not present. 170 return command_line->HasSwitch(chromeos::switches::kOobeSkipPostLogin) || 171 (!command_line->HasSwitch( 172 chromeos::switches::kEnterpriseEnrollmentInitialModulus) && 173 !command_line->HasSwitch( 174 chromeos::switches::kEnterpriseEnrollmentModulusLimit)); 175} 176 177// static 178AutoEnrollmentClient* AutoEnrollmentClient::Create( 179 const ProgressCallback& progress_callback) { 180 // The client won't do anything if |service| is NULL. 181 DeviceManagementService* service = NULL; 182 if (IsDisabled()) { 183 VLOG(1) << "Auto-enrollment is disabled"; 184 } else { 185 BrowserPolicyConnector* connector = 186 g_browser_process->browser_policy_connector(); 187 service = connector->device_management_service(); 188 service->ScheduleInitialization(0); 189 } 190 191 int power_initial = GetSanitizedArg( 192 chromeos::switches::kEnterpriseEnrollmentInitialModulus); 193 int power_limit = GetSanitizedArg( 194 chromeos::switches::kEnterpriseEnrollmentModulusLimit); 195 if (power_initial > power_limit) { 196 LOG(ERROR) << "Initial auto-enrollment modulus is larger than the limit, " 197 << "clamping to the limit."; 198 power_initial = power_limit; 199 } 200 201 bool retrieve_device_state = false; 202 std::string device_id; 203 if (CommandLine::ForCurrentProcess()->HasSwitch( 204 chromeos::switches::kEnterpriseEnableForcedReEnrollment)) { 205 retrieve_device_state = true; 206 device_id = DeviceCloudPolicyManagerChromeOS::GetDeviceStateKey(); 207 } else { 208 device_id = DeviceCloudPolicyManagerChromeOS::GetMachineID(); 209 } 210 211 return new AutoEnrollmentClient( 212 progress_callback, 213 service, 214 g_browser_process->local_state(), 215 g_browser_process->system_request_context(), 216 device_id, 217 retrieve_device_state, 218 power_initial, 219 power_limit); 220} 221 222// static 223void AutoEnrollmentClient::CancelAutoEnrollment() { 224 PrefService* local_state = g_browser_process->local_state(); 225 local_state->SetBoolean(prefs::kShouldAutoEnroll, false); 226 local_state->ClearPref(prefs::kServerBackedDeviceState); 227 local_state->CommitPendingWrite(); 228} 229 230void AutoEnrollmentClient::Start() { 231 // Drop the previous job and reset state. 232 request_job_.reset(); 233 state_ = STATE_PENDING; 234 time_start_ = base::Time::Now(); 235 modulus_updates_received_ = 0; 236 has_server_state_ = false; 237 device_state_available_ = false; 238 239 NextStep(); 240} 241 242void AutoEnrollmentClient::CancelAndDeleteSoon() { 243 if (time_start_.is_null() || !request_job_) { 244 // The client isn't running, just delete it. 245 delete this; 246 } else { 247 // Client still running, but our owner isn't interested in the result 248 // anymore. Wait until the protocol completes to measure the extra time 249 // needed. 250 time_extra_start_ = base::Time::Now(); 251 progress_callback_.Reset(); 252 } 253} 254 255void AutoEnrollmentClient::OnNetworkChanged( 256 net::NetworkChangeNotifier::ConnectionType type) { 257 if (type != net::NetworkChangeNotifier::CONNECTION_NONE && 258 !progress_callback_.is_null()) { 259 RetryStep(); 260 } 261} 262 263bool AutoEnrollmentClient::GetCachedDecision() { 264 const PrefService::Preference* has_server_state_pref = 265 local_state_->FindPreference(prefs::kShouldAutoEnroll); 266 const PrefService::Preference* previous_limit_pref = 267 local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit); 268 bool has_server_state = false; 269 int previous_limit = -1; 270 271 if (!has_server_state_pref || 272 has_server_state_pref->IsDefaultValue() || 273 !has_server_state_pref->GetValue()->GetAsBoolean(&has_server_state) || 274 !previous_limit_pref || 275 previous_limit_pref->IsDefaultValue() || 276 !previous_limit_pref->GetValue()->GetAsInteger(&previous_limit) || 277 power_limit_ > previous_limit) { 278 return false; 279 } 280 281 has_server_state_ = has_server_state; 282 return true; 283} 284 285bool AutoEnrollmentClient::RetryStep() { 286 // If there is a pending request job, let it finish. 287 if (request_job_) 288 return true; 289 290 if (GetCachedDecision()) { 291 // The bucket download check has completed already. If it came back 292 // positive, then device state should be (re-)downloaded. 293 if (has_server_state_) { 294 if (retrieve_device_state_ && !device_state_available_ && 295 SendDeviceStateRequest()) { 296 return true; 297 } 298 } 299 } else { 300 // Start bucket download. 301 if (SendBucketDownloadRequest()) 302 return true; 303 } 304 305 return false; 306} 307 308void AutoEnrollmentClient::ReportProgress(State state) { 309 state_ = state; 310 if (progress_callback_.is_null()) { 311 base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this); 312 } else { 313 progress_callback_.Run(state_); 314 } 315} 316 317void AutoEnrollmentClient::NextStep() { 318 if (!RetryStep()) { 319 // Protocol finished successfully, report result. 320 bool trigger_enrollment = false; 321 if (retrieve_device_state_) { 322 const base::DictionaryValue* device_state_dict = 323 local_state_->GetDictionary(prefs::kServerBackedDeviceState); 324 std::string restore_mode; 325 device_state_dict->GetString(kDeviceStateRestoreMode, &restore_mode); 326 trigger_enrollment = 327 (restore_mode == kDeviceStateRestoreModeReEnrollmentRequested || 328 restore_mode == kDeviceStateRestoreModeReEnrollmentEnforced); 329 } else { 330 trigger_enrollment = has_server_state_; 331 } 332 333 ReportProgress(trigger_enrollment ? STATE_TRIGGER_ENROLLMENT 334 : STATE_NO_ENROLLMENT); 335 } 336} 337 338bool AutoEnrollmentClient::SendBucketDownloadRequest() { 339 if (server_backed_state_key_hash_.empty()) 340 return false; 341 342 // Only power-of-2 moduli are supported for now. These are computed by taking 343 // the lower |current_power_| bits of the hash. 344 uint64 remainder = 0; 345 for (int i = 0; 8 * i < current_power_; ++i) { 346 uint64 byte = server_backed_state_key_hash_[31 - i] & 0xff; 347 remainder = remainder | (byte << (8 * i)); 348 } 349 remainder = remainder & ((GG_UINT64_C(1) << current_power_) - 1); 350 351 ReportProgress(STATE_PENDING); 352 353 request_job_.reset( 354 device_management_service_->CreateJob( 355 DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT, 356 request_context_.get())); 357 request_job_->SetClientID(device_id_); 358 em::DeviceAutoEnrollmentRequest* request = 359 request_job_->GetRequest()->mutable_auto_enrollment_request(); 360 request->set_remainder(remainder); 361 request->set_modulus(GG_INT64_C(1) << current_power_); 362 request_job_->Start( 363 base::Bind(&AutoEnrollmentClient::HandleRequestCompletion, 364 base::Unretained(this), 365 &AutoEnrollmentClient::OnBucketDownloadRequestCompletion)); 366 return true; 367} 368 369bool AutoEnrollmentClient::SendDeviceStateRequest() { 370 ReportProgress(STATE_PENDING); 371 372 request_job_.reset( 373 device_management_service_->CreateJob( 374 DeviceManagementRequestJob::TYPE_DEVICE_STATE_RETRIEVAL, 375 request_context_.get())); 376 request_job_->SetClientID(device_id_); 377 em::DeviceStateRetrievalRequest* request = 378 request_job_->GetRequest()->mutable_device_state_retrieval_request(); 379 request->set_server_backed_state_key(server_backed_state_key_); 380 request_job_->Start( 381 base::Bind(&AutoEnrollmentClient::HandleRequestCompletion, 382 base::Unretained(this), 383 &AutoEnrollmentClient::OnDeviceStateRequestCompletion)); 384 return true; 385} 386 387void AutoEnrollmentClient::HandleRequestCompletion( 388 RequestCompletionHandler handler, 389 DeviceManagementStatus status, 390 int net_error, 391 const em::DeviceManagementResponse& response) { 392 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, status); 393 if (status != DM_STATUS_SUCCESS) { 394 LOG(ERROR) << "Auto enrollment error: " << status; 395 if (status == DM_STATUS_REQUEST_FAILED) 396 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMANetworkErrorCode, -net_error); 397 request_job_.reset(); 398 399 // Abort if CancelAndDeleteSoon has been called meanwhile. 400 if (progress_callback_.is_null()) { 401 base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this); 402 } else { 403 ReportProgress(status == DM_STATUS_REQUEST_FAILED ? STATE_CONNECTION_ERROR 404 : STATE_SERVER_ERROR); 405 } 406 return; 407 } 408 409 bool progress = (this->*handler)(status, net_error, response); 410 request_job_.reset(); 411 if (progress) 412 NextStep(); 413 else 414 ReportProgress(STATE_SERVER_ERROR); 415} 416 417bool AutoEnrollmentClient::OnBucketDownloadRequestCompletion( 418 DeviceManagementStatus status, 419 int net_error, 420 const em::DeviceManagementResponse& response) { 421 bool progress = false; 422 const em::DeviceAutoEnrollmentResponse& enrollment_response = 423 response.auto_enrollment_response(); 424 if (!response.has_auto_enrollment_response()) { 425 LOG(ERROR) << "Server failed to provide auto-enrollment response."; 426 } else if (enrollment_response.has_expected_modulus()) { 427 // Server is asking us to retry with a different modulus. 428 modulus_updates_received_++; 429 430 int64 modulus = enrollment_response.expected_modulus(); 431 int power = NextPowerOf2(modulus); 432 if ((GG_INT64_C(1) << power) != modulus) { 433 LOG(WARNING) << "Auto enrollment: the server didn't ask for a power-of-2 " 434 << "modulus. Using the closest power-of-2 instead " 435 << "(" << modulus << " vs 2^" << power << ")"; 436 } 437 if (modulus_updates_received_ >= 2) { 438 LOG(ERROR) << "Auto enrollment error: already retried with an updated " 439 << "modulus but the server asked for a new one again: " 440 << power; 441 } else if (power > power_limit_) { 442 LOG(ERROR) << "Auto enrollment error: the server asked for a larger " 443 << "modulus than the client accepts (" << power << " vs " 444 << power_limit_ << ")."; 445 } else { 446 // Retry at most once with the modulus that the server requested. 447 if (power <= current_power_) { 448 LOG(WARNING) << "Auto enrollment: the server asked to use a modulus (" 449 << power << ") that isn't larger than the first used (" 450 << current_power_ << "). Retrying anyway."; 451 } 452 // Remember this value, so that eventual retries start with the correct 453 // modulus. 454 current_power_ = power; 455 return true; 456 } 457 } else { 458 // Server should have sent down a list of hashes to try. 459 has_server_state_ = IsIdHashInProtobuf(enrollment_response.hash()); 460 // Cache the current decision in local_state, so that it is reused in case 461 // the device reboots before enrolling. 462 local_state_->SetBoolean(prefs::kShouldAutoEnroll, has_server_state_); 463 local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_); 464 local_state_->CommitPendingWrite(); 465 VLOG(1) << "Auto enrollment check complete, has_server_state_ = " 466 << has_server_state_; 467 progress = true; 468 } 469 470 // Bucket download done, update UMA. 471 UpdateBucketDownloadTimingHistograms(); 472 return progress; 473} 474 475bool AutoEnrollmentClient::OnDeviceStateRequestCompletion( 476 DeviceManagementStatus status, 477 int net_error, 478 const enterprise_management::DeviceManagementResponse& response) { 479 bool progress = false; 480 if (!response.has_device_state_retrieval_response()) { 481 LOG(ERROR) << "Server failed to provide auto-enrollment response."; 482 } else { 483 const em::DeviceStateRetrievalResponse& state_response = 484 response.device_state_retrieval_response(); 485 { 486 DictionaryPrefUpdate dict(local_state_, prefs::kServerBackedDeviceState); 487 UpdateDict(dict.Get(), 488 kDeviceStateManagementDomain, 489 state_response.has_management_domain(), 490 new base::StringValue(state_response.management_domain())); 491 492 std::string restore_mode = 493 ConvertRestoreMode(state_response.restore_mode()); 494 UpdateDict(dict.Get(), 495 kDeviceStateRestoreMode, 496 !restore_mode.empty(), 497 new base::StringValue(restore_mode)); 498 } 499 local_state_->CommitPendingWrite(); 500 device_state_available_ = true; 501 progress = true; 502 } 503 504 return progress; 505} 506 507bool AutoEnrollmentClient::IsIdHashInProtobuf( 508 const google::protobuf::RepeatedPtrField<std::string>& hashes) { 509 for (int i = 0; i < hashes.size(); ++i) { 510 if (hashes.Get(i) == server_backed_state_key_hash_) 511 return true; 512 } 513 return false; 514} 515 516void AutoEnrollmentClient::UpdateBucketDownloadTimingHistograms() { 517 // The mininum time can't be 0, must be at least 1. 518 static const base::TimeDelta kMin = base::TimeDelta::FromMilliseconds(1); 519 static const base::TimeDelta kMax = base::TimeDelta::FromMinutes(5); 520 // However, 0 can still be sampled. 521 static const base::TimeDelta kZero = base::TimeDelta::FromMilliseconds(0); 522 static const int kBuckets = 50; 523 524 base::Time now = base::Time::Now(); 525 if (!time_start_.is_null()) { 526 base::TimeDelta delta = now - time_start_; 527 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAProtocolTime, delta, kMin, kMax, kBuckets); 528 } 529 base::TimeDelta delta = kZero; 530 if (!time_extra_start_.is_null()) 531 delta = now - time_extra_start_; 532 // This samples |kZero| when there was no need for extra time, so that we can 533 // measure the ratio of users that succeeded without needing a delay to the 534 // total users going through OOBE. 535 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAExtraTime, delta, kMin, kMax, kBuckets); 536} 537 538} // namespace policy 539