blob: 613494687fff05c5e983f512c1d09130a181e2f0 [file] [log] [blame]
// Copyright 2022 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/support_tool/policy_data_collector.h"
#include <map>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include "base/containers/fixed_flat_map.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/policy/policy_ui_utils.h"
#include "chrome/browser/policy/policy_value_and_status_aggregator.h"
#include "chrome/browser/support_tool/data_collector.h"
#include "components/feedback/redaction_tool/pii_types.h"
#include "components/feedback/redaction_tool/redaction_tool.h"
#include "components/policy/core/browser/webui/json_generation.h"
#include "components/policy/core/browser/webui/policy_status_provider.h"
#include "components/policy/core/browser/webui/policy_webui_constants.h"
#if !BUILDFLAG(IS_CHROMEOS)
#include "components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h"
#endif // !BUILDFLAG(IS_CHROMEOS)
namespace {
// Returns the PII type that `status_field` is categorised in if it's considered
// as PII.
std::optional<redaction::PIIType> GetPIITypeOfStatusField(
std::string_view status_field) {
// List of keys in policy status that will be considered as PII and will be
// redacted selectively.
// TODO(crbug.com/41486252): Convert to MakeFixedFlatMap().
static constexpr auto kPersonallyIdentifiableStatusFields =
base::MakeFixedFlatMap<std::string_view, redaction::PIIType>({
#if !BUILDFLAG(IS_CHROMEOS)
{policy::kDeviceIdKey, redaction::PIIType::kStableIdentifier},
{policy::kEnrollmentTokenKey, redaction::PIIType::kStableIdentifier},
{policy::kMachineKey, redaction::PIIType::kStableIdentifier},
#endif // !BUILDFLAG(IS_CHROMEOS)
{policy::kAssetIdKey, redaction::PIIType::kStableIdentifier},
// kLocationKey is the "Asset location" which is an identifier for
// the device that is set during enterprise enrollment or by the
// administrator.
{policy::kLocationKey, redaction::PIIType::kStableIdentifier},
{policy::kDirectoryApiIdKey, redaction::PIIType::kStableIdentifier},
{policy::kGaiaIdKey, redaction::PIIType::kGaiaID},
{policy::kClientIdKey, redaction::PIIType::kStableIdentifier},
{policy::kUsernameKey, redaction::PIIType::kEmail},
{policy::kEnterpriseDomainManagerKey, redaction::PIIType::kEmail},
{policy::kDomainKey, redaction::PIIType::kEmail}});
return kPersonallyIdentifiableStatusFields.contains(status_field)
? std::make_optional(
kPersonallyIdentifiableStatusFields.at(status_field))
: std::nullopt;
}
// Opens a file named "policies.json" in `target_directory` and writes
// `policy_data`.
bool WritePolicyData(std::string policy_data, base::FilePath target_directory) {
base::FilePath target_file =
target_directory.Append(FILE_PATH_LITERAL("policies.json"));
return base::WriteFile(target_file, policy_data);
}
} // namespace
const char kRedactedPlaceholder[] = "REDACTED";
PolicyDataCollector::PolicyDataCollector(Profile* profile) {
policy_aggregator_ = policy::PolicyValueAndStatusAggregator::
CreateDefaultPolicyValueAndStatusAggregator(profile);
}
PolicyDataCollector::~PolicyDataCollector() = default;
std::string PolicyDataCollector::GetName() const {
return "Policies";
}
std::string PolicyDataCollector::GetDescription() const {
return "Collects available policy values and metadata about policies and "
"writes it to \"policies.json\" file.";
}
const PIIMap& PolicyDataCollector::GetDetectedPII() {
return pii_map_;
}
void PolicyDataCollector::CollectDataAndDetectPII(
DataCollectorDoneCallback on_data_collected_callback,
scoped_refptr<base::SequencedTaskRunner> task_runner_for_redaction_tool,
scoped_refptr<redaction::RedactionToolContainer> redaction_tool_container) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
policy_values_ = policy_aggregator_->GetAggregatedPolicyValues();
// `policy_aggregator_` returns the list of policy IDs in
// policy::kPolicyIdsKey. Remove the list of policy IDs from the output as we
// don't need it and don't want it to clutter the output.
policy_values_.Remove(policy::kPolicyIdsKey);
policy_status_ = policy_aggregator_->GetAggregatedPolicyStatus();
// Policy values are not expected to contain PII so we only detect the PII in
// policy status.
DetectPIIInPolicyStatus();
std::move(on_data_collected_callback).Run(/*error=*/std::nullopt);
}
void PolicyDataCollector::ExportCollectedDataWithPII(
std::set<redaction::PIIType> pii_types_to_keep,
base::FilePath target_directory,
scoped_refptr<base::SequencedTaskRunner> task_runner_for_redaction_tool,
scoped_refptr<redaction::RedactionToolContainer> redaction_tool_container,
DataCollectorDoneCallback on_exported_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Policy values are not expected to contain PII so we only redact the PII in
// policy status.
RedactPIIInPolicyStatus(pii_types_to_keep);
std::string policy_json = policy::GenerateJson(
std::move(policy_values_), std::move(policy_status_),
policy::GetChromeMetadataParams(/*application_name=*/"Support Tool"));
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&WritePolicyData, policy_json, target_directory),
base::BindOnce(&PolicyDataCollector::OnFileWritten,
weak_ptr_factory_.GetWeakPtr(),
std::move(on_exported_callback)));
}
void PolicyDataCollector::OnFileWritten(
DataCollectorDoneCallback on_exported_callback,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!success) {
SupportToolError error = {SupportToolErrorCode::kDataCollectorError,
"PolicyDataCollector failed on data export."};
std::move(on_exported_callback).Run(error);
return;
}
std::move(on_exported_callback).Run(/*error=*/std::nullopt);
}
void PolicyDataCollector::OnPolicyValueAndStatusChanged() {}
void PolicyDataCollector::DetectPIIInPolicyStatus() {
for (auto entry : policy_status_) {
for (const auto [status_key, status_value] : entry.second.GetDict()) {
std::optional<redaction::PIIType> pii_type =
GetPIITypeOfStatusField(status_key);
if (!pii_type)
continue;
// A field of policy status can be stored in string or bool. Convert it to
// string.
std::string status_value_as_string;
base::JSONWriter::WriteWithOptions(status_value,
base::JSONWriter::OPTIONS_PRETTY_PRINT,
&status_value_as_string);
pii_map_[pii_type.value()].insert(status_value_as_string);
}
}
}
void PolicyDataCollector::RedactPIIInPolicyStatus(
std::set<redaction::PIIType> pii_types_to_keep) {
// Iterator is a reference std::pair<const std::string&, Value&> as explained
// in base/value_iterators.h documentation.
for (std::pair<const std::string&, base::Value&> entry : policy_status_) {
for (std::pair<const std::string&, base::Value&> status_pair :
entry.second.GetDict()) {
std::optional<redaction::PIIType> pii_type =
GetPIITypeOfStatusField(status_pair.first);
if (!pii_type || base::Contains(pii_types_to_keep, pii_type.value()))
continue;
// Replace the value with `kRedactedPlaceholder` if it's a PII type that
// user didn't select to keep.
status_pair.second = base::Value(kRedactedPlaceholder);
}
}
}