blob: 9dec49c2ec25002b291f0af41b76f7f197d8f7eb [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/metrics/per_user_state_manager_chromeos.h"
#include "base/callback_list.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/uuid.h"
#include "chrome/browser/ash/login/login_pref_names.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/browser/ash/settings/stats_reporting_controller.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/metrics/profile_pref_names.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/common/pref_names.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_service_client.h"
#include "components/metrics/unsent_log_store_metrics_impl.h"
#include "components/user_manager/user.h"
namespace metrics {
namespace {
constexpr char kDaemonStorePath[] = "/run/daemon-store/uma-consent/";
constexpr char kDaemonStoreFileName[] = "consent-enabled";
// We want to collect crashes that cause the OS to reboot based on the consent
// of the user logged in at the time the OS rebooted. These "boot collectors"
// can't access the cryptohome of that user, so we track the current consent
// state outside of cryptohome, solely for the purpose of respecting user
// consent while collecting these crashes.
constexpr char kOutOfCryptohomeConsent[] = "/home/chronos/boot-collect-consent";
constexpr char kWriteFileFailMetric[] =
"UMA.CrosPerUser.DaemonStoreWriteFailed";
absl::optional<bool> g_is_managed_for_testing;
std::string GenerateUserId() {
return base::Uuid::GenerateRandomV4().AsLowercaseString();
}
// Keep in sync with PerUserDaemonStoreFail enum.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused
enum class DaemonStoreFailType {
kFailedDisabling = 0,
kFailedEnabling = 1,
kFailedWritingBoot = 2,
kFailedDeletingBoot = 3,
kWritesAttempted = 4,
kRemoveSucceeded = 5,
kMaxValue = kRemoveSucceeded,
};
// Keep in sync with PerUserIdType enum.
enum class IdResetType {
kClientId = 0,
kUserId = 1,
kMaxValue = kUserId,
};
// Keep in sync with enum PerUserIdType.
void RecordIdReset(IdResetType id_type) {
base::UmaHistogramEnumeration("UMA.CrosPerUser.IdReset", id_type);
}
void WriteDaemonStore(base::FilePath path, bool metrics_consent) {
const std::string file_contents = metrics_consent ? "1" : "0";
if (!base::WriteFile(path, file_contents)) {
LOG(ERROR) << "Failed to persist consent change " << file_contents
<< " to daemon-store. State on disk will be inaccurate!";
base::UmaHistogramEnumeration(kWriteFileFailMetric,
metrics_consent
? DaemonStoreFailType::kFailedEnabling
: DaemonStoreFailType::kFailedDisabling);
}
if (!base::WriteFile(base::FilePath(kOutOfCryptohomeConsent),
file_contents)) {
LOG(ERROR) << "Failed to write out-of-cryptohome consent change: "
<< file_contents;
base::UmaHistogramEnumeration(kWriteFileFailMetric,
DaemonStoreFailType::kFailedWritingBoot);
}
base::UmaHistogramEnumeration(kWriteFileFailMetric,
DaemonStoreFailType::kWritesAttempted);
}
void RemoveGlobalMetricsConsent() {
if (!base::DeleteFile(base::FilePath(kOutOfCryptohomeConsent))) {
LOG(ERROR) << "failed to remove out-of-cryptohome consent file";
base::UmaHistogramEnumeration(kWriteFileFailMetric,
DaemonStoreFailType::kFailedDeletingBoot);
} else {
base::UmaHistogramEnumeration(kWriteFileFailMetric,
DaemonStoreFailType::kRemoveSucceeded);
}
}
} // namespace
PerUserStateManagerChromeOS::PerUserStateManagerChromeOS(
MetricsServiceClient* metrics_service_client,
user_manager::UserManager* user_manager,
PrefService* local_state,
const MetricsLogStore::StorageLimits& storage_limits,
const std::string& signing_key)
: metrics_service_client_(metrics_service_client),
user_manager_(user_manager),
local_state_(local_state),
storage_limits_(storage_limits),
signing_key_(signing_key) {
user_manager_->AddSessionStateObserver(this);
user_manager_->AddObserver(this);
// This could be null in very narrow cases during early browser startup
// (PreMainMessageLoopRun) or shutdown (destruction of
// ChromeMainBrowserPartsAsh).
// It's unlikely that we'd be running this constructor that early or that
// late, but just in case, avoid crashing. We won't behave correctly on
// logout, but if there's no termination manager there's not much we can do.
if (ash::SessionTerminationManager::Get()) {
ash::SessionTerminationManager::Get()->AddObserver(this);
}
task_runner_ =
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()});
}
PerUserStateManagerChromeOS::PerUserStateManagerChromeOS(
MetricsServiceClient* metrics_service_client,
PrefService* local_state)
: PerUserStateManagerChromeOS(
metrics_service_client,
user_manager::UserManager::Get(),
local_state,
metrics_service_client->GetStorageLimits(),
metrics_service_client->GetUploadSigningKey()) {
// Ensure that user_manager has been initialized.
DCHECK(user_manager::UserManager::IsInitialized());
}
PerUserStateManagerChromeOS::~PerUserStateManagerChromeOS() {
user_manager_->RemoveObserver(this);
user_manager_->RemoveSessionStateObserver(this);
}
// static
void PerUserStateManagerChromeOS::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterStringPref(prefs::kMetricsCurrentUserId, "");
}
// static
void PerUserStateManagerChromeOS::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(prefs::kMetricsUserMetricLogs);
registry->RegisterDictionaryPref(prefs::kMetricsUserMetricLogsMetadata);
registry->RegisterStringPref(prefs::kMetricsUserId, "");
registry->RegisterBooleanPref(prefs::kMetricsUserConsent, false);
registry->RegisterBooleanPref(prefs::kMetricsRequiresClientIdResetOnConsent,
false);
registry->RegisterBooleanPref(prefs::kMetricsUserInheritOwnerConsent, true);
}
absl::optional<std::string> PerUserStateManagerChromeOS::GetCurrentUserId()
const {
if (state_ != State::USER_LOG_STORE_HANDLED)
return absl::nullopt;
auto user_id = GetCurrentUserPrefs()->GetString(prefs::kMetricsUserId);
if (user_id.empty())
return absl::nullopt;
return user_id;
}
absl::optional<bool>
PerUserStateManagerChromeOS::GetCurrentUserReportingConsentIfApplicable()
const {
if (state_ != State::USER_LOG_STORE_HANDLED || !IsDeviceStatusKnown()) {
return absl::nullopt;
}
// Guest sessions with no device owner should use the guest's metrics
// consent set during guest OOBE flow with no device owner.
bool is_guest_with_no_owner =
current_user_->GetType() == user_manager::USER_TYPE_GUEST &&
!IsDeviceOwned();
// Cases in which user permissions should be applied to metrics reporting.
if (IsUserAllowedToChangeConsent(current_user_) || is_guest_with_no_owner)
return GetCurrentUserPrefs()->GetBoolean(prefs::kMetricsUserConsent);
return absl::nullopt;
}
void PerUserStateManagerChromeOS::SetCurrentUserMetricsConsent(
bool metrics_consent) {
DCHECK_EQ(state_, State::USER_LOG_STORE_HANDLED);
DCHECK(current_user_);
// No-op if user should not be able to change metrics consent.
if (!IsUserAllowedToChangeConsent(current_user_))
return;
auto* user_prefs = GetCurrentUserPrefs();
// Value of |metrics_consent| is the same as the current consent. Terminate
// early.
if (user_prefs->GetBoolean(prefs::kMetricsUserConsent) == metrics_consent)
return;
// |new_user_id| = "" for on->off.
std::string new_user_id;
// off -> on state.
if (metrics_consent) {
new_user_id = GenerateUserId();
}
UpdateCurrentUserId(new_user_id);
SetReportingState(metrics_consent);
UpdateLocalStatePrefs(metrics_consent);
}
bool PerUserStateManagerChromeOS::ShouldUseUserLogStore() const {
DCHECK(state_ > State::CONSTRUCTED);
if (!IsDeviceStatusKnown()) {
LOG(ERROR) << "Device ownership status unknown. Cannot accurately "
"determine whether per-user should be used or not.";
return false;
}
if (user_manager_->IsCurrentUserCryptohomeDataEphemeral()) {
// Sessions using ephemeral cryptohome should hold the logs if owner has
// disabled metrics reporting. This way the recorded logs are deleted when
// the session ends.
if (IsDeviceOwned())
return !GetDeviceMetricsConsent();
// If the device is not owned, use the ephemeral partition.
return true;
}
// Users with persistent cryptohome data should store logs in the user
// cryptohome.
return true;
}
bool PerUserStateManagerChromeOS::IsUserAllowedToChangeConsent(
user_manager::User* user) const {
// Devices with managed policy should not have control to toggle metrics
// consent.
if (IsReportingPolicyManaged())
return false;
// Owner should not use per-user. Owner should use the device local pref.
if (user->GetAccountId() == user_manager_->GetOwnerAccountId())
return false;
// If the device status is not known yet, then the user should not be allowed
// to opt-in until the status is known.
DCHECK(IsDeviceStatusKnown());
if (!IsDeviceStatusKnown()) {
LOG(ERROR) << "Device ownership status unknown. Cannot accurately "
"determine whether per-user should be used or not.";
return false;
}
auto user_type = user->GetType();
// Guest sessions for non-owned devices should be allowed to modify metrics
// consent during the lifetime of the session.
if (user_type == user_manager::USER_TYPE_GUEST)
return !IsDeviceOwned();
// Non-managed devices only have control if owner has enabled metrics
// reporting.
if (!GetDeviceMetricsConsent())
return false;
return user_type == user_manager::USER_TYPE_REGULAR ||
user_type == user_manager::USER_TYPE_ACTIVE_DIRECTORY;
}
base::CallbackListSubscription PerUserStateManagerChromeOS::AddObserver(
const MetricsConsentHandler& callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return callback_list_.Add(callback);
}
// static
void PerUserStateManagerChromeOS::SetIsManagedForTesting(bool is_managed) {
g_is_managed_for_testing = is_managed;
}
void PerUserStateManagerChromeOS::SetUserLogStore(
std::unique_ptr<UnsentLogStore> log_store) {
DCHECK(state_ == State::USER_PROFILE_READY);
metrics_service_client_->GetMetricsService()->SetUserLogStore(
std::move(log_store));
}
void PerUserStateManagerChromeOS::UnsetUserLogStore() {
DCHECK_EQ(state_, State::USER_LOG_STORE_HANDLED);
metrics_service_client_->GetMetricsService()->UnsetUserLogStore();
}
void PerUserStateManagerChromeOS::ForceClientIdReset() {
metrics_service_client_->GetMetricsService()->ResetClientId();
}
void PerUserStateManagerChromeOS::SetReportingState(bool metrics_consent) {
DCHECK_EQ(state_, State::USER_LOG_STORE_HANDLED);
DCHECK(IsUserAllowedToChangeConsent(current_user_));
GetCurrentUserPrefs()->SetBoolean(prefs::kMetricsUserConsent,
metrics_consent);
std::string hash = user_manager_->GetActiveUser()->username_hash();
base::FilePath daemon_store_consent = base::FilePath(kDaemonStorePath)
.Append(hash)
.Append(kDaemonStoreFileName);
// Do this asynchronously so we don't block in a potentially non-blocking
// context.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(WriteDaemonStore, daemon_store_consent, metrics_consent));
NotifyObservers(metrics_consent);
}
bool PerUserStateManagerChromeOS::IsReportingPolicyManaged() const {
if (g_is_managed_for_testing.has_value())
return g_is_managed_for_testing.value();
return metrics_service_client_->IsReportingPolicyManaged();
}
bool PerUserStateManagerChromeOS::GetDeviceMetricsConsent() const {
return ash::StatsReportingController::Get()->IsEnabled();
}
bool PerUserStateManagerChromeOS::HasUserLogStore() const {
return metrics_service_client_->GetMetricsService()->HasUserLogStore();
}
bool PerUserStateManagerChromeOS::IsDeviceOwned() const {
DCHECK(IsDeviceStatusKnown());
return ash::DeviceSettingsService::Get()->GetOwnershipStatus() ==
ash::DeviceSettingsService::OwnershipStatus::OWNERSHIP_TAKEN;
}
bool PerUserStateManagerChromeOS::IsDeviceStatusKnown() const {
return ash::DeviceSettingsService::Get()->GetOwnershipStatus() !=
ash::DeviceSettingsService::OwnershipStatus::OWNERSHIP_UNKNOWN;
}
void PerUserStateManagerChromeOS::ActiveUserChanged(user_manager::User* user) {
// Logged-in user is already detected. Do nothing since multi-user is
// deprecated and since the first user is the primary user.
if (state_ > State::CONSTRUCTED) {
return;
}
state_ = State::USER_LOGIN;
current_user_ = user;
user->AddProfileCreatedObserver(
base::BindOnce(&PerUserStateManagerChromeOS::WaitForOwnershipStatus,
weak_ptr_factory_.GetWeakPtr()));
}
void PerUserStateManagerChromeOS::OnUserToBeRemoved(
const AccountId& account_id) {
auto* user = user_manager_->FindUser(account_id);
// Resets the state of |this| if user to be removed is the current logged in
// user.
if (user && current_user_ == user) {
ResetState();
}
}
void PerUserStateManagerChromeOS::OnSessionWillBeTerminated() {
// A logout will log out *all* signed-in users, so remove the user-specific
// consent file.
task_runner_->PostTask(FROM_HERE, base::BindOnce(RemoveGlobalMetricsConsent));
}
void PerUserStateManagerChromeOS::WaitForOwnershipStatus() {
DCHECK_EQ(state_, State::USER_LOGIN);
// Device ownership determination happens asynchronously in parallel with
// profile loading, so there is a chance that status is not known yet.
ash::DeviceSettingsService::Get()->GetOwnershipStatusAsync(base::BindOnce(
&PerUserStateManagerChromeOS::InitializeProfileMetricsState,
weak_ptr_factory_.GetWeakPtr()));
}
void PerUserStateManagerChromeOS::InitializeProfileMetricsState(
ash::DeviceSettingsService::OwnershipStatus status) {
DCHECK_NE(status, ash::DeviceSettingsService::OWNERSHIP_UNKNOWN);
DCHECK_EQ(state_, State::USER_LOGIN);
state_ = State::USER_PROFILE_READY;
if (ShouldUseUserLogStore()) {
// Sets the metrics log store to one in the user cryptohome. Before this
// happens, all pending logs recorded before the user login will be
// flushed to local state.
AssignUserLogStore();
} else {
DCHECK(!HasUserLogStore());
}
state_ = State::USER_LOG_STORE_HANDLED;
const bool is_guest =
current_user_->GetType() == user_manager::USER_TYPE_GUEST;
// If a guest session is about to be started, the metrics reporting will
// normally inherit from the device owner's setting. If there is no owner,
// then the guest will set metrics reporting during ToS.
//
// Note that device status is guaranteed to be known.
if (is_guest && !IsDeviceOwned()) {
SetReportingState(
local_state_->GetBoolean(ash::prefs::kOobeGuestMetricsEnabled));
// Reset state set during guest OOBE. This should be fine as if the guest
// session crashes, the consent is saved in the guest's profile pref and
// will be loaded correctly.
local_state_->ClearPref(ash::prefs::kOobeGuestMetricsEnabled);
return;
}
// Sets the current logged-in user ID to handle crashes. Assigns a user ID if
// current user has one. Otherwise, clear the pref to reflect that current
// user does not have a user id.
auto user_id = GetCurrentUserId();
if (user_id.has_value()) {
local_state_->SetString(prefs::kMetricsCurrentUserId, user_id.value());
} else {
RecordIdReset(IdResetType::kUserId);
local_state_->ClearPref(prefs::kMetricsCurrentUserId);
}
auto* user_prefs = GetCurrentUserPrefs();
// Inherit owner consent if needed. This is to migrate existing users logging
// in for the first time since the feature was enabled.
//
// New users will inherit the device owner consent initially but will be asked
// on OOBE to set the consent.
bool should_inherit_owner_consent =
user_prefs->GetBoolean(prefs::kMetricsUserInheritOwnerConsent) &&
IsUserAllowedToChangeConsent(current_user_);
if (should_inherit_owner_consent) {
bool device_metrics_reporting = GetDeviceMetricsConsent();
user_prefs->SetBoolean(prefs::kMetricsUserConsent,
device_metrics_reporting);
user_prefs->SetBoolean(prefs::kMetricsUserInheritOwnerConsent, false);
// Generate and set user ID if device owner enabled metrics reporting.
if (device_metrics_reporting) {
UpdateCurrentUserId(GenerateUserId());
UpdateLocalStatePrefs(device_metrics_reporting);
}
}
// Only initialize metrics consent to user metrics consent if user is
// allowed to change the metrics consent.
if (IsUserAllowedToChangeConsent(current_user_)) {
SetReportingState(user_prefs->GetBoolean(prefs::kMetricsUserConsent));
} else {
// Clear out the non-cryptohome consent file, as we shouldn't allow this
// user to set consent. (Either we're the owner, or per-user consent is
// disabled. In either case, this user doesn't have any say over consent
// beyond device policy.)
// In general, the non-cryptohome consent file should be cleared on logout,
// but on an abnormal logout -- e.g. a session-manager crash -- it wouldn't
// be.
task_runner_->PostTask(FROM_HERE,
base::BindOnce(RemoveGlobalMetricsConsent));
}
}
void PerUserStateManagerChromeOS::UpdateCurrentUserId(
const std::string& new_user_id) {
DCHECK_EQ(state_, State::USER_LOG_STORE_HANDLED);
// Guest sessions should not have a user id.
if (current_user_->GetType() == user_manager::USER_TYPE_GUEST) {
GetCurrentUserPrefs()->ClearPref(prefs::kMetricsUserId);
local_state_->ClearPref(prefs::kMetricsCurrentUserId);
return;
}
// Updates both the user profile as well as the user ID stored in local
// state to handle crashes appropriately.
GetCurrentUserPrefs()->SetString(prefs::kMetricsUserId, new_user_id);
local_state_->SetString(prefs::kMetricsCurrentUserId, new_user_id);
}
void PerUserStateManagerChromeOS::ResetState() {
UnsetUserLogStore();
current_user_ = nullptr;
local_state_->ClearPref(prefs::kMetricsCurrentUserId);
state_ = State::CONSTRUCTED;
}
PrefService* PerUserStateManagerChromeOS::GetCurrentUserPrefs() const {
DCHECK(state_ >= State::USER_PROFILE_READY);
return ash::ProfileHelper::Get()->GetProfileByUser(current_user_)->GetPrefs();
}
void PerUserStateManagerChromeOS::AssignUserLogStore() {
SetUserLogStore(std::make_unique<UnsentLogStore>(
std::make_unique<UnsentLogStoreMetricsImpl>(), GetCurrentUserPrefs(),
prefs::kMetricsUserMetricLogs, prefs::kMetricsUserMetricLogsMetadata,
storage_limits_.min_ongoing_log_queue_count,
storage_limits_.min_ongoing_log_queue_size,
storage_limits_.max_ongoing_log_size, signing_key_,
// |logs_event_manager| will be set by the metrics service directly in
// MetricsLogStore::SetAlternateOngoingLogStore().
/*logs_event_manager=*/nullptr));
}
void PerUserStateManagerChromeOS::NotifyObservers(bool metrics_consent) {
callback_list_.Notify(metrics_consent);
}
void PerUserStateManagerChromeOS::UpdateLocalStatePrefs(bool metrics_enabled) {
// If user consent is turned off, client ID does not need to be reset. Only
// profile prefs (ie user_id) should be reset.
if (!metrics_enabled)
return;
auto* user_prefs = GetCurrentUserPrefs();
// TODO(crbug/1266086): In the case that multiple users toggle consent off
// to on, this will cause the client ID to be reset each time, which is not
// necessary. Look for a way to allow resetting client id less eager.
if (user_prefs->GetBoolean(prefs::kMetricsRequiresClientIdResetOnConsent)) {
RecordIdReset(IdResetType::kClientId);
ForceClientIdReset();
} else {
user_prefs->SetBoolean(prefs::kMetricsRequiresClientIdResetOnConsent, true);
}
}
} // namespace metrics