Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / chromeos / policy / auto_enrollment_client.cc
blob49b9709633bc043cb914337a01cfed0418759e81
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.
5 #include "chrome/browser/chromeos/policy/auto_enrollment_client.h"
7 #include <stdint.h>
9 #include "base/bind.h"
10 #include "base/guid.h"
11 #include "base/location.h"
12 #include "base/logging.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/thread_task_runner_handle.h"
19 #include "chrome/browser/chromeos/policy/server_backed_device_state.h"
20 #include "chrome/common/chrome_content_client.h"
21 #include "chrome/common/pref_names.h"
22 #include "components/policy/core/common/cloud/device_management_service.h"
23 #include "components/policy/core/common/cloud/system_policy_request_context.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "crypto/sha2.h"
26 #include "net/url_request/url_request_context_getter.h"
27 #include "policy/proto/device_management_backend.pb.h"
28 #include "url/gurl.h"
30 using content::BrowserThread;
32 namespace em = enterprise_management;
34 namespace policy {
36 namespace {
38 // UMA histogram names.
39 const char kUMAProtocolTime[] = "Enterprise.AutoEnrollmentProtocolTime";
40 const char kUMAExtraTime[] = "Enterprise.AutoEnrollmentExtraTime";
41 const char kUMARequestStatus[] = "Enterprise.AutoEnrollmentRequestStatus";
42 const char kUMANetworkErrorCode[] =
43 "Enterprise.AutoEnrollmentRequestNetworkErrorCode";
45 // Returns the power of the next power-of-2 starting at |value|.
46 int NextPowerOf2(int64 value) {
47 for (int i = 0; i <= AutoEnrollmentClient::kMaximumPower; ++i) {
48 if ((INT64_C(1) << i) >= value)
49 return i;
51 // No other value can be represented in an int64.
52 return AutoEnrollmentClient::kMaximumPower + 1;
55 // Sets or clears a value in a dictionary.
56 void UpdateDict(base::DictionaryValue* dict,
57 const char* pref_path,
58 bool set_or_clear,
59 base::Value* value) {
60 scoped_ptr<base::Value> scoped_value(value);
61 if (set_or_clear)
62 dict->Set(pref_path, scoped_value.release());
63 else
64 dict->Remove(pref_path, NULL);
67 // Converts a restore mode enum value from the DM protocol into the
68 // corresponding prefs string constant.
69 std::string ConvertRestoreMode(
70 em::DeviceStateRetrievalResponse::RestoreMode restore_mode) {
71 switch (restore_mode) {
72 case em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE:
73 return std::string();
74 case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_REQUESTED:
75 return kDeviceStateRestoreModeReEnrollmentRequested;
76 case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED:
77 return kDeviceStateRestoreModeReEnrollmentEnforced;
78 case em::DeviceStateRetrievalResponse::RESTORE_MODE_DISABLED:
79 return kDeviceStateRestoreModeDisabled;
82 // Return is required to avoid compiler warning.
83 NOTREACHED() << "Bad restore mode " << restore_mode;
84 return std::string();
87 } // namespace
89 AutoEnrollmentClient::AutoEnrollmentClient(
90 const ProgressCallback& callback,
91 DeviceManagementService* service,
92 PrefService* local_state,
93 scoped_refptr<net::URLRequestContextGetter> system_request_context,
94 const std::string& server_backed_state_key,
95 int power_initial,
96 int power_limit)
97 : progress_callback_(callback),
98 state_(AUTO_ENROLLMENT_STATE_IDLE),
99 has_server_state_(false),
100 device_state_available_(false),
101 device_id_(base::GenerateGUID()),
102 server_backed_state_key_(server_backed_state_key),
103 current_power_(power_initial),
104 power_limit_(power_limit),
105 modulus_updates_received_(0),
106 device_management_service_(service),
107 local_state_(local_state) {
108 request_context_ = new SystemPolicyRequestContext(
109 system_request_context, GetUserAgent());
111 DCHECK_LE(current_power_, power_limit_);
112 DCHECK(!progress_callback_.is_null());
113 CHECK(!server_backed_state_key_.empty());
114 server_backed_state_key_hash_ =
115 crypto::SHA256HashString(server_backed_state_key_);
118 AutoEnrollmentClient::~AutoEnrollmentClient() {
119 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
122 // static
123 void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) {
124 registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
125 registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
128 void AutoEnrollmentClient::Start() {
129 // (Re-)register the network change observer.
130 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
131 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
133 // Drop the previous job and reset state.
134 request_job_.reset();
135 state_ = AUTO_ENROLLMENT_STATE_PENDING;
136 time_start_ = base::Time::Now();
137 modulus_updates_received_ = 0;
138 has_server_state_ = false;
139 device_state_available_ = false;
141 NextStep();
144 void AutoEnrollmentClient::Retry() {
145 RetryStep();
148 void AutoEnrollmentClient::CancelAndDeleteSoon() {
149 if (time_start_.is_null() || !request_job_) {
150 // The client isn't running, just delete it.
151 delete this;
152 } else {
153 // Client still running, but our owner isn't interested in the result
154 // anymore. Wait until the protocol completes to measure the extra time
155 // needed.
156 time_extra_start_ = base::Time::Now();
157 progress_callback_.Reset();
161 void AutoEnrollmentClient::OnNetworkChanged(
162 net::NetworkChangeNotifier::ConnectionType type) {
163 if (type != net::NetworkChangeNotifier::CONNECTION_NONE &&
164 !progress_callback_.is_null()) {
165 RetryStep();
169 bool AutoEnrollmentClient::GetCachedDecision() {
170 const PrefService::Preference* has_server_state_pref =
171 local_state_->FindPreference(prefs::kShouldAutoEnroll);
172 const PrefService::Preference* previous_limit_pref =
173 local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit);
174 bool has_server_state = false;
175 int previous_limit = -1;
177 if (!has_server_state_pref ||
178 has_server_state_pref->IsDefaultValue() ||
179 !has_server_state_pref->GetValue()->GetAsBoolean(&has_server_state) ||
180 !previous_limit_pref ||
181 previous_limit_pref->IsDefaultValue() ||
182 !previous_limit_pref->GetValue()->GetAsInteger(&previous_limit) ||
183 power_limit_ > previous_limit) {
184 return false;
187 has_server_state_ = has_server_state;
188 return true;
191 bool AutoEnrollmentClient::RetryStep() {
192 // If there is a pending request job, let it finish.
193 if (request_job_)
194 return true;
196 if (GetCachedDecision()) {
197 // The bucket download check has completed already. If it came back
198 // positive, then device state should be (re-)downloaded.
199 if (has_server_state_) {
200 if (!device_state_available_) {
201 SendDeviceStateRequest();
202 return true;
205 } else {
206 // Start bucket download.
207 SendBucketDownloadRequest();
208 return true;
211 return false;
214 void AutoEnrollmentClient::ReportProgress(AutoEnrollmentState state) {
215 state_ = state;
216 if (progress_callback_.is_null()) {
217 base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
218 } else {
219 progress_callback_.Run(state_);
223 void AutoEnrollmentClient::NextStep() {
224 if (!RetryStep()) {
225 // Protocol finished successfully, report result.
226 const RestoreMode restore_mode = GetRestoreMode();
227 bool trigger_enrollment =
228 (restore_mode == RESTORE_MODE_REENROLLMENT_REQUESTED ||
229 restore_mode == RESTORE_MODE_REENROLLMENT_ENFORCED);
231 ReportProgress(trigger_enrollment ? AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT
232 : AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
236 void AutoEnrollmentClient::SendBucketDownloadRequest() {
237 // Only power-of-2 moduli are supported for now. These are computed by taking
238 // the lower |current_power_| bits of the hash.
239 uint64 remainder = 0;
240 for (int i = 0; 8 * i < current_power_; ++i) {
241 uint64 byte = server_backed_state_key_hash_[31 - i] & 0xff;
242 remainder = remainder | (byte << (8 * i));
244 remainder = remainder & ((UINT64_C(1) << current_power_) - 1);
246 ReportProgress(AUTO_ENROLLMENT_STATE_PENDING);
248 request_job_.reset(
249 device_management_service_->CreateJob(
250 DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT,
251 request_context_.get()));
252 request_job_->SetClientID(device_id_);
253 em::DeviceAutoEnrollmentRequest* request =
254 request_job_->GetRequest()->mutable_auto_enrollment_request();
255 request->set_remainder(remainder);
256 request->set_modulus(INT64_C(1) << current_power_);
257 request_job_->Start(
258 base::Bind(&AutoEnrollmentClient::HandleRequestCompletion,
259 base::Unretained(this),
260 &AutoEnrollmentClient::OnBucketDownloadRequestCompletion));
263 void AutoEnrollmentClient::SendDeviceStateRequest() {
264 ReportProgress(AUTO_ENROLLMENT_STATE_PENDING);
266 request_job_.reset(
267 device_management_service_->CreateJob(
268 DeviceManagementRequestJob::TYPE_DEVICE_STATE_RETRIEVAL,
269 request_context_.get()));
270 request_job_->SetClientID(device_id_);
271 em::DeviceStateRetrievalRequest* request =
272 request_job_->GetRequest()->mutable_device_state_retrieval_request();
273 request->set_server_backed_state_key(server_backed_state_key_);
274 request_job_->Start(
275 base::Bind(&AutoEnrollmentClient::HandleRequestCompletion,
276 base::Unretained(this),
277 &AutoEnrollmentClient::OnDeviceStateRequestCompletion));
280 void AutoEnrollmentClient::HandleRequestCompletion(
281 RequestCompletionHandler handler,
282 DeviceManagementStatus status,
283 int net_error,
284 const em::DeviceManagementResponse& response) {
285 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, status);
286 if (status != DM_STATUS_SUCCESS) {
287 LOG(ERROR) << "Auto enrollment error: " << status;
288 if (status == DM_STATUS_REQUEST_FAILED)
289 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMANetworkErrorCode, -net_error);
290 request_job_.reset();
292 // Abort if CancelAndDeleteSoon has been called meanwhile.
293 if (progress_callback_.is_null()) {
294 base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
295 } else {
296 ReportProgress(status == DM_STATUS_REQUEST_FAILED
297 ? AUTO_ENROLLMENT_STATE_CONNECTION_ERROR
298 : AUTO_ENROLLMENT_STATE_SERVER_ERROR);
300 return;
303 bool progress = (this->*handler)(status, net_error, response);
304 request_job_.reset();
305 if (progress)
306 NextStep();
307 else
308 ReportProgress(AUTO_ENROLLMENT_STATE_SERVER_ERROR);
311 bool AutoEnrollmentClient::OnBucketDownloadRequestCompletion(
312 DeviceManagementStatus status,
313 int net_error,
314 const em::DeviceManagementResponse& response) {
315 bool progress = false;
316 const em::DeviceAutoEnrollmentResponse& enrollment_response =
317 response.auto_enrollment_response();
318 if (!response.has_auto_enrollment_response()) {
319 LOG(ERROR) << "Server failed to provide auto-enrollment response.";
320 } else if (enrollment_response.has_expected_modulus()) {
321 // Server is asking us to retry with a different modulus.
322 modulus_updates_received_++;
324 int64 modulus = enrollment_response.expected_modulus();
325 int power = NextPowerOf2(modulus);
326 if ((INT64_C(1) << power) != modulus) {
327 LOG(WARNING) << "Auto enrollment: the server didn't ask for a power-of-2 "
328 << "modulus. Using the closest power-of-2 instead "
329 << "(" << modulus << " vs 2^" << power << ")";
331 if (modulus_updates_received_ >= 2) {
332 LOG(ERROR) << "Auto enrollment error: already retried with an updated "
333 << "modulus but the server asked for a new one again: "
334 << power;
335 } else if (power > power_limit_) {
336 LOG(ERROR) << "Auto enrollment error: the server asked for a larger "
337 << "modulus than the client accepts (" << power << " vs "
338 << power_limit_ << ").";
339 } else {
340 // Retry at most once with the modulus that the server requested.
341 if (power <= current_power_) {
342 LOG(WARNING) << "Auto enrollment: the server asked to use a modulus ("
343 << power << ") that isn't larger than the first used ("
344 << current_power_ << "). Retrying anyway.";
346 // Remember this value, so that eventual retries start with the correct
347 // modulus.
348 current_power_ = power;
349 return true;
351 } else {
352 // Server should have sent down a list of hashes to try.
353 has_server_state_ = IsIdHashInProtobuf(enrollment_response.hash());
354 // Cache the current decision in local_state, so that it is reused in case
355 // the device reboots before enrolling.
356 local_state_->SetBoolean(prefs::kShouldAutoEnroll, has_server_state_);
357 local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_);
358 local_state_->CommitPendingWrite();
359 VLOG(1) << "Auto enrollment check complete, has_server_state_ = "
360 << has_server_state_;
361 progress = true;
364 // Bucket download done, update UMA.
365 UpdateBucketDownloadTimingHistograms();
366 return progress;
369 bool AutoEnrollmentClient::OnDeviceStateRequestCompletion(
370 DeviceManagementStatus status,
371 int net_error,
372 const enterprise_management::DeviceManagementResponse& response) {
373 bool progress = false;
374 if (!response.has_device_state_retrieval_response()) {
375 LOG(ERROR) << "Server failed to provide auto-enrollment response.";
376 } else {
377 const em::DeviceStateRetrievalResponse& state_response =
378 response.device_state_retrieval_response();
380 DictionaryPrefUpdate dict(local_state_, prefs::kServerBackedDeviceState);
381 UpdateDict(dict.Get(),
382 kDeviceStateManagementDomain,
383 state_response.has_management_domain(),
384 new base::StringValue(state_response.management_domain()));
386 std::string restore_mode =
387 ConvertRestoreMode(state_response.restore_mode());
388 UpdateDict(dict.Get(),
389 kDeviceStateRestoreMode,
390 !restore_mode.empty(),
391 new base::StringValue(restore_mode));
393 UpdateDict(dict.Get(),
394 kDeviceStateDisabledMessage,
395 state_response.has_disabled_state(),
396 new base::StringValue(
397 state_response.disabled_state().message()));
399 local_state_->CommitPendingWrite();
400 device_state_available_ = true;
401 progress = true;
404 return progress;
407 bool AutoEnrollmentClient::IsIdHashInProtobuf(
408 const google::protobuf::RepeatedPtrField<std::string>& hashes) {
409 for (int i = 0; i < hashes.size(); ++i) {
410 if (hashes.Get(i) == server_backed_state_key_hash_)
411 return true;
413 return false;
416 void AutoEnrollmentClient::UpdateBucketDownloadTimingHistograms() {
417 // The minimum time can't be 0, must be at least 1.
418 static const base::TimeDelta kMin = base::TimeDelta::FromMilliseconds(1);
419 static const base::TimeDelta kMax = base::TimeDelta::FromMinutes(5);
420 // However, 0 can still be sampled.
421 static const base::TimeDelta kZero = base::TimeDelta::FromMilliseconds(0);
422 static const int kBuckets = 50;
424 base::Time now = base::Time::Now();
425 if (!time_start_.is_null()) {
426 base::TimeDelta delta = now - time_start_;
427 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAProtocolTime, delta, kMin, kMax, kBuckets);
429 base::TimeDelta delta = kZero;
430 if (!time_extra_start_.is_null())
431 delta = now - time_extra_start_;
432 // This samples |kZero| when there was no need for extra time, so that we can
433 // measure the ratio of users that succeeded without needing a delay to the
434 // total users going through OOBE.
435 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAExtraTime, delta, kMin, kMax, kBuckets);
438 } // namespace policy