blob: dbad0865964d6bbd035de19af925522af6ba0324 [file] [log] [blame]
// Copyright 2019 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/enterprise/reporting/report_scheduler.h"
#include <string>
#include <utility>
#include <vector>
#include "base/metrics/histogram_functions.h"
#include "base/syslog_logging.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/reporting/prefs.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/policy/browser_dm_token_storage.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/upgrade_detector/build_state.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/pref_names.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/device_management_service.h"
#include "components/prefs/pref_service.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace em = enterprise_management;
namespace enterprise_reporting {
namespace {
constexpr base::TimeDelta kDefaultUploadInterval =
base::TimeDelta::FromHours(24); // Default upload interval is 24 hours.
const int kMaximumRetry = 10; // Retry 10 times takes about 15 to 19 hours.
const int kMaximumTrackedProfiles = 21;
// Returns true if cloud reporting is enabled.
bool IsReportingEnabled() {
return g_browser_process->local_state()->GetBoolean(
prefs::kCloudReportingEnabled);
}
// Returns true if this build should generate basic reports when an update is
// detected.
constexpr bool ShouldReportUpdates() {
#if defined(OS_CHROMEOS)
return false;
#else
return true;
#endif
}
} // namespace
ReportScheduler::ReportScheduler(
policy::CloudPolicyClient* client,
std::unique_ptr<ReportGenerator> report_generator)
: cloud_policy_client_(std::move(client)),
report_generator_(std::move(report_generator)) {
RegisterPrefObserver();
}
ReportScheduler::~ReportScheduler() {
// Stop observing ProfileManager if we are tracking stale profiles.
if (stale_profiles_)
g_browser_process->profile_manager()->RemoveObserver(this);
if (IsReportingEnabled() && stale_profiles_) {
base::UmaHistogramExactLinear("Enterprise.CloudReportingStaleProfileCount",
stale_profiles_->size(),
kMaximumTrackedProfiles);
}
if (ShouldReportUpdates())
g_browser_process->GetBuildState()->RemoveObserver(this);
}
bool ReportScheduler::IsNextReportScheduledForTesting() const {
return request_timer_.IsRunning();
}
void ReportScheduler::SetReportUploaderForTesting(
std::unique_ptr<ReportUploader> uploader) {
report_uploader_ = std::move(uploader);
}
void ReportScheduler::OnDMTokenUpdated() {
OnReportEnabledPrefChanged();
}
void ReportScheduler::OnUpdate(const BuildState* build_state) {
DCHECK(ShouldReportUpdates());
// A new version has been detected on the machine and a restart is now needed
// for it to take effect. Send a basic report (without profile info)
// immediately.
GenerateAndUploadReport(kTriggerUpdate);
}
void ReportScheduler::RegisterPrefObserver() {
pref_change_registrar_.Init(g_browser_process->local_state());
pref_change_registrar_.Add(
prefs::kCloudReportingEnabled,
base::BindRepeating(&ReportScheduler::OnReportEnabledPrefChanged,
base::Unretained(this)));
// Trigger first pref check during launch process.
OnReportEnabledPrefChanged();
}
void ReportScheduler::OnReportEnabledPrefChanged() {
if (!IsReportingEnabled()) {
Stop();
return;
}
// For Chrome OS, it needn't register the cloud policy client here. The
// |dm_token| and |client_id| should have already existed after the client is
// initialized, and will keep valid during whole life-cycle.
#if !defined(OS_CHROMEOS)
if (!SetupBrowserPolicyClientRegistration()) {
Stop();
return;
}
#endif
// Start the periodic report timer.
const base::Time last_upload_timestamp =
g_browser_process->local_state()->GetTime(kLastUploadTimestamp);
Start(last_upload_timestamp);
if (ShouldReportUpdates()) {
// Watch for browser updates if not already doing so.
auto* build_state = g_browser_process->GetBuildState();
if (!build_state->HasObserver(this)) {
build_state->AddObserver(this);
// Generate and upload a basic report immediately if the version has
// changed since the last upload and the last upload was less than 24h
// ago.
if (g_browser_process->local_state()->GetString(kLastUploadVersion) !=
chrome::kChromeVersion &&
last_upload_timestamp + kDefaultUploadInterval > base::Time::Now()) {
GenerateAndUploadReport(kTriggerNewVersion);
}
}
}
}
void ReportScheduler::Stop() {
request_timer_.Stop();
if (ShouldReportUpdates())
g_browser_process->GetBuildState()->RemoveObserver(this);
}
bool ReportScheduler::SetupBrowserPolicyClientRegistration() {
if (cloud_policy_client_->is_registered())
return true;
policy::DMToken browser_dm_token =
policy::BrowserDMTokenStorage::Get()->RetrieveDMToken();
std::string client_id =
policy::BrowserDMTokenStorage::Get()->RetrieveClientId();
if (!browser_dm_token.is_valid() || client_id.empty()) {
VLOG(1)
<< "Enterprise reporting is disabled because device is not enrolled.";
return false;
}
cloud_policy_client_->SetupRegistration(browser_dm_token.value(), client_id,
std::vector<std::string>());
return true;
}
void ReportScheduler::Start(base::Time last_upload_time) {
// The next report is triggered 24h after the previous was uploaded.
const base::Time next_upload_time = last_upload_time + kDefaultUploadInterval;
if (VLOG_IS_ON(1)) {
base::TimeDelta first_request_delay = next_upload_time - base::Time::Now();
VLOG(1) << "Schedule the first report in about "
<< first_request_delay.InHours() << " hour(s) and "
<< first_request_delay.InMinutes() % 60 << " minute(s).";
}
request_timer_.Start(FROM_HERE, next_upload_time,
base::BindOnce(&ReportScheduler::GenerateAndUploadReport,
base::Unretained(this), kTriggerTimer));
}
void ReportScheduler::GenerateAndUploadReport(ReportTrigger trigger) {
if (active_trigger_ != kTriggerNone) {
// A report is already being generated. Remember this trigger to be handled
// once the current report completes.
pending_triggers_ |= trigger;
return;
}
active_trigger_ = trigger;
bool with_profiles = true;
switch (trigger) {
case kTriggerNone:
NOTREACHED();
FALLTHROUGH;
case kTriggerTimer:
VLOG(1) << "Generating enterprise report.";
break;
case kTriggerUpdate:
VLOG(1) << "Generating basic enterprise report upon update.";
with_profiles = false;
break;
case kTriggerNewVersion:
VLOG(1) << "Generating basic enterprise report upon new version.";
with_profiles = false;
break;
}
report_generator_->Generate(
with_profiles, base::BindOnce(&ReportScheduler::OnReportGenerated,
base::Unretained(this)));
}
void ReportScheduler::OnReportGenerated(
ReportGenerator::ReportRequests requests) {
DCHECK_NE(active_trigger_, kTriggerNone);
if (requests.empty()) {
SYSLOG(ERROR)
<< "No cloud report can be generated. Likely the report is too large.";
// Do not restart the periodic report timer, as it's likely that subsequent
// attempts to generate full reports would also fail.
active_trigger_ = kTriggerNone;
RunPendingTriggers();
return;
}
VLOG(1) << "Uploading enterprise report.";
if (!report_uploader_) {
report_uploader_ =
std::make_unique<ReportUploader>(cloud_policy_client_, kMaximumRetry);
}
RecordUploadTrigger(active_trigger_);
report_uploader_->SetRequestAndUpload(
std::move(requests), base::BindOnce(&ReportScheduler::OnReportUploaded,
base::Unretained(this)));
}
void ReportScheduler::OnReportUploaded(ReportUploader::ReportStatus status) {
DCHECK_NE(active_trigger_, kTriggerNone);
VLOG(1) << "The enterprise report upload result " << status << ".";
switch (status) {
case ReportUploader::kSuccess:
// Schedule the next report for success. Reset uploader to reset failure
// count.
report_uploader_.reset();
if (IsReportingEnabled())
TrackStaleProfiles();
if (ShouldReportUpdates()) {
// Remember what browser version made this upload.
g_browser_process->local_state()->SetString(kLastUploadVersion,
chrome::kChromeVersion);
}
FALLTHROUGH;
case ReportUploader::kTransientError:
// Stop retrying and schedule the next report to avoid stale report.
// Failure count is not reset so retry delay remains.
if (active_trigger_ == kTriggerTimer) {
const base::Time now = base::Time::Now();
g_browser_process->local_state()->SetTime(kLastUploadTimestamp, now);
if (IsReportingEnabled())
Start(now);
}
break;
case ReportUploader::kPersistentError:
// No future upload until Chrome relaunch or pref change event.
break;
}
active_trigger_ = kTriggerNone;
RunPendingTriggers();
}
void ReportScheduler::RunPendingTriggers() {
DCHECK_EQ(active_trigger_, kTriggerNone);
if (!pending_triggers_)
return;
// Timer-triggered reports are a superset of those triggered by an update or a
// new version, so favor them and consider that they serve all purposes.
uint32_t pending_triggers = std::exchange(pending_triggers_, 0);
ReportTrigger trigger = kTriggerTimer;
if ((pending_triggers & kTriggerTimer) == 0) {
trigger = (pending_triggers & kTriggerUpdate) != 0 ? kTriggerUpdate
: kTriggerNewVersion;
}
GenerateAndUploadReport(trigger);
}
void ReportScheduler::TrackStaleProfiles() {
if (!stale_profiles_) {
// If we haven't, start the tracking.
stale_profiles_ = std::make_unique<base::flat_set<base::FilePath>>();
g_browser_process->profile_manager()->AddObserver(this);
} else {
stale_profiles_->clear();
}
}
// static
void ReportScheduler::RecordUploadTrigger(ReportTrigger trigger) {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class Sample {
kNone = 0,
kTimer = 1,
kUpdate = 2,
kNewVersion = 3,
kMaxValue = kNewVersion
} sample = Sample::kNone;
switch (trigger) {
case kTriggerNone:
break;
case kTriggerTimer:
sample = Sample::kTimer;
break;
case kTriggerUpdate:
sample = Sample::kUpdate;
break;
case kTriggerNewVersion:
sample = Sample::kNewVersion;
break;
}
base::UmaHistogramEnumeration("Enterprise.CloudReportingUploadTrigger",
sample);
}
void ReportScheduler::OnProfileAdded(Profile* profile) {
if (profile->IsSystemProfile() || profile->IsGuestSession() ||
profile->IsIncognitoProfile()) {
return;
}
DCHECK(stale_profiles_);
stale_profiles_->insert(profile->GetPath());
}
void ReportScheduler::OnProfileMarkedForPermanentDeletion(Profile* profile) {
DCHECK(stale_profiles_);
stale_profiles_->erase(profile->GetPath());
}
} // namespace enterprise_reporting