blob: 5724ac43dbaa771d6cc6e93cbc70a534ee42ea47 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/policy/auto_enrollment_client_impl.h"
#include <stdint.h>
#include "base/bind.h"
#include "base/guid.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/optional.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/chromeos/policy/server_backed_device_state.h"
#include "chrome/common/chrome_content_client.h"
#include "chrome/common/pref_names.h"
#include "components/policy/core/common/cloud/device_management_service.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/network_service_instance.h"
#include "crypto/sha2.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "url/gurl.h"
using content::BrowserThread;
namespace em = enterprise_management;
namespace policy {
namespace {
using EnrollmentCheckType =
em::DeviceAutoEnrollmentRequest::EnrollmentCheckType;
// UMA histogram names.
constexpr char kUMAProtocolTime[] = "Enterprise.AutoEnrollmentProtocolTime";
constexpr char kUMABucketDownloadTime[] =
"Enterprise.AutoEnrollmentBucketDownloadTime";
constexpr char kUMAExtraTime[] = "Enterprise.AutoEnrollmentExtraTime";
constexpr char kUMARequestStatus[] = "Enterprise.AutoEnrollmentRequestStatus";
constexpr char kUMANetworkErrorCode[] =
"Enterprise.AutoEnrollmentRequestNetworkErrorCode";
// Suffix for initial enrollment.
constexpr char kUMASuffixInitialEnrollment[] = ".InitialEnrollment";
// Suffix for Forced Re-Enrollment.
constexpr char kUMASuffixFRE[] = ".ForcedReenrollment";
// Returns the power of the next power-of-2 starting at |value|.
int NextPowerOf2(int64_t value) {
for (int i = 0; i <= AutoEnrollmentClient::kMaximumPower; ++i) {
if ((INT64_C(1) << i) >= value)
return i;
}
// No other value can be represented in an int64_t.
return AutoEnrollmentClient::kMaximumPower + 1;
}
// Sets or clears a value in a dictionary.
void UpdateDict(base::DictionaryValue* dict,
const char* pref_path,
bool set_or_clear,
std::unique_ptr<base::Value> value) {
if (set_or_clear)
dict->Set(pref_path, std::move(value));
else
dict->Remove(pref_path, NULL);
}
// Converts a restore mode enum value from the DM protocol for FRE into the
// corresponding prefs string constant.
std::string ConvertRestoreMode(
em::DeviceStateRetrievalResponse::RestoreMode restore_mode) {
switch (restore_mode) {
case em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE:
return std::string();
case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_REQUESTED:
return kDeviceStateRestoreModeReEnrollmentRequested;
case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED:
return kDeviceStateRestoreModeReEnrollmentEnforced;
case em::DeviceStateRetrievalResponse::RESTORE_MODE_DISABLED:
return kDeviceStateRestoreModeDisabled;
case em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ZERO_TOUCH:
return kDeviceStateRestoreModeReEnrollmentZeroTouch;
}
// Return is required to avoid compiler warning.
NOTREACHED() << "Bad restore_mode=" << restore_mode;
return std::string();
}
// Converts an initial enrollment mode enum value from the DM protocol for
// initial enrollment into the corresponding prefs string constant. Note that we
// use the |kDeviceStateRestoreMode*| constants on the client for simplicity,
// because every initial enrollment mode has a matching restore mode (but not
// vice versa).
std::string ConvertInitialEnrollmentMode(
em::DeviceInitialEnrollmentStateResponse::InitialEnrollmentMode
initial_enrollment_mode) {
switch (initial_enrollment_mode) {
case em::DeviceInitialEnrollmentStateResponse::INITIAL_ENROLLMENT_MODE_NONE:
return std::string();
case em::DeviceInitialEnrollmentStateResponse::
INITIAL_ENROLLMENT_MODE_ENROLLMENT_ENFORCED:
return kDeviceStateRestoreModeReEnrollmentEnforced;
}
}
} // namespace
// Subclasses of this class provide an identifier and specify the identifier
// set for the DeviceAutoEnrollmentRequest,
class AutoEnrollmentClientImpl::DeviceIdentifierProvider {
public:
virtual ~DeviceIdentifierProvider() {}
// Should return the EnrollmentCheckType to be used in the
// DeviceAutoEnrollmentRequest. This specifies the identifier set used on
// the server.
virtual enterprise_management::DeviceAutoEnrollmentRequest::
EnrollmentCheckType
GetEnrollmentCheckType() const = 0;
// Should return the hash of this device's identifier. The
// DeviceAutoEnrollmentRequest exchange will check if this hash is in the
// server-side identifier set specified by |GetEnrollmentCheckType()|
virtual const std::string& GetIdHash() const = 0;
};
// Subclasses of this class generate the request to download the device state
// (after determining that there is server-side device state) and parse the
// response.
class AutoEnrollmentClientImpl::StateDownloadMessageProcessor {
public:
virtual ~StateDownloadMessageProcessor() {}
// Returns the request job type. This must match the request filled in
// |FillRequest|.
virtual DeviceManagementRequestJob::JobType GetJobType() const = 0;
// Fills the specific request type in |request|.
virtual void FillRequest(
enterprise_management::DeviceManagementRequest* request) = 0;
// Parses the |response|. If it is valid, extracts |restore_mode|,
// |management_domain| and |disabled_message| and returns true. Otherwise,
// returns false.
virtual bool ParseResponse(
const enterprise_management::DeviceManagementResponse& response,
std::string* restore_mode,
base::Optional<std::string>* management_domain,
base::Optional<std::string>* disabled_message) = 0;
};
namespace {
// Provides device identifier for Forced Re-Enrollment (FRE), where the
// server-backed state key is used.
class DeviceIdentifierProviderFRE
: public AutoEnrollmentClientImpl::DeviceIdentifierProvider {
public:
explicit DeviceIdentifierProviderFRE(
const std::string& server_backed_state_key) {
CHECK(!server_backed_state_key.empty());
server_backed_state_key_hash_ =
crypto::SHA256HashString(server_backed_state_key);
}
EnrollmentCheckType GetEnrollmentCheckType() const override {
return em::DeviceAutoEnrollmentRequest::ENROLLMENT_CHECK_TYPE_FRE;
}
const std::string& GetIdHash() const override {
return server_backed_state_key_hash_;
}
private:
// SHA-256 digest of the stable identifier.
std::string server_backed_state_key_hash_;
};
// Provides device identifier for Forced Initial Enrollment, where the brand
// code and serial number is used.
class DeviceIdentifierProviderInitialEnrollment
: public AutoEnrollmentClientImpl::DeviceIdentifierProvider {
public:
DeviceIdentifierProviderInitialEnrollment(
const std::string& device_serial_number,
const std::string& device_brand_code) {
CHECK(!device_serial_number.empty());
CHECK(!device_brand_code.empty());
// The hash for initial enrollment is the first 8 bytes of
// SHA256(<brnad_code>_<serial_number>).
id_hash_ =
crypto::SHA256HashString(device_brand_code + "_" + device_serial_number)
.substr(0, 8);
}
EnrollmentCheckType GetEnrollmentCheckType() const override {
return em::DeviceAutoEnrollmentRequest::
ENROLLMENT_CHECK_TYPE_FORCED_ENROLLMENT;
}
const std::string& GetIdHash() const override { return id_hash_; }
private:
// 8-byte Hash built from serial number and brand code passed to the
// constructor.
std::string id_hash_;
};
// Handles DeviceStateRetrievalRequest / DeviceStateRetrievalResponse for
// Forced Re-Enrollment (FRE).
class StateDownloadMessageProcessorFRE
: public AutoEnrollmentClientImpl::StateDownloadMessageProcessor {
public:
explicit StateDownloadMessageProcessorFRE(
const std::string& server_backed_state_key)
: server_backed_state_key_(server_backed_state_key) {}
DeviceManagementRequestJob::JobType GetJobType() const override {
return DeviceManagementRequestJob::TYPE_DEVICE_STATE_RETRIEVAL;
}
void FillRequest(em::DeviceManagementRequest* request) override {
request->mutable_device_state_retrieval_request()
->set_server_backed_state_key(server_backed_state_key_);
}
bool ParseResponse(const em::DeviceManagementResponse& response,
std::string* restore_mode,
base::Optional<std::string>* management_domain,
base::Optional<std::string>* disabled_message) override {
if (!response.has_device_state_retrieval_response()) {
LOG(ERROR) << "Server failed to provide auto-enrollment response.";
return false;
}
const em::DeviceStateRetrievalResponse& state_response =
response.device_state_retrieval_response();
*restore_mode = ConvertRestoreMode(state_response.restore_mode());
if (state_response.has_management_domain())
*management_domain = state_response.management_domain();
else
management_domain->reset();
if (state_response.has_disabled_state())
*disabled_message = state_response.disabled_state().message();
else
disabled_message->reset();
// Logging as "WARNING" to make sure it's preserved in the logs.
LOG(WARNING) << "Received restore_mode=" << state_response.restore_mode();
return true;
}
private:
// Stable state key.
std::string server_backed_state_key_;
};
// Handles DeviceInitialEnrollmentStateRequest /
// DeviceInitialEnrollmentStateResponse for Forced Initial Enrollment.
class StateDownloadMessageProcessorInitialEnrollment
: public AutoEnrollmentClientImpl::StateDownloadMessageProcessor {
public:
StateDownloadMessageProcessorInitialEnrollment(
const std::string& device_serial_number,
const std::string& device_brand_code)
: device_serial_number_(device_serial_number),
device_brand_code_(device_brand_code) {}
DeviceManagementRequestJob::JobType GetJobType() const override {
return DeviceManagementRequestJob::TYPE_INITIAL_ENROLLMENT_STATE_RETRIEVAL;
}
void FillRequest(em::DeviceManagementRequest* request) override {
auto* inner_request =
request->mutable_device_initial_enrollment_state_request();
inner_request->set_brand_code(device_brand_code_);
inner_request->set_serial_number(device_serial_number_);
}
bool ParseResponse(const em::DeviceManagementResponse& response,
std::string* restore_mode,
base::Optional<std::string>* management_domain,
base::Optional<std::string>* disabled_message) override {
if (!response.has_device_initial_enrollment_state_response()) {
LOG(ERROR) << "Server failed to provide initial enrollment response.";
return false;
}
const em::DeviceInitialEnrollmentStateResponse& state_response =
response.device_initial_enrollment_state_response();
if (state_response.has_initial_enrollment_mode()) {
*restore_mode = ConvertInitialEnrollmentMode(
state_response.initial_enrollment_mode());
} else {
// Unknown initial enrollment mode - treat as no enrollment.
*restore_mode = std::string();
}
if (state_response.has_management_domain())
*management_domain = state_response.management_domain();
else
management_domain->reset();
// Device disabling is not supported in initial forced enrollment.
disabled_message->reset();
// Logging as "WARNING" to make sure it's preserved in the logs.
LOG(WARNING) << "Received initial_enrollment_mode="
<< state_response.initial_enrollment_mode();
return true;
}
private:
// Serial number of the device.
std::string device_serial_number_;
// 4-character brand code of the device.
std::string device_brand_code_;
};
} // namespace
AutoEnrollmentClientImpl::FactoryImpl::FactoryImpl() {}
AutoEnrollmentClientImpl::FactoryImpl::~FactoryImpl() {}
std::unique_ptr<AutoEnrollmentClient>
AutoEnrollmentClientImpl::FactoryImpl::CreateForFRE(
const ProgressCallback& progress_callback,
DeviceManagementService* device_management_service,
PrefService* local_state,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const std::string& server_backed_state_key,
int power_initial,
int power_limit) {
return base::WrapUnique(new AutoEnrollmentClientImpl(
progress_callback, device_management_service, local_state,
url_loader_factory,
std::make_unique<DeviceIdentifierProviderFRE>(server_backed_state_key),
std::make_unique<StateDownloadMessageProcessorFRE>(
server_backed_state_key),
power_initial, power_limit, base::nullopt, kUMASuffixFRE));
}
std::unique_ptr<AutoEnrollmentClient>
AutoEnrollmentClientImpl::FactoryImpl::CreateForInitialEnrollment(
const ProgressCallback& progress_callback,
DeviceManagementService* device_management_service,
PrefService* local_state,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const std::string& device_serial_number,
const std::string& device_brand_code,
int power_initial,
int power_limit,
int power_outdated_server_detect) {
return base::WrapUnique(new AutoEnrollmentClientImpl(
progress_callback, device_management_service, local_state,
url_loader_factory,
std::make_unique<DeviceIdentifierProviderInitialEnrollment>(
device_serial_number, device_brand_code),
std::make_unique<StateDownloadMessageProcessorInitialEnrollment>(
device_serial_number, device_brand_code),
power_initial, power_limit,
base::make_optional(power_outdated_server_detect),
kUMASuffixInitialEnrollment));
}
AutoEnrollmentClientImpl::~AutoEnrollmentClientImpl() {
content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
}
// static
void AutoEnrollmentClientImpl::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterBooleanPref(prefs::kShouldAutoEnroll, false);
registry->RegisterIntegerPref(prefs::kAutoEnrollmentPowerLimit, -1);
}
void AutoEnrollmentClientImpl::Start() {
// (Re-)register the network change observer.
content::GetNetworkConnectionTracker()->RemoveNetworkConnectionObserver(this);
content::GetNetworkConnectionTracker()->AddNetworkConnectionObserver(this);
// Drop the previous job and reset state.
request_job_.reset();
state_ = AUTO_ENROLLMENT_STATE_PENDING;
time_start_ = base::Time::Now();
modulus_updates_received_ = 0;
has_server_state_ = false;
device_state_available_ = false;
NextStep();
}
void AutoEnrollmentClientImpl::Retry() {
RetryStep();
}
void AutoEnrollmentClientImpl::CancelAndDeleteSoon() {
if (time_start_.is_null() || !request_job_) {
// The client isn't running, just delete it.
delete this;
} else {
// Client still running, but our owner isn't interested in the result
// anymore. Wait until the protocol completes to measure the extra time
// needed.
time_extra_start_ = base::Time::Now();
progress_callback_.Reset();
}
}
std::string AutoEnrollmentClientImpl::device_id() const {
return device_id_;
}
AutoEnrollmentState AutoEnrollmentClientImpl::state() const {
return state_;
}
void AutoEnrollmentClientImpl::OnConnectionChanged(
network::mojom::ConnectionType type) {
if (type != network::mojom::ConnectionType::CONNECTION_NONE &&
!progress_callback_.is_null()) {
RetryStep();
}
}
AutoEnrollmentClientImpl::AutoEnrollmentClientImpl(
const ProgressCallback& callback,
DeviceManagementService* service,
PrefService* local_state,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
std::unique_ptr<DeviceIdentifierProvider> device_identifier_provider,
std::unique_ptr<StateDownloadMessageProcessor>
state_download_message_processor,
int power_initial,
int power_limit,
base::Optional<int> power_outdated_server_detect,
std::string uma_suffix)
: progress_callback_(callback),
state_(AUTO_ENROLLMENT_STATE_IDLE),
has_server_state_(false),
device_state_available_(false),
device_id_(base::GenerateGUID()),
current_power_(power_initial),
power_limit_(power_limit),
power_outdated_server_detect_(power_outdated_server_detect),
modulus_updates_received_(0),
device_management_service_(service),
local_state_(local_state),
url_loader_factory_(url_loader_factory),
device_identifier_provider_(std::move(device_identifier_provider)),
state_download_message_processor_(
std::move(state_download_message_processor)),
uma_suffix_(uma_suffix) {
DCHECK_LE(current_power_, power_limit_);
DCHECK(!progress_callback_.is_null());
}
bool AutoEnrollmentClientImpl::GetCachedDecision() {
const PrefService::Preference* has_server_state_pref =
local_state_->FindPreference(prefs::kShouldAutoEnroll);
const PrefService::Preference* previous_limit_pref =
local_state_->FindPreference(prefs::kAutoEnrollmentPowerLimit);
bool has_server_state = false;
int previous_limit = -1;
if (!has_server_state_pref || has_server_state_pref->IsDefaultValue() ||
!has_server_state_pref->GetValue()->GetAsBoolean(&has_server_state) ||
!previous_limit_pref || previous_limit_pref->IsDefaultValue() ||
!previous_limit_pref->GetValue()->GetAsInteger(&previous_limit) ||
power_limit_ > previous_limit) {
return false;
}
has_server_state_ = has_server_state;
return true;
}
bool AutoEnrollmentClientImpl::RetryStep() {
// If there is a pending request job, let it finish.
if (request_job_)
return true;
if (GetCachedDecision()) {
VLOG(1) << "Cached: has_state=" << has_server_state_;
// The bucket download check has completed already. If it came back
// positive, then device state should be (re-)downloaded.
if (has_server_state_) {
if (!device_state_available_) {
SendDeviceStateRequest();
return true;
}
}
} else {
// Start bucket download.
SendBucketDownloadRequest();
return true;
}
return false;
}
void AutoEnrollmentClientImpl::ReportProgress(AutoEnrollmentState state) {
state_ = state;
if (progress_callback_.is_null()) {
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
} else {
progress_callback_.Run(state_);
}
}
void AutoEnrollmentClientImpl::NextStep() {
if (RetryStep())
return;
// Protocol finished successfully, report result.
const RestoreMode restore_mode = GetRestoreMode();
switch (restore_mode) {
case RESTORE_MODE_NONE:
case RESTORE_MODE_DISABLED:
ReportProgress(AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
break;
case RESTORE_MODE_REENROLLMENT_REQUESTED:
case RESTORE_MODE_REENROLLMENT_ENFORCED:
ReportProgress(AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
break;
case RESTORE_MODE_REENROLLMENT_ZERO_TOUCH:
ReportProgress(AUTO_ENROLLMENT_STATE_TRIGGER_ZERO_TOUCH);
break;
}
}
void AutoEnrollmentClientImpl::SendBucketDownloadRequest() {
std::string id_hash = device_identifier_provider_->GetIdHash();
// Currently AutoEnrollmentClientImpl supports working with hashes that are at
// least 8 bytes long. If this is reduced, the computation of the remainder
// must also be adapted to handle the case of a shorter hash gracefully.
DCHECK_GE(id_hash.size(), 8u);
uint64_t remainder = 0;
const size_t last_byte_index = id_hash.size() - 1;
for (int i = 0; 8 * i < current_power_; ++i) {
uint64_t byte = id_hash[last_byte_index - i] & 0xff;
remainder = remainder | (byte << (8 * i));
}
remainder = remainder & ((UINT64_C(1) << current_power_) - 1);
ReportProgress(AUTO_ENROLLMENT_STATE_PENDING);
// Record the time when the bucket download request is started. Note that the
// time may be set multiple times. This is fine, only the last request is the
// one where the hash bucket is actually downloaded.
time_start_bucket_download_ = base::Time::Now();
VLOG(1) << "Request bucket #" << remainder;
request_job_.reset(device_management_service_->CreateJob(
DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT, url_loader_factory_));
request_job_->SetClientID(device_id_);
em::DeviceAutoEnrollmentRequest* request =
request_job_->GetRequest()->mutable_auto_enrollment_request();
request->set_remainder(remainder);
request->set_modulus(INT64_C(1) << current_power_);
request->set_enrollment_check_type(
device_identifier_provider_->GetEnrollmentCheckType());
request_job_->Start(base::BindRepeating(
&AutoEnrollmentClientImpl::HandleRequestCompletion,
base::Unretained(this),
&AutoEnrollmentClientImpl::OnBucketDownloadRequestCompletion));
}
void AutoEnrollmentClientImpl::SendDeviceStateRequest() {
ReportProgress(AUTO_ENROLLMENT_STATE_PENDING);
request_job_.reset(device_management_service_->CreateJob(
state_download_message_processor_->GetJobType(), url_loader_factory_));
request_job_->SetClientID(device_id_);
state_download_message_processor_->FillRequest(request_job_->GetRequest());
request_job_->Start(base::BindRepeating(
&AutoEnrollmentClientImpl::HandleRequestCompletion,
base::Unretained(this),
&AutoEnrollmentClientImpl::OnDeviceStateRequestCompletion));
}
void AutoEnrollmentClientImpl::HandleRequestCompletion(
RequestCompletionHandler handler,
DeviceManagementStatus status,
int net_error,
const em::DeviceManagementResponse& response) {
base::UmaHistogramSparse(kUMARequestStatus + uma_suffix_, status);
if (status != DM_STATUS_SUCCESS) {
LOG(ERROR) << "Auto enrollment error: " << status;
if (status == DM_STATUS_REQUEST_FAILED)
base::UmaHistogramSparse(kUMANetworkErrorCode + uma_suffix_, -net_error);
request_job_.reset();
// Abort if CancelAndDeleteSoon has been called meanwhile.
if (progress_callback_.is_null()) {
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
} else {
ReportProgress(status == DM_STATUS_REQUEST_FAILED
? AUTO_ENROLLMENT_STATE_CONNECTION_ERROR
: AUTO_ENROLLMENT_STATE_SERVER_ERROR);
}
return;
}
bool progress = (this->*handler)(status, net_error, response);
request_job_.reset();
if (progress)
NextStep();
else
ReportProgress(AUTO_ENROLLMENT_STATE_SERVER_ERROR);
}
bool AutoEnrollmentClientImpl::OnBucketDownloadRequestCompletion(
DeviceManagementStatus status,
int net_error,
const em::DeviceManagementResponse& response) {
bool progress = false;
const em::DeviceAutoEnrollmentResponse& enrollment_response =
response.auto_enrollment_response();
if (!response.has_auto_enrollment_response()) {
LOG(ERROR) << "Server failed to provide auto-enrollment response.";
} else if (enrollment_response.has_expected_modulus()) {
// Server is asking us to retry with a different modulus.
modulus_updates_received_++;
int64_t modulus = enrollment_response.expected_modulus();
int power = NextPowerOf2(modulus);
if ((INT64_C(1) << power) != modulus) {
LOG(ERROR) << "Auto enrollment: the server didn't ask for a power-of-2 "
<< "modulus. Using the closest power-of-2 instead "
<< "(" << modulus << " vs 2^" << power << ")";
}
if (modulus_updates_received_ >= 2) {
LOG(ERROR) << "Auto enrollment error: already retried with an updated "
<< "modulus but the server asked for a new one again: "
<< power;
} else if (power_outdated_server_detect_.has_value() &&
power >= power_outdated_server_detect_.value()) {
LOG(ERROR) << "Skipping auto enrollment: The server was detected as "
<< "outdated (power=" << power
<< ", power_outdated_server_detect="
<< power_outdated_server_detect_.value() << ").";
has_server_state_ = false;
// Cache the decision in local_state, so that it is reused in case
// the device reboots before completing OOBE. Note that this does not
// disable Forced Re-Enrollment for this device, because local state will
// be empty after the device is wiped.
local_state_->SetBoolean(prefs::kShouldAutoEnroll, false);
local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_);
local_state_->CommitPendingWrite();
return true;
} else if (power > power_limit_) {
LOG(ERROR) << "Auto enrollment error: the server asked for a larger "
<< "modulus than the client accepts (" << power << " vs "
<< power_limit_ << ").";
} else {
// Retry at most once with the modulus that the server requested.
if (power <= current_power_) {
LOG(WARNING) << "Auto enrollment: the server asked to use a modulus ("
<< power << ") that isn't larger than the first used ("
<< current_power_ << "). Retrying anyway.";
}
// Remember this value, so that eventual retries start with the correct
// modulus.
current_power_ = power;
return true;
}
} else {
// Server should have sent down a list of hashes to try.
has_server_state_ = IsIdHashInProtobuf(enrollment_response.hash());
// Cache the current decision in local_state, so that it is reused in case
// the device reboots before enrolling.
local_state_->SetBoolean(prefs::kShouldAutoEnroll, has_server_state_);
local_state_->SetInteger(prefs::kAutoEnrollmentPowerLimit, power_limit_);
local_state_->CommitPendingWrite();
VLOG(1) << "Received has_state=" << has_server_state_;
progress = true;
}
// Bucket download done, update UMA.
UpdateBucketDownloadTimingHistograms();
return progress;
}
bool AutoEnrollmentClientImpl::OnDeviceStateRequestCompletion(
DeviceManagementStatus status,
int net_error,
const em::DeviceManagementResponse& response) {
std::string restore_mode;
base::Optional<std::string> management_domain;
base::Optional<std::string> disabled_message;
bool progress = state_download_message_processor_->ParseResponse(
response, &restore_mode, &management_domain, &disabled_message);
if (!progress)
return false;
{
DictionaryPrefUpdate dict(local_state_, prefs::kServerBackedDeviceState);
UpdateDict(dict.Get(), kDeviceStateManagementDomain,
management_domain.has_value(),
std::make_unique<base::Value>(
management_domain.value_or(std::string())));
UpdateDict(dict.Get(), kDeviceStateRestoreMode, !restore_mode.empty(),
std::make_unique<base::Value>(restore_mode));
UpdateDict(dict.Get(), kDeviceStateDisabledMessage,
disabled_message.has_value(),
std::make_unique<base::Value>(
disabled_message.value_or(std::string())));
}
local_state_->CommitPendingWrite();
device_state_available_ = true;
return true;
}
bool AutoEnrollmentClientImpl::IsIdHashInProtobuf(
const google::protobuf::RepeatedPtrField<std::string>& hashes) {
std::string id_hash = device_identifier_provider_->GetIdHash();
for (int i = 0; i < hashes.size(); ++i) {
if (hashes.Get(i) == id_hash)
return true;
}
return false;
}
void AutoEnrollmentClientImpl::UpdateBucketDownloadTimingHistograms() {
// The minimum time can't be 0, must be at least 1.
static const base::TimeDelta kMin = base::TimeDelta::FromMilliseconds(1);
static const base::TimeDelta kMax = base::TimeDelta::FromMinutes(5);
// However, 0 can still be sampled.
static const base::TimeDelta kZero = base::TimeDelta::FromMilliseconds(0);
static const int kBuckets = 50;
base::Time now = base::Time::Now();
if (!time_start_.is_null()) {
base::TimeDelta delta = now - time_start_;
base::UmaHistogramCustomTimes(kUMAProtocolTime + uma_suffix_, delta, kMin,
kMax, kBuckets);
}
if (!time_start_bucket_download_.is_null()) {
base::TimeDelta delta = now - time_start_bucket_download_;
base::UmaHistogramCustomTimes(kUMABucketDownloadTime + uma_suffix_, delta,
kMin, kMax, kBuckets);
}
base::TimeDelta delta = kZero;
if (!time_extra_start_.is_null())
delta = now - time_extra_start_;
// This samples |kZero| when there was no need for extra time, so that we can
// measure the ratio of users that succeeded without needing a delay to the
// total users going through OOBE.
base::UmaHistogramCustomTimes(kUMAExtraTime + uma_suffix_, delta, kMin, kMax,
kBuckets);
}
} // namespace policy