blob: 7ee1f1e8639ff848f0cb48fdc015a481703edebc [file] [log] [blame]
// Copyright 2014 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/login/easy_unlock/easy_unlock_service_regular.h"
#include <stdint.h>
#include <utility>
#include "apps/app_lifetime_monitor_factory.h"
#include "base/base64url.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/json/json_string_value_serializer.h"
#include "base/linux_util.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/system/sys_info.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/cryptauth/chrome_cryptauth_service_factory.h"
#include "chrome/browser/chromeos/login/easy_unlock/chrome_proximity_auth_client.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_key_manager.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_key_names.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_notification_controller.h"
#include "chrome/browser/chromeos/login/session/user_session_manager.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/gcm/gcm_profile_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/webui/chromeos/multidevice_setup/multidevice_setup_dialog.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "chromeos/chromeos_features.h"
#include "chromeos/components/proximity_auth/logging/logging.h"
#include "chromeos/components/proximity_auth/proximity_auth_pref_names.h"
#include "chromeos/components/proximity_auth/proximity_auth_profile_pref_manager.h"
#include "chromeos/components/proximity_auth/proximity_auth_system.h"
#include "chromeos/components/proximity_auth/screenlock_bridge.h"
#include "chromeos/components/proximity_auth/smart_lock_metrics_recorder.h"
#include "chromeos/components/proximity_auth/switches.h"
#include "components/cryptauth/cryptauth_enrollment_manager.h"
#include "components/cryptauth/cryptauth_enrollment_utils.h"
#include "components/cryptauth/cryptauth_gcm_manager_impl.h"
#include "components/cryptauth/local_device_data_provider.h"
#include "components/cryptauth/remote_device_loader.h"
#include "components/cryptauth/secure_message_delegate_impl.h"
#include "components/gcm_driver/gcm_profile_service.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/translate/core/browser/translate_download_manager.h"
#include "components/user_manager/user_manager.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
#include "extensions/browser/event_router.h"
#include "extensions/common/constants.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "services/identity/public/cpp/identity_manager.h"
namespace chromeos {
namespace {
// Key name of the local device permit record dictonary in kEasyUnlockPairing.
const char kKeyPermitAccess[] = "permitAccess";
// Key name of the remote device list in kEasyUnlockPairing.
const char kKeyDevices[] = "devices";
enum class SmartLockToggleFeature { DISABLE = false, ENABLE = true };
// The result of a SmartLock operation.
enum class SmartLockResult { FAILURE = false, SUCCESS = true };
enum class SmartLockEnabledState {
ENABLED = 0,
DISABLED = 1,
UNSET = 2,
COUNT
};
void LogSmartLockEnabledState(SmartLockEnabledState state) {
UMA_HISTOGRAM_ENUMERATION("SmartLock.EnabledState", state,
SmartLockEnabledState::COUNT);
}
} // namespace
EasyUnlockServiceRegular::EasyUnlockServiceRegular(
Profile* profile,
secure_channel::SecureChannelClient* secure_channel_client,
device_sync::DeviceSyncClient* device_sync_client,
multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client)
: EasyUnlockServiceRegular(
profile,
secure_channel_client,
std::make_unique<EasyUnlockNotificationController>(profile),
device_sync_client,
multidevice_setup_client) {}
EasyUnlockServiceRegular::EasyUnlockServiceRegular(
Profile* profile,
secure_channel::SecureChannelClient* secure_channel_client,
std::unique_ptr<EasyUnlockNotificationController> notification_controller,
device_sync::DeviceSyncClient* device_sync_client,
multidevice_setup::MultiDeviceSetupClient* multidevice_setup_client)
: EasyUnlockService(profile, secure_channel_client),
scoped_crypt_auth_device_manager_observer_(this),
will_unlock_using_easy_unlock_(false),
lock_screen_last_shown_timestamp_(base::TimeTicks::Now()),
deferring_device_load_(false),
notification_controller_(std::move(notification_controller)),
device_sync_client_(device_sync_client),
multidevice_setup_client_(multidevice_setup_client),
shown_pairing_changed_notification_(false),
weak_ptr_factory_(this) {
if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
// If |device_sync_client_| is not ready yet, wait for it to call back on
// OnReady().
if (device_sync_client_->is_ready())
OnReady();
device_sync_client_->AddObserver(this);
if (base::FeatureList::IsEnabled(
chromeos::features::kEnableUnifiedMultiDeviceSetup)) {
OnFeatureStatesChanged(multidevice_setup_client_->GetFeatureStates());
multidevice_setup_client_->AddObserver(this);
}
}
}
EasyUnlockServiceRegular::~EasyUnlockServiceRegular() = default;
// TODO(jhawkins): This method with |has_unlock_keys| == true is the only signal
// that SmartLock setup has completed successfully. Make this signal more
// explicit.
void EasyUnlockServiceRegular::LoadRemoteDevices() {
if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi) &&
!device_sync_client_->is_ready()) {
// OnEnrollmentFinished() or OnNewDevicesSynced() will call back on this
// method once |device_sync_client_| is ready.
PA_LOG(VERBOSE) << "DeviceSyncClient is not ready yet, delaying "
"UseLoadedRemoteDevices().";
return;
}
// TODO(crbug.com/894585): Remove this legacy special case after M71.
bool is_in_legacy_host_mode = IsInLegacyHostMode();
pref_manager_->SetIsInLegacyHostMode(is_in_legacy_host_mode);
bool is_in_valid_legacy_host_state =
is_in_legacy_host_mode &&
feature_state_ ==
multidevice_setup::mojom::FeatureState::kUnavailableNoVerifiedHost;
if (base::FeatureList::IsEnabled(
chromeos::features::kEnableUnifiedMultiDeviceSetup) &&
feature_state_ !=
multidevice_setup::mojom::FeatureState::kEnabledByUser &&
!is_in_valid_legacy_host_state) {
// OnFeatureStatesChanged() will call back on this method when feature state
// changes.
PA_LOG(VERBOSE) << "Smart Lock is disabled; aborting.";
SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
base::nullopt /* local_device */);
return;
}
bool has_unlock_keys;
if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
has_unlock_keys = !GetUnlockKeys().empty();
} else {
has_unlock_keys = !GetCryptAuthDeviceManager()->GetUnlockKeys().empty();
}
// TODO(jhawkins): The enabled pref should not be tied to whether unlock keys
// exist; instead, both of these variables should be used to determine
// IsEnabled().
pref_manager_->SetIsEasyUnlockEnabled(has_unlock_keys);
if (has_unlock_keys) {
// If |has_unlock_keys| is true, then the user must have successfully
// completed setup. Track that the IsEasyUnlockEnabled pref is actively set
// by the user, as opposed to passively being set to disabled (the default
// state).
pref_manager_->SetEasyUnlockEnabledStateSet();
LogSmartLockEnabledState(SmartLockEnabledState::ENABLED);
} else {
SetProximityAuthDevices(GetAccountId(), multidevice::RemoteDeviceRefList(),
base::nullopt /* local_device */);
if (pref_manager_->IsEasyUnlockEnabledStateSet()) {
LogSmartLockEnabledState(SmartLockEnabledState::DISABLED);
} else {
LogSmartLockEnabledState(SmartLockEnabledState::UNSET);
}
return;
}
// This code path may be hit by:
// 1. New devices were synced on the lock screen.
// 2. The service was initialized while the login screen is still up.
if (proximity_auth::ScreenlockBridge::Get()->IsLocked()) {
PA_LOG(VERBOSE) << "Deferring device load until screen is unlocked.";
deferring_device_load_ = true;
return;
}
if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
UseLoadedRemoteDevices(GetUnlockKeys());
} else {
remote_device_loader_.reset(new cryptauth::RemoteDeviceLoader(
GetCryptAuthDeviceManager()->GetUnlockKeys(),
proximity_auth_client()->GetAccountId(),
GetCryptAuthEnrollmentManager()->GetUserPrivateKey(),
cryptauth::SecureMessageDelegateImpl::Factory::NewInstance()));
// OnRemoteDevicesLoaded() will call on UseLoadedRemoteDevices() with the
// devices it receives.
remote_device_loader_->Load(
base::Bind(&EasyUnlockServiceRegular::OnRemoteDevicesLoaded,
weak_ptr_factory_.GetWeakPtr()));
}
}
void EasyUnlockServiceRegular::OnRemoteDevicesLoaded(
const multidevice::RemoteDeviceList& remote_devices) {
DCHECK(!base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi));
multidevice::RemoteDeviceRefList remote_device_refs;
for (auto& remote_device : remote_devices) {
remote_device_refs.push_back(multidevice::RemoteDeviceRef(
std::make_shared<multidevice::RemoteDevice>(remote_device)));
}
UseLoadedRemoteDevices(remote_device_refs);
}
void EasyUnlockServiceRegular::UseLoadedRemoteDevices(
const multidevice::RemoteDeviceRefList& remote_devices) {
// When EasyUnlock is enabled, only one EasyUnlock host should exist.
DCHECK(remote_devices.size() == 1u);
SetProximityAuthDevices(
GetAccountId(), remote_devices,
base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)
? device_sync_client_->GetLocalDeviceMetadata()
: base::nullopt);
// We need to store a copy of |local_and_remote_devices| in the TPM, so it can
// be retrieved on the sign-in screen when a user session has not been started
// yet.
// If |chromeos::features::kMultiDeviceApi| is enabled, it will expect a final
// size of 2 (the one remote device, and the local device).
// If |chromeos::features::kMultiDeviceApi| is disabled, it will expect a
// final size of 1 (just the remote device).
// TODO(crbug.com/856380): For historical reasons, the local and remote device
// are persisted together in a list. This is awkward and hacky; they should
// be persisted in a dictionary.
multidevice::RemoteDeviceRefList local_and_remote_devices;
local_and_remote_devices.push_back(remote_devices[0]);
if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
local_and_remote_devices.push_back(
*device_sync_client_->GetLocalDeviceMetadata());
}
std::unique_ptr<base::ListValue> device_list(new base::ListValue());
for (const auto& device : local_and_remote_devices) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
std::string b64_public_key, b64_psk;
base::Base64UrlEncode(device.public_key(),
base::Base64UrlEncodePolicy::INCLUDE_PADDING,
&b64_public_key);
base::Base64UrlEncode(device.persistent_symmetric_key(),
base::Base64UrlEncodePolicy::INCLUDE_PADDING,
&b64_psk);
dict->SetString(key_names::kKeyPsk, b64_psk);
// TODO(jhawkins): Remove the bluetoothAddress field from this proto.
dict->SetString(key_names::kKeyBluetoothAddress, std::string());
if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
dict->SetString(
key_names::kKeyPermitPermitId,
base::StringPrintf(
key_names::kPermitPermitIdFormat,
gaia::CanonicalizeEmail(GetAccountId().GetUserEmail()).c_str()));
} else {
dict->SetString(
key_names::kKeyPermitPermitId,
base::StringPrintf(key_names::kPermitPermitIdFormat,
proximity_auth_client()->GetAccountId().c_str()));
}
dict->SetString(key_names::kKeyPermitId, b64_public_key);
dict->SetString(key_names::kKeyPermitType, key_names::kPermitTypeLicence);
dict->SetString(key_names::kKeyPermitData, b64_public_key);
std::unique_ptr<base::ListValue> beacon_seed_list(new base::ListValue());
for (const auto& beacon_seed : device.beacon_seeds()) {
std::string b64_beacon_seed;
base::Base64UrlEncode(
multidevice::ToCryptAuthSeed(beacon_seed).SerializeAsString(),
base::Base64UrlEncodePolicy::INCLUDE_PADDING, &b64_beacon_seed);
beacon_seed_list->AppendString(b64_beacon_seed);
}
std::string serialized_beacon_seeds;
JSONStringValueSerializer serializer(&serialized_beacon_seeds);
serializer.Serialize(*beacon_seed_list);
dict->SetString(key_names::kKeySerializedBeaconSeeds,
serialized_beacon_seeds);
// This differentiates the local device from the remote device.
bool unlock_key = device.GetSoftwareFeatureState(
multidevice::SoftwareFeature::kSmartLockHost) ==
multidevice::SoftwareFeatureState::kEnabled;
dict->SetBoolean(key_names::kKeyUnlockKey, unlock_key);
device_list->Append(std::move(dict));
}
// TODO(tengs): Rename this function after the easy_unlock app is replaced.
SetRemoteDevices(*device_list);
}
proximity_auth::ProximityAuthPrefManager*
EasyUnlockServiceRegular::GetProximityAuthPrefManager() {
return pref_manager_.get();
}
EasyUnlockService::Type EasyUnlockServiceRegular::GetType() const {
return EasyUnlockService::TYPE_REGULAR;
}
AccountId EasyUnlockServiceRegular::GetAccountId() const {
identity::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile());
// |profile| has to be a signed-in profile with IdentityManager already
// created. Otherwise, just crash to collect stack.
DCHECK(identity_manager);
const AccountInfo account_info = identity_manager->GetPrimaryAccountInfo();
// A regular signed-in (i.e., non-login) profile should always have an email.
// TODO(crbug.com/857494): Enable this DCHECK once all browser tests create
// correctly signed in profiles.
// DCHECK(!account_info.email.empty());
return account_info.email.empty()
? EmptyAccountId()
: AccountId::FromUserEmailGaiaId(
gaia::CanonicalizeEmail(account_info.email),
account_info.gaia);
}
void EasyUnlockServiceRegular::SetHardlockAfterKeyOperation(
EasyUnlockScreenlockStateHandler::HardlockState state_on_success,
bool success) {
if (success)
SetHardlockStateForUser(GetAccountId(), state_on_success);
// Even if the refresh keys operation suceeded, we still fetch and check the
// cryptohome keys against the keys in local preferences as a sanity check.
CheckCryptohomeKeysAndMaybeHardlock();
}
void EasyUnlockServiceRegular::ClearPermitAccess() {
DictionaryPrefUpdate pairing_update(profile()->GetPrefs(),
prefs::kEasyUnlockPairing);
pairing_update->RemoveWithoutPathExpansion(kKeyPermitAccess, NULL);
}
const base::ListValue* EasyUnlockServiceRegular::GetRemoteDevices() const {
const base::DictionaryValue* pairing_dict =
profile()->GetPrefs()->GetDictionary(prefs::kEasyUnlockPairing);
const base::ListValue* devices = NULL;
if (pairing_dict && pairing_dict->GetList(kKeyDevices, &devices))
return devices;
return NULL;
}
void EasyUnlockServiceRegular::SetRemoteDevices(
const base::ListValue& devices) {
std::string remote_devices_json;
JSONStringValueSerializer serializer(&remote_devices_json);
serializer.Serialize(devices);
PA_LOG(VERBOSE) << "Setting RemoteDevices:\n " << remote_devices_json;
DictionaryPrefUpdate pairing_update(profile()->GetPrefs(),
prefs::kEasyUnlockPairing);
if (devices.empty())
pairing_update->RemoveWithoutPathExpansion(kKeyDevices, NULL);
else
pairing_update->SetKey(kKeyDevices, devices.Clone());
RefreshCryptohomeKeysIfPossible();
}
std::string EasyUnlockServiceRegular::GetChallenge() const {
return std::string();
}
std::string EasyUnlockServiceRegular::GetWrappedSecret() const {
return std::string();
}
void EasyUnlockServiceRegular::RecordEasySignInOutcome(
const AccountId& account_id,
bool success) const {
NOTREACHED();
}
void EasyUnlockServiceRegular::RecordPasswordLoginEvent(
const AccountId& account_id) const {
NOTREACHED();
}
void EasyUnlockServiceRegular::InitializeInternal() {
proximity_auth::ScreenlockBridge::Get()->AddObserver(this);
pref_manager_.reset(new proximity_auth::ProximityAuthProfilePrefManager(
profile()->GetPrefs(), multidevice_setup_client_));
// TODO(tengs): Due to badly configured browser_tests, Chrome crashes during
// shutdown. Revisit this condition after migration is fully completed.
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) {
// Note: There is no local state in tests.
if (g_browser_process->local_state()) {
pref_manager_->StartSyncingToLocalState(g_browser_process->local_state(),
GetAccountId());
}
if (!base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
scoped_crypt_auth_device_manager_observer_.Add(
GetCryptAuthDeviceManager());
}
LoadRemoteDevices();
}
registrar_.Init(profile()->GetPrefs());
registrar_.Add(
proximity_auth::prefs::kProximityAuthIsChromeOSLoginEnabled,
base::Bind(&EasyUnlockServiceRegular::RefreshCryptohomeKeysIfPossible,
weak_ptr_factory_.GetWeakPtr()));
}
void EasyUnlockServiceRegular::ShutdownInternal() {
pref_manager_.reset();
proximity_auth::ScreenlockBridge::Get()->RemoveObserver(this);
if (!base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
scoped_crypt_auth_device_manager_observer_.RemoveAll();
}
registrar_.RemoveAll();
if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi)) {
device_sync_client_->RemoveObserver(this);
if (base::FeatureList::IsEnabled(
chromeos::features::kEnableUnifiedMultiDeviceSetup)) {
multidevice_setup_client_->RemoveObserver(this);
}
}
}
bool EasyUnlockServiceRegular::IsAllowedInternal() const {
user_manager::UserManager* user_manager = user_manager::UserManager::Get();
if (!user_manager->IsLoggedInAsUserWithGaiaAccount())
return false;
// TODO(tengs): Ephemeral accounts generate a new enrollment every time they
// are added, so disable Smart Lock to reduce enrollments on server. However,
// ephemeral accounts can be locked, so we should revisit this use case.
if (user_manager->IsCurrentUserNonCryptohomeDataEphemeral())
return false;
if (!ProfileHelper::IsPrimaryProfile(profile()))
return false;
if (base::FeatureList::IsEnabled(
chromeos::features::kEnableUnifiedMultiDeviceSetup) &&
feature_state_ ==
multidevice_setup::mojom::FeatureState::kProhibitedByPolicy) {
return false;
}
if (!profile()->GetPrefs()->GetBoolean(prefs::kEasyUnlockAllowed))
return false;
return true;
}
bool EasyUnlockServiceRegular::IsEnabled() const {
// TODO(crbug.com/894585): Remove the legacy special case after M71.
if (base::FeatureList::IsEnabled(
chromeos::features::kEnableUnifiedMultiDeviceSetup) &&
!IsInLegacyHostMode()) {
return feature_state_ ==
multidevice_setup::mojom::FeatureState::kEnabledByUser;
}
return pref_manager_ && pref_manager_->IsEasyUnlockEnabled();
}
bool EasyUnlockServiceRegular::IsChromeOSLoginEnabled() const {
return pref_manager_ && pref_manager_->IsChromeOSLoginEnabled();
}
bool EasyUnlockServiceRegular::IsInLegacyHostMode() const {
if (!base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi))
return false;
if (!device_sync_client_->is_ready()) {
PA_LOG(WARNING) << "EasyUnlockServiceRegular::IsInLegacyHostMode: "
<< "DeviceSyncClient not ready. Returning false.";
return false;
}
bool has_supported_easy_unlock_host = false;
for (const multidevice::RemoteDeviceRef& remote_device_ref :
device_sync_client_->GetSyncedDevices()) {
multidevice::SoftwareFeatureState better_together_host_state =
remote_device_ref.GetSoftwareFeatureState(
multidevice::SoftwareFeature::kBetterTogetherHost);
// If there's any valid Better Together host, don't support legacy mode.
if (better_together_host_state ==
multidevice::SoftwareFeatureState::kSupported ||
better_together_host_state ==
multidevice::SoftwareFeatureState::kEnabled) {
return false;
}
multidevice::SoftwareFeatureState easy_unlock_host_state =
remote_device_ref.GetSoftwareFeatureState(
multidevice::SoftwareFeature::kSmartLockHost);
if (easy_unlock_host_state ==
multidevice::SoftwareFeatureState::kSupported ||
easy_unlock_host_state == multidevice::SoftwareFeatureState::kEnabled) {
has_supported_easy_unlock_host = true;
}
}
return has_supported_easy_unlock_host;
}
void EasyUnlockServiceRegular::OnWillFinalizeUnlock(bool success) {
will_unlock_using_easy_unlock_ = success;
}
void EasyUnlockServiceRegular::OnSuspendDoneInternal() {
lock_screen_last_shown_timestamp_ = base::TimeTicks::Now();
}
void EasyUnlockServiceRegular::OnSyncStarted() {
unlock_keys_before_sync_ = GetCryptAuthDeviceManager()->GetUnlockKeys();
}
void EasyUnlockServiceRegular::OnSyncFinished(
cryptauth::CryptAuthDeviceManager::SyncResult sync_result,
cryptauth::CryptAuthDeviceManager::DeviceChangeResult
device_change_result) {
DCHECK(!base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi));
if (sync_result == cryptauth::CryptAuthDeviceManager::SyncResult::FAILURE)
return;
std::set<std::string> public_keys_before_sync;
for (const auto& device_info : unlock_keys_before_sync_) {
public_keys_before_sync.insert(device_info.public_key());
}
unlock_keys_before_sync_.clear();
std::vector<cryptauth::ExternalDeviceInfo> unlock_keys_after_sync =
GetCryptAuthDeviceManager()->GetUnlockKeys();
std::set<std::string> public_keys_after_sync;
for (const auto& device_info : unlock_keys_after_sync) {
public_keys_after_sync.insert(device_info.public_key());
}
ShowNotificationIfNewDevicePresent(public_keys_before_sync,
public_keys_after_sync);
LoadRemoteDevices();
}
void EasyUnlockServiceRegular::OnReady() {
// If the local device and synced devices are ready for the first time,
// establish what the unlock keys were before the next sync. This is necessary
// in order for OnNewDevicesSynced() to determine if new devices were added
// since the last sync.
remote_device_unlock_keys_before_sync_ = GetUnlockKeys();
}
void EasyUnlockServiceRegular::OnEnrollmentFinished() {
// The local device may be ready for the first time, or it may have been
// updated, so reload devices.
LoadRemoteDevices();
}
void EasyUnlockServiceRegular::OnNewDevicesSynced() {
// This method copies EasyUnlockServiceRegular::OnSyncFinished().
// TODO(crbug.com/848956): Remove EasyUnlockServiceRegular::OnSyncFinished().
DCHECK(base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi));
std::set<std::string> public_keys_before_sync;
for (const auto& remote_device : remote_device_unlock_keys_before_sync_) {
public_keys_before_sync.insert(remote_device.public_key());
}
multidevice::RemoteDeviceRefList remote_device_unlock_keys_after_sync =
GetUnlockKeys();
std::set<std::string> public_keys_after_sync;
for (const auto& remote_device : remote_device_unlock_keys_after_sync) {
public_keys_after_sync.insert(remote_device.public_key());
}
ShowNotificationIfNewDevicePresent(public_keys_before_sync,
public_keys_after_sync);
LoadRemoteDevices();
remote_device_unlock_keys_before_sync_ = remote_device_unlock_keys_after_sync;
}
void EasyUnlockServiceRegular::OnFeatureStatesChanged(
const multidevice_setup::MultiDeviceSetupClient::FeatureStatesMap&
feature_states_map) {
// TODO(crbug.com/894585): Remove after M71.
bool is_in_legacy_host_mode = IsInLegacyHostMode();
if (pref_manager_)
pref_manager_->SetIsInLegacyHostMode(is_in_legacy_host_mode);
const auto it =
feature_states_map.find(multidevice_setup::mojom::Feature::kSmartLock);
if (it == feature_states_map.end()) {
feature_state_ =
multidevice_setup::mojom::FeatureState::kUnavailableNoVerifiedHost;
if (!is_in_legacy_host_mode)
return;
} else {
feature_state_ = it->second;
}
// Note: In order to properly start the EasyUnlock app when MultiDeviceSetup
// is enabled, we must ensure that UpdateAppState() gets called when the
// EasyUnlock feature-state changes from kProhibitedByPolicy to
// kUnavailableNoVerifiedHost.
if (is_in_legacy_host_mode)
UpdateAppState();
LoadRemoteDevices();
}
void EasyUnlockServiceRegular::ShowChromebookAddedNotification() {
DCHECK(base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi) &&
base::FeatureList::IsEnabled(
chromeos::features::kEnableUnifiedMultiDeviceSetup));
// The user may have decided to disable Smart Lock or the whole multidevice
// suite immediately after completing setup, so ensure that Smart Lock is
// enabled.
if (feature_state_ == multidevice_setup::mojom::FeatureState::kEnabledByUser)
notification_controller_->ShowChromebookAddedNotification();
}
void EasyUnlockServiceRegular::ShowNotificationIfNewDevicePresent(
const std::set<std::string>& public_keys_before_sync,
const std::set<std::string>& public_keys_after_sync) {
if (public_keys_after_sync.empty())
ClearPermitAccess();
if (public_keys_before_sync == public_keys_after_sync)
return;
// Show the appropriate notification if an unlock key is first synced or if it
// changes an existing key.
// Note: We do not show a notification when EasyUnlock is disabled by sync nor
// if EasyUnlock was enabled through the setup app.
if (!public_keys_after_sync.empty()) {
if (public_keys_before_sync.empty()) {
if (base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi) &&
base::FeatureList::IsEnabled(
chromeos::features::kEnableUnifiedMultiDeviceSetup)) {
multidevice_setup::MultiDeviceSetupDialog* multidevice_setup_dialog =
multidevice_setup::MultiDeviceSetupDialog::Get();
if (multidevice_setup_dialog) {
// Delay showing the "Chromebook added" notification until the
// MultiDeviceSetupDialog is closed.
multidevice_setup_dialog->AddOnCloseCallback(base::BindOnce(
&EasyUnlockServiceRegular::ShowChromebookAddedNotification,
weak_ptr_factory_.GetWeakPtr()));
return;
}
}
notification_controller_->ShowChromebookAddedNotification();
} else {
shown_pairing_changed_notification_ = true;
notification_controller_->ShowPairingChangeNotification();
}
}
}
void EasyUnlockServiceRegular::OnForceSyncCompleted(bool success) {
if (!success)
PA_LOG(WARNING) << "Failed to force device sync.";
}
void EasyUnlockServiceRegular::OnScreenDidLock(
proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
will_unlock_using_easy_unlock_ = false;
lock_screen_last_shown_timestamp_ = base::TimeTicks::Now();
}
void EasyUnlockServiceRegular::OnScreenDidUnlock(
proximity_auth::ScreenlockBridge::LockHandler::ScreenType screen_type) {
// If we tried to load remote devices (e.g. after a sync or the
// service was initialized) while the screen was locked, we can now
// load the new remote devices.
//
// It's important to go through this code path even if unlocking the
// login screen. Because when the service is initialized while the
// user is signing in we need to load the remotes. Otherwise, the
// first time the user locks the screen the feature won't work.
if (deferring_device_load_) {
PA_LOG(VERBOSE) << "Loading deferred devices after screen unlock.";
deferring_device_load_ = false;
LoadRemoteDevices();
}
// Do not process events for the login screen.
if (screen_type != proximity_auth::ScreenlockBridge::LockHandler::LOCK_SCREEN)
return;
if (shown_pairing_changed_notification_) {
shown_pairing_changed_notification_ = false;
if (!GetUnlockKeys().empty()) {
notification_controller_->ShowPairingChangeAppliedNotification(
GetUnlockKeys()[0].name());
}
}
// Only record metrics for users who have enabled the feature.
if (IsEnabled()) {
EasyUnlockAuthEvent event = will_unlock_using_easy_unlock_
? EASY_UNLOCK_SUCCESS
: GetPasswordAuthEvent();
RecordEasyUnlockScreenUnlockEvent(event);
if (will_unlock_using_easy_unlock_ ||
event == PASSWORD_ENTRY_PHONE_LOCKED ||
event == PASSWORD_ENTRY_PHONE_NOT_LOCKABLE ||
event == PASSWORD_ENTRY_RSSI_TOO_LOW ||
event == PASSWORD_ENTRY_PHONE_LOCKED_AND_RSSI_TOO_LOW ||
event == PASSWORD_ENTRY_WITH_AUTHENTICATED_PHONE) {
SmartLockMetricsRecorder::RecordGetRemoteStatusResultUnlockSuccess();
}
if (will_unlock_using_easy_unlock_) {
SmartLockMetricsRecorder::RecordAuthResultUnlockSuccess();
RecordEasyUnlockScreenUnlockDuration(base::TimeTicks::Now() -
lock_screen_last_shown_timestamp_);
} else {
OnUserEnteredPassword();
}
if (event == PASSWORD_ENTRY_BLUETOOTH_CONNECTING) {
SmartLockMetricsRecorder::RecordGetRemoteStatusResultUnlockFailure(
SmartLockMetricsRecorder::
SmartLockGetRemoteStatusResultFailureReason::
kUserEnteredPasswordWhileConnecting);
} else if (event == PASSWORD_ENTRY_NO_BLUETOOTH) {
SmartLockMetricsRecorder::RecordGetRemoteStatusResultUnlockFailure(
SmartLockMetricsRecorder::
SmartLockGetRemoteStatusResultFailureReason::
kUserEnteredPasswordWhileBluetoothDisabled);
}
}
will_unlock_using_easy_unlock_ = false;
}
void EasyUnlockServiceRegular::OnFocusedUserChanged(
const AccountId& account_id) {
// Nothing to do.
}
cryptauth::CryptAuthEnrollmentManager*
EasyUnlockServiceRegular::GetCryptAuthEnrollmentManager() {
DCHECK(!base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi));
cryptauth::CryptAuthEnrollmentManager* manager =
ChromeCryptAuthServiceFactory::GetInstance()
->GetForBrowserContext(profile())
->GetCryptAuthEnrollmentManager();
DCHECK(manager);
return manager;
}
cryptauth::CryptAuthDeviceManager*
EasyUnlockServiceRegular::GetCryptAuthDeviceManager() {
DCHECK(!base::FeatureList::IsEnabled(chromeos::features::kMultiDeviceApi));
cryptauth::CryptAuthDeviceManager* manager =
ChromeCryptAuthServiceFactory::GetInstance()
->GetForBrowserContext(profile())
->GetCryptAuthDeviceManager();
DCHECK(manager);
return manager;
}
void EasyUnlockServiceRegular::RefreshCryptohomeKeysIfPossible() {
CheckCryptohomeKeysAndMaybeHardlock();
}
multidevice::RemoteDeviceRefList EasyUnlockServiceRegular::GetUnlockKeys() {
multidevice::RemoteDeviceRefList unlock_keys;
for (const auto& remote_device : device_sync_client_->GetSyncedDevices()) {
bool unlock_key = remote_device.GetSoftwareFeatureState(
multidevice::SoftwareFeature::kSmartLockHost) ==
multidevice::SoftwareFeatureState::kEnabled;
if (unlock_key)
unlock_keys.push_back(remote_device);
}
return unlock_keys;
}
} // namespace chromeos