Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / chromeos / policy / auto_enrollment_client.cc
blob78bde1e71e3eb9b5fe168df35f2f6229a10b80c7
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 "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/chromeos/policy/server_backed_device_state.h"
18 #include "chrome/common/chrome_content_client.h"
19 #include "chrome/common/pref_names.h"
20 #include "components/policy/core/common/cloud/device_management_service.h"
21 #include "components/policy/core/common/cloud/system_policy_request_context.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "crypto/sha2.h"
24 #include "net/url_request/url_request_context_getter.h"
25 #include "policy/proto/device_management_backend.pb.h"
26 #include "url/gurl.h"
28 using content::BrowserThread;
30 namespace em = enterprise_management;
32 namespace policy {
34 namespace {
36 // UMA histogram names.
37 const char kUMAProtocolTime[] = "Enterprise.AutoEnrollmentProtocolTime";
38 const char kUMAExtraTime[] = "Enterprise.AutoEnrollmentExtraTime";
39 const char kUMARequestStatus[] = "Enterprise.AutoEnrollmentRequestStatus";
40 const char kUMANetworkErrorCode[] =
41 "Enterprise.AutoEnrollmentRequestNetworkErrorCode";
43 // Returns the power of the next power-of-2 starting at |value|.
44 int NextPowerOf2(int64 value) {
45 for (int i = 0; i <= AutoEnrollmentClient::kMaximumPower; ++i) {
46 if ((GG_INT64_C(1) << i) >= value)
47 return i;
49 // No other value can be represented in an int64.
50 return AutoEnrollmentClient::kMaximumPower + 1;
53 // Sets or clears a value in a dictionary.
54 void UpdateDict(base::DictionaryValue* dict,
55 const char* pref_path,
56 bool set_or_clear,
57 base::Value* value) {
58 scoped_ptr<base::Value> scoped_value(value);
59 if (set_or_clear)
60 dict->Set(pref_path, scoped_value.release());
61 else
62 dict->Remove(pref_path, NULL);
65 // Converts a restore mode enum value from the DM protocol into the
66 // corresponding prefs string constant.
67 std::string ConvertRestoreMode(
68 em::DeviceStateRetrievalResponse::RestoreMode restore_mode) {
69 switch (restore_mode) {
70 case em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE:
71 return std::string();
72 case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_REQUESTED:
73 return kDeviceStateRestoreModeReEnrollmentRequested;
74 case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED:
75 return kDeviceStateRestoreModeReEnrollmentEnforced;
76 case em::DeviceStateRetrievalResponse::RESTORE_MODE_DISABLED:
77 return kDeviceStateRestoreModeDisabled;
80 // Return is required to avoid compiler warning.
81 NOTREACHED() << "Bad restore mode " << restore_mode;
82 return std::string();
85 } // namespace
87 AutoEnrollmentClient::AutoEnrollmentClient(
88 const ProgressCallback& callback,
89 DeviceManagementService* service,
90 PrefService* local_state,
91 scoped_refptr<net::URLRequestContextGetter> system_request_context,
92 const std::string& server_backed_state_key,
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 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());
109 DCHECK_LE(current_power_, power_limit_);
110 DCHECK(!progress_callback_.is_null());
111 CHECK(!server_backed_state_key_.empty());
112 server_backed_state_key_hash_ =
113 crypto::SHA256HashString(server_backed_state_key_);
116 AutoEnrollmentClient::~AutoEnrollmentClient() {
117 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
120 // static
121 void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) {
122 registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
123 registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
126 void AutoEnrollmentClient::Start() {
127 // (Re-)register the network change observer.
128 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
129 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
131 // Drop the previous job and reset state.
132 request_job_.reset();
133 state_ = AUTO_ENROLLMENT_STATE_PENDING;
134 time_start_ = base::Time::Now();
135 modulus_updates_received_ = 0;
136 has_server_state_ = false;
137 device_state_available_ = false;
139 NextStep();
142 void AutoEnrollmentClient::Retry() {
143 RetryStep();
146 void AutoEnrollmentClient::CancelAndDeleteSoon() {
147 if (time_start_.is_null() || !request_job_) {
148 // The client isn't running, just delete it.
149 delete this;
150 } else {
151 // Client still running, but our owner isn't interested in the result
152 // anymore. Wait until the protocol completes to measure the extra time
153 // needed.
154 time_extra_start_ = base::Time::Now();
155 progress_callback_.Reset();
159 void AutoEnrollmentClient::OnNetworkChanged(
160 net::NetworkChangeNotifier::ConnectionType type) {
161 if (type != net::NetworkChangeNotifier::CONNECTION_NONE &&
162 !progress_callback_.is_null()) {
163 RetryStep();
167 bool AutoEnrollmentClient::GetCachedDecision() {
168 const PrefService::Preference* has_server_state_pref =
169 local_state_->FindPreference(prefs::kShouldAutoEnroll);
170 const PrefService::Preference* previous_limit_pref =
171 local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit);
172 bool has_server_state = false;
173 int previous_limit = -1;
175 if (!has_server_state_pref ||
176 has_server_state_pref->IsDefaultValue() ||
177 !has_server_state_pref->GetValue()->GetAsBoolean(&has_server_state) ||
178 !previous_limit_pref ||
179 previous_limit_pref->IsDefaultValue() ||
180 !previous_limit_pref->GetValue()->GetAsInteger(&previous_limit) ||
181 power_limit_ > previous_limit) {
182 return false;
185 has_server_state_ = has_server_state;
186 return true;
189 bool AutoEnrollmentClient::RetryStep() {
190 // If there is a pending request job, let it finish.
191 if (request_job_)
192 return true;
194 if (GetCachedDecision()) {
195 // The bucket download check has completed already. If it came back
196 // positive, then device state should be (re-)downloaded.
197 if (has_server_state_) {
198 if (!device_state_available_) {
199 SendDeviceStateRequest();
200 return true;
203 } else {
204 // Start bucket download.
205 SendBucketDownloadRequest();
206 return true;
209 return false;
212 void AutoEnrollmentClient::ReportProgress(AutoEnrollmentState state) {
213 state_ = state;
214 if (progress_callback_.is_null()) {
215 base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this);
216 } else {
217 progress_callback_.Run(state_);
221 void AutoEnrollmentClient::NextStep() {
222 if (!RetryStep()) {
223 // Protocol finished successfully, report result.
224 const RestoreMode restore_mode = GetRestoreMode();
225 bool trigger_enrollment =
226 (restore_mode == RESTORE_MODE_REENROLLMENT_REQUESTED ||
227 restore_mode == RESTORE_MODE_REENROLLMENT_ENFORCED);
229 ReportProgress(trigger_enrollment ? AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT
230 : AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
234 void AutoEnrollmentClient::SendBucketDownloadRequest() {
235 // Only power-of-2 moduli are supported for now. These are computed by taking
236 // the lower |current_power_| bits of the hash.
237 uint64 remainder = 0;
238 for (int i = 0; 8 * i < current_power_; ++i) {
239 uint64 byte = server_backed_state_key_hash_[31 - i] & 0xff;
240 remainder = remainder | (byte << (8 * i));
242 remainder = remainder & ((GG_UINT64_C(1) << current_power_) - 1);
244 ReportProgress(AUTO_ENROLLMENT_STATE_PENDING);
246 request_job_.reset(
247 device_management_service_->CreateJob(
248 DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT,
249 request_context_.get()));
250 request_job_->SetClientID(device_id_);
251 em::DeviceAutoEnrollmentRequest* request =
252 request_job_->GetRequest()->mutable_auto_enrollment_request();
253 request->set_remainder(remainder);
254 request->set_modulus(GG_INT64_C(1) << current_power_);
255 request_job_->Start(
256 base::Bind(&AutoEnrollmentClient::HandleRequestCompletion,
257 base::Unretained(this),
258 &AutoEnrollmentClient::OnBucketDownloadRequestCompletion));
261 void AutoEnrollmentClient::SendDeviceStateRequest() {
262 ReportProgress(AUTO_ENROLLMENT_STATE_PENDING);
264 request_job_.reset(
265 device_management_service_->CreateJob(
266 DeviceManagementRequestJob::TYPE_DEVICE_STATE_RETRIEVAL,
267 request_context_.get()));
268 request_job_->SetClientID(device_id_);
269 em::DeviceStateRetrievalRequest* request =
270 request_job_->GetRequest()->mutable_device_state_retrieval_request();
271 request->set_server_backed_state_key(server_backed_state_key_);
272 request_job_->Start(
273 base::Bind(&AutoEnrollmentClient::HandleRequestCompletion,
274 base::Unretained(this),
275 &AutoEnrollmentClient::OnDeviceStateRequestCompletion));
278 void AutoEnrollmentClient::HandleRequestCompletion(
279 RequestCompletionHandler handler,
280 DeviceManagementStatus status,
281 int net_error,
282 const em::DeviceManagementResponse& response) {
283 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, status);
284 if (status != DM_STATUS_SUCCESS) {
285 LOG(ERROR) << "Auto enrollment error: " << status;
286 if (status == DM_STATUS_REQUEST_FAILED)
287 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMANetworkErrorCode, -net_error);
288 request_job_.reset();
290 // Abort if CancelAndDeleteSoon has been called meanwhile.
291 if (progress_callback_.is_null()) {
292 base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this);
293 } else {
294 ReportProgress(status == DM_STATUS_REQUEST_FAILED
295 ? AUTO_ENROLLMENT_STATE_CONNECTION_ERROR
296 : AUTO_ENROLLMENT_STATE_SERVER_ERROR);
298 return;
301 bool progress = (this->*handler)(status, net_error, response);
302 request_job_.reset();
303 if (progress)
304 NextStep();
305 else
306 ReportProgress(AUTO_ENROLLMENT_STATE_SERVER_ERROR);
309 bool AutoEnrollmentClient::OnBucketDownloadRequestCompletion(
310 DeviceManagementStatus status,
311 int net_error,
312 const em::DeviceManagementResponse& response) {
313 bool progress = false;
314 const em::DeviceAutoEnrollmentResponse& enrollment_response =
315 response.auto_enrollment_response();
316 if (!response.has_auto_enrollment_response()) {
317 LOG(ERROR) << "Server failed to provide auto-enrollment response.";
318 } else if (enrollment_response.has_expected_modulus()) {
319 // Server is asking us to retry with a different modulus.
320 modulus_updates_received_++;
322 int64 modulus = enrollment_response.expected_modulus();
323 int power = NextPowerOf2(modulus);
324 if ((GG_INT64_C(1) << power) != modulus) {
325 LOG(WARNING) << "Auto enrollment: the server didn't ask for a power-of-2 "
326 << "modulus. Using the closest power-of-2 instead "
327 << "(" << modulus << " vs 2^" << power << ")";
329 if (modulus_updates_received_ >= 2) {
330 LOG(ERROR) << "Auto enrollment error: already retried with an updated "
331 << "modulus but the server asked for a new one again: "
332 << power;
333 } else if (power > power_limit_) {
334 LOG(ERROR) << "Auto enrollment error: the server asked for a larger "
335 << "modulus than the client accepts (" << power << " vs "
336 << power_limit_ << ").";
337 } else {
338 // Retry at most once with the modulus that the server requested.
339 if (power <= current_power_) {
340 LOG(WARNING) << "Auto enrollment: the server asked to use a modulus ("
341 << power << ") that isn't larger than the first used ("
342 << current_power_ << "). Retrying anyway.";
344 // Remember this value, so that eventual retries start with the correct
345 // modulus.
346 current_power_ = power;
347 return true;
349 } else {
350 // Server should have sent down a list of hashes to try.
351 has_server_state_ = IsIdHashInProtobuf(enrollment_response.hash());
352 // Cache the current decision in local_state, so that it is reused in case
353 // the device reboots before enrolling.
354 local_state_->SetBoolean(prefs::kShouldAutoEnroll, has_server_state_);
355 local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_);
356 local_state_->CommitPendingWrite();
357 VLOG(1) << "Auto enrollment check complete, has_server_state_ = "
358 << has_server_state_;
359 progress = true;
362 // Bucket download done, update UMA.
363 UpdateBucketDownloadTimingHistograms();
364 return progress;
367 bool AutoEnrollmentClient::OnDeviceStateRequestCompletion(
368 DeviceManagementStatus status,
369 int net_error,
370 const enterprise_management::DeviceManagementResponse& response) {
371 bool progress = false;
372 if (!response.has_device_state_retrieval_response()) {
373 LOG(ERROR) << "Server failed to provide auto-enrollment response.";
374 } else {
375 const em::DeviceStateRetrievalResponse& state_response =
376 response.device_state_retrieval_response();
378 DictionaryPrefUpdate dict(local_state_, prefs::kServerBackedDeviceState);
379 UpdateDict(dict.Get(),
380 kDeviceStateManagementDomain,
381 state_response.has_management_domain(),
382 new base::StringValue(state_response.management_domain()));
384 std::string restore_mode =
385 ConvertRestoreMode(state_response.restore_mode());
386 UpdateDict(dict.Get(),
387 kDeviceStateRestoreMode,
388 !restore_mode.empty(),
389 new base::StringValue(restore_mode));
391 UpdateDict(dict.Get(),
392 kDeviceStateDisabledMessage,
393 state_response.has_disabled_state(),
394 new base::StringValue(
395 state_response.disabled_state().message()));
397 local_state_->CommitPendingWrite();
398 device_state_available_ = true;
399 progress = true;
402 return progress;
405 bool AutoEnrollmentClient::IsIdHashInProtobuf(
406 const google::protobuf::RepeatedPtrField<std::string>& hashes) {
407 for (int i = 0; i < hashes.size(); ++i) {
408 if (hashes.Get(i) == server_backed_state_key_hash_)
409 return true;
411 return false;
414 void AutoEnrollmentClient::UpdateBucketDownloadTimingHistograms() {
415 // The minimum time can't be 0, must be at least 1.
416 static const base::TimeDelta kMin = base::TimeDelta::FromMilliseconds(1);
417 static const base::TimeDelta kMax = base::TimeDelta::FromMinutes(5);
418 // However, 0 can still be sampled.
419 static const base::TimeDelta kZero = base::TimeDelta::FromMilliseconds(0);
420 static const int kBuckets = 50;
422 base::Time now = base::Time::Now();
423 if (!time_start_.is_null()) {
424 base::TimeDelta delta = now - time_start_;
425 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAProtocolTime, delta, kMin, kMax, kBuckets);
427 base::TimeDelta delta = kZero;
428 if (!time_extra_start_.is_null())
429 delta = now - time_extra_start_;
430 // This samples |kZero| when there was no need for extra time, so that we can
431 // measure the ratio of users that succeeded without needing a delay to the
432 // total users going through OOBE.
433 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAExtraTime, delta, kMin, kMax, kBuckets);
436 } // namespace policy