Skip to content

Commit

Permalink
Cros MultiDevice] Re-enroll device if SoftwareFeature supported state…
Browse files Browse the repository at this point in the history
… changes.

Create a class, DeviceReenroller, that compares GcmDeviceInfo's list of
supported SoftwareFeatures--eg., BETTER_TOGETHER_CLIENT or
MAGIC_TETHER_CLIENT--with those stored in the local device metadata on the
CryptAuth server. If a SoftwareFeature state has changed from supported to
unsupported, or vice versa, then re-enroll the device with CryptAuth. Finally,
after a successful re-enrollment, re-sync with CryptAuth and verify that the
local device metadata was updated correctly.

The flow is as follows:

1) The supported states of CrOS-relevant SoftwareFeatures are set in
//src/chrome/browser/chromeos/cryptauth/gcm_device_info_provider_impl.cc, based
on FeatureList flags.

2) This information is stored in GcmDeviceInfo, which is passed along to the
MultiDeviceSetupService via a ctor argument.

3) On creation, MultiDeviceSetupImpl creates a DeviceReenroller, providing it
   - a DeviceSyncClient, for communicating with CryptAuth to retrieve local
     device metadata, to re-enroll the device, and to sync the device;
   - and access to GcmDeviceInfo, in order to retrieve the latest list of
     supported SoftwareFeatures.

4) In the DeviceReenroller constructor, the supported software features from
the local device metadata are compared with the latest supported-state given
by GcmDeviceInfo's supported_software_features list.

5) If any supported-state changed, a re-enrollment is attempted via the
DeviceSyncClient.

6) If the re-enrollment is successful, a device sync is attempted via the
  DeviceSyncClient.
  - If the re-enrollment is unsuccessful, another attempt is scheduled to
    run after 5 minutes.

7) If the device sync is unsuccessful, another attempt is scheduled to
    run after 5 minutes.

Note that DeviceReenroller logic only runs when the MultiDeviceSetupService is
created, except for possibly retrying failed enrollments or syncs after some
delay. It does NOT act as an observer, checking for changes to GcmDeviceInfo's
supported features list, because we only expect this to change when Chrome
flags change, i.e., when the Chrome binary restarts.

Bug: 870770
Change-Id: I5f26a68448f3ee0e5dd8759aac12d05d1e4760a6
Reviewed-on: https://chromium-review.googlesource.com/1182419
Reviewed-by: Ryan Hansberry <[email protected]>
Reviewed-by: Ryan Hamilton <[email protected]>
Reviewed-by: Jeremy Klein <[email protected]>
Commit-Queue: Josh Nohle <[email protected]>
Cr-Commit-Position: refs/heads/master@{#589387}
  • Loading branch information
nohle authored and Commit Bot committed Sep 7, 2018
1 parent 25c6a34 commit 43c7ee4
Show file tree
Hide file tree
Showing 16 changed files with 787 additions and 37 deletions.
3 changes: 2 additions & 1 deletion chrome/browser/profiles/profile_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1533,7 +1533,8 @@ ProfileImpl::CreateMultiDeviceSetupService() {
chromeos::multidevice_setup::AuthTokenValidatorFactory::GetForProfile(
this),
std::make_unique<
chromeos::multidevice_setup::AndroidSmsAppHelperDelegateImpl>(this));
chromeos::multidevice_setup::AndroidSmsAppHelperDelegateImpl>(this),
chromeos::GcmDeviceInfoProviderImpl::GetInstance());
}

#endif // defined(OS_CHROMEOS)
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ FakeDeviceSyncClient::~FakeDeviceSyncClient() = default;

void FakeDeviceSyncClient::ForceEnrollmentNow(
mojom::DeviceSync::ForceEnrollmentNowCallback callback) {
std::move(callback).Run(force_enrollment_now_success_);
force_enrollment_now_callback_queue_.push(std::move(callback));
}

void FakeDeviceSyncClient::ForceSyncNow(
mojom::DeviceSync::ForceSyncNowCallback callback) {
std::move(callback).Run(force_sync_now_success_);
force_sync_now_callback_queue_.push(std::move(callback));
}

cryptauth::RemoteDeviceRefList FakeDeviceSyncClient::GetSyncedDevices() {
Expand Down Expand Up @@ -54,6 +54,27 @@ void FakeDeviceSyncClient::GetDebugInfo(
get_debug_info_callback_queue_.push(std::move(callback));
}

int FakeDeviceSyncClient::GetForceEnrollmentNowCallbackQueueSize() {
return force_enrollment_now_callback_queue_.size();
}

int FakeDeviceSyncClient::GetForceSyncNowCallbackQueueSize() {
return force_sync_now_callback_queue_.size();
}

void FakeDeviceSyncClient::InvokePendingForceEnrollmentNowCallback(
bool success) {
DCHECK(force_enrollment_now_callback_queue_.size() > 0);
std::move(force_enrollment_now_callback_queue_.front()).Run(success);
force_enrollment_now_callback_queue_.pop();
}

void FakeDeviceSyncClient::InvokePendingForceSyncNowCallback(bool success) {
DCHECK(force_sync_now_callback_queue_.size() > 0);
std::move(force_sync_now_callback_queue_.front()).Run(success);
force_sync_now_callback_queue_.pop();
}

void FakeDeviceSyncClient::InvokePendingSetSoftwareFeatureStateCallback(
mojom::NetworkRequestResult result_code) {
std::move(set_software_feature_state_callback_queue_.front())
Expand Down
20 changes: 9 additions & 11 deletions chromeos/services/device_sync/public/cpp/fake_device_sync_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class FakeDeviceSyncClient : public DeviceSyncClient {
FakeDeviceSyncClient();
~FakeDeviceSyncClient() override;

int GetForceEnrollmentNowCallbackQueueSize();
int GetForceSyncNowCallbackQueueSize();
void InvokePendingForceEnrollmentNowCallback(bool success);
void InvokePendingForceSyncNowCallback(bool success);
void InvokePendingSetSoftwareFeatureStateCallback(
mojom::NetworkRequestResult result_code);
void InvokePendingFindEligibleDevicesCallback(
Expand All @@ -35,14 +39,6 @@ class FakeDeviceSyncClient : public DeviceSyncClient {
cryptauth::RemoteDeviceRefList ineligible_devices);
void InvokePendingGetDebugInfoCallback(mojom::DebugInfoPtr debug_info_ptr);

void set_force_enrollment_now_success(bool force_enrollment_now_success) {
force_enrollment_now_success_ = force_enrollment_now_success;
}

void set_force_sync_now_success(bool force_sync_now_success) {
force_sync_now_success_ = force_sync_now_success;
}

void set_synced_devices(cryptauth::RemoteDeviceRefList synced_devices) {
synced_devices_ = synced_devices;
}
Expand All @@ -52,9 +48,9 @@ class FakeDeviceSyncClient : public DeviceSyncClient {
local_device_metadata_ = local_device_metadata;
}

using DeviceSyncClient::NotifyReady;
using DeviceSyncClient::NotifyEnrollmentFinished;
using DeviceSyncClient::NotifyNewDevicesSynced;
using DeviceSyncClient::NotifyReady;

private:
// DeviceSyncClient:
Expand All @@ -73,11 +69,13 @@ class FakeDeviceSyncClient : public DeviceSyncClient {
FindEligibleDevicesCallback callback) override;
void GetDebugInfo(mojom::DeviceSync::GetDebugInfoCallback callback) override;

bool force_enrollment_now_success_;
bool force_sync_now_success_;
cryptauth::RemoteDeviceRefList synced_devices_;
base::Optional<cryptauth::RemoteDeviceRef> local_device_metadata_;

std::queue<mojom::DeviceSync::ForceEnrollmentNowCallback>
force_enrollment_now_callback_queue_;
std::queue<mojom::DeviceSync::ForceSyncNowCallback>
force_sync_now_callback_queue_;
std::queue<mojom::DeviceSync::SetSoftwareFeatureStateCallback>
set_software_feature_state_callback_queue_;
std::queue<FindEligibleDevicesCallback> find_eligible_devices_callback_queue_;
Expand Down
3 changes: 3 additions & 0 deletions chromeos/services/multidevice_setup/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ static_library("multidevice_setup") {
"account_status_change_delegate_notifier.h",
"account_status_change_delegate_notifier_impl.cc",
"account_status_change_delegate_notifier_impl.h",
"device_reenroller.cc",
"device_reenroller.h",
"eligible_host_devices_provider.h",
"eligible_host_devices_provider_impl.cc",
"eligible_host_devices_provider_impl.h",
Expand Down Expand Up @@ -110,6 +112,7 @@ source_set("unit_tests") {

sources = [
"account_status_change_delegate_notifier_impl_unittest.cc",
"device_reenroller_unittest.cc",
"eligible_host_devices_provider_impl_unittest.cc",
"feature_state_manager_impl_unittest.cc",
"host_backend_delegate_impl_unittest.cc",
Expand Down
166 changes: 166 additions & 0 deletions chromeos/services/multidevice_setup/device_reenroller.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// 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 "chromeos/services/multidevice_setup/device_reenroller.h"

#include "base/containers/flat_set.h"
#include "base/no_destructor.h"
#include "chromeos/components/proximity_auth/logging/logging.h"
#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
#include "components/cryptauth/gcm_device_info_provider.h"

namespace chromeos {

namespace multidevice_setup {

namespace {

// The number of minutes to wait before retrying a failed re-enrollment or
// device sync attempt.
const int kNumMinutesBetweenRetries = 5;

std::vector<cryptauth::SoftwareFeature>
ComputeSupportedSoftwareFeaturesSortedDedupedListFromGcmDeviceInfo(
const cryptauth::GcmDeviceInfo& gcm_device_info) {
base::flat_set<cryptauth::SoftwareFeature> sorted_and_deduped_set;
for (int i = 0; i < gcm_device_info.supported_software_features_size(); ++i) {
sorted_and_deduped_set.insert(
gcm_device_info.supported_software_features(i));
}
return std::vector<cryptauth::SoftwareFeature>(sorted_and_deduped_set.begin(),
sorted_and_deduped_set.end());
}

std::vector<cryptauth::SoftwareFeature>
ComputeSupportedSoftwareFeaturesSortedDedupedListFromLocalDeviceMetadata(
const cryptauth::RemoteDeviceRef& local_device_metadata) {
base::flat_set<cryptauth::SoftwareFeature> sorted_and_deduped_set;
for (int i = cryptauth::SoftwareFeature_MIN;
i <= cryptauth::SoftwareFeature_MAX; ++i) {
cryptauth::SoftwareFeature feature =
static_cast<cryptauth::SoftwareFeature>(i);
if (local_device_metadata.GetSoftwareFeatureState(feature) !=
cryptauth::SoftwareFeatureState::kNotSupported) {
sorted_and_deduped_set.insert(feature);
}
}
return std::vector<cryptauth::SoftwareFeature>(sorted_and_deduped_set.begin(),
sorted_and_deduped_set.end());
}

} // namespace

// static
DeviceReenroller::Factory* DeviceReenroller::Factory::test_factory_ = nullptr;

// static
DeviceReenroller::Factory* DeviceReenroller::Factory::Get() {
if (test_factory_)
return test_factory_;

static base::NoDestructor<Factory> factory;
return factory.get();
}

// static
void DeviceReenroller::Factory::SetFactoryForTesting(Factory* test_factory) {
test_factory_ = test_factory;
}

DeviceReenroller::Factory::~Factory() = default;

std::unique_ptr<DeviceReenroller> DeviceReenroller::Factory::BuildInstance(
device_sync::DeviceSyncClient* device_sync_client,
const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider,
std::unique_ptr<base::OneShotTimer> timer) {
return base::WrapUnique(new DeviceReenroller(
device_sync_client, gcm_device_info_provider, std::move(timer)));
}

DeviceReenroller::~DeviceReenroller() = default;

DeviceReenroller::DeviceReenroller(
device_sync::DeviceSyncClient* device_sync_client,
const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider,
std::unique_ptr<base::OneShotTimer> timer)
: device_sync_client_(device_sync_client),
gcm_supported_software_features_(
ComputeSupportedSoftwareFeaturesSortedDedupedListFromGcmDeviceInfo(
gcm_device_info_provider->GetGcmDeviceInfo())),
timer_(std::move(timer)) {
// If the current set of supported software features from GcmDeviceInfo
// differs from that of the local device metadata on the CryptAuth server,
// attempt re-enrollment. Note: Both lists here are sorted and duplicate-free.
if (gcm_supported_software_features_ !=
ComputeSupportedSoftwareFeaturesSortedDedupedListFromLocalDeviceMetadata(
*device_sync_client_->GetLocalDeviceMetadata())) {
AttemptReenrollment();
}
}

void DeviceReenroller::AttemptReenrollment() {
DCHECK(!timer_->IsRunning());
device_sync_client_->ForceEnrollmentNow(base::BindOnce(
&DeviceReenroller::OnForceEnrollmentNowComplete, base::Unretained(this)));
}

void DeviceReenroller::AttemptDeviceSync() {
DCHECK(!timer_->IsRunning());
device_sync_client_->ForceSyncNow(base::BindOnce(
&DeviceReenroller::OnForceSyncNowComplete, base::Unretained(this)));
}

void DeviceReenroller::OnForceEnrollmentNowComplete(bool success) {
if (success) {
PA_LOG(INFO) << "DeviceReenroller::OnForceEnrollmentNowComplete(): "
<< "Forced enrollment attempt was successful. "
<< "Syncing devices now.";
AttemptDeviceSync();
return;
}
PA_LOG(WARNING) << "DeviceReenroller::OnForceEnrollmentNowComplete(): "
<< "Forced enrollment attempt was unsuccessful. Retrying in "
<< kNumMinutesBetweenRetries << " minutes.";
timer_->Start(FROM_HERE,
base::TimeDelta::FromMinutes(kNumMinutesBetweenRetries),
base::BindOnce(&DeviceReenroller::AttemptReenrollment,
base::Unretained(this)));
}

void DeviceReenroller::OnForceSyncNowComplete(bool success) {
// This is used to track if the device sync properly updated the local device
// metadata to reflect the supported software features from GcmDeviceInfo.
bool local_device_metadata_agrees =
device_sync_client_->GetLocalDeviceMetadata() &&
ComputeSupportedSoftwareFeaturesSortedDedupedListFromLocalDeviceMetadata(
*device_sync_client_->GetLocalDeviceMetadata()) ==
gcm_supported_software_features_;

if (success && local_device_metadata_agrees) {
PA_LOG(INFO) << "DeviceReenroller::OnForceSyncNowComplete(): "
<< "Forced device sync attempt was successful.";
return;
}
if (!success) {
PA_LOG(WARNING) << "DeviceReenroller::OnForceSyncNowComplete(): "
<< "Forced device sync attempt was unsuccessful. "
<< "Retrying in " << kNumMinutesBetweenRetries
<< " minutes.";
} else {
DCHECK(!local_device_metadata_agrees);
PA_LOG(WARNING) << "DeviceReenroller::OnForceSyncNowComplete(): "
<< "The local device metadata's supported software "
<< "features do not agree with the set extracted from GCM "
<< "device info. Retrying in " << kNumMinutesBetweenRetries
<< " minutes.";
}
timer_->Start(FROM_HERE,
base::TimeDelta::FromMinutes(kNumMinutesBetweenRetries),
base::BindOnce(&DeviceReenroller::AttemptDeviceSync,
base::Unretained(this)));
}

} // namespace multidevice_setup

} // namespace chromeos
82 changes: 82 additions & 0 deletions chromeos/services/multidevice_setup/device_reenroller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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.

#ifndef CHROMEOS_SERVICES_MULTIDEVICE_SETUP_DEVICE_REENROLLER_H_
#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_DEVICE_REENROLLER_H_

#include <memory>

#include "base/macros.h"
#include "components/cryptauth/proto/cryptauth_api.pb.h"

namespace base {
class OneShotTimer;
} // namespace base

namespace cryptauth {
class GcmDeviceInfoProvider;
} // namespace cryptauth

namespace chromeos {

namespace device_sync {
class DeviceSyncClient;
} // namespace device_sync

namespace multidevice_setup {

// The DeviceReenroller constructor re-enrolls and syncs the device if the set
// of supported SoftwareFeatures in the current GCM device info differs from
// that of the local device metadata on the CryptAuth server.
class DeviceReenroller {
public:
class Factory {
public:
static Factory* Get();
static void SetFactoryForTesting(Factory* test_factory);
virtual ~Factory();
virtual std::unique_ptr<DeviceReenroller> BuildInstance(
device_sync::DeviceSyncClient* device_sync_client,
const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider,
std::unique_ptr<base::OneShotTimer> timer =
std::make_unique<base::OneShotTimer>());

private:
static Factory* test_factory_;
};

virtual ~DeviceReenroller();

private:
DeviceReenroller(
device_sync::DeviceSyncClient* device_sync_client,
const cryptauth::GcmDeviceInfoProvider* gcm_device_info_provider,
std::unique_ptr<base::OneShotTimer> timer);

void AttemptReenrollment();
void AttemptDeviceSync();

// If the re-enrollment was successful, force a device sync; otherwise, retry
// re-enrollment every 5 minutes or until success.
void OnForceEnrollmentNowComplete(bool success);
// If the device sync was successful and the list of supported software
// features on the CryptAuth server now agrees with the list of supported
// software features in GcmDeviceInfo, log the success; otherwise, retry
// device sync every 5 minutes or until success.
void OnForceSyncNowComplete(bool success);

device_sync::DeviceSyncClient* device_sync_client_;
// The sorted and deduped list of supported software features extracted from
// GcmDeviceInfo.
std::vector<cryptauth::SoftwareFeature> gcm_supported_software_features_;
std::unique_ptr<base::OneShotTimer> timer_;

DISALLOW_COPY_AND_ASSIGN(DeviceReenroller);
};

} // namespace multidevice_setup

} // namespace chromeos

#endif // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_DEVICE_REENROLLER_H_
Loading

0 comments on commit 43c7ee4

Please sign in to comment.