blob: beff857e4da7373520df186c0b560a4b33786b78 [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 "components/permissions/permission_hats_trigger_helper.h"
#include <utility>
#include "base/ranges/algorithm.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "components/permissions/constants.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_uma_util.h"
namespace {
std::vector<std::string> SplitCsvString(const std::string& csv_string) {
return base::SplitString(csv_string, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
}
bool StringMatchesFilter(const std::string& string, const std::string& filter) {
return filter.empty() ||
base::ranges::any_of(SplitCsvString(filter),
[string](base::StringPiece current_filter) {
return base::EqualsCaseInsensitiveASCII(
string, current_filter);
});
}
std::map<std::string, std::pair<std::string, std::string>>
GetKeyToValueFilterPairMap(
permissions::PermissionHatsTriggerHelper::PromptParametersForHaTS
prompt_parameters) {
// configuration key -> {current value for key, configured filter for key}
return {
{permissions::kPermissionsPromptSurveyPromptDispositionKey,
{permissions::PermissionUmaUtil::GetPromptDispositionString(
prompt_parameters.prompt_disposition),
permissions::feature_params::
kPermissionsPromptSurveyPromptDispositionFilter.Get()}},
{permissions::kPermissionsPromptSurveyPromptDispositionReasonKey,
{permissions::PermissionUmaUtil::GetPromptDispositionReasonString(
prompt_parameters.prompt_disposition_reason),
permissions::feature_params::
kPermissionsPromptSurveyPromptDispositionReasonFilter.Get()}},
{permissions::kPermissionsPromptSurveyActionKey,
{prompt_parameters.action.has_value()
? permissions::PermissionUmaUtil::GetPermissionActionString(
prompt_parameters.action.value())
: "",
permissions::feature_params::kPermissionsPromptSurveyActionFilter
.Get()}},
{permissions::kPermissionsPromptSurveyRequestTypeKey,
{permissions::PermissionUmaUtil::GetRequestTypeString(
prompt_parameters.request_type),
permissions::feature_params::kPermissionsPromptSurveyRequestTypeFilter
.Get()}},
{permissions::kPermissionsPromptSurveyHadGestureKey,
{prompt_parameters.gesture_type ==
permissions::PermissionRequestGestureType::GESTURE
? permissions::kTrueStr
: permissions::kFalseStr,
permissions::feature_params::kPermissionsPromptSurveyHadGestureFilter
.Get()}},
{permissions::kPermissionsPromptSurveyReleaseChannelKey,
{prompt_parameters.channel,
permissions::feature_params::kPermissionPromptSurveyReleaseChannelFilter
.Get()}},
{permissions::kPermissionsPromptSurveyDisplayTimeKey,
{prompt_parameters.survey_display_time,
permissions::feature_params::kPermissionsPromptSurveyDisplayTime
.Get()}}};
}
// Typos in the gcl configuration cannot be verified and may be missed by
// reviewers. In the worst case, no filters are configured. By definition of
// our filters, this would match all requests. To safeguard against this kind
// of misconfiguration (which would lead to very high HaTS QPS), we enforce
// that at least one valid filter must be configured.
bool IsValidConfiguration(
permissions::PermissionHatsTriggerHelper::PromptParametersForHaTS
prompt_parameters) {
auto filter_pair_map = GetKeyToValueFilterPairMap(prompt_parameters);
if (filter_pair_map[permissions::kPermissionsPromptSurveyDisplayTimeKey]
.second.empty()) {
// When no display time is configured, the survey should never be triggered.
return false;
}
// Returns false if all filter parameters are empty.
return !base::ranges::all_of(
filter_pair_map,
[](std::pair<std::string, std::pair<std::string, std::string>> entry) {
return entry.second.second.empty();
});
}
} // namespace
namespace permissions {
PermissionHatsTriggerHelper::PromptParametersForHaTS::PromptParametersForHaTS(
permissions::RequestType request_type,
absl::optional<permissions::PermissionAction> action,
permissions::PermissionPromptDisposition prompt_disposition,
permissions::PermissionPromptDispositionReason prompt_disposition_reason,
permissions::PermissionRequestGestureType gesture_type,
std::string channel,
std::string survey_display_time,
absl::optional<base::TimeDelta> prompt_display_duration)
: request_type(request_type),
action(action),
prompt_disposition(prompt_disposition),
prompt_disposition_reason(prompt_disposition_reason),
gesture_type(gesture_type),
channel(channel),
survey_display_time(survey_display_time),
prompt_display_duration(prompt_display_duration) {}
PermissionHatsTriggerHelper::PromptParametersForHaTS::PromptParametersForHaTS(
const PromptParametersForHaTS& other) = default;
PermissionHatsTriggerHelper::PromptParametersForHaTS::
~PromptParametersForHaTS() = default;
PermissionHatsTriggerHelper::SurveyProductSpecificData::
SurveyProductSpecificData(SurveyBitsData survey_bits_data,
SurveyStringData survey_string_data)
: survey_bits_data(survey_bits_data),
survey_string_data(survey_string_data) {}
PermissionHatsTriggerHelper::SurveyProductSpecificData::
~SurveyProductSpecificData() = default;
PermissionHatsTriggerHelper::SurveyProductSpecificData
PermissionHatsTriggerHelper::SurveyProductSpecificData::PopulateFrom(
PromptParametersForHaTS prompt_parameters) {
const static std::vector<std::string> product_specific_bits_fields = {
kPermissionsPromptSurveyHadGestureKey};
const static std::vector<std::string> product_specific_string_fields{
kPermissionsPromptSurveyPromptDispositionKey,
kPermissionsPromptSurveyPromptDispositionReasonKey,
kPermissionsPromptSurveyActionKey,
kPermissionsPromptSurveyRequestTypeKey,
kPermissionsPromptSurveyReleaseChannelKey,
kPermissionsPromptSurveyDisplayTimeKey};
auto key_to_value_filter_pair = GetKeyToValueFilterPairMap(prompt_parameters);
std::map<std::string, bool> bits_data;
for (auto product_specific_bits_field : product_specific_bits_fields) {
auto value_type =
key_to_value_filter_pair.find(product_specific_bits_field);
if (value_type != key_to_value_filter_pair.end()) {
bits_data.insert(
{value_type->first, value_type->second.first == kTrueStr});
}
}
std::map<std::string, std::string> string_data;
for (auto product_specific_string_field : product_specific_string_fields) {
auto value_type =
key_to_value_filter_pair.find(product_specific_string_field);
if (value_type != key_to_value_filter_pair.end()) {
string_data.insert({value_type->first, value_type->second.first});
}
}
return SurveyProductSpecificData(bits_data, string_data);
}
bool PermissionHatsTriggerHelper::ArePromptTriggerCriteriaSatisfied(
PromptParametersForHaTS prompt_parameters) {
if (!IsValidConfiguration(prompt_parameters)) {
return false;
}
if (prompt_parameters.action == permissions::PermissionAction::IGNORED &&
prompt_parameters.prompt_display_duration >
permissions::feature_params::
kPermissionPromptSurveyIgnoredPromptsMaximumAge.Get()) {
return false;
}
auto key_to_value_filter_pair = GetKeyToValueFilterPairMap(prompt_parameters);
for (auto value_type : key_to_value_filter_pair) {
const auto& value = value_type.second.first;
const auto& filter = value_type.second.second;
if (!StringMatchesFilter(value, filter)) {
// if any filter doesn't match, no survey should be triggered
return false;
}
}
return true;
}
} // namespace permissions