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"
8 #include "base/command_line.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"
31 using content::BrowserThread
;
33 namespace em
= enterprise_management
;
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
))
55 std::string value
= command_line
->GetSwitchValueASCII(switch_name
);
57 if (!base::StringToInt(value
, &int_value
)) {
58 LOG(ERROR
) << "Switch \"" << switch_name
<< "\" is not a valid int. "
59 << "Defaulting to 0.";
63 LOG(ERROR
) << "Switch \"" << switch_name
<< "\" can't be negative. "
67 if (int_value
> kMaximumPower
) {
68 LOG(ERROR
) << "Switch \"" << switch_name
<< "\" can't be greater than "
69 << kMaximumPower
<< ". Using " << kMaximumPower
;
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
)
81 // No other value can be represented in an int64.
82 return kMaximumPower
+ 1;
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
,
97 : completion_callback_(callback
),
98 should_auto_enroll_(false),
99 device_id_(base::GenerateGUID()),
100 power_initial_(power_initial
),
101 power_limit_(power_limit
),
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);
122 void AutoEnrollmentClient::RegisterPrefs(PrefRegistrySimple
* registry
) {
123 registry
->RegisterBooleanPref(prefs::kShouldAutoEnroll
, false);
124 registry
->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit
, -1);
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
));
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
;
147 VLOG(1) << "Auto-enrollment is disabled";
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(
168 g_browser_process
->local_state(),
169 g_browser_process
->system_request_context(),
170 DeviceCloudPolicyManagerChromeOS::GetMachineID(),
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.";
196 time_start_
= base::Time::Now();
197 SendRequest(power_initial_
);
198 // Don't invoke the callback now.
203 // Auto-enrollment can't even start, so we're done.
207 void AutoEnrollmentClient::CancelAndDeleteSoon() {
208 if (time_start_
.is_null()) {
209 // The client isn't running, just delete it.
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
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.
228 if (type
!= net::NetworkChangeNotifier::CONNECTION_NONE
&&
229 !completion_callback_
.is_null() &&
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
) {
257 should_auto_enroll_
= should_auto_enroll
;
261 void AutoEnrollmentClient::SendRequest(int power
) {
262 if (power
< 0 || power
> power_limit_
|| serial_number_hash_
.empty()) {
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);
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
,
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.
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: "
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_
<< ").";
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
334 power_initial_
= power
;
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
);
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_
)
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();
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