[go: nahoru, domu]

blob: 1396c68159f8c00dee43dda9de9cfccb213da26c [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.h"
#include <stdint.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/constants/ash_switches.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task/current_thread.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/values.h"
#include "chrome/browser/ash/policy/enrollment/auto_enrollment_controller.h"
#include "chrome/browser/ash/policy/enrollment/psm/fake_rlwe_dmserver_client.h"
#include "chrome/browser/ash/policy/server_backed_state/server_backed_device_state.h"
#include "chrome/browser/browser_process.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/enterprise_metrics.h"
#include "components/policy/core/common/cloud/mock_device_management_service.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/test/browser_task_environment.h"
#include "crypto/sha2.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace em = enterprise_management;
// An enum for PSM execution result values.
using PsmExecutionResult = em::DeviceRegisterRequest::PsmExecutionResult;
// A struct reporesents the PSM execution result params.
using PsmResultHolder = policy::psm::RlweDmserverClient::ResultHolder;
namespace policy {
namespace {
const char kStateKey[] = "state_key";
const char kStateKeyHash[] =
"\xde\x74\xcd\xf0\x03\x36\x8c\x21\x79\xba\xb1\x5a\xc4\x32\xee\xd6"
"\xb3\x4a\x5e\xff\x73\x7e\x92\xd9\xf8\x6e\x72\x44\xd0\x97\xc3\xe6";
const char kDisabledMessage[] = "This device has been disabled.";
const char kSerialNumber[] = "SN123456";
const char kBrandCode[] = "AABC";
const bool kNotWithLicense = false;
const bool kWithLicense = true;
const char kNoLicenseType[] = "";
// Start and limit powers for the hash dance clients.
const int kPowerStart = 4;
const int kPowerLimit = 8;
using ::testing::_;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::SaveArg;
enum class AutoEnrollmentProtocol { kFRE = 0, kInitialEnrollment = 1 };
class AutoEnrollmentClientImplBaseTest : public testing::Test {
public:
AutoEnrollmentClientImplBaseTest(const AutoEnrollmentClientImplBaseTest&) =
delete;
AutoEnrollmentClientImplBaseTest& operator=(
const AutoEnrollmentClientImplBaseTest&) = delete;
protected:
explicit AutoEnrollmentClientImplBaseTest(AutoEnrollmentProtocol protocol)
: scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()),
local_state_(scoped_testing_local_state_.Get()),
state_(AutoEnrollmentState::kPending),
protocol_(protocol) {
CreateClient(kPowerStart, kPowerLimit);
}
~AutoEnrollmentClientImplBaseTest() override {
// Flush any deletion tasks.
base::RunLoop().RunUntilIdle();
}
void CreateClient(int power_initial, int power_limit) {
state_ = AutoEnrollmentState::kPending;
service_ =
std::make_unique<FakeDeviceManagementService>(&job_creation_handler_);
service_->ScheduleInitialization(0);
base::RunLoop().RunUntilIdle();
auto progress_callback =
base::BindRepeating(&AutoEnrollmentClientImplBaseTest::ProgressCallback,
base::Unretained(this));
shared_url_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&url_loader_factory_);
if (protocol_ == AutoEnrollmentProtocol::kFRE) {
client_ = AutoEnrollmentClientImpl::FactoryImpl().CreateForFRE(
progress_callback, service_.get(), local_state_,
shared_url_loader_factory_, kStateKey, power_initial, power_limit);
} else {
// Store a non-owned smart pointer of `psm::FakelweDmserverClient` in
// `fake_psm_rlwe_dmserver_client_ptr_`.
auto fake_psm_rlwe_dmserver_client =
std::make_unique<psm::FakeRlweDmserverClient>();
fake_psm_rlwe_dmserver_client_ptr_ = fake_psm_rlwe_dmserver_client.get();
client_ =
AutoEnrollmentClientImpl::FactoryImpl().CreateForInitialEnrollment(
progress_callback, service_.get(), local_state_,
shared_url_loader_factory_, kSerialNumber, kBrandCode,
std::move(fake_psm_rlwe_dmserver_client));
}
}
void ProgressCallback(AutoEnrollmentState state) { state_ = state; }
void ServerWillFail(int net_error, int response_code) {
EXPECT_CALL(job_creation_handler_, OnJobCreation)
.WillOnce(
DoAll(service_->CaptureJobType(&failed_job_type_),
service_->CaptureRequest(&last_request_),
service_->SendJobResponseAsync(net_error, response_code)))
.RetiresOnSaturation();
}
em::DeviceInitialEnrollmentStateResponse::InitialEnrollmentMode
MapRestoreModeToInitialEnrollmentMode(
em::DeviceStateRetrievalResponse::RestoreMode restore_mode) {
using DeviceStateRetrieval = em::DeviceStateRetrievalResponse;
using DeviceInitialEnrollmentState =
em::DeviceInitialEnrollmentStateResponse;
switch (restore_mode) {
case DeviceStateRetrieval::RESTORE_MODE_NONE:
return DeviceInitialEnrollmentState::INITIAL_ENROLLMENT_MODE_NONE;
case DeviceStateRetrieval::RESTORE_MODE_REENROLLMENT_REQUESTED:
return DeviceInitialEnrollmentState::INITIAL_ENROLLMENT_MODE_NONE;
case DeviceStateRetrieval::RESTORE_MODE_REENROLLMENT_ENFORCED:
return DeviceInitialEnrollmentState::
INITIAL_ENROLLMENT_MODE_ENROLLMENT_ENFORCED;
case DeviceStateRetrieval::RESTORE_MODE_DISABLED:
return DeviceInitialEnrollmentState::INITIAL_ENROLLMENT_MODE_DISABLED;
case DeviceStateRetrieval::RESTORE_MODE_REENROLLMENT_ZERO_TOUCH:
return DeviceInitialEnrollmentState::
INITIAL_ENROLLMENT_MODE_ZERO_TOUCH_ENFORCED;
}
}
std::string MapDeviceRestoreStateToDeviceInitialState(
const std::string& restore_state) const {
if (restore_state == kDeviceStateRestoreModeReEnrollmentEnforced)
return kDeviceStateInitialModeEnrollmentEnforced;
if (restore_state == kDeviceStateRestoreModeReEnrollmentZeroTouch)
return kDeviceStateInitialModeEnrollmentZeroTouch;
NOTREACHED();
return "";
}
void ServerWillSendState(
const std::string& management_domain,
em::DeviceStateRetrievalResponse::RestoreMode restore_mode,
const std::string& device_disabled_message,
bool is_license_packaged_with_device,
em::DeviceInitialEnrollmentStateResponse::LicensePackagingSKU
license_sku) {
if (protocol_ == AutoEnrollmentProtocol::kFRE) {
ServerWillSendStateForFRE(management_domain, restore_mode,
device_disabled_message, absl::nullopt);
} else {
ServerWillSendStateForInitialEnrollment(
management_domain, is_license_packaged_with_device, license_sku,
MapRestoreModeToInitialEnrollmentMode(restore_mode));
}
}
void ServerWillSendStateForFRE(
const std::string& management_domain,
em::DeviceStateRetrievalResponse::RestoreMode restore_mode,
const std::string& device_disabled_message,
absl::optional<em::DeviceInitialEnrollmentStateResponse>
initial_state_response) {
em::DeviceManagementResponse response;
em::DeviceStateRetrievalResponse* state_response =
response.mutable_device_state_retrieval_response();
state_response->set_restore_mode(restore_mode);
if (!management_domain.empty())
state_response->set_management_domain(management_domain);
state_response->mutable_disabled_state()->set_message(
device_disabled_message);
ASSERT_TRUE(!initial_state_response ||
restore_mode ==
em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE);
if (initial_state_response) {
state_response->mutable_initial_state_response()->MergeFrom(
*initial_state_response);
}
EXPECT_CALL(job_creation_handler_, OnJobCreation)
.WillOnce(DoAll(service_->CaptureJobType(&state_retrieval_job_type_),
service_->CaptureRequest(&last_request_),
service_->SendJobOKAsync(response)))
.RetiresOnSaturation();
}
void ServerWillSendStateForInitialEnrollment(
const std::string& management_domain,
bool is_license_packaged_with_device,
em::DeviceInitialEnrollmentStateResponse::LicensePackagingSKU license_sku,
em::DeviceInitialEnrollmentStateResponse::InitialEnrollmentMode
initial_enrollment_mode) {
em::DeviceManagementResponse response;
em::DeviceInitialEnrollmentStateResponse* state_response =
response.mutable_device_initial_enrollment_state_response();
state_response->set_initial_enrollment_mode(initial_enrollment_mode);
if (!management_domain.empty())
state_response->set_management_domain(management_domain);
state_response->set_is_license_packaged_with_device(
is_license_packaged_with_device);
state_response->set_license_packaging_sku(license_sku);
EXPECT_CALL(job_creation_handler_, OnJobCreation)
.WillOnce(DoAll(service_->CaptureJobType(&state_retrieval_job_type_),
service_->CaptureRequest(&last_request_),
service_->SendJobOKAsync(response)))
.RetiresOnSaturation();
}
void ServerWillReplyEmptyStateRetrievalResponse() {
EXPECT_CALL(job_creation_handler_, OnJobCreation)
.WillOnce(
DoAll(service_->CaptureJobType(&state_retrieval_job_type_),
service_->CaptureRequest(&last_request_),
service_->SendJobOKAsync(em::DeviceManagementResponse())))
.RetiresOnSaturation();
}
DeviceManagementService::JobConfiguration::JobType
GetExpectedStateRetrievalJobType() {
return protocol_ == AutoEnrollmentProtocol::kFRE
? DeviceManagementService::JobConfiguration::
TYPE_DEVICE_STATE_RETRIEVAL
: DeviceManagementService::JobConfiguration::
TYPE_INITIAL_ENROLLMENT_STATE_RETRIEVAL;
}
void ServerWillReplyAsync(DeviceManagementService::JobForTesting* job) {
EXPECT_CALL(job_creation_handler_, OnJobCreation)
.WillOnce(DoAll(service_->CaptureJobType(&last_async_job_type_),
SaveArg<0>(job)));
}
void ServerRepliesEmptyResponseForAsyncJob(
DeviceManagementService::JobForTesting* job) {
em::DeviceManagementResponse dummy_response;
service_->SendJobOKNow(job, dummy_response);
}
bool HasServerBackedState() {
return local_state_->GetUserPref(prefs::kServerBackedDeviceState);
}
void VerifyServerBackedState(const std::string& expected_management_domain,
const std::string& expected_restore_mode,
const std::string& expected_disabled_message,
bool expected_is_license_packaged_with_device,
const std::string& expected_license_type) {
if (protocol_ == AutoEnrollmentProtocol::kFRE) {
VerifyServerBackedStateForFRE(expected_management_domain,
expected_restore_mode,
expected_disabled_message);
} else {
VerifyServerBackedStateForInitialEnrollment(
expected_management_domain, expected_restore_mode,
expected_is_license_packaged_with_device, expected_license_type);
}
}
void VerifyServerBackedStateForAll(
const std::string& expected_management_domain,
const std::string& expected_restore_mode,
base::Value::Dict& local_state_dict) {
const base::Value* state =
local_state_->GetUserPref(prefs::kServerBackedDeviceState);
ASSERT_TRUE(state);
ASSERT_TRUE(state->is_dict());
const base::Value::Dict& state_dict = state->GetDict();
local_state_dict = state_dict.Clone();
const std::string* actual_management_domain =
state_dict.FindString(kDeviceStateManagementDomain);
if (expected_management_domain.empty()) {
EXPECT_FALSE(actual_management_domain);
} else {
EXPECT_TRUE(actual_management_domain);
EXPECT_EQ(expected_management_domain, *actual_management_domain);
}
if (!expected_restore_mode.empty())
EXPECT_TRUE(state_dict.FindString(kDeviceStateMode));
else
EXPECT_EQ(state_dict.Find(kDeviceStateMode), nullptr);
}
void VerifyServerBackedStateForFRE(
const std::string& expected_management_domain,
const std::string& expected_restore_mode,
const std::string& expected_disabled_message) {
base::Value::Dict state_dict;
VerifyServerBackedStateForAll(expected_management_domain,
expected_restore_mode, state_dict);
if (!expected_restore_mode.empty()) {
const std::string* actual_restore_mode =
state_dict.FindString(kDeviceStateMode);
EXPECT_TRUE(actual_restore_mode);
EXPECT_EQ(protocol_ == AutoEnrollmentProtocol::kFRE
? expected_restore_mode
: MapDeviceRestoreStateToDeviceInitialState(
expected_restore_mode),
*actual_restore_mode);
}
const std::string* actual_disabled_message =
state_dict.FindString(kDeviceStateDisabledMessage);
EXPECT_TRUE(actual_disabled_message);
EXPECT_EQ(expected_disabled_message, *actual_disabled_message);
EXPECT_FALSE(state_dict.FindBool(kDeviceStatePackagedLicense));
EXPECT_FALSE(state_dict.FindString(kDeviceStateLicenseType));
}
void VerifyServerBackedStateForInitialEnrollment(
const std::string& expected_management_domain,
const std::string& expected_restore_mode,
bool expected_is_license_packaged_with_device,
const std::string& expected_license_type) {
base::Value::Dict state_dict;
VerifyServerBackedStateForAll(expected_management_domain,
expected_restore_mode, state_dict);
EXPECT_FALSE(state_dict.FindString(kDeviceStateDisabledMessage));
absl::optional<bool> actual_is_license_packaged_with_device;
actual_is_license_packaged_with_device =
state_dict.FindBool(kDeviceStatePackagedLicense);
if (actual_is_license_packaged_with_device.has_value()) {
EXPECT_EQ(expected_is_license_packaged_with_device,
actual_is_license_packaged_with_device.value());
} else {
EXPECT_FALSE(expected_is_license_packaged_with_device);
}
const std::string* actual_license_type =
state_dict.FindString(kDeviceStateLicenseType);
EXPECT_TRUE(actual_license_type);
EXPECT_EQ(*actual_license_type, expected_license_type);
}
const em::DeviceAutoEnrollmentRequest& auto_enrollment_request() {
return last_request_.auto_enrollment_request();
}
// Returns |client_| as |AutoEnrollmentClientImpl*|. This is fine because this
// test only creates |client_| using |AutoEnrollmentClientImpl::FactoryImpl|.
AutoEnrollmentClientImpl* client() {
return static_cast<AutoEnrollmentClientImpl*>(client_.get());
}
// Releases |client_| and returns the pointer as |AutoEnrollmentClientImpl*|.
// This is fine because this test only creates |client_| using
// |AutoEnrollmentClientImpl::FactoryImpl|.
AutoEnrollmentClientImpl* release_client() {
return static_cast<AutoEnrollmentClientImpl*>(client_.release());
}
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
base::HistogramTester histogram_tester_;
ScopedTestingLocalState scoped_testing_local_state_;
raw_ptr<TestingPrefServiceSimple, ExperimentalAsh> local_state_;
testing::StrictMock<MockJobCreationHandler> job_creation_handler_;
std::unique_ptr<FakeDeviceManagementService> service_;
em::DeviceManagementRequest last_request_;
AutoEnrollmentState state_;
DeviceManagementService::JobConfiguration::JobType failed_job_type_ =
DeviceManagementService::JobConfiguration::TYPE_INVALID;
DeviceManagementService::JobConfiguration::JobType last_async_job_type_ =
DeviceManagementService::JobConfiguration::TYPE_INVALID;
DeviceManagementService::JobConfiguration::JobType state_retrieval_job_type_ =
DeviceManagementService::JobConfiguration::TYPE_INVALID;
// Sets the final result of PSM protocol for testing.
base::raw_ptr<psm::FakeRlweDmserverClient>
fake_psm_rlwe_dmserver_client_ptr_ = nullptr;
private:
const AutoEnrollmentProtocol protocol_;
network::TestURLLoaderFactory url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
std::unique_ptr<AutoEnrollmentClient> client_;
};
class AutoEnrollmentClientImplTest : public AutoEnrollmentClientImplBaseTest {
protected:
AutoEnrollmentClientImplTest()
: AutoEnrollmentClientImplBaseTest(AutoEnrollmentProtocol::kFRE) {}
void SetUp() override {
ASSERT_FALSE(local_state_->GetUserPref(prefs::kShouldAutoEnroll));
ASSERT_FALSE(local_state_->GetUserPref(prefs::kAutoEnrollmentPowerLimit));
AutoEnrollmentClientImplBaseTest::SetUp();
}
void ServerWillReply(int64_t modulus, bool with_hashes, bool with_id_hash) {
em::DeviceManagementResponse response =
GetAutoEnrollmentResponse(modulus, with_hashes, with_id_hash);
EXPECT_CALL(job_creation_handler_, OnJobCreation)
.WillOnce(DoAll(service_->CaptureJobType(&auto_enrollment_job_type_),
service_->CaptureRequest(&last_request_),
service_->SendJobOKAsync(response)))
.RetiresOnSaturation();
}
void ServerWillReplyEmptyAutoEnrollmentResponse() {
EXPECT_CALL(job_creation_handler_, OnJobCreation)
.WillOnce(
DoAll(service_->CaptureJobType(&auto_enrollment_job_type_),
service_->CaptureRequest(&last_request_),
service_->SendJobOKAsync(em::DeviceManagementResponse())))
.RetiresOnSaturation();
}
void ServerReplyAsyncJobWithAutoEnrollmentResponse(
int64_t modulus,
bool with_hashes,
bool with_id_hash,
DeviceManagementService::JobForTesting* job) {
em::DeviceManagementResponse response =
GetAutoEnrollmentResponse(modulus, with_hashes, with_id_hash);
service_->SendJobOKNow(job, response);
}
bool HasCachedDecision() {
return local_state_->GetUserPref(prefs::kShouldAutoEnroll);
}
void VerifyCachedResult(bool should_enroll, int power_limit) {
base::Value value_should_enroll(should_enroll);
base::Value value_power_limit(power_limit);
EXPECT_EQ(value_should_enroll,
*local_state_->GetUserPref(prefs::kShouldAutoEnroll));
EXPECT_EQ(value_power_limit,
*local_state_->GetUserPref(prefs::kAutoEnrollmentPowerLimit));
}
// Expects one sample for |kUMAHashDanceNetworkErrorCode| which has value of
// |network_error|.
void ExpectHashDanceNetworkErrorHistogram(int network_error) const {
histogram_tester_.ExpectBucketCount(
std::string(kUMAHashDanceNetworkErrorCode) + kUMASuffixFRE,
network_error, /*expected_count=*/1);
}
// Expects a sample for |kUMAHashDanceRequestStatus| with count
// |dm_status_count|.
void ExpectHashDanceRequestStatusHistogram(DeviceManagementStatus dm_status,
int dm_status_count) const {
histogram_tester_.ExpectBucketCount(
std::string(kUMAHashDanceRequestStatus) + kUMASuffixFRE, dm_status,
dm_status_count);
}
// Expects a sample for |kUMAHashDanceProtocolTime| to have value
// |expected_time_recorded|.
// if |success_time_recorded| is true it expects one sample for
// |kUMAHashDanceSuccessTime| to have value |expected_time_recorded|.
// Otherwise, expects no sample for |kUMAHashDanceSuccessTime|.
void ExpectHashDanceExecutionTimeHistogram(
base::TimeDelta expected_time_recorded,
bool success_time_recorded) const {
histogram_tester_.ExpectUniqueTimeSample(
std::string(kUMAHashDanceProtocolTime) + kUMASuffixFRE,
expected_time_recorded, /*expected_bucket_count=*/1);
histogram_tester_.ExpectUniqueTimeSample(
std::string(kUMAHashDanceSuccessTime) + kUMASuffixFRE,
expected_time_recorded, success_time_recorded ? 1 : 0);
}
void ExpectHashDanceSyncExecutionTimeHistogram(bool success_time_recorded) {
// Note: The expected time is the difference between starting off the
// client, and finishing executing the protocol successfully. In this test,
// the protocol requests are synchronized. Then the recorded time will be
// zero.
ExpectHashDanceExecutionTimeHistogram(
/*expected_time_recorded=*/base::TimeDelta(), success_time_recorded);
}
DeviceManagementService::JobConfiguration::JobType auto_enrollment_job_type_ =
DeviceManagementService::JobConfiguration::TYPE_INVALID;
private:
em::DeviceManagementResponse GetAutoEnrollmentResponse(
int64_t modulus,
bool with_hashes,
bool with_id_hash) const {
em::DeviceManagementResponse response;
em::DeviceAutoEnrollmentResponse* enrollment_response =
response.mutable_auto_enrollment_response();
if (modulus >= 0)
enrollment_response->set_expected_modulus(modulus);
if (with_hashes) {
for (int i = 0; i < 10; ++i) {
std::string state_key = base::StringPrintf("state_key %d", i);
std::string hash = crypto::SHA256HashString(state_key);
enrollment_response->mutable_hashes()->Add()->assign(hash);
}
}
if (with_id_hash) {
enrollment_response->mutable_hashes()->Add()->assign(
kStateKeyHash, crypto::kSHA256Length);
}
return response;
}
};
TEST_F(AutoEnrollmentClientImplTest, NetworkFailure) {
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_TEMPORARY_UNAVAILABLE,
/*dm_status_count=*/1);
EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
failed_job_type_);
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, EmptyReply) {
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/false,
/*with_id_hash=*/false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
VerifyCachedResult(/*should_enroll=*/false, kPowerLimit);
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, EmptyAutoEnrollmentRespose) {
ServerWillReplyEmptyAutoEnrollmentResponse();
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
// Verify Hash dance protocol overall execution time histogram has been
// recorded correctly. And its success time histogram has not been recorded.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/false);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, ClientUploadsRightBits) {
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/false,
/*with_id_hash=*/false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
EXPECT_TRUE(auto_enrollment_request().has_remainder());
EXPECT_TRUE(auto_enrollment_request().has_modulus());
EXPECT_EQ(16, auto_enrollment_request().modulus());
EXPECT_EQ(kStateKeyHash[31] & 0xf, auto_enrollment_request().remainder());
VerifyCachedResult(/*should_enroll=*/false, kPowerLimit);
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, AskForMoreThenFail) {
InSequence sequence;
ServerWillReply(/*modulus=*/32, /*with_hashes=*/false,
/*with_id_hash=*/false);
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
ExpectHashDanceRequestStatusHistogram(DM_STATUS_TEMPORARY_UNAVAILABLE,
/*dm_status_count=*/1);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(failed_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, AskForMoreThenEvenMore) {
InSequence sequence;
ServerWillReply(/*modulus=*/32, /*with_hashes=*/false,
/*with_id_hash=*/false);
ServerWillReply(/*modulus=*/64, /*with_hashes=*/false,
/*with_id_hash=*/false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
// Verify Hash dance protocol overall execution time histogram has been
// recorded correctly. And its success time histogram has not been recorded.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/false);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, AskForLess) {
InSequence sequence;
ServerWillReply(/*modulus=*/8, /*with_hashes=*/false, /*with_id_hash=*/false);
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kWithLicense,
em::DeviceInitialEnrollmentStateResponse::CHROME_EDUCATION);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/3);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedState(
"example.com", kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kWithLicense, kDeviceStateLicenseTypeEducation);
}
TEST_F(AutoEnrollmentClientImplTest, AskForSame) {
InSequence sequence;
ServerWillReply(/*modulus=*/16, /*with_hashes=*/false,
/*with_id_hash=*/false);
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/3);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
}
TEST_F(AutoEnrollmentClientImplTest, AskForSameTwice) {
InSequence sequence;
ServerWillReply(/*modulus=*/16, /*with_hashes=*/false,
/*with_id_hash=*/false);
ServerWillReply(/*modulus=*/16, /*with_hashes=*/false,
/*with_id_hash=*/false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
// Verify Hash dance protocol overall execution time histogram has been
// recorded correctly. And its success time histogram has not been recorded.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/false);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, AskForTooMuch) {
ServerWillReply(/*modulus=*/512, /*with_hashes=*/false,
/*with_id_hash=*/false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
// Verify Hash dance protocol overall execution time histogram has been
// recorded correctly. And its success time histogram has not been recorded.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/false);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, ServerRepliesWithTooLargeModulus) {
constexpr int64_t max_modulus =
(UINT64_C(1) << (AutoEnrollmentClient::kMaximumPower + 1)) - 1;
ServerWillReply(max_modulus, /*with_hashes=*/false, /*with_id_hash=*/false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
// Verify Hash dance protocol overall execution time histogram has been
// recorded correctly. And its success time histogram has not been recorded.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/false);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, AskNonPowerOf2) {
InSequence sequence;
ServerWillReply(/*modulus=*/100, /*with_hashes=*/false,
/*with_id_hash=*/false);
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/false,
/*with_id_hash=*/false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
EXPECT_TRUE(auto_enrollment_request().has_remainder());
EXPECT_TRUE(auto_enrollment_request().has_modulus());
EXPECT_EQ(128, auto_enrollment_request().modulus());
EXPECT_EQ(kStateKeyHash[31] & 0x7f, auto_enrollment_request().remainder());
VerifyCachedResult(/*should_enroll=*/false, kPowerLimit);
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, ConsumerDevice) {
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
VerifyCachedResult(/*should_enroll=*/false, kPowerLimit);
EXPECT_FALSE(HasServerBackedState());
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
}
TEST_F(AutoEnrollmentClientImplTest, ForcedReEnrollment) {
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
}
TEST_F(AutoEnrollmentClientImplTest, ForcedReEnrollmentStateRetrivalfailure) {
InSequence sequence;
const base::TimeDelta kOneSecondTimeDelta = base::Seconds(1);
DeviceManagementService::JobForTesting hash_dance_job;
DeviceManagementService::JobForTesting device_state_job;
// Expect two requests and capture them, in order, when available in
// |hash_dance_job| and |device_state_job|.
ServerWillReplyAsync(&hash_dance_job);
ServerWillReplyAsync(&device_state_job);
// Expect none of the jobs have been captured.
EXPECT_FALSE(hash_dance_job.IsActive());
EXPECT_FALSE(device_state_job.IsActive());
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify the only job that has been captured is the Hash dance request.
EXPECT_EQ(last_async_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
ASSERT_TRUE(hash_dance_job.IsActive());
EXPECT_FALSE(device_state_job.IsActive());
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
// Succeed for Hash dance request i.e. DeviceAutoEnrollmentRequest.
ServerReplyAsyncJobWithAutoEnrollmentResponse(
/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true,
&hash_dance_job);
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
// Verify Hash dance success.
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceExecutionTimeHistogram(
/*expected_time_recorded=*/base::Seconds(1),
/*success_time_recorded=*/true);
// Verify device state job has been captured.
ASSERT_TRUE(device_state_job.IsActive());
EXPECT_EQ(last_async_job_type_, GetExpectedStateRetrievalJobType());
// Fail for DeviceStateRetrievalRequest request by sending an empty response.
ServerRepliesEmptyResponseForAsyncJob(&device_state_job);
// Verify that no enrollment has been done, and no state has been retrieved.
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasServerBackedState());
// Verify all jobs have finished.
EXPECT_FALSE(hash_dance_job.IsActive());
EXPECT_FALSE(device_state_job.IsActive());
}
TEST_F(AutoEnrollmentClientImplTest, ForcedEnrollmentZeroTouch) {
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ZERO_TOUCH,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentZeroTouch,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
}
TEST_F(AutoEnrollmentClientImplTest, RequestedReEnrollment) {
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_REQUESTED,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentRequested,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
}
TEST_F(AutoEnrollmentClientImplTest, DeviceDisabled) {
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState("example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_DISABLED,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kDisabled);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedState("example.com", kDeviceStateModeDisabled,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
}
TEST_F(AutoEnrollmentClientImplTest, NoReEnrollment) {
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(std::string(),
em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
std::string(), kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedState(std::string(), std::string(), std::string(),
kNotWithLicense, kNoLicenseType);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
}
TEST_F(AutoEnrollmentClientImplTest, NoBitsUploaded) {
CreateClient(/*power_initial=*/0, /*power_limit=*/0);
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/false,
/*with_id_hash=*/false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
EXPECT_TRUE(auto_enrollment_request().has_remainder());
EXPECT_TRUE(auto_enrollment_request().has_modulus());
EXPECT_EQ(1, auto_enrollment_request().modulus());
EXPECT_EQ(0, auto_enrollment_request().remainder());
VerifyCachedResult(/*should_enroll=*/false, /*power_limit=*/0);
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, ManyBitsUploaded) {
int64_t bottom62 = INT64_C(0x386e7244d097c3e6);
for (int i = 0; i <= 62; ++i) {
CreateClient(/*power_initial=*/i, /*power_limit=*/i);
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/false,
/*with_id_hash=*/false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/i + 1);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
EXPECT_TRUE(auto_enrollment_request().has_remainder());
EXPECT_TRUE(auto_enrollment_request().has_modulus());
EXPECT_EQ(INT64_C(1) << i, auto_enrollment_request().modulus());
EXPECT_EQ(bottom62 % (INT64_C(1) << i),
auto_enrollment_request().remainder());
VerifyCachedResult(/*should_enroll=*/false, /*power_limit=*/i);
EXPECT_FALSE(HasServerBackedState());
}
}
TEST_F(AutoEnrollmentClientImplTest, MoreThan32BitsUploaded) {
CreateClient(/*power_initial=*/10, /*power_limit=*/37);
InSequence sequence;
ServerWillReply(/*modulus=*/INT64_C(1) << 37, /*with_hashes=*/false,
/*with_id_hash=*/false);
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/3);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyCachedResult(/*should_enroll=*/true, /*power_limit=*/37);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
}
TEST_F(AutoEnrollmentClientImplTest, ReuseCachedDecision) {
// No bucket download requests should be issued.
EXPECT_CALL(job_creation_handler_, OnJobCreation).Times(0);
local_state_->SetUserPref(prefs::kShouldAutoEnroll,
std::make_unique<base::Value>(true));
local_state_->SetUserPref(prefs::kAutoEnrollmentPowerLimit,
std::make_unique<base::Value>(8));
// Note that device state will be retrieved every time, regardless of any
// cached information. This is intentional, the idea is that device state on
// the server may change.
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
}
TEST_F(AutoEnrollmentClientImplTest, RetryIfPowerLargerThanCached) {
local_state_->SetUserPref(prefs::kShouldAutoEnroll,
std::make_unique<base::Value>(false));
local_state_->SetUserPref(prefs::kAutoEnrollmentPowerLimit,
std::make_unique<base::Value>(8));
CreateClient(/*power_initial=*/5, /*power_limit=*/10);
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
}
TEST_F(AutoEnrollmentClientImplTest, NetworkChangeRetryAfterErrors) {
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_TEMPORARY_UNAVAILABLE,
/*dm_status_count=*/1);
// Don't invoke the callback if there was a network failure.
EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
failed_job_type_);
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
// The client doesn't retry if no new connection became available.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_NONE);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
// Retry once the network is back.
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
EXPECT_TRUE(HasCachedDecision());
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
// Subsequent network changes don't trigger retries.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_NONE);
base::RunLoop().RunUntilIdle();
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
EXPECT_TRUE(HasCachedDecision());
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
}
TEST_F(AutoEnrollmentClientImplTest, NetworkFailureThenRequireUpdatedModulus) {
// This test verifies that if the first request fails due to a network
// problem then the second request will correctly handle an updated
// modulus request from the server.
ServerWillFail(net::ERR_FAILED, DeviceManagementService::kSuccess);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceNetworkErrorHistogram(-net::ERR_FAILED);
// Callback should signal the connection error.
EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
failed_job_type_);
EXPECT_EQ(state_, AutoEnrollmentState::kConnectionError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
Mock::VerifyAndClearExpectations(service_.get());
InSequence sequence;
// The default client uploads 4 bits. Make the server ask for 5.
ServerWillReply(/*modulus=*/1 << 5, /*with_hashes=*/false,
/*with_id_hash=*/false);
// Then reply with a valid response and include the hash.
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
// State download triggers.
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
// Trigger a network change event.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_REQUEST_FAILED,
/*dm_status_count=*/1);
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/3);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
EXPECT_TRUE(HasCachedDecision());
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
Mock::VerifyAndClearExpectations(service_.get());
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
}
TEST_F(AutoEnrollmentClientImplTest,
NetworkFailureDuringStateRetrievalRequest) {
// Set up cached server state availability response. The client will use it
// to initiate state retrieval request instead of requesting the server for
// state availability.
local_state_->SetUserPref(prefs::kShouldAutoEnroll,
std::make_unique<base::Value>(true));
local_state_->SetUserPref(prefs::kAutoEnrollmentPowerLimit,
std::make_unique<base::Value>(8));
ServerWillFail(net::ERR_FAILED, DeviceManagementService::kSuccess);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceNetworkErrorHistogram(-net::ERR_FAILED);
EXPECT_EQ(failed_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kConnectionError);
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest, RetryIsSameAsStart) {
// First, the server replies correctly to server state availability and
// server state retrieval requests.
{
// EXPECT_CALL for state availablility and state retrieval requests are
// indistinguishable for gMock as there is currently no way to check
// arguments of `MockJobCreationHandler::OnJobCreation`, and that the
// created job is correct and corresponds with the request. The InSequence
// below is still needed as it ensures that the order in which we return
// values matches the order in which they are added.
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true,
/*with_id_hash=*/true);
ServerWillSendState(std::string(),
em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
std::string(), kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
}
client()->Retry();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedState(std::string(), std::string(), std::string(),
kNotWithLicense, kNoLicenseType);
// Finally, the client does not request the server on connection change and
// uses its cached values.
EXPECT_CALL(job_creation_handler_, OnJobCreation).Times(0);
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
}
TEST_F(AutoEnrollmentClientImplTest,
RetryStateAvailabilityAfterConnectionErrorAndServerError) {
// First, the server fails with a connection error.
ServerWillFail(net::ERR_FAILED, DeviceManagementService::kSuccess);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceNetworkErrorHistogram(-net::ERR_FAILED);
EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
failed_job_type_);
EXPECT_EQ(state_, AutoEnrollmentState::kConnectionError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
// Second, the server fails with an internal error.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Retry();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_TEMPORARY_UNAVAILABLE,
/*dm_status_count=*/1);
EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
failed_job_type_);
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
// Finally, the server responds with a correct server state availability
// result.
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/false,
/*with_id_hash=*/false);
client()->Retry();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceSyncExecutionTimeHistogram(/*success_time_recorded=*/true);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
VerifyCachedResult(/*should_enroll=*/false, kPowerLimit);
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(AutoEnrollmentClientImplTest,
RetryStateRetrievalAfterConnectionErrorAndServerError) {
// Set up cached server state availability to skip the availability request.
local_state_->SetUserPref(prefs::kShouldAutoEnroll,
std::make_unique<base::Value>(true));
local_state_->SetUserPref(prefs::kAutoEnrollmentPowerLimit,
std::make_unique<base::Value>(8));
// First, the server fails with a connection error.
ServerWillFail(net::ERR_FAILED, DeviceManagementService::kSuccess);
client()->Retry();
base::RunLoop().RunUntilIdle();
ExpectHashDanceNetworkErrorHistogram(-net::ERR_FAILED);
EXPECT_EQ(failed_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kConnectionError);
EXPECT_FALSE(HasServerBackedState());
// Second, the server fails with an internal error.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Retry();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_TEMPORARY_UNAVAILABLE,
/*dm_status_count=*/1);
EXPECT_EQ(failed_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasServerBackedState());
// Third, the server responds with a correct server state.
ServerWillSendState(std::string(),
em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
std::string(), kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Retry();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedState(std::string(), std::string(), std::string(),
kNotWithLicense, kNoLicenseType);
// Finally, the client uses its cached result instead of requesting the
// server.
EXPECT_CALL(job_creation_handler_, OnJobCreation).Times(0);
client()->Retry();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedState(std::string(), std::string(), std::string(),
kNotWithLicense, kNoLicenseType);
}
using AutoEnrollmentClientImplFREToInitialEnrollmentTest =
AutoEnrollmentClientImplTest;
TEST_F(AutoEnrollmentClientImplFREToInitialEnrollmentTest,
NoReEnrollmentInitialEnrollmentLicensePackaging) {
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
em::DeviceInitialEnrollmentStateResponse initial_state_response;
initial_state_response.set_is_license_packaged_with_device(kWithLicense);
initial_state_response.set_license_packaging_sku(
em::DeviceInitialEnrollmentStateResponse::CHROME_ENTERPRISE);
ServerWillSendStateForFRE(
/*management_domain=*/std::string(),
em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
/*device_disabled_message=*/std::string(), initial_state_response);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedStateForInitialEnrollment(
std::string(), std::string(), kWithLicense,
kDeviceStateLicenseTypeEnterprise);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
}
TEST_F(AutoEnrollmentClientImplFREToInitialEnrollmentTest,
NoReEnrollmentInitialEnrollmentZeroTouch) {
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
em::DeviceInitialEnrollmentStateResponse initial_state_response;
initial_state_response.set_initial_enrollment_mode(
em::DeviceInitialEnrollmentStateResponse::
INITIAL_ENROLLMENT_MODE_ZERO_TOUCH_ENFORCED);
initial_state_response.set_management_domain("example.com");
initial_state_response.set_is_license_packaged_with_device(kWithLicense);
initial_state_response.set_license_packaging_sku(
em::DeviceInitialEnrollmentStateResponse::CHROME_ENTERPRISE);
ServerWillSendStateForFRE(
/*management_domain=*/std::string(),
em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
/*device_disabled_message=*/std::string(), initial_state_response);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedStateForInitialEnrollment(
"example.com", kDeviceStateInitialModeEnrollmentZeroTouch, kWithLicense,
kDeviceStateLicenseTypeEnterprise);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
}
TEST_F(AutoEnrollmentClientImplFREToInitialEnrollmentTest,
NoReEnrollmentInitialEnrollmentGuaranteed) {
InSequence sequence;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
em::DeviceInitialEnrollmentStateResponse initial_state_response;
initial_state_response.set_initial_enrollment_mode(
em::DeviceInitialEnrollmentStateResponse::
INITIAL_ENROLLMENT_MODE_ENROLLMENT_ENFORCED);
initial_state_response.set_management_domain("example.com");
initial_state_response.set_is_license_packaged_with_device(kWithLicense);
initial_state_response.set_license_packaging_sku(
em::DeviceInitialEnrollmentStateResponse::CHROME_ENTERPRISE);
ServerWillSendStateForFRE(
/*management_domain=*/std::string(),
em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
/*device_disabled_message=*/std::string(), initial_state_response);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/2);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyCachedResult(/*should_enroll=*/true, kPowerLimit);
VerifyServerBackedStateForInitialEnrollment(
"example.com", kDeviceStateInitialModeEnrollmentEnforced, kWithLicense,
kDeviceStateLicenseTypeEnterprise);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
}
class PsmHelperInitialEnrollmentTest : public AutoEnrollmentClientImplBaseTest {
protected:
// Indicates the state of the PSM protocol.
enum class StateDiscoveryResult {
// Failed.
kFailure = 0,
// Succeeded, the result was that the server does not have any state for the
// device.
kSuccessNoServerSideState = 1,
// Succeeded, the result was that the server does have a state for the
// device.
kSuccessHasServerSideState = 2,
};
PsmHelperInitialEnrollmentTest()
: AutoEnrollmentClientImplBaseTest(
AutoEnrollmentProtocol::kInitialEnrollment) {}
void SetUp() override {
// Verify that all PSM prefs have not been set before.
ASSERT_EQ(local_state_->GetUserPref(prefs::kShouldRetrieveDeviceState),
nullptr);
ASSERT_EQ(local_state_->GetUserPref(prefs::kEnrollmentPsmDeterminationTime),
nullptr);
ASSERT_EQ(local_state_->GetUserPref(prefs::kEnrollmentPsmResult), nullptr);
AutoEnrollmentClientImplBaseTest::SetUp();
}
void PsmWillReplyWith(psm::RlweResult psm_result,
absl::optional<bool> membership_result = absl::nullopt,
absl::optional<base::Time>
membership_determination_time = absl::nullopt) {
fake_psm_rlwe_dmserver_client_ptr_->WillReplyWith(PsmResultHolder(
psm_result, membership_result, membership_determination_time));
}
// Returns the PSM execution result that has been stored in
// prefs::kEnrollmentPsmResult. If prefs::kEnrollmentPsmResult is not set, or
// its value is invalid compared to PsmExecutionResult enum values, then it
// will return PSM_RESULT_UNKNOWN. Otherwise, it will return the coressponding
// result.
PsmExecutionResult GetPsmExecutionResult() const {
const base::Value* psm_execution_result =
local_state_->GetUserPref(prefs::kEnrollmentPsmResult);
if (!psm_execution_result ||
!em::DeviceRegisterRequest::PsmExecutionResult_IsValid(
psm_execution_result->GetInt()))
return em::DeviceRegisterRequest::PSM_RESULT_UNKNOWN;
return static_cast<PsmExecutionResult>(psm_execution_result->GetInt());
}
// Returns the PSM determination timestamp that has been stored in
// prefs::kEnrollmentPsmDeterminationTime.
// Note: The function will return a NULL object of base::Time if
// prefs::kEnrollmentPsmDeterminationTime is not set.
base::Time GetPsmDeterminationTimestamp() const {
return local_state_->GetTime(prefs::kEnrollmentPsmDeterminationTime);
}
StateDiscoveryResult GetStateDiscoveryResult() const {
const base::Value* device_state_value =
local_state_->GetUserPref(prefs::kShouldRetrieveDeviceState);
if (!device_state_value)
return StateDiscoveryResult::kFailure;
return device_state_value->GetBool()
? StateDiscoveryResult::kSuccessHasServerSideState
: StateDiscoveryResult::kSuccessNoServerSideState;
}
// Style guide requires the class to be non-copyable/non-movable by default.
PsmHelperInitialEnrollmentTest(const PsmHelperInitialEnrollmentTest&) =
delete;
PsmHelperInitialEnrollmentTest& operator=(
const PsmHelperInitialEnrollmentTest&) = delete;
};
TEST_F(PsmHelperInitialEnrollmentTest,
RetryLogicAfterNetworkFailureForRlweQueryResponse) {
PsmWillReplyWith(psm::RlweResult::kServerError);
client()->Start();
base::RunLoop().RunUntilIdle();
const StateDiscoveryResult kExpectedStateResult =
StateDiscoveryResult::kFailure;
const PsmExecutionResult kExpectedPsmExecutionResult =
em::DeviceRegisterRequest::PSM_RESULT_ERROR;
EXPECT_EQ(GetStateDiscoveryResult(), kExpectedStateResult);
EXPECT_EQ(GetPsmExecutionResult(), kExpectedPsmExecutionResult);
EXPECT_TRUE(GetPsmDeterminationTimestamp().is_null());
// Verify that PSM cached membership result hasn't changed.
client()->Retry();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), kExpectedStateResult);
EXPECT_EQ(GetPsmExecutionResult(), kExpectedPsmExecutionResult);
EXPECT_TRUE(GetPsmDeterminationTimestamp().is_null());
// Verify initial enrollment state retrieval.
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
}
TEST_F(PsmHelperInitialEnrollmentTest,
RetryLogicAfterMembershipSuccessfullyRetrieved) {
const bool kExpectedMembershipResult = false;
const base::TimeDelta kOneSecondTimeDelta = base::Seconds(1);
const base::Time kExpectedPsmDeterminationTimestamp =
base::Time::NowFromSystemTime() + kOneSecondTimeDelta;
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
PsmWillReplyWith(psm::RlweResult::kSuccessfulDetermination,
kExpectedMembershipResult,
kExpectedPsmDeterminationTimestamp);
// Fail for DeviceInitialEnrollmentStateRequest if the device has a
// server-backed state.
if (kExpectedMembershipResult)
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
const StateDiscoveryResult expected_state_result =
kExpectedMembershipResult
? StateDiscoveryResult::kSuccessHasServerSideState
: StateDiscoveryResult::kSuccessNoServerSideState;
EXPECT_EQ(GetStateDiscoveryResult(), expected_state_result);
EXPECT_EQ(
GetPsmExecutionResult(),
kExpectedMembershipResult
? em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE
: em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITHOUT_STATE);
EXPECT_EQ(kExpectedPsmDeterminationTimestamp, GetPsmDeterminationTimestamp());
// Verify that PSM cached membership result hasn't changed.
// Fail for DeviceInitialEnrollmentStateRequest with connection error, if the
// device has a server-backed state.
if (kExpectedMembershipResult)
ServerWillFail(net::ERR_FAILED, DeviceManagementService::kSuccess);
client()->Retry();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), expected_state_result);
// Verify initial enrollment state retrieval.
if (kExpectedMembershipResult) {
EXPECT_EQ(failed_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kConnectionError);
} else {
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
}
}
TEST_F(PsmHelperInitialEnrollmentTest, PsmSucceedAndStateRetrievalSucceed) {
const bool kExpectedMembershipResult = true;
const base::TimeDelta kOneSecondTimeDelta = base::Seconds(1);
const base::Time kExpectedPsmDeterminationTimestamp =
base::Time::NowFromSystemTime() + kOneSecondTimeDelta;
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
// Succeed for DeviceInitialEnrollmentStateRequest if the device has a
// server-backed state.
if (kExpectedMembershipResult) {
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kWithLicense,
em::DeviceInitialEnrollmentStateResponse::CHROME_ENTERPRISE);
}
PsmWillReplyWith(psm::RlweResult::kSuccessfulDetermination,
kExpectedMembershipResult,
kExpectedPsmDeterminationTimestamp);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify PSM result.
EXPECT_EQ(GetStateDiscoveryResult(),
kExpectedMembershipResult
? StateDiscoveryResult::kSuccessHasServerSideState
: StateDiscoveryResult::kSuccessNoServerSideState);
EXPECT_EQ(
GetPsmExecutionResult(),
kExpectedMembershipResult
? em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE
: em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITHOUT_STATE);
EXPECT_EQ(kExpectedPsmDeterminationTimestamp, GetPsmDeterminationTimestamp());
// Verify initial enrollment state retrieval.
if (kExpectedMembershipResult) {
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kEnrollment);
VerifyServerBackedState(
"example.com", kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kWithLicense, kDeviceStateLicenseTypeEnterprise);
} else {
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
}
}
TEST_F(PsmHelperInitialEnrollmentTest, PsmSucceedAndStateRetrievalFailed) {
const bool kExpectedMembershipResult = true;
const base::TimeDelta kOneSecondTimeDelta = base::Seconds(1);
const base::Time kExpectedPsmDeterminationTimestamp =
base::Time::NowFromSystemTime() + kOneSecondTimeDelta;
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
// Fail for DeviceInitialEnrollmentStateRequest if the device has a
// server-backed state.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
PsmWillReplyWith(psm::RlweResult::kSuccessfulDetermination,
kExpectedMembershipResult,
kExpectedPsmDeterminationTimestamp);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify PSM result.
EXPECT_EQ(GetStateDiscoveryResult(),
kExpectedMembershipResult
? StateDiscoveryResult::kSuccessHasServerSideState
: StateDiscoveryResult::kSuccessNoServerSideState);
EXPECT_EQ(
GetPsmExecutionResult(),
kExpectedMembershipResult
? em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE
: em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITHOUT_STATE);
EXPECT_EQ(kExpectedPsmDeterminationTimestamp, GetPsmDeterminationTimestamp());
// Verify initial enrollment state retrieval.
if (kExpectedMembershipResult) {
EXPECT_EQ(failed_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
} else {
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
}
}
TEST_F(PsmHelperInitialEnrollmentTest, PsmSucceedAndStateRetrievalIsEmpty) {
const base::TimeDelta kOneSecondTimeDelta = base::Seconds(1);
const base::Time kExpectedPsmDeterminationTimestamp =
base::Time::NowFromSystemTime() + kOneSecondTimeDelta;
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
PsmWillReplyWith(psm::RlweResult::kSuccessfulDetermination,
/*membership_result=*/true,
kExpectedPsmDeterminationTimestamp);
ServerWillReplyEmptyStateRetrievalResponse();
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify PSM result.
EXPECT_EQ(GetStateDiscoveryResult(),
StateDiscoveryResult::kSuccessHasServerSideState);
EXPECT_EQ(GetPsmExecutionResult(),
em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE);
EXPECT_EQ(kExpectedPsmDeterminationTimestamp, GetPsmDeterminationTimestamp());
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kServerError);
EXPECT_FALSE(HasServerBackedState());
}
TEST_F(PsmHelperInitialEnrollmentTest, PsmSucceedAndDeviceDisabled) {
const base::TimeDelta kOneSecondTimeDelta = base::Seconds(1);
const base::Time kExpectedPsmDeterminationTimestamp =
base::Time::NowFromSystemTime() + kOneSecondTimeDelta;
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
PsmWillReplyWith(psm::RlweResult::kSuccessfulDetermination,
/*membership_result=*/true,
kExpectedPsmDeterminationTimestamp);
ServerWillSendState("example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_DISABLED,
kDisabledMessage, kNotWithLicense,
em::DeviceInitialEnrollmentStateResponse::NOT_EXIST);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify PSM result.
EXPECT_EQ(GetStateDiscoveryResult(),
StateDiscoveryResult::kSuccessHasServerSideState);
EXPECT_EQ(GetPsmExecutionResult(),
em::DeviceRegisterRequest::PSM_RESULT_SUCCESSFUL_WITH_STATE);
EXPECT_EQ(kExpectedPsmDeterminationTimestamp, GetPsmDeterminationTimestamp());
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AutoEnrollmentState::kDisabled);
VerifyServerBackedState("example.com", kDeviceStateModeDisabled,
kDisabledMessage, kNotWithLicense, kNoLicenseType);
}
class PsmHelperInitialEnrollmentInternalErrorTest
: public PsmHelperInitialEnrollmentTest,
public testing::WithParamInterface<psm::RlweResult> {
protected:
void SetUp() override {
ASSERT_NE(GetPsmInternalErrorResult(),
psm::RlweResult::kSuccessfulDetermination);
ASSERT_NE(GetPsmInternalErrorResult(), psm::RlweResult::kConnectionError);
ASSERT_NE(GetPsmInternalErrorResult(), psm::RlweResult::kServerError);
PsmHelperInitialEnrollmentTest::SetUp();
}
psm::RlweResult GetPsmInternalErrorResult() const { return GetParam(); }
};
TEST_P(PsmHelperInitialEnrollmentInternalErrorTest, PsmFails) {
// This test verifies that after PSM client fails with an internal error, the
// client reports `AutoEnrollmentState::kNoEnrollment` and retry does not
// change the decision.
PsmWillReplyWith(GetPsmInternalErrorResult());
client()->Start();
base::RunLoop().RunUntilIdle();
const StateDiscoveryResult kExpectedStateResult =
StateDiscoveryResult::kFailure;
const PsmExecutionResult kExpectedPsmExecutionResult =
em::DeviceRegisterRequest::PSM_RESULT_ERROR;
EXPECT_EQ(GetStateDiscoveryResult(), kExpectedStateResult);
EXPECT_EQ(GetPsmExecutionResult(), kExpectedPsmExecutionResult);
EXPECT_TRUE(GetPsmDeterminationTimestamp().is_null());
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
// Verify that PSM cached membership result hasn't changed.
client()->Retry();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), kExpectedStateResult);
EXPECT_EQ(GetPsmExecutionResult(), kExpectedPsmExecutionResult);
EXPECT_TRUE(GetPsmDeterminationTimestamp().is_null());
EXPECT_EQ(state_, AutoEnrollmentState::kNoEnrollment);
}
INSTANTIATE_TEST_SUITE_P(
PsmForInitialEnrollmentInternalError,
PsmHelperInitialEnrollmentInternalErrorTest,
testing::ValuesIn({psm::RlweResult::kCreateRlweClientLibraryError,
psm::RlweResult::kCreateOprfRequestLibraryError,
psm::RlweResult::kCreateQueryRequestLibraryError,
psm::RlweResult::kProcessingQueryResponseLibraryError,
psm::RlweResult::kEmptyOprfResponseError,
psm::RlweResult::kEmptyQueryResponseError}));
} // namespace
} // namespace policy