1 // Copyright 2016 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.
5 #include "components/permissions/permission_decision_auto_blocker.h"
12 #include "base/feature_list.h"
13 #include "base/metrics/field_trial_params.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/values.h"
16 #include "components/content_settings/core/browser/host_content_settings_map.h"
17 #include "components/keyed_service/content/browser_context_dependency_manager.h"
18 #include "components/permissions/features.h"
19 #include "components/permissions/permission_util.h"
22 namespace permissions {
25 using PermissionStatus = blink::mojom::PermissionStatus;
27 constexpr int kDefaultDismissalsBeforeBlock = 3;
28 constexpr int kDefaultIgnoresBeforeBlock = 4;
29 constexpr int kDefaultDismissalsBeforeBlockWithQuietUi = 1;
30 constexpr int kDefaultIgnoresBeforeBlockWithQuietUi = 2;
31 constexpr int kDefaultEmbargoDays = 7;
33 // The number of times that users may explicitly dismiss a
34 // FEDERATED_IDENTITY_API permission prompt from an origin before it is
35 // automatically blocked.
36 constexpr int kFederatedIdentityApiDismissalsBeforeBlock = 1;
38 // The durations that an origin will stay under embargo for the
39 // FEDERATED_IDENTITY_API permission due to the user explicitly dismissing the
41 constexpr base::TimeDelta kFederatedIdentityApiEmbargoDurationDismiss[] = {
42 base::Hours(2) /* 1st dismissal */, base::Days(1) /* 2nd dismissal */,
43 base::Days(7), base::Days(28)};
45 // The duration that an origin will stay under embargo for the
46 // FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION permission due to an auto re-authn
47 // prompt being displayed recently.
48 constexpr base::TimeDelta kFederatedIdentityAutoReauthnEmbargoDuration =
51 // The number of times that users may explicitly dismiss a permission prompt
52 // from an origin before it is automatically blocked.
53 int g_dismissals_before_block = kDefaultDismissalsBeforeBlock;
55 // The number of times that users may ignore a permission prompt from an origin
56 // before it is automatically blocked.
57 int g_ignores_before_block = kDefaultIgnoresBeforeBlock;
59 // The number of times that users may dismiss a permission prompt that uses the
60 // quiet UI from an origin before it is automatically blocked.
61 int g_dismissals_before_block_with_quiet_ui =
62 kDefaultDismissalsBeforeBlockWithQuietUi;
64 // The number of times that users may ignore a permission prompt that uses the
65 // quiet UI from an origin before it is automatically blocked.
66 int g_ignores_before_block_with_quiet_ui =
67 kDefaultIgnoresBeforeBlockWithQuietUi;
69 // The number of days that an origin will stay under embargo for a requested
70 // permission due to repeated dismissals.
71 int g_dismissal_embargo_days = kDefaultEmbargoDays;
73 // The number of days that an origin will stay under embargo for a requested
74 // permission due to repeated ignores.
75 int g_ignore_embargo_days = kDefaultEmbargoDays;
77 std::string GetStringForContentType(ContentSettingsType content_type) {
78 if (content_type == ContentSettingsType::FEDERATED_IDENTITY_API)
79 return "FederatedIdentityApi";
82 ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION) {
83 return "FederatedIdentityAutoReauthn";
86 if (content_type == ContentSettingsType::FILE_SYSTEM_WRITE_GUARD) {
87 return "FileSystemWriteGuard";
90 if (content_type == ContentSettingsType::AUTO_PICTURE_IN_PICTURE) {
91 return "AutoPictureInPicture";
94 return PermissionUtil::GetPermissionString(content_type);
97 base::Value::Dict GetOriginAutoBlockerData(HostContentSettingsMap* settings,
98 const GURL& origin_url) {
99 base::Value website_setting = settings->GetWebsiteSetting(
100 origin_url, GURL(), ContentSettingsType::PERMISSION_AUTOBLOCKER_DATA);
101 if (!website_setting.is_dict()) {
102 return base::Value::Dict();
105 return std::move(website_setting.GetDict());
108 base::Value::Dict* GetOrCreatePermissionDict(base::Value::Dict& origin_dict,
109 const std::string& permission) {
110 return origin_dict.EnsureDict(permission);
113 int RecordActionInWebsiteSettings(const GURL& url,
114 ContentSettingsType permission,
116 HostContentSettingsMap* settings_map) {
117 base::Value::Dict dict = GetOriginAutoBlockerData(settings_map, url);
119 base::Value::Dict* permission_dict =
120 GetOrCreatePermissionDict(dict, GetStringForContentType(permission));
122 absl::optional<int> value = permission_dict->FindInt(key);
123 int current_count = value.value_or(0);
124 permission_dict->Set(key, base::Value(++current_count));
126 settings_map->SetWebsiteSettingDefaultScope(
127 url, GURL(), ContentSettingsType::PERMISSION_AUTOBLOCKER_DATA,
128 base::Value(std::move(dict)));
130 return current_count;
133 int GetActionCount(const GURL& url,
134 ContentSettingsType permission,
136 HostContentSettingsMap* settings_map) {
137 base::Value::Dict dict = GetOriginAutoBlockerData(settings_map, url);
138 base::Value::Dict* permission_dict =
139 GetOrCreatePermissionDict(dict, GetStringForContentType(permission));
141 absl::optional<int> value = permission_dict->FindInt(key);
142 return value.value_or(0);
145 // Returns the number of times that users may explicitly dismiss a permission
146 // prompt for an origin for the passed-in |permission| before it is
147 // automatically blocked.
148 int GetDismissalsBeforeBlockForContentSettingsType(
149 ContentSettingsType permission) {
150 return (permission == ContentSettingsType::FEDERATED_IDENTITY_API)
151 ? kFederatedIdentityApiDismissalsBeforeBlock
152 : g_dismissals_before_block;
155 // The duration that an origin will stay under embargo for the passed-in
156 // |permission| due to the user explicitly dismissing the permission prompt.
157 base::TimeDelta GetEmbargoDurationForContentSettingsType(
158 ContentSettingsType permission,
160 if (permission == ContentSettingsType::FEDERATED_IDENTITY_API) {
161 int duration_index = std::clamp(
162 dismiss_count - 1, 0,
164 std::size(kFederatedIdentityApiEmbargoDurationDismiss) - 1));
165 return kFederatedIdentityApiEmbargoDurationDismiss[duration_index];
169 ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION) {
170 return kFederatedIdentityAutoReauthnEmbargoDuration;
173 return base::Days(g_dismissal_embargo_days);
176 base::Time GetEmbargoStartTime(base::Value::Dict* permission_dict,
177 const base::Feature& feature,
179 absl::optional<double> found = permission_dict->FindDouble(key);
180 if (found && base::FeatureList::IsEnabled(feature)) {
181 return base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(*found));
186 bool IsUnderEmbargo(base::Value::Dict* permission_dict,
187 const base::Feature& feature,
189 base::Time current_time,
190 base::TimeDelta offset) {
191 absl::optional<double> found = permission_dict->FindDouble(key);
192 if (found && base::FeatureList::IsEnabled(feature) &&
193 current_time < base::Time::FromInternalValue(*found) + offset) {
200 void UpdateValueFromVariation(const std::string& variation_value,
202 const int default_value) {
204 if (base::StringToInt(variation_value, &tmp_value) && tmp_value > 0)
205 *value_store = tmp_value;
207 *value_store = default_value;
213 const char PermissionDecisionAutoBlocker::kPromptDismissCountKey[] =
217 const char PermissionDecisionAutoBlocker::kPromptIgnoreCountKey[] =
221 const char PermissionDecisionAutoBlocker::kPromptDismissCountWithQuietUiKey[] =
222 "dismiss_count_quiet_ui";
225 const char PermissionDecisionAutoBlocker::kPromptIgnoreCountWithQuietUiKey[] =
226 "ignore_count_quiet_ui";
229 const char PermissionDecisionAutoBlocker::kPermissionDismissalEmbargoKey[] =
230 "dismissal_embargo_days";
233 const char PermissionDecisionAutoBlocker::kPermissionIgnoreEmbargoKey[] =
234 "ignore_embargo_days";
237 const char PermissionDecisionAutoBlocker::kPermissionDisplayEmbargoKey[] =
238 "display_embargo_minutes";
241 bool PermissionDecisionAutoBlocker::IsEnabledForContentSetting(
242 ContentSettingsType content_setting) {
243 return PermissionUtil::IsPermission(content_setting) ||
244 content_setting == ContentSettingsType::FEDERATED_IDENTITY_API ||
246 ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION ||
247 content_setting == ContentSettingsType::FILE_SYSTEM_WRITE_GUARD ||
248 content_setting == ContentSettingsType::AUTO_PICTURE_IN_PICTURE;
252 absl::optional<content::PermissionResult>
253 PermissionDecisionAutoBlocker::GetEmbargoResult(
254 HostContentSettingsMap* settings_map,
255 const GURL& request_origin,
256 ContentSettingsType permission,
257 base::Time current_time) {
258 DCHECK(settings_map);
259 DCHECK(IsEnabledForContentSetting(permission));
261 base::Value::Dict dict =
262 GetOriginAutoBlockerData(settings_map, request_origin);
263 base::Value::Dict* permission_dict =
264 GetOrCreatePermissionDict(dict, GetStringForContentType(permission));
266 int dismiss_count = GetActionCount(request_origin, permission,
267 kPromptDismissCountKey, settings_map);
268 if (IsUnderEmbargo(permission_dict, features::kBlockPromptsIfDismissedOften,
269 kPermissionDismissalEmbargoKey, current_time,
270 GetEmbargoDurationForContentSettingsType(permission,
272 return content::PermissionResult(
273 PermissionStatus::DENIED,
274 content::PermissionStatusSource::MULTIPLE_DISMISSALS);
277 if (IsUnderEmbargo(permission_dict, features::kBlockPromptsIfIgnoredOften,
278 kPermissionIgnoreEmbargoKey, current_time,
279 base::Days(g_ignore_embargo_days))) {
280 return content::PermissionResult(
281 PermissionStatus::DENIED,
282 content::PermissionStatusSource::MULTIPLE_IGNORES);
285 if (IsUnderEmbargo(permission_dict,
286 features::kBlockRepeatedAutoReauthnPrompts,
287 kPermissionDisplayEmbargoKey, current_time,
288 GetEmbargoDurationForContentSettingsType(
289 permission, /*dismiss_count=*/0))) {
290 return content::PermissionResult(
291 PermissionStatus::DENIED,
292 content::PermissionStatusSource::RECENT_DISPLAY);
295 return absl::nullopt;
299 void PermissionDecisionAutoBlocker::UpdateFromVariations() {
300 std::string dismissals_before_block_value =
301 base::GetFieldTrialParamValueByFeature(
302 features::kBlockPromptsIfDismissedOften, kPromptDismissCountKey);
303 std::string ignores_before_block_value =
304 base::GetFieldTrialParamValueByFeature(
305 features::kBlockPromptsIfIgnoredOften, kPromptIgnoreCountKey);
306 std::string dismissals_before_block_value_with_quiet_ui =
307 base::GetFieldTrialParamValueByFeature(
308 features::kBlockPromptsIfDismissedOften,
309 kPromptDismissCountWithQuietUiKey);
310 std::string ignores_before_block_value_with_quiet_ui =
311 base::GetFieldTrialParamValueByFeature(
312 features::kBlockPromptsIfIgnoredOften,
313 kPromptIgnoreCountWithQuietUiKey);
314 std::string dismissal_embargo_days_value =
315 base::GetFieldTrialParamValueByFeature(
316 features::kBlockPromptsIfDismissedOften,
317 kPermissionDismissalEmbargoKey);
318 std::string ignore_embargo_days_value =
319 base::GetFieldTrialParamValueByFeature(
320 features::kBlockPromptsIfIgnoredOften, kPermissionIgnoreEmbargoKey);
322 // If converting the value fails, revert to the original value.
323 UpdateValueFromVariation(dismissals_before_block_value,
324 &g_dismissals_before_block,
325 kDefaultDismissalsBeforeBlock);
326 UpdateValueFromVariation(ignores_before_block_value, &g_ignores_before_block,
327 kDefaultIgnoresBeforeBlock);
328 UpdateValueFromVariation(dismissals_before_block_value_with_quiet_ui,
329 &g_dismissals_before_block_with_quiet_ui,
330 kDefaultDismissalsBeforeBlockWithQuietUi);
331 UpdateValueFromVariation(ignores_before_block_value_with_quiet_ui,
332 &g_ignores_before_block_with_quiet_ui,
333 kDefaultIgnoresBeforeBlockWithQuietUi);
334 UpdateValueFromVariation(dismissal_embargo_days_value,
335 &g_dismissal_embargo_days, kDefaultEmbargoDays);
336 UpdateValueFromVariation(ignore_embargo_days_value, &g_ignore_embargo_days,
337 kDefaultEmbargoDays);
340 bool PermissionDecisionAutoBlocker::IsEmbargoed(
341 const GURL& request_origin,
342 ContentSettingsType permission) {
343 return GetEmbargoResult(request_origin, permission).has_value();
346 absl::optional<content::PermissionResult>
347 PermissionDecisionAutoBlocker::GetEmbargoResult(
348 const GURL& request_origin,
349 ContentSettingsType permission) {
350 return GetEmbargoResult(settings_map_, request_origin, permission,
354 base::Time PermissionDecisionAutoBlocker::GetEmbargoStartTime(
355 const GURL& request_origin,
356 ContentSettingsType permission) {
357 DCHECK(settings_map_);
358 base::Value::Dict dict =
359 GetOriginAutoBlockerData(settings_map_, request_origin);
360 base::Value::Dict* permission_dict =
361 GetOrCreatePermissionDict(dict, GetStringForContentType(permission));
363 // A permission may have a record for both dismisal and ignore, return the
364 // most recent. A permission will only actually be under one embargo, but
365 // the record of embargo start will persist until explicitly deleted
366 base::Time dismissal_start_time = permissions::GetEmbargoStartTime(
367 permission_dict, features::kBlockPromptsIfDismissedOften,
368 kPermissionDismissalEmbargoKey);
369 base::Time ignore_start_time = permissions::GetEmbargoStartTime(
370 permission_dict, features::kBlockPromptsIfIgnoredOften,
371 kPermissionIgnoreEmbargoKey);
373 return dismissal_start_time > ignore_start_time ? dismissal_start_time
377 std::set<GURL> PermissionDecisionAutoBlocker::GetEmbargoedOrigins(
378 ContentSettingsType content_type) {
379 return GetEmbargoedOrigins(std::vector<ContentSettingsType>{content_type});
382 std::set<GURL> PermissionDecisionAutoBlocker::GetEmbargoedOrigins(
383 std::vector<ContentSettingsType> content_types) {
384 DCHECK(settings_map_);
386 std::vector<ContentSettingsType> filtered_content_types;
387 for (ContentSettingsType content_type : content_types) {
388 if (IsEnabledForContentSetting(content_type))
389 filtered_content_types.emplace_back(content_type);
391 if (filtered_content_types.empty())
392 return std::set<GURL>();
394 std::set<GURL> origins;
395 for (const auto& e : settings_map_->GetSettingsForOneType(
396 ContentSettingsType::PERMISSION_AUTOBLOCKER_DATA)) {
397 for (auto content_type : filtered_content_types) {
398 const GURL url(e.primary_pattern.ToString());
399 if (IsEmbargoed(url, content_type)) {
408 int PermissionDecisionAutoBlocker::GetDismissCount(
410 ContentSettingsType permission) {
411 return GetActionCount(url, permission, kPromptDismissCountKey, settings_map_);
414 int PermissionDecisionAutoBlocker::GetIgnoreCount(
416 ContentSettingsType permission) {
417 return GetActionCount(url, permission, kPromptIgnoreCountKey, settings_map_);
420 bool PermissionDecisionAutoBlocker::RecordDismissAndEmbargo(
422 ContentSettingsType permission,
423 bool dismissed_prompt_was_quiet) {
424 int current_dismissal_count = RecordActionInWebsiteSettings(
425 url, permission, kPromptDismissCountKey, settings_map_);
427 int current_dismissal_count_with_quiet_ui =
428 dismissed_prompt_was_quiet
429 ? RecordActionInWebsiteSettings(url, permission,
430 kPromptDismissCountWithQuietUiKey,
434 // TODO(dominickn): ideally we would have a method
435 // PermissionContextBase::ShouldEmbargoAfterRepeatedDismissals() to specify
436 // if a permission is opted in. This is difficult right now because:
437 // 1. PermissionQueueController needs to call this method at a point where it
438 // does not have a PermissionContextBase available
439 // 2. Not calling RecordDismissAndEmbargo means no repeated dismissal metrics
441 if (base::FeatureList::IsEnabled(features::kBlockPromptsIfDismissedOften)) {
442 if (current_dismissal_count >=
443 GetDismissalsBeforeBlockForContentSettingsType(permission)) {
444 PlaceUnderEmbargo(url, permission, kPermissionDismissalEmbargoKey);
448 if (current_dismissal_count_with_quiet_ui >=
449 g_dismissals_before_block_with_quiet_ui) {
450 DCHECK(permission == ContentSettingsType::NOTIFICATIONS ||
451 permission == ContentSettingsType::GEOLOCATION);
452 PlaceUnderEmbargo(url, permission, kPermissionDismissalEmbargoKey);
459 bool PermissionDecisionAutoBlocker::RecordIgnoreAndEmbargo(
461 ContentSettingsType permission,
462 bool ignored_prompt_was_quiet) {
463 int current_ignore_count = RecordActionInWebsiteSettings(
464 url, permission, kPromptIgnoreCountKey, settings_map_);
466 int current_ignore_count_with_quiet_ui =
467 ignored_prompt_was_quiet
468 ? RecordActionInWebsiteSettings(url, permission,
469 kPromptIgnoreCountWithQuietUiKey,
473 if (base::FeatureList::IsEnabled(features::kBlockPromptsIfIgnoredOften)) {
474 if (current_ignore_count >= g_ignores_before_block) {
475 PlaceUnderEmbargo(url, permission, kPermissionIgnoreEmbargoKey);
479 if (current_ignore_count_with_quiet_ui >=
480 g_ignores_before_block_with_quiet_ui) {
481 DCHECK(permission == ContentSettingsType::NOTIFICATIONS ||
482 permission == ContentSettingsType::GEOLOCATION);
483 PlaceUnderEmbargo(url, permission, kPermissionIgnoreEmbargoKey);
491 bool PermissionDecisionAutoBlocker::RecordDisplayAndEmbargo(
493 ContentSettingsType permission) {
494 DCHECK_EQ(permission,
495 ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION);
496 if (base::FeatureList::IsEnabled(
497 features::kBlockRepeatedAutoReauthnPrompts)) {
498 PlaceUnderEmbargo(url, permission, kPermissionDisplayEmbargoKey);
504 void PermissionDecisionAutoBlocker::RemoveEmbargoAndResetCounts(
506 ContentSettingsType permission) {
507 if (!IsEnabledForContentSetting(permission))
510 base::Value::Dict dict = GetOriginAutoBlockerData(settings_map_, url);
512 dict.Remove(GetStringForContentType(permission));
514 settings_map_->SetWebsiteSettingDefaultScope(
515 url, GURL(), ContentSettingsType::PERMISSION_AUTOBLOCKER_DATA,
516 base::Value(std::move(dict)));
519 void PermissionDecisionAutoBlocker::RemoveEmbargoAndResetCounts(
520 base::RepeatingCallback<bool(const GURL& url)> filter) {
521 for (const auto& site : settings_map_->GetSettingsForOneType(
522 ContentSettingsType::PERMISSION_AUTOBLOCKER_DATA)) {
523 GURL origin(site.primary_pattern.ToString());
525 if (origin.is_valid() && filter.Run(origin)) {
526 settings_map_->SetWebsiteSettingDefaultScope(
527 origin, GURL(), ContentSettingsType::PERMISSION_AUTOBLOCKER_DATA,
533 void PermissionDecisionAutoBlocker::AddObserver(Observer* obs) {
534 observers_.AddObserver(obs);
537 void PermissionDecisionAutoBlocker::RemoveObserver(Observer* obs) {
538 observers_.RemoveObserver(obs);
543 PermissionDecisionAutoBlocker::GetPromptDismissCountKeyForTesting() {
544 return kPromptDismissCountKey;
547 PermissionDecisionAutoBlocker::PermissionDecisionAutoBlocker(
548 HostContentSettingsMap* settings_map)
549 : settings_map_(settings_map), clock_(base::DefaultClock::GetInstance()) {}
551 PermissionDecisionAutoBlocker::~PermissionDecisionAutoBlocker() {}
553 void PermissionDecisionAutoBlocker::PlaceUnderEmbargo(
554 const GURL& request_origin,
555 ContentSettingsType permission,
557 base::Value::Dict dict =
558 GetOriginAutoBlockerData(settings_map_, request_origin);
559 base::Value::Dict* permission_dict =
560 GetOrCreatePermissionDict(dict, GetStringForContentType(permission));
561 permission_dict->Set(
562 key, base::Value(static_cast<double>(clock_->Now().ToInternalValue())));
563 settings_map_->SetWebsiteSettingDefaultScope(
564 request_origin, GURL(), ContentSettingsType::PERMISSION_AUTOBLOCKER_DATA,
565 base::Value(std::move(dict)));
566 NotifyEmbargoStarted(request_origin, permission);
569 void PermissionDecisionAutoBlocker::NotifyEmbargoStarted(
571 ContentSettingsType content_setting) {
572 for (Observer& obs : observers_)
573 obs.OnEmbargoStarted(origin, content_setting);
576 void PermissionDecisionAutoBlocker::SetClockForTesting(base::Clock* clock) {
580 } // namespace permissions