Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / chromeos / policy / auto_enrollment_client.cc
blob8b01ffbd3cd020ed32183d3063aa44a2f59435cd
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/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/strings/string_number_conversions.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.h"
20 #include "chrome/browser/policy/browser_policy_connector.h"
21 #include "chrome/common/pref_names.h"
22 #include "chromeos/chromeos_switches.h"
23 #include "components/policy/core/common/cloud/device_management_service.h"
24 #include "components/policy/core/common/cloud/system_policy_request_context.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/common/content_client.h"
27 #include "crypto/sha2.h"
28 #include "net/url_request/url_request_context_getter.h"
29 #include "url/gurl.h"
31 using content::BrowserThread;
33 namespace em = enterprise_management;
35 namespace {
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";
44 // The modulus value is sent in an int64 field in the protobuf, whose maximum
45 // value is 2^63-1. So 2^64 and 2^63 can't be represented as moduli and the
46 // max is 2^62 (when the moduli are restricted to powers-of-2).
47 const int kMaximumPower = 62;
49 // Returns the int value of the |switch_name| argument, clamped to the [0, 62]
50 // interval. Returns 0 if the argument doesn't exist or isn't an int value.
51 int GetSanitizedArg(const std::string& switch_name) {
52 CommandLine* command_line = CommandLine::ForCurrentProcess();
53 if (!command_line->HasSwitch(switch_name))
54 return 0;
55 std::string value = command_line->GetSwitchValueASCII(switch_name);
56 int int_value;
57 if (!base::StringToInt(value, &int_value)) {
58 LOG(ERROR) << "Switch \"" << switch_name << "\" is not a valid int. "
59 << "Defaulting to 0.";
60 return 0;
62 if (int_value < 0) {
63 LOG(ERROR) << "Switch \"" << switch_name << "\" can't be negative. "
64 << "Using 0";
65 return 0;
67 if (int_value > kMaximumPower) {
68 LOG(ERROR) << "Switch \"" << switch_name << "\" can't be greater than "
69 << kMaximumPower << ". Using " << kMaximumPower;
70 return kMaximumPower;
72 return int_value;
75 // Returns the power of the next power-of-2 starting at |value|.
76 int NextPowerOf2(int64 value) {
77 for (int i = 0; i <= kMaximumPower; ++i) {
78 if ((GG_INT64_C(1) << i) >= value)
79 return i;
81 // No other value can be represented in an int64.
82 return kMaximumPower + 1;
85 } // namespace
87 namespace policy {
89 AutoEnrollmentClient::AutoEnrollmentClient(
90 const base::Closure& callback,
91 DeviceManagementService* service,
92 PrefService* local_state,
93 scoped_refptr<net::URLRequestContextGetter> system_request_context,
94 const std::string& serial_number,
95 int power_initial,
96 int power_limit)
97 : completion_callback_(callback),
98 should_auto_enroll_(false),
99 device_id_(base::GenerateGUID()),
100 power_initial_(power_initial),
101 power_limit_(power_limit),
102 requests_sent_(0),
103 device_management_service_(service),
104 local_state_(local_state) {
105 request_context_ = new SystemPolicyRequestContext(
106 system_request_context,
107 content::GetUserAgent(
108 GURL(device_management_service_->GetServerUrl())));
110 DCHECK_LE(power_initial_, power_limit_);
111 DCHECK(!completion_callback_.is_null());
112 if (!serial_number.empty())
113 serial_number_hash_ = crypto::SHA256HashString(serial_number);
114 net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
117 AutoEnrollmentClient::~AutoEnrollmentClient() {
118 net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
121 // static
122 void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple* registry) {
123 registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
124 registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
127 // static
128 bool AutoEnrollmentClient::IsDisabled() {
129 CommandLine* command_line = CommandLine::ForCurrentProcess();
130 // Do not communicate auto-enrollment data to the server if
131 // 1. we are running integration or perf tests with telemetry.
132 // 2. modulus configuration is not present.
133 return command_line->HasSwitch(
134 chromeos::switches::kOobeSkipPostLogin) ||
135 (!command_line->HasSwitch(
136 chromeos::switches::kEnterpriseEnrollmentInitialModulus) &&
137 !command_line->HasSwitch(
138 chromeos::switches::kEnterpriseEnrollmentModulusLimit));
141 // static
142 AutoEnrollmentClient* AutoEnrollmentClient::Create(
143 const base::Closure& completion_callback) {
144 // The client won't do anything if |service| is NULL.
145 DeviceManagementService* service = NULL;
146 if (IsDisabled()) {
147 VLOG(1) << "Auto-enrollment is disabled";
148 } else {
149 BrowserPolicyConnector* connector =
150 g_browser_process->browser_policy_connector();
151 service = connector->device_management_service();
152 service->ScheduleInitialization(0);
155 int power_initial = GetSanitizedArg(
156 chromeos::switches::kEnterpriseEnrollmentInitialModulus);
157 int power_limit = GetSanitizedArg(
158 chromeos::switches::kEnterpriseEnrollmentModulusLimit);
159 if (power_initial > power_limit) {
160 LOG(ERROR) << "Initial auto-enrollment modulus is larger than the limit, "
161 << "clamping to the limit.";
162 power_initial = power_limit;
165 return new AutoEnrollmentClient(
166 completion_callback,
167 service,
168 g_browser_process->local_state(),
169 g_browser_process->system_request_context(),
170 DeviceCloudPolicyManagerChromeOS::GetMachineID(),
171 power_initial,
172 power_limit);
175 // static
176 void AutoEnrollmentClient::CancelAutoEnrollment() {
177 PrefService* local_state = g_browser_process->local_state();
178 local_state->SetBoolean(prefs::kShouldAutoEnroll, false);
179 local_state->CommitPendingWrite();
182 void AutoEnrollmentClient::Start() {
183 // Drop the previous job and reset state.
184 request_job_.reset();
185 should_auto_enroll_ = false;
186 time_start_ = base::Time(); // reset to null.
188 if (GetCachedDecision()) {
189 VLOG(1) << "AutoEnrollmentClient: using cached decision: "
190 << should_auto_enroll_;
191 } else if (device_management_service_) {
192 if (serial_number_hash_.empty()) {
193 LOG(ERROR) << "Failed to get the hash of the serial number, "
194 << "will not attempt to auto-enroll.";
195 } else {
196 time_start_ = base::Time::Now();
197 SendRequest(power_initial_);
198 // Don't invoke the callback now.
199 return;
203 // Auto-enrollment can't even start, so we're done.
204 OnProtocolDone();
207 void AutoEnrollmentClient::CancelAndDeleteSoon() {
208 if (time_start_.is_null()) {
209 // The client isn't running, just delete it.
210 delete this;
211 } else {
212 // Client still running, but our owner isn't interested in the result
213 // anymore. Wait until the protocol completes to measure the extra time
214 // needed.
215 time_extra_start_ = base::Time::Now();
216 completion_callback_.Reset();
220 void AutoEnrollmentClient::OnNetworkChanged(
221 net::NetworkChangeNotifier::ConnectionType type) {
222 if (GetCachedDecision()) {
223 // A previous request already obtained a definitive response from the
224 // server, so there is no point in retrying; it will get the same decision.
225 return;
228 if (type != net::NetworkChangeNotifier::CONNECTION_NONE &&
229 !completion_callback_.is_null() &&
230 !request_job_ &&
231 device_management_service_ &&
232 !serial_number_hash_.empty()) {
233 VLOG(1) << "Retrying auto enrollment check after network changed";
234 time_start_ = base::Time::Now();
235 SendRequest(power_initial_);
239 bool AutoEnrollmentClient::GetCachedDecision() {
240 const PrefService::Preference* should_enroll_pref =
241 local_state_->FindPreference(prefs::kShouldAutoEnroll);
242 const PrefService::Preference* previous_limit_pref =
243 local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit);
244 bool should_auto_enroll = false;
245 int previous_limit = -1;
247 if (!should_enroll_pref ||
248 should_enroll_pref->IsDefaultValue() ||
249 !should_enroll_pref->GetValue()->GetAsBoolean(&should_auto_enroll) ||
250 !previous_limit_pref ||
251 previous_limit_pref->IsDefaultValue() ||
252 !previous_limit_pref->GetValue()->GetAsInteger(&previous_limit) ||
253 power_limit_ > previous_limit) {
254 return false;
257 should_auto_enroll_ = should_auto_enroll;
258 return true;
261 void AutoEnrollmentClient::SendRequest(int power) {
262 if (power < 0 || power > power_limit_ || serial_number_hash_.empty()) {
263 NOTREACHED();
264 OnRequestDone();
265 return;
268 requests_sent_++;
270 // Only power-of-2 moduli are supported for now. These are computed by taking
271 // the lower |power| bits of the hash.
272 uint64 remainder = 0;
273 for (int i = 0; 8 * i < power; ++i) {
274 uint64 byte = serial_number_hash_[31 - i] & 0xff;
275 remainder = remainder | (byte << (8 * i));
277 remainder = remainder & ((GG_UINT64_C(1) << power) - 1);
279 request_job_.reset(
280 device_management_service_->CreateJob(
281 DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT,
282 request_context_.get()));
283 request_job_->SetClientID(device_id_);
284 em::DeviceAutoEnrollmentRequest* request =
285 request_job_->GetRequest()->mutable_auto_enrollment_request();
286 request->set_remainder(remainder);
287 request->set_modulus(GG_INT64_C(1) << power);
288 request_job_->Start(base::Bind(&AutoEnrollmentClient::OnRequestCompletion,
289 base::Unretained(this)));
292 void AutoEnrollmentClient::OnRequestCompletion(
293 DeviceManagementStatus status,
294 int net_error,
295 const em::DeviceManagementResponse& response) {
296 if (status != DM_STATUS_SUCCESS || !response.has_auto_enrollment_response()) {
297 LOG(ERROR) << "Auto enrollment error: " << status;
298 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, status);
299 if (status == DM_STATUS_REQUEST_FAILED)
300 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMANetworkErrorCode, -net_error);
301 // The client will retry if a network change is detected.
302 OnRequestDone();
303 return;
306 const em::DeviceAutoEnrollmentResponse& enrollment_response =
307 response.auto_enrollment_response();
308 if (enrollment_response.has_expected_modulus()) {
309 // Server is asking us to retry with a different modulus.
310 int64 modulus = enrollment_response.expected_modulus();
311 int power = NextPowerOf2(modulus);
312 if ((GG_INT64_C(1) << power) != modulus) {
313 LOG(WARNING) << "Auto enrollment: the server didn't ask for a power-of-2 "
314 << "modulus. Using the closest power-of-2 instead "
315 << "(" << modulus << " vs 2^" << power << ")";
317 if (requests_sent_ >= 2) {
318 LOG(ERROR) << "Auto enrollment error: already retried with an updated "
319 << "modulus but the server asked for a new one again: "
320 << power;
321 } else if (power > power_limit_) {
322 LOG(ERROR) << "Auto enrollment error: the server asked for a larger "
323 << "modulus than the client accepts (" << power << " vs "
324 << power_limit_ << ").";
325 } else {
326 // Retry at most once with the modulus that the server requested.
327 if (power <= power_initial_) {
328 LOG(WARNING) << "Auto enrollment: the server asked to use a modulus ("
329 << power << ") that isn't larger than the first used ("
330 << power_initial_ << "). Retrying anyway.";
332 // Remember this value, so that eventual retries start with the correct
333 // modulus.
334 power_initial_ = power;
335 SendRequest(power);
336 return;
338 } else {
339 // Server should have sent down a list of hashes to try.
340 should_auto_enroll_ = IsSerialInProtobuf(enrollment_response.hash());
341 // Cache the current decision in local_state, so that it is reused in case
342 // the device reboots before enrolling.
343 local_state_->SetBoolean(prefs::kShouldAutoEnroll, should_auto_enroll_);
344 local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_);
345 local_state_->CommitPendingWrite();
346 VLOG(1) << "Auto enrollment complete, should_auto_enroll = "
347 << should_auto_enroll_;
350 // Auto-enrollment done.
351 UMA_HISTOGRAM_SPARSE_SLOWLY(kUMARequestStatus, DM_STATUS_SUCCESS);
352 OnProtocolDone();
355 bool AutoEnrollmentClient::IsSerialInProtobuf(
356 const google::protobuf::RepeatedPtrField<std::string>& hashes) {
357 for (int i = 0; i < hashes.size(); ++i) {
358 if (hashes.Get(i) == serial_number_hash_)
359 return true;
361 return false;
364 void AutoEnrollmentClient::OnProtocolDone() {
365 // The mininum time can't be 0, must be at least 1.
366 static const base::TimeDelta kMin = base::TimeDelta::FromMilliseconds(1);
367 static const base::TimeDelta kMax = base::TimeDelta::FromMinutes(5);
368 // However, 0 can still be sampled.
369 static const base::TimeDelta kZero = base::TimeDelta::FromMilliseconds(0);
370 static const int kBuckets = 50;
372 base::Time now = base::Time::Now();
373 if (!time_start_.is_null()) {
374 base::TimeDelta delta = now - time_start_;
375 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAProtocolTime, delta, kMin, kMax, kBuckets);
377 base::TimeDelta delta = kZero;
378 if (!time_extra_start_.is_null())
379 delta = now - time_extra_start_;
380 // This samples |kZero| when there was no need for extra time, so that we can
381 // measure the ratio of users that succeeded without needing a delay to the
382 // total users going through OOBE.
383 UMA_HISTOGRAM_CUSTOM_TIMES(kUMAExtraTime, delta, kMin, kMax, kBuckets);
385 if (!completion_callback_.is_null())
386 completion_callback_.Run();
388 OnRequestDone();
391 void AutoEnrollmentClient::OnRequestDone() {
392 request_job_.reset();
393 time_start_ = base::Time();
395 if (completion_callback_.is_null()) {
396 // CancelAndDeleteSoon() was invoked before.
397 base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this);
401 } // namespace policy