Upload upstream chromium 120.0.6099.5
[platform/framework/web/chromium-efl.git] / components / permissions / permission_hats_trigger_helper.cc
1 // Copyright 2022 The Chromium Authors
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 "components/permissions/permission_hats_trigger_helper.h"
6
7 #include <utility>
8
9 #include "base/check_is_test.h"
10 #include "base/no_destructor.h"
11 #include "base/rand_util.h"
12 #include "base/ranges/algorithm.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/time/time.h"
17 #include "components/permissions/constants.h"
18 #include "components/permissions/features.h"
19 #include "components/permissions/permission_uma_util.h"
20 #include "components/permissions/pref_names.h"
21 #include "components/pref_registry/pref_registry_syncable.h"
22 #include "components/prefs/pref_service.h"
23
24 namespace permissions {
25
26 namespace {
27
28 bool is_test = false;
29
30 std::vector<std::string> SplitCsvString(const std::string& csv_string) {
31   return base::SplitString(csv_string, ",", base::TRIM_WHITESPACE,
32                            base::SPLIT_WANT_NONEMPTY);
33 }
34
35 bool StringMatchesFilter(const std::string& string, const std::string& filter) {
36   return filter.empty() ||
37          base::ranges::any_of(SplitCsvString(filter),
38                               [string](base::StringPiece current_filter) {
39                                 return base::EqualsCaseInsensitiveASCII(
40                                     string, current_filter);
41                               });
42 }
43
44 std::map<std::string, std::pair<std::string, std::string>>
45 GetKeyToValueFilterPairMap(
46     PermissionHatsTriggerHelper::PromptParametersForHaTS prompt_parameters) {
47   // configuration key -> {current value for key, configured filter for key}
48   return {
49       {kPermissionsPromptSurveyPromptDispositionKey,
50        {PermissionUmaUtil::GetPromptDispositionString(
51             prompt_parameters.prompt_disposition),
52         feature_params::kPermissionsPromptSurveyPromptDispositionFilter.Get()}},
53       {kPermissionsPromptSurveyPromptDispositionReasonKey,
54        {PermissionUmaUtil::GetPromptDispositionReasonString(
55             prompt_parameters.prompt_disposition_reason),
56         feature_params::kPermissionsPromptSurveyPromptDispositionReasonFilter
57             .Get()}},
58       {kPermissionsPromptSurveyActionKey,
59        {prompt_parameters.action.has_value()
60             ? PermissionUmaUtil::GetPermissionActionString(
61                   prompt_parameters.action.value())
62             : "",
63         feature_params::kPermissionsPromptSurveyActionFilter.Get()}},
64       {kPermissionsPromptSurveyRequestTypeKey,
65        {PermissionUmaUtil::GetRequestTypeString(prompt_parameters.request_type),
66         feature_params::kPermissionsPromptSurveyRequestTypeFilter.Get()}},
67       {kPermissionsPromptSurveyHadGestureKey,
68        {prompt_parameters.gesture_type == PermissionRequestGestureType::GESTURE
69             ? kTrueStr
70             : kFalseStr,
71         feature_params::kPermissionsPromptSurveyHadGestureFilter.Get()}},
72       {kPermissionsPromptSurveyReleaseChannelKey,
73        {prompt_parameters.channel,
74         feature_params::kPermissionPromptSurveyReleaseChannelFilter.Get()}},
75       {kPermissionsPromptSurveyDisplayTimeKey,
76        {prompt_parameters.survey_display_time,
77         feature_params::kPermissionsPromptSurveyDisplayTime.Get()}},
78       {kPermissionPromptSurveyOneTimePromptsDecidedBucketKey,
79        {PermissionHatsTriggerHelper::GetOneTimePromptsDecidedBucketString(
80             prompt_parameters.one_time_prompts_decided_bucket),
81         feature_params::kPermissionPromptSurveyOneTimePromptsDecidedBucket
82             .Get()}},
83       {kPermissionPromptSurveyUrlKey, {prompt_parameters.url, ""}}};
84 }
85
86 // Typos in the gcl configuration cannot be verified and may be missed by
87 // reviewers. In the worst case, no filters are configured. By definition of
88 // our filters, this would match all requests. To safeguard against this kind
89 // of misconfiguration (which would lead to very high HaTS QPS), we enforce
90 // that at least one valid filter must be configured.
91 bool IsValidConfiguration(
92     PermissionHatsTriggerHelper::PromptParametersForHaTS prompt_parameters) {
93   auto filter_pair_map = GetKeyToValueFilterPairMap(prompt_parameters);
94
95   if (filter_pair_map[kPermissionsPromptSurveyDisplayTimeKey].second.empty()) {
96     // When no display time is configured, the survey should never be triggered.
97     return false;
98   }
99
100   // Returns false if all filter parameters are empty.
101   return !base::ranges::all_of(
102       filter_pair_map,
103       [](std::pair<std::string, std::pair<std::string, std::string>> entry) {
104         return entry.second.second.empty();
105       });
106 }
107
108 std::vector<double> ParseProbabilityVector(std::string probability_vector_csv) {
109   std::vector<std::string> probability_string_vector =
110       base::SplitString(feature_params::kProbabilityVector.Get(), ",",
111                         base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
112   std::vector<double> checked_probability_vector;
113   double probability;
114   for (std::string probability_string : probability_string_vector) {
115     if (!base::StringToDouble(probability_string, &probability)) {
116       // Parsing failed, configuration error. Return empty array.
117       return std::vector<double>();
118     }
119     checked_probability_vector.push_back(probability);
120   }
121   return checked_probability_vector;
122 }
123
124 std::vector<double>& GetProbabilityVector(std::string probability_vector_csv) {
125   static base::NoDestructor<std::vector<double>> probability_vector(
126       [probability_vector_csv] {
127         return ParseProbabilityVector(probability_vector_csv);
128       }());
129
130   if (is_test) {
131     CHECK_IS_TEST();
132     *probability_vector = ParseProbabilityVector(probability_vector_csv);
133   }
134   return *probability_vector;
135 }
136
137 std::vector<std::string> ParseRequestFilterVector(
138     std::string request_vector_csv) {
139   return base::SplitString(
140       feature_params::kPermissionsPromptSurveyRequestTypeFilter.Get(), ",",
141       base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
142 }
143
144 std::vector<std::string>& GetRequestFilterVector(
145     std::string request_vector_csv) {
146   static base::NoDestructor<std::vector<std::string>> request_filter_vector(
147       [request_vector_csv] {
148         return ParseRequestFilterVector(request_vector_csv);
149       }());
150   if (is_test) {
151     CHECK_IS_TEST();
152     *request_filter_vector = ParseRequestFilterVector(request_vector_csv);
153   }
154   return *request_filter_vector;
155 }
156
157 std::vector<std::pair<std::string, std::string>>
158 ComputePermissionPromptTriggerIdPairs(const std::string& trigger_name_base) {
159   std::vector<std::string> permission_trigger_id_vector(
160       base::SplitString(feature_params::kPermissionsPromptSurveyTriggerId.Get(),
161                         ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY));
162   int trigger_index = 0;
163   std::vector<std::pair<std::string, std::string>> pairs;
164   pairs.clear();
165   for (const auto& trigger_id : permission_trigger_id_vector) {
166     pairs.emplace_back(
167         trigger_name_base + base::NumberToString(trigger_index++), trigger_id);
168   }
169   return pairs;
170 }
171
172 }  // namespace
173
174 PermissionHatsTriggerHelper::PromptParametersForHaTS::PromptParametersForHaTS(
175     RequestType request_type,
176     absl::optional<PermissionAction> action,
177     PermissionPromptDisposition prompt_disposition,
178     PermissionPromptDispositionReason prompt_disposition_reason,
179     PermissionRequestGestureType gesture_type,
180     const std::string& channel,
181     const std::string& survey_display_time,
182     absl::optional<base::TimeDelta> prompt_display_duration,
183     OneTimePermissionPromptsDecidedBucket one_time_prompts_decided_bucket,
184     absl::optional<GURL> gurl)
185     : request_type(request_type),
186       action(action),
187       prompt_disposition(prompt_disposition),
188       prompt_disposition_reason(prompt_disposition_reason),
189       gesture_type(gesture_type),
190       channel(channel),
191       survey_display_time(survey_display_time),
192       prompt_display_duration(prompt_display_duration),
193       one_time_prompts_decided_bucket(one_time_prompts_decided_bucket),
194       url(gurl.has_value() ? gurl->spec() : "") {}
195
196 PermissionHatsTriggerHelper::PromptParametersForHaTS::PromptParametersForHaTS(
197     const PromptParametersForHaTS& other) = default;
198 PermissionHatsTriggerHelper::PromptParametersForHaTS::
199     ~PromptParametersForHaTS() = default;
200
201 PermissionHatsTriggerHelper::SurveyProductSpecificData::
202     SurveyProductSpecificData(SurveyBitsData survey_bits_data,
203                               SurveyStringData survey_string_data)
204     : survey_bits_data(survey_bits_data),
205       survey_string_data(survey_string_data) {}
206
207 PermissionHatsTriggerHelper::SurveyProductSpecificData::
208     ~SurveyProductSpecificData() = default;
209
210 PermissionHatsTriggerHelper::SurveyProductSpecificData
211 PermissionHatsTriggerHelper::SurveyProductSpecificData::PopulateFrom(
212     PromptParametersForHaTS prompt_parameters) {
213   static const char* const kProductSpecificBitsFields[] = {
214       kPermissionsPromptSurveyHadGestureKey};
215   static const char* const kProductSpecificStringFields[] = {
216       kPermissionsPromptSurveyPromptDispositionKey,
217       kPermissionsPromptSurveyPromptDispositionReasonKey,
218       kPermissionsPromptSurveyActionKey,
219       kPermissionsPromptSurveyRequestTypeKey,
220       kPermissionsPromptSurveyReleaseChannelKey,
221       kPermissionsPromptSurveyDisplayTimeKey,
222       kPermissionPromptSurveyOneTimePromptsDecidedBucketKey,
223       kPermissionPromptSurveyUrlKey};
224
225   auto key_to_value_filter_pair = GetKeyToValueFilterPairMap(prompt_parameters);
226   std::map<std::string, bool> bits_data;
227   for (const char* product_specific_bits_field : kProductSpecificBitsFields) {
228     auto it = key_to_value_filter_pair.find(product_specific_bits_field);
229     if (it != key_to_value_filter_pair.end()) {
230       bits_data.insert({it->first, it->second.first == kTrueStr});
231     }
232   }
233
234   std::map<std::string, std::string> string_data;
235   for (const char* product_specific_string_field :
236        kProductSpecificStringFields) {
237     auto it = key_to_value_filter_pair.find(product_specific_string_field);
238     if (it != key_to_value_filter_pair.end()) {
239       string_data.insert({it->first, it->second.first});
240     }
241   }
242
243   return SurveyProductSpecificData(bits_data, string_data);
244 }
245
246 // static
247 void PermissionHatsTriggerHelper::RegisterProfilePrefs(
248     user_prefs::PrefRegistrySyncable* registry) {
249   registry->RegisterIntegerPref(prefs::kOneTimePermissionPromptsDecidedCount,
250                                 0);
251 }
252
253 bool PermissionHatsTriggerHelper::ArePromptTriggerCriteriaSatisfied(
254     PromptParametersForHaTS prompt_parameters,
255     const std::string& trigger_name_base) {
256   auto trigger_and_probability = PermissionHatsTriggerHelper::
257       GetPermissionPromptTriggerNameAndProbabilityForRequestType(
258           trigger_name_base, PermissionUmaUtil::GetRequestTypeString(
259                                  prompt_parameters.request_type));
260
261   if (!trigger_and_probability.has_value() ||
262       base::RandDouble() >= trigger_and_probability->second) {
263     return false;
264   }
265
266   if (!IsValidConfiguration(prompt_parameters)) {
267     return false;
268   }
269
270   if (prompt_parameters.action == PermissionAction::IGNORED &&
271       prompt_parameters.prompt_display_duration >
272           feature_params::kPermissionPromptSurveyIgnoredPromptsMaximumAge
273               .Get()) {
274     return false;
275   }
276
277   auto key_to_value_filter_pair = GetKeyToValueFilterPairMap(prompt_parameters);
278   for (const auto& value_type : key_to_value_filter_pair) {
279     const auto& value = value_type.second.first;
280     const auto& filter = value_type.second.second;
281     if (!StringMatchesFilter(value, filter)) {
282       // if any filter doesn't match, no survey should be triggered
283       return false;
284     }
285   }
286
287   return true;
288 }
289
290 // static
291 void PermissionHatsTriggerHelper::
292     IncrementOneTimePermissionPromptsDecidedIfApplicable(
293         ContentSettingsType type,
294         PrefService* pref_service) {
295   if (base::FeatureList::IsEnabled(features::kOneTimePermission) &&
296       PermissionUtil::CanPermissionBeAllowedOnce(type)) {
297     pref_service->SetInteger(
298         prefs::kOneTimePermissionPromptsDecidedCount,
299         pref_service->GetInteger(prefs::kOneTimePermissionPromptsDecidedCount) +
300             1);
301   }
302 }
303
304 // static
305 PermissionHatsTriggerHelper::OneTimePermissionPromptsDecidedBucket
306 PermissionHatsTriggerHelper::GetOneTimePromptsDecidedBucket(
307     PrefService* pref_service) {
308   int count =
309       pref_service->GetInteger(prefs::kOneTimePermissionPromptsDecidedCount);
310   if (count <= 1) {
311     return OneTimePermissionPromptsDecidedBucket::BUCKET_0_1;
312   } else if (count <= 3) {
313     return OneTimePermissionPromptsDecidedBucket::BUCKET_2_3;
314   } else if (count <= 5) {
315     return OneTimePermissionPromptsDecidedBucket::BUCKET_4_5;
316   } else if (count <= 10) {
317     return OneTimePermissionPromptsDecidedBucket::BUCKET_6_10;
318   } else if (count <= 20) {
319     return OneTimePermissionPromptsDecidedBucket::BUCKET_11_20;
320   } else {
321     return OneTimePermissionPromptsDecidedBucket::BUCKET_GT20;
322   }
323 }
324
325 // static
326 std::string PermissionHatsTriggerHelper::GetOneTimePromptsDecidedBucketString(
327     OneTimePermissionPromptsDecidedBucket bucket) {
328   switch (bucket) {
329     case BUCKET_0_1:
330       return "0_1";
331     case BUCKET_2_3:
332       return "2_3";
333     case BUCKET_4_5:
334       return "4_5";
335     case BUCKET_6_10:
336       return "6_10";
337     case BUCKET_11_20:
338       return "11_20";
339     case BUCKET_GT20:
340       return "GT20";
341     default:
342       NOTREACHED();
343   }
344 }
345
346 // static
347 std::vector<std::pair<std::string, std::string>>&
348 PermissionHatsTriggerHelper::GetPermissionPromptTriggerIdPairs(
349     const std::string& trigger_name_base) {
350   static base::NoDestructor<std::vector<std::pair<std::string, std::string>>>
351       trigger_id_pairs([trigger_name_base] {
352         return ComputePermissionPromptTriggerIdPairs(trigger_name_base);
353       }());
354   if (is_test) {
355     CHECK_IS_TEST();
356     *trigger_id_pairs =
357         ComputePermissionPromptTriggerIdPairs(trigger_name_base);
358   }
359   return *trigger_id_pairs;
360 }
361
362 // static
363 absl::optional<std::pair<std::string, double>> PermissionHatsTriggerHelper::
364     GetPermissionPromptTriggerNameAndProbabilityForRequestType(
365         const std::string& trigger_name_base,
366         const std::string& request_type) {
367   auto& trigger_id_pairs = GetPermissionPromptTriggerIdPairs(trigger_name_base);
368   auto& probability_vector =
369       GetProbabilityVector(feature_params::kProbabilityVector.Get());
370
371   if (trigger_id_pairs.size() == 1 && probability_vector.size() <= 1) {
372     // If a value is configured, use it, otherwise set it to 1.
373     return std::make_pair(
374         trigger_id_pairs[0].first,
375         probability_vector.size() == 1 ? probability_vector[0] : 1.0);
376   } else if (trigger_id_pairs.size() != probability_vector.size()) {
377     // Configuration error
378     return absl::nullopt;
379   } else {
380     auto& request_filter_vector = GetRequestFilterVector(
381         feature_params::kPermissionsPromptSurveyRequestTypeFilter.Get());
382
383     if (request_filter_vector.size() != trigger_id_pairs.size()) {
384       // Configuration error
385       return absl::nullopt;
386     }
387
388     for (unsigned long i = 0; i < trigger_id_pairs.size(); i++) {
389       if (base::EqualsCaseInsensitiveASCII(request_type,
390                                            request_filter_vector[i])) {
391         return std::make_pair(trigger_id_pairs.at(i).first,
392                               probability_vector[i]);
393       }
394     }
395
396     // No matching filter
397     return absl::nullopt;
398   }
399 }
400
401 // static
402 void PermissionHatsTriggerHelper::SetIsTest() {
403   is_test = true;
404 }
405
406 }  // namespace permissions