blob: 1c0f94ece2e0a8b0586cadf51d4d3e2f5d225b97 [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 <iterator>
#include <string>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/values.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "tools/aggregation_service/aggregation_service_tool.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_canon.h"
namespace {
// If you change any of the switch strings, update the `kHelpMsg`,
// `kAllowedSwitches` and `kRequiredSwitches` accordingly.
constexpr char kSwitchHelp[] = "help";
constexpr char kSwitchHelpShort[] = "h";
constexpr char kSwitchOperation[] = "operation";
constexpr char kSwitchBucket[] = "bucket";
constexpr char kSwitchValue[] = "value";
constexpr char kSwitchAlternativeAggregationMode[] =
"alternative-aggregation-mode";
constexpr char kSwitchReportingOrigin[] = "reporting-origin";
constexpr char kSwitchHelperKeyUrls[] = "helper-key-urls";
constexpr char kSwitchHelperKeyFiles[] = "helper-key-files";
constexpr char kSwitchOutputFile[] = "output-file";
constexpr char kSwitchOutputUrl[] = "output-url";
constexpr char kSwitchDisablePayloadEncryption[] = "disable-payload-encryption";
constexpr char kSwitchAdditionalFields[] = "additional-fields";
constexpr char kSwitchAdditionalSharedInfoFields[] =
"additional-shared-info-fields";
constexpr char kSwitchEnableDebugMode[] = "enable-debug-mode";
constexpr char kSwitchApiVersion[] = "api-version";
constexpr char kSwitchApi[] = "api";
constexpr char kHelpMsg[] = R"(
aggregation_service_tool [--operation=<operation>] --bucket=<bucket>
--value=<value> --aggregation-mode=<aggregation_mode>
--reporting-origin=<reporting_origin>
--helper-keys=<helper_server_keys> [--output=<output_file>]
[--output-url=<output_url>] [--disable-payload-encryption]
[--additional-fields=<additional_fields>]
[--additional-shared-info-fields=<additional_shared_info_fields]
[--debug-mode] [--api-version=<api_version>] [--api=<api_identifier>]
Examples:
aggregation_service_tool --operation="histogram" --bucket=1234 --value=5
--alternative-aggregation-mode="experimental-poplar" --reporting-origin="https://example.com"
--helper-key-urls="https://a.com/keys.json https://b.com/path/to/keys.json"
--output-file="output.json" --enable-debug-mode --api-version="1.0"
--api="attribution-reporting" --additional-fields=
"source_site=https://publisher.example,attribution_destination=https://advertiser.example"
or
aggregation_service_tool --bucket=1234 --value=5
--reporting-origin="https://example.com"
--helper-key-files="keys.json"
--output-url="https://c.com/reports"
aggregation_service_tool is a command-line tool that accepts report contents
and mapping of origins to public key json files as input and either output an
aggregatable report to a file on disk or send the aggregatable report to an
endpoint origin over network. `scheduled_report_time` will be default to 30
seconds later.
Switches:
--operation = Optional switch. Currently only supports "histogram". Default is
"histogram".
--bucket = Bucket key of the histogram contribution, must be non-negative
integer.
--value = Bucket value of the histogram contribution, must be non-negative
integer.
--alternative-aggregation-mode = Optional switch to specify an alternative
aggregation mode. Supports "tee-based",
"experimental-poplar" and "default"
(default value, equivalent to "tee-based").
--reporting-origin = The reporting origin endpoint.
--helper-key-urls = Optional switch to specify the URL(s) to fetch the public
key json file(s) from. Spaces are used as separators.
Either this or "--helper-key-files" must be specified.
--helper-key-files = Optional switch to specify the local public key json
file(s) to use. Spaces are used as separators. Either
this or "--helper-key-urls" must be specified.
--output-file = Optional switch to specify the output file path. Eiter this or
"--output-url" must be specified.
--output-url = Optional switch to specify the output url. Eiter this or
"--output-file" must be specified.
--additional-fields = List of key-value pairs of additional fields to be
included in the aggregatable report. Only supports
string valued fields.
--additional-shared-info-fields = List of key-value pairs of additional
fields to be included in the aggregatable
report's shared_info dictionary.
Only supports string valued fields.
--disable-payload-encryption = Optional switch. If provided, the aggregatable
report's payload(s) will not be encrypted after
serialization.
--enable-debug-mode = Optional switch. If provided, debug mode is enabled.
Otherwise, it is disabled.
--api-version = Optional switch to specify the API version. Default is "".
--api = Optional switch to specify the enum string identifying which API
created the report. Default is "attribution-reporting".
)";
void PrintHelp() {
LOG(INFO) << kHelpMsg;
}
} // namespace
int main(int argc, char* argv[]) {
base::SingleThreadTaskExecutor executor(base::MessagePumpType::IO);
base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
"aggregation_service_tool");
base::CommandLine::Init(argc, argv);
base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
base::CommandLine::StringVector args = command_line.GetArgs();
if (args.size() != 0U) {
LOG(ERROR)
<< "aggregation_service_tool does not expect any additional arguments.";
PrintHelp();
return 1;
}
const std::vector<std::string> kAllowedSwitches = {
kSwitchHelp,
kSwitchHelpShort,
kSwitchOperation,
kSwitchBucket,
kSwitchValue,
kSwitchAlternativeAggregationMode,
kSwitchReportingOrigin,
kSwitchHelperKeyUrls,
kSwitchHelperKeyFiles,
kSwitchOutputFile,
kSwitchOutputUrl,
kSwitchDisablePayloadEncryption,
kSwitchAdditionalFields,
kSwitchAdditionalSharedInfoFields,
kSwitchEnableDebugMode,
kSwitchApiVersion,
kSwitchApi};
for (const auto& provided_switch : command_line.GetSwitches()) {
if (!base::Contains(kAllowedSwitches, provided_switch.first)) {
LOG(ERROR) << "aggregation_service_tool did not expect "
<< provided_switch.first << " to be specified.";
PrintHelp();
return 1;
}
}
if (command_line.GetSwitches().empty() ||
command_line.HasSwitch(kSwitchHelp) ||
command_line.HasSwitch(kSwitchHelpShort)) {
PrintHelp();
return 1;
}
const std::vector<std::string> kRequiredSwitches = {
kSwitchBucket, kSwitchValue, kSwitchReportingOrigin};
for (const std::string& required_switch : kRequiredSwitches) {
if (!command_line.HasSwitch(required_switch.c_str())) {
LOG(ERROR) << "aggregation_service_tool expects " << required_switch
<< " to be specified.";
PrintHelp();
return 1;
}
}
// Either output or reporting url should be specified, but not both.
if (!(command_line.HasSwitch(kSwitchOutputFile) ^
command_line.HasSwitch(kSwitchOutputUrl))) {
LOG(ERROR) << "aggregation_service_tool expects either "
<< kSwitchOutputFile << " or " << kSwitchOutputUrl
<< " to be specified, but not both.";
PrintHelp();
return 1;
}
// Either helper key URL or file should be specified, but not both.
if (!(command_line.HasSwitch(kSwitchHelperKeyUrls) ^
command_line.HasSwitch(kSwitchHelperKeyFiles))) {
LOG(ERROR) << "aggregation_service_tool expects either "
<< kSwitchHelperKeyUrls << " or " << kSwitchHelperKeyFiles
<< " to be specified, but not both.";
PrintHelp();
return 1;
}
aggregation_service::AggregationServiceTool tool;
tool.SetDisablePayloadEncryption(
/*should_disable=*/command_line.HasSwitch(
kSwitchDisablePayloadEncryption));
std::vector<GURL> processing_urls;
if (command_line.HasSwitch(kSwitchHelperKeyUrls)) {
std::string switch_value =
command_line.GetSwitchValueASCII(kSwitchHelperKeyUrls);
std::vector<std::string> helper_key_url_strings =
base::SplitString(switch_value, /*separators=*/" ",
base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
for (const std::string& url_string : helper_key_url_strings) {
GURL helper_key_url(url_string);
if (!network::IsUrlPotentiallyTrustworthy(helper_key_url)) {
LOG(ERROR) << "Helper key URL " << url_string
<< " is not potentially trustworthy.";
return 1;
}
processing_urls.emplace_back(std::move(helper_key_url));
}
} else {
std::string switch_value =
command_line.GetSwitchValueASCII(kSwitchHelperKeyFiles);
std::vector<std::string> helper_key_file_strings =
base::SplitString(switch_value, /*separators=*/" ",
base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (helper_key_file_strings.empty() || helper_key_file_strings.size() > 2) {
LOG(ERROR) << kSwitchHelperKeyFiles
<< " specified an invalid number of files: "
<< helper_key_file_strings.size();
return 1;
}
std::vector<aggregation_service::UrlKeyFile> key_files;
for (size_t i = 0; i < helper_key_file_strings.size(); ++i) {
// We need to choose some URL to store each set of public keys under.
std::string fake_helper_url =
base::StringPrintf("https://fake_%zu.example/keys.json", i);
key_files.emplace_back(GURL(fake_helper_url), helper_key_file_strings[i]);
processing_urls.emplace_back(std::move(fake_helper_url));
}
if (!tool.SetPublicKeys(key_files)) {
LOG(ERROR) << "aggregation_service_tool failed to set public keys.";
return 1;
}
}
std::string operation =
command_line.HasSwitch(kSwitchOperation)
? command_line.GetSwitchValueASCII(kSwitchOperation)
: "histogram";
std::string aggregation_mode =
command_line.HasSwitch(kSwitchAlternativeAggregationMode)
? command_line.GetSwitchValueASCII(kSwitchAlternativeAggregationMode)
: "default";
url::Origin reporting_origin = url::Origin::Create(
GURL(command_line.GetSwitchValueASCII(kSwitchReportingOrigin)));
bool is_debug_mode_enabled = command_line.HasSwitch(kSwitchEnableDebugMode);
base::Value::Dict additional_shared_info_fields;
if (command_line.HasSwitch(kSwitchAdditionalSharedInfoFields)) {
std::string additional_shared_info_fields_str =
command_line.GetSwitchValueASCII(kSwitchAdditionalSharedInfoFields);
// `additional_shared_info_fields_str` is formatted like
// "key1=value1,key2=value2".
base::StringPairs kv_pairs;
base::SplitStringIntoKeyValuePairs(
additional_shared_info_fields_str, /*key_value_delimiter=*/'=',
/*key_value_pair_delimiter=*/',', &kv_pairs);
for (std::pair<std::string, std::string>& kv : kv_pairs) {
additional_shared_info_fields.Set(std::move(kv.first),
std::move(kv.second));
}
}
std::string api_version =
command_line.HasSwitch(kSwitchApiVersion)
? command_line.GetSwitchValueASCII(kSwitchApiVersion)
: "";
std::string api_identifier =
command_line.HasSwitch(kSwitchApi)
? command_line.GetSwitchValueASCII(kSwitchApi)
: "attribution-reporting";
base::Value::Dict report_dict = tool.AssembleReport(
std::move(operation), command_line.GetSwitchValueASCII(kSwitchBucket),
command_line.GetSwitchValueASCII(kSwitchValue),
std::move(aggregation_mode), std::move(reporting_origin),
std::move(processing_urls), is_debug_mode_enabled,
std::move(additional_shared_info_fields), std::move(api_version),
std::move(api_identifier));
if (report_dict.empty()) {
LOG(ERROR)
<< "aggregation_service_tool failed to create the aggregatable report.";
return 1;
}
if (command_line.HasSwitch(kSwitchAdditionalFields)) {
std::string additional_fields =
command_line.GetSwitchValueASCII(kSwitchAdditionalFields);
// `additional_fields` is formatted like "key1=value1,key2=value2".
base::StringPairs kv_pairs;
base::SplitStringIntoKeyValuePairs(
additional_fields, /*key_value_delimiter=*/'=',
/*key_value_pair_delimiter=*/',', &kv_pairs);
for (std::pair<std::string, std::string>& kv : kv_pairs) {
report_dict.Set(std::move(kv.first), std::move(kv.second));
}
}
base::Value report_contents(std::move(report_dict));
bool succeeded = false;
if (command_line.HasSwitch(kSwitchOutputFile)) {
base::FilePath output_file =
command_line.GetSwitchValuePath(kSwitchOutputFile);
succeeded = tool.WriteReportToFile(report_contents, output_file);
if (!succeeded) {
LOG(ERROR) << "aggregation_service_tool failed to write to "
<< output_file << ".";
}
} else {
std::string output_url = command_line.GetSwitchValueASCII(kSwitchOutputUrl);
succeeded = tool.SendReport(report_contents, GURL(output_url));
if (!succeeded) {
LOG(ERROR) << "aggregation_service_tool failed to send the report to "
<< output_url << ".";
}
}
if (!succeeded)
return 1;
return 0;
}