blob: ef67bddbe88e1bad864fdd63dc0a30af29d52ee1 [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 "chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h"
#include <set>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/time/clock.h"
#include "chromeos/components/proximity_auth/logging/logging.h"
#include "chromeos/services/multidevice_setup/setup_flow_completion_recorder.h"
#include "components/cryptauth/proto/cryptauth_api.pb.h"
#include "components/cryptauth/remote_device_ref.h"
#include "components/cryptauth/software_feature_state.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
namespace chromeos {
namespace multidevice_setup {
namespace {
const int64_t kTimestampNotSet = 0;
const char kNoHost[] = "";
base::Optional<std::string> GetHostPublicKey(
const cryptauth::RemoteDeviceRefList& device_ref_list) {
for (const auto& device_ref : device_ref_list) {
if (device_ref.GetSoftwareFeatureState(
cryptauth::SoftwareFeature::BETTER_TOGETHER_HOST) ==
cryptauth::SoftwareFeatureState::kEnabled) {
DCHECK(!device_ref.public_key().empty());
return device_ref.public_key();
}
}
return base::nullopt;
}
} // namespace
// static
AccountStatusChangeDelegateNotifierImpl::Factory*
AccountStatusChangeDelegateNotifierImpl::Factory::test_factory_ = nullptr;
// static
AccountStatusChangeDelegateNotifierImpl::Factory*
AccountStatusChangeDelegateNotifierImpl::Factory::Get() {
if (test_factory_)
return test_factory_;
static base::NoDestructor<Factory> factory;
return factory.get();
}
// static
void AccountStatusChangeDelegateNotifierImpl::Factory::SetFactoryForTesting(
Factory* test_factory) {
test_factory_ = test_factory;
}
AccountStatusChangeDelegateNotifierImpl::Factory::~Factory() = default;
std::unique_ptr<AccountStatusChangeDelegateNotifier>
AccountStatusChangeDelegateNotifierImpl::Factory::BuildInstance(
device_sync::DeviceSyncClient* device_sync_client,
PrefService* pref_service,
SetupFlowCompletionRecorder* setup_flow_completion_recorder,
base::Clock* clock) {
return base::WrapUnique(new AccountStatusChangeDelegateNotifierImpl(
device_sync_client, pref_service, setup_flow_completion_recorder, clock));
}
// static
void AccountStatusChangeDelegateNotifierImpl::RegisterPrefs(
PrefRegistrySimple* registry) {
// Records the timestamps (in milliseconds since UNIX Epoch, aka JavaTime) of
// the last instance the delegate was notified for each of the changes listed
// in the class description.
registry->RegisterInt64Pref(kNewUserPotentialHostExistsPrefName,
kTimestampNotSet);
registry->RegisterInt64Pref(kExistingUserHostSwitchedPrefName,
kTimestampNotSet);
registry->RegisterInt64Pref(kExistingUserChromebookAddedPrefName,
kTimestampNotSet);
registry->RegisterStringPref(kHostPublicKeyFromMostRecentSyncPrefName,
kNoHost);
}
AccountStatusChangeDelegateNotifierImpl::
~AccountStatusChangeDelegateNotifierImpl() {
device_sync_client_->RemoveObserver(this);
}
void AccountStatusChangeDelegateNotifierImpl::OnDelegateSet() {
CheckForMultiDeviceEvents();
}
// static
const char AccountStatusChangeDelegateNotifierImpl::
kNewUserPotentialHostExistsPrefName[] =
"multidevice_setup.new_user_potential_host_exists";
// static
const char AccountStatusChangeDelegateNotifierImpl::
kExistingUserHostSwitchedPrefName[] =
"multidevice_setup.existing_user_host_switched";
// static
const char AccountStatusChangeDelegateNotifierImpl::
kExistingUserChromebookAddedPrefName[] =
"multidevice_setup.existing_user_chromebook_added";
// static
const char AccountStatusChangeDelegateNotifierImpl::
kHostPublicKeyFromMostRecentSyncPrefName[] =
"multidevice_setup.host_public_key_from_most_recent_sync";
AccountStatusChangeDelegateNotifierImpl::
AccountStatusChangeDelegateNotifierImpl(
device_sync::DeviceSyncClient* device_sync_client,
PrefService* pref_service,
SetupFlowCompletionRecorder* setup_flow_completion_recorder,
base::Clock* clock)
: device_sync_client_(device_sync_client),
pref_service_(pref_service),
setup_flow_completion_recorder_(setup_flow_completion_recorder),
clock_(clock) {
host_public_key_from_most_recent_sync_ =
LoadHostPublicKeyFromEndOfPreviousSession();
cryptauth::RemoteDeviceRefList device_ref_list =
device_sync_client_->GetSyncedDevices();
base::Optional<cryptauth::RemoteDeviceRef> local_device =
device_sync_client_->GetLocalDeviceMetadata();
// AccountStatusChangeDelegateNotifierImpl should not be constructed if
// DeviceSyncClient is not initialized.
DCHECK(local_device);
local_device_is_enabled_client_ =
local_device->GetSoftwareFeatureState(
cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT) ==
cryptauth::SoftwareFeatureState::kEnabled;
device_sync_client_->AddObserver(this);
}
void AccountStatusChangeDelegateNotifierImpl::OnNewDevicesSynced() {
CheckForMultiDeviceEvents();
}
void AccountStatusChangeDelegateNotifierImpl::CheckForMultiDeviceEvents() {
if (!delegate()) {
PA_LOG(INFO) << "AccountStatusChangeDelegateNotifierImpl::"
<< "CheckForMultiDeviceEvents(): Tried to check for potential "
<< "events, but no delegate was set.";
return;
}
cryptauth::RemoteDeviceRefList device_ref_list =
device_sync_client_->GetSyncedDevices();
// Track and update host info.
base::Optional<std::string> host_public_key_before_sync =
host_public_key_from_most_recent_sync_;
host_public_key_from_most_recent_sync_ = GetHostPublicKey(device_ref_list);
if (host_public_key_from_most_recent_sync_)
pref_service_->SetString(kHostPublicKeyFromMostRecentSyncPrefName,
*host_public_key_from_most_recent_sync_);
// Track and update local client info.
bool local_device_was_enabled_client_before_sync =
local_device_is_enabled_client_;
local_device_is_enabled_client_ =
device_sync_client_->GetLocalDeviceMetadata()->GetSoftwareFeatureState(
cryptauth::SoftwareFeature::BETTER_TOGETHER_CLIENT) ==
cryptauth::SoftwareFeatureState::kEnabled;
CheckForNewUserPotentialHostExistsEvent(device_ref_list);
CheckForExistingUserHostSwitchedEvent(host_public_key_before_sync);
CheckForExistingUserChromebookAddedEvent(
local_device_was_enabled_client_before_sync);
}
void AccountStatusChangeDelegateNotifierImpl::
CheckForNewUserPotentialHostExistsEvent(
const cryptauth::RemoteDeviceRefList& device_ref_list) {
// We only check for new user events if there is no enabled host.
if (host_public_key_from_most_recent_sync_)
return;
// If the observer has been notified of this event before, the user is not
// new.
if (pref_service_->GetInt64(kNewUserPotentialHostExistsPrefName) !=
kTimestampNotSet) {
return;
}
for (const auto& device_ref : device_ref_list) {
if (device_ref.GetSoftwareFeatureState(
cryptauth::SoftwareFeature::BETTER_TOGETHER_HOST) !=
cryptauth::SoftwareFeatureState::kSupported) {
continue;
}
delegate()->OnPotentialHostExistsForNewUser();
pref_service_->SetInt64(kNewUserPotentialHostExistsPrefName,
clock_->Now().ToJavaTime());
return;
}
}
void AccountStatusChangeDelegateNotifierImpl::
CheckForExistingUserHostSwitchedEvent(
base::Optional<std::string> host_public_key_before_sync) {
// If the local device is not an enabled client, the account's new host is not
// yet the local device's new host.
if (!local_device_is_enabled_client_)
return;
// The host switched event requires both a pre-sync and a post-sync host.
if (!host_public_key_from_most_recent_sync_ || !host_public_key_before_sync)
return;
// If the host stayed the same, there was no switch.
if (host_public_key_before_sync == host_public_key_from_most_recent_sync_)
return;
delegate()->OnConnectedHostSwitchedForExistingUser();
pref_service_->SetInt64(kExistingUserHostSwitchedPrefName,
clock_->Now().ToJavaTime());
}
void AccountStatusChangeDelegateNotifierImpl::
CheckForExistingUserChromebookAddedEvent(
bool local_device_was_enabled_client_before_sync) {
// The chromebook added event requires that the local device changed its
// client status in the sync from not being enabled to being enabled.
if (!local_device_is_enabled_client_ ||
local_device_was_enabled_client_before_sync) {
return;
}
// This event only applies if the user completed the setup flow on a different
// device.
if (setup_flow_completion_recorder_->GetCompletionTimestamp())
return;
// Without an enabled host, the local device cannot be an enabled client.
DCHECK(host_public_key_from_most_recent_sync_);
delegate()->OnNewChromebookAddedForExistingUser();
pref_service_->SetInt64(kExistingUserChromebookAddedPrefName,
clock_->Now().ToJavaTime());
}
base::Optional<std::string> AccountStatusChangeDelegateNotifierImpl::
LoadHostPublicKeyFromEndOfPreviousSession() {
std::string host_public_key_from_most_recent_sync =
pref_service_->GetString(kHostPublicKeyFromMostRecentSyncPrefName);
if (host_public_key_from_most_recent_sync.empty())
return base::nullopt;
return host_public_key_from_most_recent_sync;
}
} // namespace multidevice_setup
} // namespace chromeos