blob: b1f05b0025c51098f8f8796be521e4bb71c9504a [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 <memory>
#include <tuple>
#include <vector>
#include "ash/constants/ash_switches.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/optional.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/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "chrome/browser/ash/login/enrollment/auto_enrollment_controller.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/policy/server_backed_device_state.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/private_membership/src/internal/testing/regression_test_data/regression_test_data.pb.h"
#include "third_party/private_membership/src/private_membership_rlwe_client.h"
#include "third_party/shell-encryption/src/testing/status_testing.h"
namespace em = enterprise_management;
namespace psm_rlwe = private_membership::rlwe;
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 char kInitialEnrollmentIdHash[] = "\x30\x18\xb7\x0f\x76\x09\xc5\xc7";
const int kInitialEnrollmentIdHashLength = 8;
const bool kNotWithLicense = false;
const bool kWithLicense = true;
// This is modulus power value used in initial enrollment to detect that the
// server is outdated and does not support initial enrollment. See the
// |DetectOutdatedServer| test case.
const int kInitialEnrollmentModulusPowerOutdatedServer = 14;
// 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;
// Number of test cases exist in cros_test_data.binarypb file, which is part of
// private_membership third_party library.
const int kNumberOfPsmTestCases = 10;
// Invalid test case index which acts as a dummy value when the PSM (private set
// membership) is disabled.
const int kInvalidPsmTestCaseIndex = -1;
// PrivateSetMembership regression tests maximum file size which is 4MB.
const size_t kMaxFileSizeInBytes = 4 * 1024 * 1024;
bool ParseProtoFromFile(const base::FilePath& file_path,
google::protobuf::MessageLite* out_proto) {
if (!out_proto) {
return false;
}
std::string file_content;
if (!base::ReadFileToStringWithMaxSize(file_path, &file_content,
kMaxFileSizeInBytes)) {
return false;
}
return out_proto->ParseFromString(file_content);
}
enum class AutoEnrollmentProtocol { kFRE = 0, kInitialEnrollment = 1 };
enum class PsmState { kEnabled = 0, kDisabled = 1 };
// Holds the state of the AutoEnrollmentClientImplTest and its subclass i.e.
// PsmHelperTest. It will be used to run their tests with different values.
struct AutoEnrollmentClientImplTestState final {
AutoEnrollmentClientImplTestState(
AutoEnrollmentProtocol auto_enrollment_protocol,
PsmState psm_state)
: auto_enrollment_protocol(auto_enrollment_protocol),
psm_state(psm_state) {}
AutoEnrollmentProtocol auto_enrollment_protocol;
PsmState psm_state;
};
// The integer parameter represents the index of PSM test case.
class AutoEnrollmentClientImplTest
: public testing::Test,
public ::testing::WithParamInterface<
std::tuple<AutoEnrollmentClientImplTestState, int>> {
protected:
AutoEnrollmentClientImplTest()
: scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()),
local_state_(scoped_testing_local_state_.Get()),
state_(AUTO_ENROLLMENT_STATE_PENDING) {}
void SetUpCommandLine(base::CommandLine* command_line) const {
// Disable PSM switch when its protocol state param is kDisabled.
if (GetPsmState() == PsmState::kDisabled) {
command_line->AppendSwitchASCII(
chromeos::switches::kEnterpriseEnablePsm,
ash::AutoEnrollmentController::kEnablePsmNever);
}
}
void SetUp() override {
SetUpCommandLine(base::CommandLine::ForCurrentProcess());
CreateClient(kPowerStart, kPowerLimit);
ASSERT_FALSE(local_state_->GetUserPref(prefs::kShouldAutoEnroll));
ASSERT_FALSE(local_state_->GetUserPref(prefs::kAutoEnrollmentPowerLimit));
}
void TearDown() override {
// Flush any deletion tasks.
base::RunLoop().RunUntilIdle();
}
AutoEnrollmentProtocol GetAutoEnrollmentProtocol() const {
return std::get<0>(GetParam()).auto_enrollment_protocol;
}
PsmState GetPsmState() const { return std::get<0>(GetParam()).psm_state; }
int GetPsmTestCaseIndex() const { return std::get<1>(GetParam()); }
std::string GetAutoEnrollmentProtocolUmaSuffix() const {
return GetAutoEnrollmentProtocol() ==
AutoEnrollmentProtocol::kInitialEnrollment
? kUMAHashDanceSuffixInitialEnrollment
: kUMAHashDanceSuffixFRE;
}
void CreateClient(int power_initial, int power_limit) {
state_ = AUTO_ENROLLMENT_STATE_PENDING;
service_ = std::make_unique<MockDeviceManagementService>();
service_->ScheduleInitialization(0);
base::RunLoop().RunUntilIdle();
auto progress_callback =
base::BindRepeating(&AutoEnrollmentClientImplTest::ProgressCallback,
base::Unretained(this));
shared_url_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&url_loader_factory_);
if (GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kFRE) {
client_ = AutoEnrollmentClientImpl::FactoryImpl().CreateForFRE(
progress_callback, service_.get(), local_state_,
shared_url_loader_factory_, kStateKey, power_initial, power_limit);
} else {
client_ =
AutoEnrollmentClientImpl::FactoryImpl().CreateForInitialEnrollment(
progress_callback, service_.get(), local_state_,
shared_url_loader_factory_, kSerialNumber, kBrandCode,
power_initial, power_limit,
kInitialEnrollmentModulusPowerOutdatedServer);
}
}
void ProgressCallback(AutoEnrollmentState state) { state_ = state; }
void ServerWillFail(int net_error, int response_code) {
em::DeviceManagementResponse dummy_response;
EXPECT_CALL(*service_, StartJob(_))
.WillOnce(DoAll(
service_->CaptureJobType(&failed_job_type_),
service_->CaptureRequest(&last_request_),
service_->StartJobAsync(net_error, response_code, dummy_response)))
.RetiresOnSaturation();
}
void ServerWillReply(int64_t modulus, bool with_hashes, bool with_id_hash) {
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_full = crypto::SHA256HashString(state_key);
std::string hash =
GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kFRE
? hash_full
: hash_full.substr(0, kInitialEnrollmentIdHashLength);
enrollment_response->mutable_hashes()->Add()->assign(hash);
}
}
if (with_id_hash) {
if (GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kFRE) {
enrollment_response->mutable_hashes()->Add()->assign(
kStateKeyHash, crypto::kSHA256Length);
} else {
enrollment_response->mutable_hashes()->Add()->assign(
kInitialEnrollmentIdHash, kInitialEnrollmentIdHashLength);
}
}
EXPECT_CALL(*service_, StartJob(_))
.WillOnce(
DoAll(service_->CaptureJobType(&auto_enrollment_job_type_),
service_->CaptureRequest(&last_request_),
service_->StartJobAsync(
net::OK, DeviceManagementService::kSuccess, response)))
.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) {
if (GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kFRE) {
ServerWillSendStateForFRE(management_domain, restore_mode,
device_disabled_message, base::nullopt);
} else {
ServerWillSendStateForInitialEnrollment(
management_domain, is_license_packaged_with_device,
MapRestoreModeToInitialEnrollmentMode(restore_mode));
}
}
DeviceManagementService::JobConfiguration::JobType
GetStateRetrievalJobType() {
return GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kFRE
? DeviceManagementService::JobConfiguration::
TYPE_DEVICE_STATE_RETRIEVAL
: DeviceManagementService::JobConfiguration::
TYPE_INITIAL_ENROLLMENT_STATE_RETRIEVAL;
}
void ServerWillSendStateForFRE(
const std::string& management_domain,
em::DeviceStateRetrievalResponse::RestoreMode restore_mode,
const std::string& device_disabled_message,
base::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(*service_, StartJob(_))
.WillOnce(
DoAll(service_->CaptureJobType(&state_retrieval_job_type_),
service_->CaptureRequest(&last_request_),
service_->StartJobAsync(
net::OK, DeviceManagementService::kSuccess, response)))
.RetiresOnSaturation();
}
void ServerWillSendStateForInitialEnrollment(
const std::string& management_domain,
bool is_license_packaged_with_device,
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);
EXPECT_CALL(*service_, StartJob(_))
.WillOnce(
DoAll(service_->CaptureJobType(&state_retrieval_job_type_),
service_->CaptureRequest(&last_request_),
service_->StartJobAsync(
net::OK, DeviceManagementService::kSuccess, response)))
.RetiresOnSaturation();
}
DeviceManagementService::JobConfiguration::JobType
GetExpectedStateRetrievalJobType() {
return GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kFRE
? DeviceManagementService::JobConfiguration::
TYPE_DEVICE_STATE_RETRIEVAL
: DeviceManagementService::JobConfiguration::
TYPE_INITIAL_ENROLLMENT_STATE_RETRIEVAL;
}
void ServerWillReplyAsync(DeviceManagementService::JobControl** job) {
EXPECT_CALL(*service_, StartJob(_))
.WillOnce(DoAll(service_->CaptureJobType(&last_async_job_type_),
service_->StartJobFullControl(job)));
}
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));
}
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) {
if (GetAutoEnrollmentProtocol() == 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);
}
}
void VerifyServerBackedStateForAll(
const std::string& expected_management_domain,
const std::string& expected_restore_mode,
const base::DictionaryValue** local_state_dict) {
const base::Value* state =
local_state_->GetUserPref(prefs::kServerBackedDeviceState);
ASSERT_TRUE(state);
const base::DictionaryValue* state_dict = nullptr;
ASSERT_TRUE(state->GetAsDictionary(&state_dict));
*local_state_dict = state_dict;
std::string actual_management_domain;
if (expected_management_domain.empty()) {
EXPECT_FALSE(state_dict->GetString(kDeviceStateManagementDomain,
&actual_management_domain));
} else {
EXPECT_TRUE(state_dict->GetString(kDeviceStateManagementDomain,
&actual_management_domain));
EXPECT_EQ(expected_management_domain, actual_management_domain);
}
if (!expected_restore_mode.empty()) {
std::string actual_restore_mode;
EXPECT_TRUE(
state_dict->GetString(kDeviceStateMode, &actual_restore_mode));
} else {
EXPECT_FALSE(state_dict->HasKey(kDeviceStateMode));
}
}
void VerifyServerBackedStateForFRE(
const std::string& expected_management_domain,
const std::string& expected_restore_mode,
const std::string& expected_disabled_message) {
const base::DictionaryValue* state_dict;
VerifyServerBackedStateForAll(expected_management_domain,
expected_restore_mode, &state_dict);
if (!expected_restore_mode.empty()) {
std::string actual_restore_mode;
EXPECT_TRUE(
state_dict->GetString(kDeviceStateMode, &actual_restore_mode));
EXPECT_EQ(GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kFRE
? expected_restore_mode
: MapDeviceRestoreStateToDeviceInitialState(
expected_restore_mode),
actual_restore_mode);
}
std::string actual_disabled_message;
EXPECT_TRUE(state_dict->GetString(kDeviceStateDisabledMessage,
&actual_disabled_message));
EXPECT_EQ(expected_disabled_message, actual_disabled_message);
EXPECT_FALSE(state_dict->FindBoolPath(kDeviceStatePackagedLicense));
}
void VerifyServerBackedStateForInitialEnrollment(
const std::string& expected_management_domain,
const std::string& expected_restore_mode,
bool expected_is_license_packaged_with_device) {
const base::DictionaryValue* state_dict;
VerifyServerBackedStateForAll(expected_management_domain,
expected_restore_mode, &state_dict);
std::string actual_disabled_message;
EXPECT_FALSE(state_dict->GetString(kDeviceStateDisabledMessage,
&actual_disabled_message));
base::Optional<bool> actual_is_license_packaged_with_device;
actual_is_license_packaged_with_device =
state_dict->FindBoolPath(kDeviceStatePackagedLicense);
if (actual_is_license_packaged_with_device) {
EXPECT_EQ(expected_is_license_packaged_with_device,
actual_is_license_packaged_with_device.value());
} else {
EXPECT_FALSE(expected_is_license_packaged_with_device);
}
}
// Expects one sample for |kUMAHashDanceNetworkErrorCode| which has value of
// |network_error|.
void ExpectHashDanceNetworkErrorHistogram(int network_error) const {
histogram_tester_.ExpectBucketCount(
kUMAHashDanceNetworkErrorCode + GetAutoEnrollmentProtocolUmaSuffix(),
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(
kUMAHashDanceRequestStatus + GetAutoEnrollmentProtocolUmaSuffix(),
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(
kUMAHashDanceProtocolTimeStr + kUMAHashDanceSuffixInitialEnrollment,
expected_time_recorded, /*expected_count=*/1);
histogram_tester_.ExpectUniqueTimeSample(
kUMAHashDanceSuccessTimeStr + kUMAHashDanceSuffixInitialEnrollment,
expected_time_recorded, success_time_recorded ? 1 : 0);
}
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());
}
base::HistogramTester histogram_tester_;
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
ScopedTestingLocalState scoped_testing_local_state_;
TestingPrefServiceSimple* local_state_;
std::unique_ptr<MockDeviceManagementService> 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 auto_enrollment_job_type_ =
DeviceManagementService::JobConfiguration::TYPE_INVALID;
DeviceManagementService::JobConfiguration::JobType state_retrieval_job_type_ =
DeviceManagementService::JobConfiguration::TYPE_INVALID;
private:
const std::string kUMAHashDanceProtocolTimeStr = kUMAHashDanceProtocolTime;
const std::string kUMAHashDanceSuccessTimeStr = kUMAHashDanceSuccessTime;
network::TestURLLoaderFactory url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
std::unique_ptr<AutoEnrollmentClient> client_;
DISALLOW_COPY_AND_ASSIGN(AutoEnrollmentClientImplTest);
};
TEST_P(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_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(AutoEnrollmentClientImplTest, EmptyReply) {
ServerWillReply(-1, false, false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
VerifyCachedResult(false, kPowerLimit);
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(AutoEnrollmentClientImplTest, ClientUploadsRightBits) {
ServerWillReply(-1, false, false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
EXPECT_TRUE(auto_enrollment_request().has_remainder());
EXPECT_TRUE(auto_enrollment_request().has_modulus());
EXPECT_EQ(16, auto_enrollment_request().modulus());
if (GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kFRE) {
EXPECT_EQ(kStateKeyHash[31] & 0xf, auto_enrollment_request().remainder());
} else {
EXPECT_EQ(kInitialEnrollmentIdHash[7] & 0xf,
auto_enrollment_request().remainder());
}
VerifyCachedResult(false, kPowerLimit);
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(AutoEnrollmentClientImplTest, AskForMoreThenFail) {
InSequence sequence;
ServerWillReply(32, false, 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_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(AutoEnrollmentClientImplTest, AskForMoreThenEvenMore) {
InSequence sequence;
ServerWillReply(32, false, false);
ServerWillReply(64, false, false);
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_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(AutoEnrollmentClientImplTest, AskForLess) {
InSequence sequence;
ServerWillReply(8, false, false);
ServerWillReply(-1, true, true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kWithLicense);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/3);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyCachedResult(true, kPowerLimit);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kWithLicense);
}
TEST_P(AutoEnrollmentClientImplTest, AskForSame) {
InSequence sequence;
ServerWillReply(16, false, false);
ServerWillReply(-1, true, true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/3);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyCachedResult(true, kPowerLimit);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense);
}
TEST_P(AutoEnrollmentClientImplTest, AskForSameTwice) {
InSequence sequence;
ServerWillReply(16, false, false);
ServerWillReply(16, false, false);
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_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(AutoEnrollmentClientImplTest, AskForTooMuch) {
ServerWillReply(512, false, false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(AutoEnrollmentClientImplTest, DetectOutdatedServer) {
CreateClient(0, kInitialEnrollmentModulusPowerOutdatedServer + 1);
InSequence sequence;
ServerWillReply(1 << kInitialEnrollmentModulusPowerOutdatedServer, false,
false);
if (GetAutoEnrollmentProtocol() ==
AutoEnrollmentProtocol::kInitialEnrollment) {
// For initial enrollment, a modulus power higher or equal to
// |kInitialEnrollmentModulusPowerOutdatedServer| means that the client will
// detect the server as outdated and will skip enrollment.
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
EXPECT_TRUE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
} else {
// For FRE, such a detection does not exist. The client will do the second
// round and upload bits of its device identifier hash.
ServerWillReply(-1, false, false);
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_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
EXPECT_TRUE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
}
}
TEST_P(AutoEnrollmentClientImplTest, AskNonPowerOf2) {
InSequence sequence;
ServerWillReply(100, false, false);
ServerWillReply(-1, false, false);
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_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
EXPECT_TRUE(auto_enrollment_request().has_remainder());
EXPECT_TRUE(auto_enrollment_request().has_modulus());
EXPECT_EQ(128, auto_enrollment_request().modulus());
if (GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kFRE) {
EXPECT_EQ(kStateKeyHash[31] & 0x7f, auto_enrollment_request().remainder());
} else {
EXPECT_EQ(kInitialEnrollmentIdHash[7] & 0x7f,
auto_enrollment_request().remainder());
}
VerifyCachedResult(false, kPowerLimit);
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(AutoEnrollmentClientImplTest, ConsumerDevice) {
ServerWillReply(-1, true, false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
VerifyCachedResult(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_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
}
TEST_P(AutoEnrollmentClientImplTest, ForcedReEnrollment) {
InSequence sequence;
ServerWillReply(-1, true, true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense);
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_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyCachedResult(true, kPowerLimit);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
}
TEST_P(AutoEnrollmentClientImplTest, ForcedEnrollmentZeroTouch) {
InSequence sequence;
ServerWillReply(-1, true, true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ZERO_TOUCH,
kDisabledMessage, kNotWithLicense);
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_, AUTO_ENROLLMENT_STATE_TRIGGER_ZERO_TOUCH);
VerifyCachedResult(true, kPowerLimit);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentZeroTouch,
kDisabledMessage, kNotWithLicense);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ZERO_TOUCH);
}
TEST_P(AutoEnrollmentClientImplTest, RequestedReEnrollment) {
// Requesting re-enrollment is currently not supported in the
// initial-enrollment exchange.
if (GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kInitialEnrollment)
return;
InSequence sequence;
ServerWillReply(-1, true, true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_REQUESTED,
kDisabledMessage, kNotWithLicense);
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_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyCachedResult(true, kPowerLimit);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentRequested,
kDisabledMessage, kNotWithLicense);
}
TEST_P(AutoEnrollmentClientImplTest, DeviceDisabled) {
InSequence sequence;
ServerWillReply(-1, true, true);
ServerWillSendState("example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_DISABLED,
kDisabledMessage, kNotWithLicense);
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_, AUTO_ENROLLMENT_STATE_DISABLED);
VerifyCachedResult(true, kPowerLimit);
VerifyServerBackedState("example.com", kDeviceStateModeDisabled,
kDisabledMessage, kNotWithLicense);
}
TEST_P(AutoEnrollmentClientImplTest, NoReEnrollment) {
InSequence sequence;
ServerWillReply(-1, true, true);
ServerWillSendState(std::string(),
em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
std::string(), kNotWithLicense);
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_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
VerifyCachedResult(true, kPowerLimit);
VerifyServerBackedState(std::string(), std::string(), std::string(),
kNotWithLicense);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
}
TEST_P(AutoEnrollmentClientImplTest, NoBitsUploaded) {
CreateClient(0, 0);
ServerWillReply(-1, false, false);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
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(false, 0);
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(AutoEnrollmentClientImplTest, ManyBitsUploaded) {
int64_t bottom62 = GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kFRE
? INT64_C(0x386e7244d097c3e6)
: INT64_C(0x3018b70f7609c5c7);
for (int i = 0; i <= 62; ++i) {
CreateClient(i, i);
ServerWillReply(-1, false, 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_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
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(false, i);
EXPECT_FALSE(HasServerBackedState());
}
}
TEST_P(AutoEnrollmentClientImplTest, MoreThan32BitsUploaded) {
// Skip for initial enrollment, because the outdated server detection would
// kick in when more than |kInitialEnrollmentModulusPowerOutdatedServer| bits
// are requested.
if (GetAutoEnrollmentProtocol() == AutoEnrollmentProtocol::kInitialEnrollment)
return;
CreateClient(10, 37);
InSequence sequence;
ServerWillReply(INT64_C(1) << 37, false, false);
ServerWillReply(-1, true, true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/3);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyCachedResult(true, 37);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense);
}
TEST_P(AutoEnrollmentClientImplTest, ReuseCachedDecision) {
// No bucket download requests should be issued.
EXPECT_CALL(*service_, StartJob(_)).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);
client()->Start();
base::RunLoop().RunUntilIdle();
ExpectHashDanceRequestStatusHistogram(DM_STATUS_SUCCESS,
/*dm_status_count=*/1);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense);
}
TEST_P(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(5, 10);
InSequence sequence;
ServerWillReply(-1, true, true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense);
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_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense);
}
TEST_P(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_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
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_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
EXPECT_FALSE(HasCachedDecision());
EXPECT_FALSE(HasServerBackedState());
// Retry once the network is back.
InSequence sequence;
ServerWillReply(-1, true, true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense);
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_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
EXPECT_TRUE(HasCachedDecision());
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense);
// 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_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
EXPECT_TRUE(HasCachedDecision());
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense);
}
TEST_P(AutoEnrollmentClientImplTest, CancelAndDeleteSoonWithPendingRequest) {
DeviceManagementService::JobControl* job = nullptr;
ServerWillReplyAsync(&job);
EXPECT_FALSE(job);
client()->Start();
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(job);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_PENDING);
// Cancel while a request is in flight.
EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting());
release_client()->CancelAndDeleteSoon();
EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting());
// The client cleans itself up once a reply is received.
service_->DoURLCompletion(&job, net::OK,
DeviceManagementService::kServiceUnavailable,
em::DeviceManagementResponse());
EXPECT_EQ(nullptr, job);
// The DeleteSoon task has been posted:
EXPECT_FALSE(base::CurrentThread::Get()->IsIdleForTesting());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_PENDING);
}
TEST_P(AutoEnrollmentClientImplTest, NetworkChangedAfterCancelAndDeleteSoon) {
DeviceManagementService::JobControl* job = nullptr;
ServerWillReplyAsync(&job);
EXPECT_FALSE(job);
client()->Start();
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(job);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_PENDING);
// Cancel while a request is in flight.
EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting());
AutoEnrollmentClientImpl* client = release_client();
client->CancelAndDeleteSoon();
EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting());
// Network change events are ignored while a request is pending.
client->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_PENDING);
// The client cleans itself up once a reply is received.
service_->DoURLCompletion(&job, net::OK,
DeviceManagementService::kServiceUnavailable,
em::DeviceManagementResponse());
EXPECT_EQ(nullptr, job);
// The DeleteSoon task has been posted:
EXPECT_FALSE(base::CurrentThread::Get()->IsIdleForTesting());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_PENDING);
// Network changes that have been posted before are also ignored:
client->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_PENDING);
}
TEST_P(AutoEnrollmentClientImplTest, CancelAndDeleteSoonAfterCompletion) {
InSequence sequence;
ServerWillReply(-1, true, true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense);
client()->Start();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense);
// The client will delete itself immediately if there are no pending
// requests.
EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting());
release_client()->CancelAndDeleteSoon();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting());
}
TEST_P(AutoEnrollmentClientImplTest, CancelAndDeleteSoonAfterNetworkFailure) {
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_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
// The client will delete itself immediately if there are no pending
// requests.
EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting());
release_client()->CancelAndDeleteSoon();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(base::CurrentThread::Get()->IsIdleForTesting());
}
TEST_P(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_, AUTO_ENROLLMENT_STATE_CONNECTION_ERROR);
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(1 << 5, false, false);
// Then reply with a valid response and include the hash.
ServerWillReply(-1, true, true);
// State download triggers.
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kNotWithLicense);
// 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);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
EXPECT_TRUE(HasCachedDecision());
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kNotWithLicense);
Mock::VerifyAndClearExpectations(service_.get());
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
}
// PSM is disabed to test only FRE case extensively instead. That is necessary
// as both protocols are running in sequential order starting off with PSM.
INSTANTIATE_TEST_SUITE_P(
FRE,
AutoEnrollmentClientImplTest,
testing::Combine(testing::Values(AutoEnrollmentClientImplTestState(
AutoEnrollmentProtocol::kFRE,
PsmState::kDisabled)),
testing::Values(kInvalidPsmTestCaseIndex)));
// PSM is disabed to test only initial enrollment case extensively instead. That
// is necessary as both protocols are running in sequential order starting off
// with PSM.
INSTANTIATE_TEST_SUITE_P(
InitialEnrollment,
AutoEnrollmentClientImplTest,
testing::Combine(testing::Values(AutoEnrollmentClientImplTestState(
AutoEnrollmentProtocol::kInitialEnrollment,
PsmState::kDisabled)),
testing::Values(kInvalidPsmTestCaseIndex)));
using AutoEnrollmentClientImplFREToInitialEnrollmentTest =
AutoEnrollmentClientImplTest;
TEST_P(AutoEnrollmentClientImplFREToInitialEnrollmentTest,
NoReEnrollmentInitialEnrollmentLicensePackaging) {
InSequence sequence;
ServerWillReply(-1, true, true);
em::DeviceInitialEnrollmentStateResponse initial_state_response;
initial_state_response.set_is_license_packaged_with_device(kWithLicense);
ServerWillSendStateForFRE(
std::string(), em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
std::string(),
base::Optional<em::DeviceInitialEnrollmentStateResponse>(
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_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
VerifyCachedResult(true, kPowerLimit);
VerifyServerBackedStateForInitialEnrollment(std::string(), std::string(),
kWithLicense);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
}
TEST_P(AutoEnrollmentClientImplFREToInitialEnrollmentTest,
NoReEnrollmentInitialEnrollmentZeroTouch) {
InSequence sequence;
ServerWillReply(-1, true, 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");
ServerWillSendStateForFRE(
std::string(), em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
std::string(),
base::Optional<em::DeviceInitialEnrollmentStateResponse>(
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_, AUTO_ENROLLMENT_STATE_TRIGGER_ZERO_TOUCH);
VerifyCachedResult(true, kPowerLimit);
VerifyServerBackedStateForInitialEnrollment(
"example.com", kDeviceStateInitialModeEnrollmentZeroTouch,
kNotWithLicense);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ZERO_TOUCH);
}
TEST_P(AutoEnrollmentClientImplFREToInitialEnrollmentTest,
NoReEnrollmentInitialEnrollmentGuaranteed) {
InSequence sequence;
ServerWillReply(-1, true, 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");
ServerWillSendStateForFRE(
std::string(), em::DeviceStateRetrievalResponse::RESTORE_MODE_NONE,
std::string(),
base::Optional<em::DeviceInitialEnrollmentStateResponse>(
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_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyCachedResult(true, kPowerLimit);
VerifyServerBackedStateForInitialEnrollment(
"example.com", kDeviceStateInitialModeEnrollmentEnforced,
kNotWithLicense);
// Network changes don't trigger retries after obtaining a response from
// the server.
client()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
}
// PSM is disabed to test only switching from FRE to initial enrollment case
// extensively instead. That is necessary as both protocols are running in
// sequential order starting off with PSM.
INSTANTIATE_TEST_SUITE_P(
FREToInitialEnrollment,
AutoEnrollmentClientImplFREToInitialEnrollmentTest,
testing::Combine(testing::Values(AutoEnrollmentClientImplTestState(
AutoEnrollmentProtocol::kFRE,
PsmState::kDisabled)),
testing::Values(kInvalidPsmTestCaseIndex)));
// This class is used to test any PSM related test cases only. Therefore, the
// PsmState param has to be kEnabled.
class PsmHelperTest : public AutoEnrollmentClientImplTest {
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,
};
PsmHelperTest() {}
~PsmHelperTest() {
// Flush any deletion tasks.
base::RunLoop().RunUntilIdle();
}
void SetUp() override {
// Verify that PsmState has value kEnabled, then enable PSM switch
// prefs::kEnterpriseEnablePsm.
ASSERT_EQ(GetPsmState(), PsmState::kEnabled);
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
chromeos::switches::kEnterpriseEnablePsm,
ash::AutoEnrollmentController::kEnablePsmAlways);
// Verify that PSM state pref has not been set before.
ASSERT_EQ(local_state_->GetUserPref(prefs::kShouldRetrieveDeviceState),
nullptr);
// Set up the base class AutoEnrollmentClientImplTest after the private set
// membership has been enabled.
AutoEnrollmentClientImplTest::SetUp();
// Create PSM test case, and its corresponding RLWE client.
CreatePsmTestCase();
SetPsmRlweClient();
}
void CreatePsmTestCase() {
// Verify that PSM is enabled, and the test case index is valid.
EXPECT_TRUE(ash::AutoEnrollmentController::IsPsmEnabled());
ASSERT_GE(GetPsmTestCaseIndex(), 0);
// Retrieve the PSM test case.
base::FilePath src_root_dir;
EXPECT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_root_dir));
const base::FilePath kPsmTestDataPath =
src_root_dir.AppendASCII("third_party")
.AppendASCII("private_membership")
.AppendASCII("src")
.AppendASCII("internal")
.AppendASCII("testing")
.AppendASCII("regression_test_data")
.AppendASCII("cros_test_data.binarypb");
EXPECT_TRUE(base::PathExists(kPsmTestDataPath));
psm_rlwe::PrivateMembershipRlweClientRegressionTestData test_data;
EXPECT_TRUE(ParseProtoFromFile(kPsmTestDataPath, &test_data));
EXPECT_EQ(test_data.test_cases_size(), kNumberOfPsmTestCases);
psm_test_case_ = test_data.test_cases(GetPsmTestCaseIndex());
}
void SetPsmRlweClient() {
auto rlwe_client_or_status =
psm_rlwe::PrivateMembershipRlweClient::CreateForTesting(
psm_test_case_.use_case(), {psm_test_case_.plaintext_id()},
psm_test_case_.ec_cipher_key(), psm_test_case_.seed());
ASSERT_OK(rlwe_client_or_status.status());
auto rlwe_client = std::move(rlwe_client_or_status.value());
client()->SetPsmRlweClientForTesting(std::move(rlwe_client),
psm_test_case_.plaintext_id());
}
void ServerWillReplyWithPsmOprfResponse() {
em::DeviceManagementResponse response = GetPsmOprfResponse();
ServerWillReplyForPsm(net::OK, DeviceManagementService::kSuccess, response);
}
void ServerWillReplyWithPsmQueryResponse() {
em::DeviceManagementResponse response = GetPsmQueryResponse();
ServerWillReplyForPsm(net::OK, DeviceManagementService::kSuccess, response);
}
void ServerWillReplyWithEmptyPsmResponse() {
em::DeviceManagementResponse dummy_response;
ServerWillReplyForPsm(net::OK, DeviceManagementService::kSuccess,
dummy_response);
}
void ServerWillFailForPsm(int net_error, int response_code) {
em::DeviceManagementResponse dummy_response;
ServerWillReplyForPsm(net_error, response_code, dummy_response);
}
// Mocks the server reply and captures the job type in |psm_last_job_type_|,
// and the request in |psm_last_request_|.
void ServerWillReplyForPsm(int net_error,
int response_code,
const em::DeviceManagementResponse& response) {
EXPECT_CALL(*service_, StartJob(_))
.WillOnce(
DoAll(service_->CaptureJobType(&psm_last_job_type_),
service_->CaptureRequest(&psm_last_request_),
service_->StartJobAsync(net_error, response_code, response)))
.RetiresOnSaturation();
}
// Holds the full control of the given job in |job| and captures the job type
// in |psm_last_job_type_|, and its request in |psm_last_request_|.
void ServerWillReplyAsyncForPsm(DeviceManagementService::JobControl** job) {
EXPECT_CALL(*service_, StartJob(_))
.WillOnce(DoAll(service_->CaptureJobType(&psm_last_job_type_),
service_->CaptureRequest(&psm_last_request_),
service_->StartJobFullControl(job)));
}
void ServerReplyForPsmAsyncJobWithOprfResponse(
DeviceManagementService::JobControl** job) {
em::DeviceManagementResponse response = GetPsmOprfResponse();
ServerReplyForAsyncJob(job, net::OK, DeviceManagementService::kSuccess,
response);
}
void ServerReplyForPsmAsyncJobWithQueryResponse(
DeviceManagementService::JobControl** job) {
em::DeviceManagementResponse response = GetPsmQueryResponse();
ServerReplyForAsyncJob(job, net::OK, DeviceManagementService::kSuccess,
response);
}
void ServerFailsForAsyncJob(DeviceManagementService::JobControl** job) {
em::DeviceManagementResponse dummy_response;
ServerReplyForAsyncJob(job, net::OK,
DeviceManagementService::kServiceUnavailable,
dummy_response);
}
void ServerRepliesEmptyResponseForAsyncJob(
DeviceManagementService::JobControl** job) {
em::DeviceManagementResponse dummy_response;
ServerReplyForAsyncJob(job, net::OK, DeviceManagementService::kSuccess,
dummy_response);
}
// Mocks the server reply for the full controlled job |job|.
void ServerReplyForAsyncJob(
DeviceManagementService::JobControl** job,
int net_error,
int response_code,
const enterprise_management::DeviceManagementResponse& response) {
service_->DoURLCompletion(job, net_error, response_code, response);
}
const em::PrivateSetMembershipRequest& psm_request() const {
return psm_last_request_.private_set_membership_request();
}
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;
}
// Returns the expected membership result for the current private set
// membership test case.
bool GetExpectedMembershipResult() const {
return psm_test_case_.is_positive_membership_expected();
}
// Expects list of samples |status_list| for kUMAPsmRequestStatus and each one
// of them to be recorded once.
// If |success_time_recorded| is true it expects one sample for
// kUMAPsmSuccessTime. Otherwise, expects no sample to be recorded for
// kUMAPsmSuccessTime.
void ExpectPsmHistograms(const std::vector<PsmStatus> status_list,
bool success_time_recorded) const {
for (PsmStatus status : status_list) {
histogram_tester_.ExpectBucketCount(kUMAPsmRequestStatus, status,
/*expected_count=*/1);
}
histogram_tester_.ExpectTotalCount(kUMAPsmSuccessTime,
success_time_recorded ? 1 : 0);
}
// Expects a sample for kUMAPsmHashDanceComparison to be recorded once with
// value |comparison|.
void ExpectPsmHashDanceComparisonRecorded(
PsmHashDanceComparison comparison) const {
histogram_tester_.ExpectUniqueSample(kUMAPsmHashDanceComparison, comparison,
/*expected_count=*/1);
}
// Expects a sample for kUMAPsmHashDanceDifferentResultsComparison to be
// recorded once with value |different_results_comparison|.
void ExpectPsmHashDanceDifferentResultsComparisonRecorded(
PsmHashDanceDifferentResultsComparison different_results_comparison)
const {
histogram_tester_.ExpectUniqueSample(
kUMAPsmHashDanceDifferentResultsComparison,
different_results_comparison,
/*expected_count=*/1);
}
void VerifyPsmLastRequestJobType() const {
EXPECT_EQ(DeviceManagementService::JobConfiguration::
TYPE_PSM_HAS_DEVICE_STATE_REQUEST,
psm_last_job_type_);
}
void VerifyPsmRlweOprfRequest() const {
EXPECT_EQ(psm_test_case_.expected_oprf_request().SerializeAsString(),
psm_request().rlwe_request().oprf_request().SerializeAsString());
}
void VerifyPsmRlweQueryRequest() const {
EXPECT_EQ(psm_test_case_.expected_query_request().SerializeAsString(),
psm_request().rlwe_request().query_request().SerializeAsString());
}
// Disallow copy constructor and assignment operator.
PsmHelperTest(const PsmHelperTest&) = delete;
PsmHelperTest& operator=(const PsmHelperTest&) = delete;
private:
em::DeviceManagementResponse GetPsmOprfResponse() const {
em::DeviceManagementResponse response;
em::PrivateSetMembershipResponse* psm_response =
response.mutable_private_set_membership_response();
*psm_response->mutable_rlwe_response()->mutable_oprf_response() =
psm_test_case_.oprf_response();
return response;
}
em::DeviceManagementResponse GetPsmQueryResponse() const {
em::DeviceManagementResponse response;
em::PrivateSetMembershipResponse* psm_response =
response.mutable_private_set_membership_response();
*psm_response->mutable_rlwe_response()->mutable_query_response() =
psm_test_case_.query_response();
return response;
}
psm_rlwe::PrivateMembershipRlweClientRegressionTestData::TestCase
psm_test_case_;
DeviceManagementService::JobConfiguration::JobType psm_last_job_type_ =
DeviceManagementService::JobConfiguration::TYPE_INVALID;
em::DeviceManagementRequest psm_last_request_;
};
TEST_P(PsmHelperTest, MembershipRetrievedSuccessfully) {
InSequence sequence;
ServerWillReplyWithPsmOprfResponse();
ServerWillReplyWithPsmQueryResponse();
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
// TODO(crbug.com/1143634) Remove all usages of RunUntilIdle for all PSM
// tests, after removing support of Hash dance from client side.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(),
GetExpectedMembershipResult()
? StateDiscoveryResult::kSuccessHasServerSideState
: StateDiscoveryResult::kSuccessNoServerSideState);
ExpectPsmHistograms(
{PsmStatus::kAttempt, PsmStatus::kSuccessfulDetermination},
/*success_time_recorded=*/true);
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
}
TEST_P(PsmHelperTest, EmptyRlweQueryResponse) {
InSequence sequence;
ServerWillReplyWithPsmOprfResponse();
ServerWillReplyWithEmptyPsmResponse();
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), StateDiscoveryResult::kFailure);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
}
TEST_P(PsmHelperTest, EmptyRlweOprfResponse) {
InSequence sequence;
ServerWillReplyWithEmptyPsmResponse();
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), StateDiscoveryResult::kFailure);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmRlweOprfRequest();
VerifyPsmLastRequestJobType();
}
TEST_P(PsmHelperTest, ConnectionErrorForRlweQueryResponse) {
InSequence sequence;
ServerWillReplyWithPsmOprfResponse();
ServerWillFailForPsm(net::ERR_FAILED, DeviceManagementService::kSuccess);
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), StateDiscoveryResult::kFailure);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
}
TEST_P(PsmHelperTest, ConnectionErrorForRlweOprfResponse) {
InSequence sequence;
ServerWillFailForPsm(net::ERR_FAILED, DeviceManagementService::kSuccess);
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), StateDiscoveryResult::kFailure);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmRlweOprfRequest();
VerifyPsmLastRequestJobType();
}
TEST_P(PsmHelperTest, NetworkFailureForRlweOprfResponse) {
InSequence sequence;
ServerWillFailForPsm(net::OK, DeviceManagementService::kServiceUnavailable);
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), StateDiscoveryResult::kFailure);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmLastRequestJobType();
}
TEST_P(PsmHelperTest, NetworkFailureForRlweQueryResponse) {
InSequence sequence;
ServerWillReplyWithPsmOprfResponse();
ServerWillFailForPsm(net::OK, net::ERR_CONNECTION_CLOSED);
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), StateDiscoveryResult::kFailure);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
}
TEST_P(PsmHelperTest, RetryLogicAfterMembershipSuccessfullyRetrieved) {
InSequence sequence;
ServerWillReplyWithPsmOprfResponse();
ServerWillReplyWithPsmQueryResponse();
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
const StateDiscoveryResult expected_state_result =
GetExpectedMembershipResult()
? StateDiscoveryResult::kSuccessHasServerSideState
: StateDiscoveryResult::kSuccessNoServerSideState;
EXPECT_EQ(GetStateDiscoveryResult(), expected_state_result);
// Verify that none of the PSM requests have been sent again. And its cached
// membership result hasn't changed.
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Retry();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), expected_state_result);
ExpectPsmHistograms(
{PsmStatus::kAttempt, PsmStatus::kSuccessfulDetermination},
/*success_time_recorded=*/true);
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
}
TEST_P(PsmHelperTest, RetryLogicAfterNetworkFailureForRlweQueryResponse) {
InSequence sequence;
ServerWillReplyWithPsmOprfResponse();
ServerWillFailForPsm(net::OK, net::ERR_CONNECTION_CLOSED);
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
const StateDiscoveryResult expected_state_result =
StateDiscoveryResult::kFailure;
EXPECT_EQ(GetStateDiscoveryResult(), expected_state_result);
// Verify that none of the PSM requests have been sent again. And its cached
// membership result hasn't changed.
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Retry();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(GetStateDiscoveryResult(), expected_state_result);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
}
INSTANTIATE_TEST_SUITE_P(
Psm,
PsmHelperTest,
testing::Combine(testing::Values(AutoEnrollmentClientImplTestState(
AutoEnrollmentProtocol::kInitialEnrollment,
PsmState::kEnabled)),
::testing::Range(0, kNumberOfPsmTestCases)));
using PsmHelperAndHashDanceTest = PsmHelperTest;
TEST_P(PsmHelperAndHashDanceTest, PsmRlweQueryFailedAndHashDanceSucceeded) {
InSequence sequence;
// Fail for PSM RLWE query request.
ServerWillReplyWithPsmOprfResponse();
ServerWillFailForPsm(net::OK, net::ERR_CONNECTION_CLOSED);
// Succeed for both DeviceAutoEnrollmentRequest and
// DeviceStateRetrievalRequest. And the result of DeviceAutoEnrollmentRequest
// is positive.
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kWithLicense);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify failure of PSM protocol.
EXPECT_EQ(GetStateDiscoveryResult(), StateDiscoveryResult::kFailure);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
// Verify Hash dance result.
VerifyCachedResult(true, kPowerLimit);
// Verify recorded comparison value between PSM and Hash dance.
ExpectPsmHashDanceComparisonRecorded(
PsmHashDanceComparison::kPSMErrorHashDanceSuccess);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceExecutionTimeHistogram(
/*expected_time_recorded=*/base::TimeDelta::FromSeconds(0),
/*success_time_recorded=*/true);
// Verify device state result.
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kWithLicense);
}
TEST_P(PsmHelperAndHashDanceTest, PsmRlweOprfFailedAndHashDanceSucceeded) {
InSequence sequence;
// Fail for PSM RLWE OPRF request.
ServerWillFailForPsm(net::OK, DeviceManagementService::kServiceUnavailable);
// Succeed for both DeviceAutoEnrollmentRequest and
// DeviceStateRetrievalRequest. And the result of DeviceAutoEnrollmentRequest
// is positive.
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true, /*with_id_hash=*/true);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kWithLicense);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify failure of PSM protocol.
EXPECT_EQ(GetStateDiscoveryResult(), StateDiscoveryResult::kFailure);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmLastRequestJobType();
// Verify Hash dance result.
VerifyCachedResult(true, kPowerLimit);
// Verify recorded comparison between PSM and Hash dance.
ExpectPsmHashDanceComparisonRecorded(
PsmHashDanceComparison::kPSMErrorHashDanceSuccess);
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceExecutionTimeHistogram(
/*expected_time_recorded=*/base::TimeDelta::FromSeconds(0),
/*success_time_recorded=*/true);
// Verify device state result.
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kWithLicense);
}
TEST_P(PsmHelperAndHashDanceTest, PsmSucceedAndHashDanceSucceed) {
InSequence sequence;
// Succeed for both PSM RLWE requests.
ServerWillReplyWithPsmOprfResponse();
ServerWillReplyWithPsmQueryResponse();
// Succeed for both DeviceAutoEnrollmentRequest and
// DeviceStateRetrievalRequest. And the result of DeviceAutoEnrollmentRequest
// is positive.
const bool kExpectedHashDanceResult = true;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true,
/*with_id_hash=*/kExpectedHashDanceResult);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kWithLicense);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify PSM result.
EXPECT_EQ(GetStateDiscoveryResult(),
GetExpectedMembershipResult()
? StateDiscoveryResult::kSuccessHasServerSideState
: StateDiscoveryResult::kSuccessNoServerSideState);
ExpectPsmHistograms(
{PsmStatus::kAttempt, PsmStatus::kSuccessfulDetermination},
/*success_time_recorded=*/true);
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
// Verify Hash dance result.
VerifyCachedResult(kExpectedHashDanceResult, kPowerLimit);
// Verify recorded comparison between PSM and Hash dance.
ExpectPsmHashDanceComparisonRecorded(
(GetExpectedMembershipResult() == kExpectedHashDanceResult)
? PsmHashDanceComparison::kEqualResults
: PsmHashDanceComparison::kDifferentResults);
if (GetExpectedMembershipResult() != kExpectedHashDanceResult) {
// Verify recorded different results for PSM and Hash dance.
ExpectPsmHashDanceDifferentResultsComparisonRecorded(
PsmHashDanceDifferentResultsComparison::kHashDanceTruePsmFalse);
}
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceExecutionTimeHistogram(
/*expected_time_recorded=*/base::TimeDelta::FromSeconds(0),
/*success_time_recorded=*/true);
// Verify device state result.
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kWithLicense);
}
TEST_P(PsmHelperAndHashDanceTest,
PsmSucceedAndHashDanceSucceedForNoEnrollment) {
InSequence sequence;
// Succeed for both PSM RLWE requests.
ServerWillReplyWithPsmOprfResponse();
ServerWillReplyWithPsmQueryResponse();
// Succeed with a negative result for DeviceAutoEnrollmentRequest i.e. Hash
// dance request.
const bool kExpectedHashDanceResult = false;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true,
/*with_id_hash=*/kExpectedHashDanceResult);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify PSM result.
EXPECT_EQ(GetStateDiscoveryResult(),
GetExpectedMembershipResult()
? StateDiscoveryResult::kSuccessHasServerSideState
: StateDiscoveryResult::kSuccessNoServerSideState);
ExpectPsmHistograms(
{PsmStatus::kAttempt, PsmStatus::kSuccessfulDetermination},
/*success_time_recorded=*/true);
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
// Verify Hash dance result.
VerifyCachedResult(kExpectedHashDanceResult, kPowerLimit);
// Verify recorded comparison between PSM and Hash dance.
ExpectPsmHashDanceComparisonRecorded(
(GetExpectedMembershipResult() == kExpectedHashDanceResult)
? PsmHashDanceComparison::kEqualResults
: PsmHashDanceComparison::kDifferentResults);
if (GetExpectedMembershipResult() != kExpectedHashDanceResult) {
// Verify recorded different results for PSM and Hash dance.
ExpectPsmHashDanceDifferentResultsComparisonRecorded(
PsmHashDanceDifferentResultsComparison::kPsmTrueHashDanceFalse);
}
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceExecutionTimeHistogram(
/*expected_time_recorded=*/base::TimeDelta::FromSeconds(0),
/*success_time_recorded=*/true);
// Verify that no enrollment has been done, and no state has been retrieved.
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_NO_ENROLLMENT);
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(PsmHelperAndHashDanceTest, PsmRlweOprfFailedAndHashDanceFailed) {
InSequence sequence;
// Fail for PSM RLWE OPRF request.
ServerWillFailForPsm(net::OK, DeviceManagementService::kServiceUnavailable);
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify failure of PSM protocol.
EXPECT_EQ(GetStateDiscoveryResult(), StateDiscoveryResult::kFailure);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmLastRequestJobType();
// Verify failure of Hash dance by inexistence of its cached decision.
EXPECT_FALSE(HasCachedDecision());
// Verify recorded comparison between PSM and Hash dance.
ExpectPsmHashDanceComparisonRecorded(PsmHashDanceComparison::kBothError);
// Verify that no enrollment has been done, and no state has been retrieved.
EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
failed_job_type_);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
EXPECT_FALSE(HasServerBackedState());
}
TEST_P(PsmHelperAndHashDanceTest,
RetryLogicAfterPsmSucceededAndHashDanceSucceeded) {
InSequence sequence;
// Succeed for both PSM RLWE requests.
ServerWillReplyWithPsmOprfResponse();
ServerWillReplyWithPsmQueryResponse();
// Succeed for both DeviceAutoEnrollmentRequest and
// DeviceStateRetrievalRequest. And the result of DeviceAutoEnrollmentRequest
// is positive.
const bool kExpectedHashDanceResult = true;
ServerWillReply(/*modulus=*/-1, /*with_hashes=*/true,
/*with_id_hash=*/kExpectedHashDanceResult);
ServerWillSendState(
"example.com",
em::DeviceStateRetrievalResponse::RESTORE_MODE_REENROLLMENT_ENFORCED,
kDisabledMessage, kWithLicense);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify PSM result.
const StateDiscoveryResult expected_psm_state_result =
GetExpectedMembershipResult()
? StateDiscoveryResult::kSuccessHasServerSideState
: StateDiscoveryResult::kSuccessNoServerSideState;
EXPECT_EQ(GetStateDiscoveryResult(), expected_psm_state_result);
// Verify Hash dance result.
VerifyCachedResult(kExpectedHashDanceResult, kPowerLimit);
// Verify device state result.
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
EXPECT_EQ(state_retrieval_job_type_, GetExpectedStateRetrievalJobType());
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_TRIGGER_ENROLLMENT);
VerifyServerBackedState("example.com",
kDeviceStateRestoreModeReEnrollmentEnforced,
kDisabledMessage, kWithLicense);
// Trigger AutoEnrollmentClientImpl retry.
client()->Retry();
base::RunLoop().RunUntilIdle();
// Verify that PSM cached decision hasn't changed, and no new requests have
// been sent.
EXPECT_EQ(GetStateDiscoveryResult(), expected_psm_state_result);
ExpectPsmHistograms(
{PsmStatus::kAttempt, PsmStatus::kSuccessfulDetermination},
/*success_time_recorded=*/true);
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
// Verify Hash dance protocol overall execution time and its success time
// histograms were recorded correctly with the same value.
ExpectHashDanceExecutionTimeHistogram(
/*expected_time_recorded=*/base::TimeDelta::FromSeconds(0),
/*success_time_recorded=*/true);
// Verify that Hash dance cached decision hasn't changed, and no new request
// has been sent.
VerifyCachedResult(kExpectedHashDanceResult, kPowerLimit);
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT);
// Verify recorded comparison between PSM and Hash dance.
ExpectPsmHashDanceComparisonRecorded(
(GetExpectedMembershipResult() == kExpectedHashDanceResult)
? PsmHashDanceComparison::kEqualResults
: PsmHashDanceComparison::kDifferentResults);
if (GetExpectedMembershipResult() != kExpectedHashDanceResult) {
// Verify recorded different results for PSM and Hash dance.
ExpectPsmHashDanceDifferentResultsComparisonRecorded(
PsmHashDanceDifferentResultsComparison::kHashDanceTruePsmFalse);
}
}
TEST_P(PsmHelperAndHashDanceTest,
RetryLogicAfterPsmRlweOprfFailedAndHashDanceFailed) {
InSequence sequence;
// Fail for PSM RLWE OPRF request.
ServerWillFailForPsm(net::OK, DeviceManagementService::kServiceUnavailable);
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify failure of PSM protocol.
const StateDiscoveryResult expected_psm_state_result =
StateDiscoveryResult::kFailure;
EXPECT_EQ(GetStateDiscoveryResult(), expected_psm_state_result);
// Verify failure of Hash dance by inexistence of its cached decision.
EXPECT_FALSE(HasCachedDecision());
// Verify that no enrollment has been done, and no state has been retrieved.
EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
failed_job_type_);
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
EXPECT_FALSE(HasServerBackedState());
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request.
ServerWillFail(net::OK, DeviceManagementService::kServiceUnavailable);
// Trigger AutoEnrollmentClientImpl retry.
client()->Retry();
base::RunLoop().RunUntilIdle();
// Verify that PSM cached decision hasn't changed, and no
// new requests have been sent.
EXPECT_EQ(GetStateDiscoveryResult(), expected_psm_state_result);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
VerifyPsmRlweOprfRequest();
VerifyPsmLastRequestJobType();
// Verify inexistence of Hash dance cached decision, and its new request
// has failed again.
EXPECT_FALSE(HasCachedDecision());
EXPECT_EQ(auto_enrollment_job_type_,
DeviceManagementService::JobConfiguration::TYPE_INVALID);
EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
failed_job_type_);
// Verify recorded comparison between PSM and Hash dance.
ExpectPsmHashDanceComparisonRecorded(PsmHashDanceComparison::kBothError);
}
TEST_P(PsmHelperAndHashDanceTest,
RetryWhileWaitingForPsmOprfResponseAndHashDanceFails) {
InSequence sequence;
const base::TimeDelta kOneSecondTimeDelta = base::TimeDelta::FromSeconds(1);
DeviceManagementService::JobControl* psm_rlwe_oprf_job = nullptr;
DeviceManagementService::JobControl* hash_dance_job = nullptr;
// Expect two requests and capture them, in order, when available in
// |psm_rlwe_oprf_job| and |hash_dance_job|.
ServerWillReplyAsyncForPsm(&psm_rlwe_oprf_job);
ServerWillReplyAsync(&hash_dance_job);
// Expect none of the jobs have been captured.
EXPECT_FALSE(psm_rlwe_oprf_job);
EXPECT_FALSE(hash_dance_job);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify the only job that has been captured is the PSM RLWE OPRF request.
VerifyPsmRlweOprfRequest();
VerifyPsmLastRequestJobType();
ASSERT_TRUE(psm_rlwe_oprf_job);
EXPECT_FALSE(hash_dance_job);
// Trigger RetryStep.
client()->Retry();
// Verify hash dance job has not been triggered after RetryStep.
EXPECT_FALSE(hash_dance_job);
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
// Fail for PSM RLWE OPRF request.
ServerFailsForAsyncJob(&psm_rlwe_oprf_job);
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
// Verify failure of PSM protocol.
EXPECT_EQ(GetStateDiscoveryResult(), StateDiscoveryResult::kFailure);
ExpectPsmHistograms({PsmStatus::kAttempt, PsmStatus::kError},
/*success_time_recorded=*/false);
// Verify hash dance job has been captured.
ASSERT_TRUE(hash_dance_job);
EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
last_async_job_type_);
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request by sending
// an empty response.
ServerRepliesEmptyResponseForAsyncJob(&hash_dance_job);
// Verify Hash dance protocol overall execution time histogram has been
// recorded correctly. And its success time histogram has not been recorded.
ExpectHashDanceExecutionTimeHistogram(
/*expected_time_recorded=*/base::TimeDelta::FromSeconds(1),
/*success_time_recorded=*/false);
// Verify failure of Hash dance by inexistence of its cached decision.
EXPECT_FALSE(HasCachedDecision());
// Verify that no enrollment has been done, and no state has been retrieved.
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
EXPECT_FALSE(HasServerBackedState());
// Verify recorded comparison between PSM and Hash dance.
ExpectPsmHashDanceComparisonRecorded(PsmHashDanceComparison::kBothError);
// Verify both jobs have finished.
EXPECT_EQ(hash_dance_job, nullptr);
EXPECT_EQ(psm_rlwe_oprf_job, nullptr);
}
TEST_P(PsmHelperAndHashDanceTest,
RetryWhileWaitingForPsmQueryResponseAndHashDanceFails) {
InSequence sequence;
const base::TimeDelta kOneSecondTimeDelta = base::TimeDelta::FromSeconds(1);
DeviceManagementService::JobControl* psm_rlwe_oprf_job = nullptr;
DeviceManagementService::JobControl* psm_rlwe_query_job = nullptr;
DeviceManagementService::JobControl* hash_dance_job = nullptr;
// Expect three requests and capture them, in order, when available in
// |psm_rlwe_oprf_job|, |psm_rlwe_query_job|, and |hash_dance_job|.
ServerWillReplyAsyncForPsm(&psm_rlwe_oprf_job);
ServerWillReplyAsyncForPsm(&psm_rlwe_query_job);
ServerWillReplyAsync(&hash_dance_job);
// Expect none of the jobs have been captured.
EXPECT_FALSE(psm_rlwe_oprf_job);
EXPECT_FALSE(psm_rlwe_query_job);
EXPECT_FALSE(hash_dance_job);
client()->Start();
base::RunLoop().RunUntilIdle();
// Verify the only job that has been captured is the PSM RLWE OPRF request.
VerifyPsmRlweOprfRequest();
VerifyPsmLastRequestJobType();
ASSERT_TRUE(psm_rlwe_oprf_job);
EXPECT_FALSE(psm_rlwe_query_job);
EXPECT_FALSE(hash_dance_job);
// Reply with PSM RLWE OPRF response.
ServerReplyForPsmAsyncJobWithOprfResponse(&psm_rlwe_oprf_job);
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
// Verify the only job that has been captured is the PSM RLWE Query request.
VerifyPsmRlweQueryRequest();
VerifyPsmLastRequestJobType();
ASSERT_TRUE(psm_rlwe_query_job);
EXPECT_FALSE(hash_dance_job);
// Trigger RetryStep.
client()->Retry();
// Verify hash dance job has not been triggered after RetryStep.
EXPECT_FALSE(hash_dance_job);
// Reply with PSM RLWE Query response.
ServerReplyForPsmAsyncJobWithQueryResponse(&psm_rlwe_query_job);
// Advance the time forward one second.
task_environment_.FastForwardBy(kOneSecondTimeDelta);
// Verify PSM result.
EXPECT_EQ(GetStateDiscoveryResult(),
GetExpectedMembershipResult()
? StateDiscoveryResult::kSuccessHasServerSideState
: StateDiscoveryResult::kSuccessNoServerSideState);
ExpectPsmHistograms(
{PsmStatus::kAttempt, PsmStatus::kSuccessfulDetermination},
/*success_time_recorded=*/true);
// Verify hash dance job has been captured.
ASSERT_TRUE(hash_dance_job);
EXPECT_EQ(DeviceManagementService::JobConfiguration::TYPE_AUTO_ENROLLMENT,
last_async_job_type_);
// Fail for DeviceAutoEnrollmentRequest i.e. hash dance request by sending
// an empty response.
ServerRepliesEmptyResponseForAsyncJob(&hash_dance_job);
// Verify Hash dance protocol overall execution time histogram has been
// recorded correctly. And its success time histogram has not been recorded.
ExpectHashDanceExecutionTimeHistogram(
/*expected_time_recorded=*/base::TimeDelta::FromSeconds(1),
/*success_time_recorded=*/false);
// Verify failure of Hash dance by inexistence of its cached decision.
EXPECT_FALSE(HasCachedDecision());
// Verify that no enrollment has been done, and no state has been retrieved.
EXPECT_EQ(state_, AUTO_ENROLLMENT_STATE_SERVER_ERROR);
EXPECT_FALSE(HasServerBackedState());
// Verify recorded comparison between PSM and Hash dance.
ExpectPsmHashDanceComparisonRecorded(
PsmHashDanceComparison::kPSMSuccessHashDanceError);
// Verify all jobs have finished.
EXPECT_EQ(hash_dance_job, nullptr);
EXPECT_EQ(psm_rlwe_oprf_job, nullptr);
EXPECT_EQ(psm_rlwe_query_job, nullptr);
}
INSTANTIATE_TEST_SUITE_P(
PsmAndHashDance,
PsmHelperAndHashDanceTest,
testing::Combine(testing::Values(AutoEnrollmentClientImplTestState(
AutoEnrollmentProtocol::kInitialEnrollment,
PsmState::kEnabled)),
::testing::Range(0, kNumberOfPsmTestCases)));
} // namespace
} // namespace policy