1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/chromeos/policy/app_install_event_logger.h"
6 
7 #include <stdint.h>
8 
9 #include <algorithm>
10 #include <iterator>
11 
12 #include "base/bind.h"
13 #include "base/files/file_path.h"
14 #include "base/location.h"
15 #include "base/system/sys_info.h"
16 #include "base/task/post_task.h"
17 #include "base/task/task_traits.h"
18 #include "base/task/thread_pool.h"
19 #include "base/time/time.h"
20 #include "base/values.h"
21 #include "chrome/browser/chromeos/arc/arc_util.h"
22 #include "chrome/browser/chromeos/arc/policy/arc_policy_util.h"
23 #include "chrome/browser/policy/profile_policy_connector.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chromeos/disks/disk.h"
26 #include "chromeos/disks/disk_mount_manager.h"
27 #include "components/arc/arc_prefs.h"
28 #include "components/policy/core/common/policy_map.h"
29 #include "components/policy/core/common/policy_namespace.h"
30 #include "components/policy/policy_constants.h"
31 #include "components/policy/proto/device_management_backend.pb.h"
32 #include "components/pref_registry/pref_registry_syncable.h"
33 #include "components/prefs/pref_service.h"
34 
35 namespace em = enterprise_management;
36 
37 namespace policy {
38 
39 namespace {
40 
41 constexpr int kNonComplianceReasonAppNotInstalled = 5;
42 
GetRequestedPackagesFromPolicy(const policy::PolicyMap & policy)43 std::set<std::string> GetRequestedPackagesFromPolicy(
44     const policy::PolicyMap& policy) {
45   const base::Value* const arc_enabled = policy.GetValue(key::kArcEnabled);
46   if (!arc_enabled || !arc_enabled->is_bool() || !arc_enabled->GetBool()) {
47     return {};
48   }
49 
50   const base::Value* const arc_policy = policy.GetValue(key::kArcPolicy);
51   if (!arc_policy || !arc_policy->is_string()) {
52     return {};
53   }
54 
55   return arc::policy_util::GetRequestedPackagesFromArcPolicy(
56       arc_policy->GetString());
57 }
58 
59 // Return all elements that are members of |first| but not |second|.
GetDifference(const std::set<std::string> & first,const std::set<std::string> & second)60 std::set<std::string> GetDifference(const std::set<std::string>& first,
61                                     const std::set<std::string>& second) {
62   std::set<std::string> difference;
63   std::set_difference(first.begin(), first.end(), second.begin(), second.end(),
64                       std::inserter(difference, difference.end()));
65   return difference;
66 }
67 
AddDiskSpaceInfoToEvent(std::unique_ptr<em::AppInstallReportLogEvent> event)68 std::unique_ptr<em::AppInstallReportLogEvent> AddDiskSpaceInfoToEvent(
69     std::unique_ptr<em::AppInstallReportLogEvent> event) {
70   for (const auto& disk :
71        chromeos::disks::DiskMountManager::GetInstance()->disks()) {
72     if (!disk.second->IsStatefulPartition()) {
73       continue;
74     }
75     const base::FilePath stateful_path(disk.second->mount_path());
76     const int64_t stateful_total =
77         base::SysInfo::AmountOfTotalDiskSpace(stateful_path);
78     if (stateful_total >= 0) {
79       event->set_stateful_total(stateful_total);
80     }
81     const int64_t stateful_free =
82         base::SysInfo::AmountOfFreeDiskSpace(stateful_path);
83     if (stateful_free >= 0) {
84       event->set_stateful_free(stateful_free);
85     }
86     break;
87   }
88   return event;
89 }
90 
EnsureTimestampSet(em::AppInstallReportLogEvent * event)91 void EnsureTimestampSet(em::AppInstallReportLogEvent* event) {
92   if (!event->has_timestamp()) {
93     event->set_timestamp(
94         (base::Time::Now() - base::Time::UnixEpoch()).InMicroseconds());
95   }
96 }
97 
CreateEvent(em::AppInstallReportLogEvent::EventType type)98 std::unique_ptr<em::AppInstallReportLogEvent> CreateEvent(
99     em::AppInstallReportLogEvent::EventType type) {
100   std::unique_ptr<em::AppInstallReportLogEvent> event =
101       std::make_unique<em::AppInstallReportLogEvent>();
102   EnsureTimestampSet(event.get());
103   event->set_event_type(type);
104   return event;
105 }
106 
107 }  // namespace
108 
AppInstallEventLogger(Delegate * delegate,Profile * profile)109 AppInstallEventLogger::AppInstallEventLogger(Delegate* delegate,
110                                              Profile* profile)
111     : delegate_(delegate), profile_(profile) {
112   if (!arc::IsArcAllowedForProfile(profile_)) {
113     AddForSetOfPackages(
114         GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending),
115         CreateEvent(em::AppInstallReportLogEvent::CANCELED));
116     Clear(profile_);
117     return;
118   }
119 
120   policy::PolicyService* const policy_service =
121       profile_->GetProfilePolicyConnector()->policy_service();
122   EvaluatePolicy(policy_service->GetPolicies(policy::PolicyNamespace(
123                      policy::POLICY_DOMAIN_CHROME, std::string())),
124                  true /* initial */);
125 
126   observing_ = true;
127   arc::ArcPolicyBridge* bridge =
128       arc::ArcPolicyBridge::GetForBrowserContext(profile_);
129   bridge->AddObserver(this);
130   policy_service->AddObserver(policy::POLICY_DOMAIN_CHROME, this);
131 }
132 
~AppInstallEventLogger()133 AppInstallEventLogger::~AppInstallEventLogger() {
134   if (log_collector_) {
135     log_collector_->AddLogoutEvent();
136   }
137   if (observing_) {
138     arc::ArcPolicyBridge::GetForBrowserContext(profile_)->RemoveObserver(this);
139     profile_->GetProfilePolicyConnector()->policy_service()->RemoveObserver(
140         policy::POLICY_DOMAIN_CHROME, this);
141   }
142 }
143 
144 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)145 void AppInstallEventLogger::RegisterProfilePrefs(
146     user_prefs::PrefRegistrySyncable* registry) {
147   registry->RegisterListPref(arc::prefs::kArcPushInstallAppsRequested);
148   registry->RegisterListPref(arc::prefs::kArcPushInstallAppsPending);
149 }
150 
151 // static
Clear(Profile * profile)152 void AppInstallEventLogger::Clear(Profile* profile) {
153   profile->GetPrefs()->ClearPref(arc::prefs::kArcPushInstallAppsRequested);
154   profile->GetPrefs()->ClearPref(arc::prefs::kArcPushInstallAppsPending);
155 }
156 
AddForAllPackages(std::unique_ptr<em::AppInstallReportLogEvent> event)157 void AppInstallEventLogger::AddForAllPackages(
158     std::unique_ptr<em::AppInstallReportLogEvent> event) {
159   EnsureTimestampSet(event.get());
160   AddForSetOfPackages(
161       GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending),
162       std::move(event));
163 }
164 
Add(const std::string & package,bool gather_disk_space_info,std::unique_ptr<em::AppInstallReportLogEvent> event)165 void AppInstallEventLogger::Add(
166     const std::string& package,
167     bool gather_disk_space_info,
168     std::unique_ptr<em::AppInstallReportLogEvent> event) {
169   EnsureTimestampSet(event.get());
170   if (gather_disk_space_info) {
171     AddForSetOfPackagesWithDiskSpaceInfo({package}, std::move(event));
172   } else {
173     AddForSetOfPackages({package}, std::move(event));
174   }
175 }
176 
OnPolicyUpdated(const policy::PolicyNamespace & ns,const policy::PolicyMap & previous,const policy::PolicyMap & current)177 void AppInstallEventLogger::OnPolicyUpdated(const policy::PolicyNamespace& ns,
178                                             const policy::PolicyMap& previous,
179                                             const policy::PolicyMap& current) {
180   EvaluatePolicy(current, false /* initial */);
181 }
182 
OnPolicySent(const std::string & policy)183 void AppInstallEventLogger::OnPolicySent(const std::string& policy) {
184   requested_in_arc_ =
185       arc::policy_util::GetRequestedPackagesFromArcPolicy(policy);
186 }
187 
OnComplianceReportReceived(const base::Value * compliance_report)188 void AppInstallEventLogger::OnComplianceReportReceived(
189     const base::Value* compliance_report) {
190   const base::Value* const details = compliance_report->FindKeyOfType(
191       "nonComplianceDetails", base::Value::Type::LIST);
192   if (!details) {
193     return;
194   }
195 
196   const std::set<std::string> previous_pending =
197       GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending);
198 
199   std::set<std::string> pending_in_arc;
200   for (const auto& detail : details->GetList()) {
201     const base::Value* const reason =
202         detail.FindKeyOfType("nonComplianceReason", base::Value::Type::INTEGER);
203     if (!reason || reason->GetInt() != kNonComplianceReasonAppNotInstalled) {
204       continue;
205     }
206     const base::Value* const app_name =
207         detail.FindKeyOfType("packageName", base::Value::Type::STRING);
208     if (!app_name || app_name->GetString().empty()) {
209       continue;
210     }
211     pending_in_arc.insert(app_name->GetString());
212   }
213   const std::set<std::string> current_pending = GetDifference(
214       previous_pending, GetDifference(requested_in_arc_, pending_in_arc));
215   const std::set<std::string> removed =
216       GetDifference(previous_pending, current_pending);
217   AddForSetOfPackagesWithDiskSpaceInfo(
218       removed, CreateEvent(em::AppInstallReportLogEvent::SUCCESS));
219 
220   if (removed.empty()) {
221     return;
222   }
223 
224   SetPref(arc::prefs::kArcPushInstallAppsPending, current_pending);
225 
226   if (!current_pending.empty()) {
227     UpdateCollector(current_pending);
228   } else {
229     StopCollector();
230   }
231 }
232 
GetPackagesFromPref(const std::string & pref_name) const233 std::set<std::string> AppInstallEventLogger::GetPackagesFromPref(
234     const std::string& pref_name) const {
235   std::set<std::string> packages;
236   for (const auto& package :
237        profile_->GetPrefs()->GetList(pref_name)->GetList()) {
238     if (!package.is_string()) {
239       continue;
240     }
241     packages.insert(package.GetString());
242   }
243   return packages;
244 }
245 
SetPref(const std::string & pref_name,const std::set<std::string> & packages)246 void AppInstallEventLogger::SetPref(const std::string& pref_name,
247                                     const std::set<std::string>& packages) {
248   base::Value value(base::Value::Type::LIST);
249   for (const std::string& package : packages) {
250     value.Append(package);
251   }
252   profile_->GetPrefs()->Set(pref_name, value);
253 }
254 
UpdateCollector(const std::set<std::string> & pending)255 void AppInstallEventLogger::UpdateCollector(
256     const std::set<std::string>& pending) {
257   if (!log_collector_) {
258     log_collector_ =
259         std::make_unique<AppInstallEventLogCollector>(this, profile_, pending);
260   } else {
261     log_collector_->OnPendingPackagesChanged(pending);
262   }
263 }
264 
StopCollector()265 void AppInstallEventLogger::StopCollector() {
266   log_collector_.reset();
267 }
268 
EvaluatePolicy(const policy::PolicyMap & policy,bool initial)269 void AppInstallEventLogger::EvaluatePolicy(const policy::PolicyMap& policy,
270                                            bool initial) {
271   const std::set<std::string> previous_requested =
272       GetPackagesFromPref(arc::prefs::kArcPushInstallAppsRequested);
273   const std::set<std::string> previous_pending =
274       GetPackagesFromPref(arc::prefs::kArcPushInstallAppsPending);
275 
276   const std::set<std::string> current_requested =
277       GetRequestedPackagesFromPolicy(policy);
278 
279   const std::set<std::string> added =
280       GetDifference(current_requested, previous_requested);
281   const std::set<std::string> removed =
282       GetDifference(previous_pending, current_requested);
283   AddForSetOfPackagesWithDiskSpaceInfo(
284       added, CreateEvent(em::AppInstallReportLogEvent::SERVER_REQUEST));
285   AddForSetOfPackages(removed,
286                       CreateEvent(em::AppInstallReportLogEvent::CANCELED));
287 
288   const std::set<std::string> current_pending = GetDifference(
289       current_requested, GetDifference(previous_requested, previous_pending));
290   SetPref(arc::prefs::kArcPushInstallAppsRequested, current_requested);
291   SetPref(arc::prefs::kArcPushInstallAppsPending, current_pending);
292 
293   if (!current_pending.empty()) {
294     UpdateCollector(current_pending);
295     if (initial) {
296       log_collector_->AddLoginEvent();
297     }
298   } else {
299     StopCollector();
300   }
301 }
302 
AddForSetOfPackagesWithDiskSpaceInfo(const std::set<std::string> & packages,std::unique_ptr<em::AppInstallReportLogEvent> event)303 void AppInstallEventLogger::AddForSetOfPackagesWithDiskSpaceInfo(
304     const std::set<std::string>& packages,
305     std::unique_ptr<em::AppInstallReportLogEvent> event) {
306   base::ThreadPool::PostTaskAndReplyWithResult(
307       FROM_HERE, {base::MayBlock()},
308       base::BindOnce(&AddDiskSpaceInfoToEvent, std::move(event)),
309       base::BindOnce(&AppInstallEventLogger::AddForSetOfPackages,
310                      weak_factory_.GetWeakPtr(), packages));
311 }
312 
AddForSetOfPackages(const std::set<std::string> & packages,std::unique_ptr<em::AppInstallReportLogEvent> event)313 void AppInstallEventLogger::AddForSetOfPackages(
314     const std::set<std::string>& packages,
315     std::unique_ptr<em::AppInstallReportLogEvent> event) {
316   delegate_->GetAndroidId(base::BindOnce(&AppInstallEventLogger::OnGetAndroidId,
317                                          weak_factory_.GetWeakPtr(), packages,
318                                          std::move(event)));
319 }
320 
OnGetAndroidId(const std::set<std::string> & packages,std::unique_ptr<em::AppInstallReportLogEvent> event,bool ok,int64_t android_id)321 void AppInstallEventLogger::OnGetAndroidId(
322     const std::set<std::string>& packages,
323     std::unique_ptr<em::AppInstallReportLogEvent> event,
324     bool ok,
325     int64_t android_id) {
326   if (ok) {
327     event->set_android_id(android_id);
328   }
329   delegate_->Add(packages, *event);
330 }
331 
332 }  // namespace policy
333