blob: 2a084ea37d2f8f17ad813f8eada4bbae467e10f0 [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 "chrome/browser/chromeos/policy/app_install_event_logger.h"
#include <stdint.h>
#include <algorithm>
#include <iterator>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/chromeos/arc/arc_util.h"
#include "chrome/browser/chromeos/arc/policy/arc_policy_util.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/disks/disk.h"
#include "chromeos/disks/disk_mount_manager.h"
#include "components/arc/arc_prefs.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
namespace em = enterprise_management;
namespace policy {
namespace {
constexpr int kNonComplianceReasonAppNotInstalled = 5;
std::set<std::string> GetRequestedPackagesFromPolicy(
const policy::PolicyMap& policy) {
const base::Value* const arc_enabled = policy.GetValue(key::kArcEnabled);
if (!arc_enabled || !arc_enabled->is_bool() || !arc_enabled->GetBool()) {
return {};
}
const base::Value* const arc_policy = policy.GetValue(key::kArcPolicy);
if (!arc_policy || !arc_policy->is_string()) {
return {};
}
return arc::policy_util::GetRequestedPackagesFromArcPolicy(
arc_policy->GetString());
}
// Return all elements that are members of |first| but not |second|.
std::set<std::string> GetDifference(const std::set<std::string>& first,
const std::set<std::string>& second) {
std::set<std::string> difference;
std::set_difference(first.begin(), first.end(), second.begin(), second.end(),
std::inserter(difference, difference.end()));
return difference;
}
std::unique_ptr<em::AppInstallReportLogEvent> AddDiskSpaceInfoToEvent(
std::unique_ptr<em::AppInstallReportLogEvent> event) {
for (const auto& disk :
chromeos::disks::DiskMountManager::GetInstance()->disks()) {
if (!disk.second->IsStatefulPartition()) {
continue;
}
const base::FilePath stateful_path(disk.second->mount_path());
const int64_t stateful_total =
base::SysInfo::AmountOfTotalDiskSpace(stateful_path);
if (stateful_total >= 0) {
event->set_stateful_total(stateful_total);
}
const int64_t stateful_free =
base::SysInfo::AmountOfFreeDiskSpace(stateful_path);
if (stateful_free >= 0) {
event->set_stateful_free(stateful_free);
}
break;
}
return event;
}
void EnsureTimestampSet(em::AppInstallReportLogEvent* event) {
if (!event->has_timestamp()) {
event->set_timestamp(
(base::Time::Now() - base::Time::UnixEpoch()).InMicroseconds());
}
}
std::unique_ptr<em::AppInstallReportLogEvent> CreateEvent(
em::AppInstallReportLogEvent::EventType type) {
std::unique_ptr<em::AppInstallReportLogEvent> event =
std::make_unique<em::AppInstallReportLogEvent>();
EnsureTimestampSet(event.get());
event->set_event_type(type);
return event;
}
} // namespace
AppInstallEventLogger::AppInstallEventLogger(Delegate* delegate,
Profile* profile)
: delegate_(delegate), profile_(profile), weak_factory_(this) {
if (!arc::IsArcAllowedForProfile(profile_)) {
AddForSetOfPackages(
GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending),
CreateEvent(em::AppInstallReportLogEvent::CANCELED));
Clear(profile_);
return;
}
policy::PolicyService* const policy_service =
policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile_)
->policy_service();
EvaluatePolicy(policy_service->GetPolicies(policy::PolicyNamespace(
policy::POLICY_DOMAIN_CHROME, std::string())),
true /* initial */);
observing_ = true;
arc::ArcPolicyBridge* bridge =
arc::ArcPolicyBridge::GetForBrowserContext(profile_);
bridge->AddObserver(this);
policy_service->AddObserver(policy::POLICY_DOMAIN_CHROME, this);
}
AppInstallEventLogger::~AppInstallEventLogger() {
if (log_collector_) {
log_collector_->AddLogoutEvent();
}
if (observing_) {
arc::ArcPolicyBridge::GetForBrowserContext(profile_)->RemoveObserver(this);
policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile_)
->policy_service()
->RemoveObserver(policy::POLICY_DOMAIN_CHROME, this);
}
}
// static
void AppInstallEventLogger::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(arc::prefs::kArcPushInstallAppsRequested);
registry->RegisterListPref(arc::prefs::kArcPushInstallAppsPending);
}
// static
void AppInstallEventLogger::Clear(Profile* profile) {
profile->GetPrefs()->ClearPref(arc::prefs::kArcPushInstallAppsRequested);
profile->GetPrefs()->ClearPref(arc::prefs::kArcPushInstallAppsPending);
}
void AppInstallEventLogger::AddForAllPackages(
std::unique_ptr<em::AppInstallReportLogEvent> event) {
EnsureTimestampSet(event.get());
AddForSetOfPackages(
GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending),
std::move(event));
}
void AppInstallEventLogger::Add(
const std::string& package,
bool gather_disk_space_info,
std::unique_ptr<em::AppInstallReportLogEvent> event) {
EnsureTimestampSet(event.get());
if (gather_disk_space_info) {
AddForSetOfPackagesWithDiskSpaceInfo({package}, std::move(event));
} else {
AddForSetOfPackages({package}, std::move(event));
}
}
void AppInstallEventLogger::OnPolicyUpdated(const policy::PolicyNamespace& ns,
const policy::PolicyMap& previous,
const policy::PolicyMap& current) {
EvaluatePolicy(current, false /* initial */);
}
void AppInstallEventLogger::OnPolicySent(const std::string& policy) {
requested_in_arc_ =
arc::policy_util::GetRequestedPackagesFromArcPolicy(policy);
}
void AppInstallEventLogger::OnComplianceReportReceived(
const base::Value* compliance_report) {
const base::Value* const details = compliance_report->FindKeyOfType(
"nonComplianceDetails", base::Value::Type::LIST);
if (!details) {
return;
}
const std::set<std::string> previous_pending =
GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending);
std::set<std::string> pending_in_arc;
for (const auto& detail : details->GetList()) {
const base::Value* const reason =
detail.FindKeyOfType("nonComplianceReason", base::Value::Type::INTEGER);
if (!reason || reason->GetInt() != kNonComplianceReasonAppNotInstalled) {
continue;
}
const base::Value* const app_name =
detail.FindKeyOfType("packageName", base::Value::Type::STRING);
if (!app_name || app_name->GetString().empty()) {
continue;
}
pending_in_arc.insert(app_name->GetString());
}
const std::set<std::string> current_pending = GetDifference(
previous_pending, GetDifference(requested_in_arc_, pending_in_arc));
const std::set<std::string> removed =
GetDifference(previous_pending, current_pending);
AddForSetOfPackagesWithDiskSpaceInfo(
removed, CreateEvent(em::AppInstallReportLogEvent::SUCCESS));
if (removed.empty()) {
return;
}
SetPref(arc::prefs::kArcPushInstallAppsPending, current_pending);
if (!current_pending.empty()) {
UpdateCollector(current_pending);
} else {
StopCollector();
}
}
std::set<std::string> AppInstallEventLogger::GetPackagesFromPref(
const std::string& pref_name) const {
std::set<std::string> packages;
for (const auto& package :
profile_->GetPrefs()->GetList(pref_name)->GetList()) {
if (!package.is_string()) {
continue;
}
packages.insert(package.GetString());
}
return packages;
}
void AppInstallEventLogger::SetPref(const std::string& pref_name,
const std::set<std::string>& packages) {
base::Value value(base::Value::Type::LIST);
auto& list = value.GetList();
for (const std::string& package : packages) {
list.push_back(base::Value(package));
}
profile_->GetPrefs()->Set(pref_name, value);
}
void AppInstallEventLogger::UpdateCollector(
const std::set<std::string>& pending) {
if (!log_collector_) {
log_collector_ =
std::make_unique<AppInstallEventLogCollector>(this, profile_, pending);
} else {
log_collector_->OnPendingPackagesChanged(pending);
}
}
void AppInstallEventLogger::StopCollector() {
log_collector_.reset();
}
void AppInstallEventLogger::EvaluatePolicy(const policy::PolicyMap& policy,
bool initial) {
const std::set<std::string> previous_requested =
GetPackagesFromPref(arc::prefs::kArcPushInstallAppsRequested);
const std::set<std::string> previous_pending =
GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending);
const std::set<std::string> current_requested =
GetRequestedPackagesFromPolicy(policy);
const std::set<std::string> added =
GetDifference(current_requested, previous_requested);
const std::set<std::string> removed =
GetDifference(previous_pending, current_requested);
AddForSetOfPackagesWithDiskSpaceInfo(
added, CreateEvent(em::AppInstallReportLogEvent::SERVER_REQUEST));
AddForSetOfPackages(removed,
CreateEvent(em::AppInstallReportLogEvent::CANCELED));
const std::set<std::string> current_pending = GetDifference(
current_requested, GetDifference(previous_requested, previous_pending));
SetPref(arc::prefs::kArcPushInstallAppsRequested, current_requested);
SetPref(arc::prefs::kArcPushInstallAppsPending, current_pending);
if (!current_pending.empty()) {
UpdateCollector(current_pending);
if (initial) {
log_collector_->AddLoginEvent();
}
} else {
StopCollector();
}
}
void AppInstallEventLogger::AddForSetOfPackagesWithDiskSpaceInfo(
const std::set<std::string>& packages,
std::unique_ptr<em::AppInstallReportLogEvent> event) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&AddDiskSpaceInfoToEvent, std::move(event)),
base::BindOnce(&AppInstallEventLogger::AddForSetOfPackages,
weak_factory_.GetWeakPtr(), packages));
}
void AppInstallEventLogger::AddForSetOfPackages(
const std::set<std::string>& packages,
std::unique_ptr<em::AppInstallReportLogEvent> event) {
delegate_->Add(packages, *event);
}
} // namespace policy