blob: a05cda19ed65c18afb1f0217d88c26660ec34823 [file] [log] [blame]
// Copyright 2019 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 "chromeos/services/device_sync/persistent_enrollment_scheduler.h"
#include <algorithm>
#include <string>
#include <utility>
#include "base/base64.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/optional.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/services/device_sync/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
namespace chromeos {
namespace device_sync {
namespace {
// The default period between successful enrollments in days. Superseded by the
// ClientDirective's checkin_delay_millis sent by CryptAuth in SyncKeysResponse.
constexpr base::TimeDelta kDefaultRefreshPeriod = base::TimeDelta::FromDays(30);
// The default period, in hours, between enrollment attempts if the previous
// enrollment attempt failed. Superseded by the ClientDirective's
// retry_period_millis sent by CryptAuth in SyncKeysResponse.
constexpr base::TimeDelta kDefaultRetryPeriod = base::TimeDelta::FromHours(12);
// The default number of immediate retries after a failed enrollment attempt.
// Superseded by the ClientDirective's retry_attempts sent by CryptAuth in the
// SyncKeysResponse.
const int kDefaultMaxImmediateRetries = 3;
bool IsClientDirectiveValid(
const cryptauthv2::ClientDirective& client_directive) {
return client_directive.checkin_delay_millis() > 0 &&
client_directive.retry_period_millis() > 0 &&
client_directive.retry_attempts() >= 0;
}
// Fills a ClientDirective with our chosen default parameters. This
// ClientDirective is used until a ClientDirective is received from CryptAuth in
// the SyncKeysResponse.
cryptauthv2::ClientDirective CreateDefaultClientDirective() {
cryptauthv2::ClientDirective client_directive;
client_directive.set_checkin_delay_millis(
kDefaultRefreshPeriod.InMilliseconds());
client_directive.set_retry_period_millis(
kDefaultRetryPeriod.InMilliseconds());
client_directive.set_retry_attempts(kDefaultMaxImmediateRetries);
return client_directive;
}
// Decodes and parses the base64-encoded serialized ClientDirective string.
base::Optional<cryptauthv2::ClientDirective> ClientDirectiveFromPrefString(
const std::string& encoded_serialized_client_directive) {
if (encoded_serialized_client_directive.empty())
return base::nullopt;
std::string decoded_serialized_client_directive;
if (!base::Base64Decode(encoded_serialized_client_directive,
&decoded_serialized_client_directive)) {
PA_LOG(ERROR) << "Error decoding ClientDirective pref string";
return base::nullopt;
}
cryptauthv2::ClientDirective client_directive;
if (!client_directive.ParseFromString(decoded_serialized_client_directive)) {
PA_LOG(ERROR) << "Error parsing ClientDirective from pref string";
return base::nullopt;
}
return client_directive;
}
// Serializes and base64 encodes the input ClientDirective.
std::string ClientDirectiveToPrefString(
const cryptauthv2::ClientDirective& client_directive) {
std::string encoded_serialized_client_directive;
base::Base64Encode(client_directive.SerializeAsString(),
&encoded_serialized_client_directive);
return encoded_serialized_client_directive;
}
cryptauthv2::ClientDirective BuildClientDirective(PrefService* pref_service) {
DCHECK(pref_service);
base::Optional<cryptauthv2::ClientDirective> client_directive_from_pref =
ClientDirectiveFromPrefString(pref_service->GetString(
prefs::kCryptAuthEnrollmentSchedulerClientDirective));
if (client_directive_from_pref)
return *client_directive_from_pref;
return CreateDefaultClientDirective();
}
} // namespace
// static
PersistentEnrollmentScheduler::Factory*
PersistentEnrollmentScheduler::Factory::test_factory_ = nullptr;
// static
PersistentEnrollmentScheduler::Factory*
PersistentEnrollmentScheduler::Factory::Get() {
if (test_factory_)
return test_factory_;
static base::NoDestructor<PersistentEnrollmentScheduler::Factory> factory;
return factory.get();
}
// static
void PersistentEnrollmentScheduler::Factory::SetFactoryForTesting(
Factory* test_factory) {
test_factory_ = test_factory;
}
PersistentEnrollmentScheduler::Factory::~Factory() = default;
std::unique_ptr<CryptAuthEnrollmentScheduler>
PersistentEnrollmentScheduler::Factory::BuildInstance(
Delegate* delegate,
PrefService* pref_service,
base::Clock* clock,
std::unique_ptr<base::OneShotTimer> timer,
scoped_refptr<base::TaskRunner> task_runner) {
return base::WrapUnique(new PersistentEnrollmentScheduler(
delegate, pref_service, clock, std::move(timer), task_runner));
}
// static
void PersistentEnrollmentScheduler::RegisterPrefs(
PrefRegistrySimple* registry) {
registry->RegisterStringPref(
prefs::kCryptAuthEnrollmentSchedulerClientDirective, std::string());
registry->RegisterTimePref(
prefs::kCryptAuthEnrollmentSchedulerLastEnrollmentAttemptTime,
base::Time());
registry->RegisterTimePref(
prefs::kCryptAuthEnrollmentSchedulerLastSuccessfulEnrollmentTime,
base::Time());
registry->RegisterUint64Pref(
prefs::kCryptAuthEnrollmentSchedulerNumConsecutiveFailures, 0);
}
PersistentEnrollmentScheduler::PersistentEnrollmentScheduler(
Delegate* delegate,
PrefService* pref_service,
base::Clock* clock,
std::unique_ptr<base::OneShotTimer> timer,
scoped_refptr<base::TaskRunner> task_runner)
: CryptAuthEnrollmentScheduler(delegate),
pref_service_(pref_service),
clock_(clock),
timer_(std::move(timer)),
client_directive_(BuildClientDirective(pref_service)),
weak_ptr_factory_(this) {
DCHECK(pref_service);
DCHECK(clock);
DCHECK(IsClientDirectiveValid(client_directive_));
// If we are recovering from a failure, set the failure count back to 1 in the
// hopes that the restart solved the issue. This will allow for immediate
// retries again if allowed by the ClientDirective.
if (GetNumConsecutiveFailures() > 1) {
pref_service_->SetUint64(
prefs::kCryptAuthEnrollmentSchedulerNumConsecutiveFailures, 1);
}
// Schedule the next enrollment as part of a new task. This ensures that the
// delegate can complete its initialization before handling an enrollment
// request.
task_runner->PostTask(
FROM_HERE,
base::BindOnce(&PersistentEnrollmentScheduler::ScheduleNextEnrollment,
weak_ptr_factory_.GetWeakPtr()));
}
PersistentEnrollmentScheduler::~PersistentEnrollmentScheduler() = default;
void PersistentEnrollmentScheduler::RequestEnrollmentNow() {
timer_->Stop();
NotifyEnrollmentRequested(GetPolicyReference());
}
void PersistentEnrollmentScheduler::HandleEnrollmentResult(
const CryptAuthEnrollmentResult& enrollment_result) {
DCHECK(!timer_->IsRunning());
base::Time now = clock_->Now();
if (enrollment_result.IsSuccess()) {
pref_service_->SetUint64(
prefs::kCryptAuthEnrollmentSchedulerNumConsecutiveFailures, 0);
pref_service_->SetTime(
prefs::kCryptAuthEnrollmentSchedulerLastSuccessfulEnrollmentTime, now);
} else {
pref_service_->SetUint64(
prefs::kCryptAuthEnrollmentSchedulerNumConsecutiveFailures,
GetNumConsecutiveFailures() + 1);
}
pref_service_->SetTime(
prefs::kCryptAuthEnrollmentSchedulerLastEnrollmentAttemptTime, now);
if (enrollment_result.client_directive() &&
IsClientDirectiveValid(*enrollment_result.client_directive())) {
client_directive_ = *enrollment_result.client_directive();
pref_service_->SetString(
prefs::kCryptAuthEnrollmentSchedulerClientDirective,
ClientDirectiveToPrefString(client_directive_));
}
ScheduleNextEnrollment();
}
base::Optional<base::Time>
PersistentEnrollmentScheduler::GetLastSuccessfulEnrollmentTime() const {
base::Time time = pref_service_->GetTime(
prefs::kCryptAuthEnrollmentSchedulerLastSuccessfulEnrollmentTime);
if (time.is_null())
return base::nullopt;
return time;
}
base::TimeDelta PersistentEnrollmentScheduler::GetRefreshPeriod() const {
return base::TimeDelta::FromMilliseconds(
client_directive_.checkin_delay_millis());
}
base::TimeDelta PersistentEnrollmentScheduler::GetTimeToNextEnrollmentRequest()
const {
if (IsWaitingForEnrollmentResult())
return base::TimeDelta::FromMilliseconds(0);
return timer_->GetCurrentDelay();
}
bool PersistentEnrollmentScheduler::IsWaitingForEnrollmentResult() const {
return !timer_->IsRunning();
}
size_t PersistentEnrollmentScheduler::GetNumConsecutiveFailures() const {
return pref_service_->GetUint64(
prefs::kCryptAuthEnrollmentSchedulerNumConsecutiveFailures);
}
base::TimeDelta
PersistentEnrollmentScheduler::CalculateTimeBetweenEnrollmentRequests() const {
size_t num_consecutive_failures = GetNumConsecutiveFailures();
if (num_consecutive_failures == 0)
return GetRefreshPeriod();
if (num_consecutive_failures <= (size_t)client_directive_.retry_attempts())
return base::TimeDelta::FromMilliseconds(0);
return base::TimeDelta::FromMilliseconds(
client_directive_.retry_period_millis());
}
void PersistentEnrollmentScheduler::ScheduleNextEnrollment() {
DCHECK(!timer_->IsRunning());
base::Time last_attempt_time = pref_service_->GetTime(
prefs::kCryptAuthEnrollmentSchedulerLastEnrollmentAttemptTime);
base::TimeDelta time_until_next_request =
base::TimeDelta::FromMilliseconds(0);
if (!last_attempt_time.is_null()) {
time_until_next_request =
std::max(base::TimeDelta::FromMilliseconds(0),
CalculateTimeBetweenEnrollmentRequests() -
(clock_->Now() - last_attempt_time));
}
timer_->Start(
FROM_HERE, time_until_next_request,
base::BindOnce(&PersistentEnrollmentScheduler::NotifyEnrollmentRequested,
base::Unretained(this), GetPolicyReference()));
}
base::Optional<cryptauthv2::PolicyReference>
PersistentEnrollmentScheduler::GetPolicyReference() const {
if (client_directive_.has_policy_reference())
return client_directive_.policy_reference();
return base::nullopt;
}
} // namespace device_sync
} // namespace chromeos