blob: 6a2e0e9ecc5b368dbcffe39a97f2146e385073f6 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/permissions/permission_decision_auto_blocker.h"
#include <memory>
#include <string>
#include <utility>
#include "base/feature_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/permission_util.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_features.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/variations/variations_associated_data.h"
#include "url/gurl.h"
namespace {
constexpr int kDefaultDismissalsBeforeBlock = 3;
constexpr int kDefaultIgnoresBeforeBlock = 4;
constexpr int kDefaultEmbargoDays = 7;
// The number of times that users may explicitly dismiss a permission prompt
// from an origin before it is automatically blocked.
int g_dismissals_before_block = kDefaultDismissalsBeforeBlock;
// The number of times that users may ignore a permission prompt from an origin
// before it is automatically blocked.
int g_ignores_before_block = kDefaultIgnoresBeforeBlock;
// The number of days that an origin will stay under embargo for a requested
// permission due to repeated dismissals.
int g_dismissal_embargo_days = kDefaultEmbargoDays;
// The number of days that an origin will stay under embargo for a requested
// permission due to repeated ignores.
int g_ignore_embargo_days = kDefaultEmbargoDays;
std::unique_ptr<base::DictionaryValue> GetOriginAutoBlockerData(
HostContentSettingsMap* settings,
const GURL& origin_url) {
std::unique_ptr<base::DictionaryValue> dict =
base::DictionaryValue::From(settings->GetWebsiteSetting(
origin_url, GURL(), CONTENT_SETTINGS_TYPE_PERMISSION_AUTOBLOCKER_DATA,
std::string(), nullptr));
if (!dict)
return std::make_unique<base::DictionaryValue>();
return dict;
}
base::Value* GetOrCreatePermissionDict(base::Value* origin_dict,
const std::string& permission) {
base::Value* permission_dict =
origin_dict->FindKeyOfType(permission, base::Value::Type::DICTIONARY);
if (permission_dict)
return permission_dict;
return origin_dict->SetKey(permission,
base::Value(base::Value::Type::DICTIONARY));
}
int RecordActionInWebsiteSettings(const GURL& url,
ContentSettingsType permission,
const char* key,
Profile* profile) {
HostContentSettingsMap* map =
HostContentSettingsMapFactory::GetForProfile(profile);
std::unique_ptr<base::DictionaryValue> dict =
GetOriginAutoBlockerData(map, url);
base::Value* permission_dict = GetOrCreatePermissionDict(
dict.get(), PermissionUtil::GetPermissionString(permission));
base::Value* value =
permission_dict->FindKeyOfType(key, base::Value::Type::INTEGER);
int current_count = value ? value->GetInt() : 0;
permission_dict->SetKey(key, base::Value(++current_count));
map->SetWebsiteSettingDefaultScope(
url, GURL(), CONTENT_SETTINGS_TYPE_PERMISSION_AUTOBLOCKER_DATA,
std::string(), std::move(dict));
return current_count;
}
int GetActionCount(const GURL& url,
ContentSettingsType permission,
const char* key,
Profile* profile) {
HostContentSettingsMap* map =
HostContentSettingsMapFactory::GetForProfile(profile);
std::unique_ptr<base::DictionaryValue> dict =
GetOriginAutoBlockerData(map, url);
base::Value* permission_dict = GetOrCreatePermissionDict(
dict.get(), PermissionUtil::GetPermissionString(permission));
base::Value* value =
permission_dict->FindKeyOfType(key, base::Value::Type::INTEGER);
return value ? value->GetInt() : 0;
}
bool IsUnderEmbargo(base::Value* permission_dict,
const base::Feature& feature,
const char* key,
base::Time current_time,
base::TimeDelta offset) {
base::Value* found =
permission_dict->FindKeyOfType(key, base::Value::Type::DOUBLE);
if (found && base::FeatureList::IsEnabled(feature) &&
current_time <
base::Time::FromInternalValue(found->GetDouble()) + offset) {
return true;
}
return false;
}
void UpdateValueFromVariation(const std::string& variation_value,
int* value_store,
const int default_value) {
int tmp_value = -1;
if (base::StringToInt(variation_value, &tmp_value) && tmp_value > 0)
*value_store = tmp_value;
else
*value_store = default_value;
}
} // namespace
// PermissionDecisionAutoBlocker::Factory --------------------------------------
// static
PermissionDecisionAutoBlocker*
PermissionDecisionAutoBlocker::Factory::GetForProfile(Profile* profile) {
return static_cast<PermissionDecisionAutoBlocker*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
// static
PermissionDecisionAutoBlocker::Factory*
PermissionDecisionAutoBlocker::Factory::GetInstance() {
return base::Singleton<PermissionDecisionAutoBlocker::Factory>::get();
}
PermissionDecisionAutoBlocker::Factory::Factory()
: BrowserContextKeyedServiceFactory(
"PermissionDecisionAutoBlocker",
BrowserContextDependencyManager::GetInstance()) {}
PermissionDecisionAutoBlocker::Factory::~Factory() {}
KeyedService* PermissionDecisionAutoBlocker::Factory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
Profile* profile = static_cast<Profile*>(context);
return new PermissionDecisionAutoBlocker(profile);
}
content::BrowserContext*
PermissionDecisionAutoBlocker::Factory::GetBrowserContextToUse(
content::BrowserContext* context) const {
return chrome::GetBrowserContextOwnInstanceInIncognito(context);
}
// PermissionDecisionAutoBlocker -----------------------------------------------
// static
const char PermissionDecisionAutoBlocker::kPromptDismissCountKey[] =
"dismiss_count";
// static
const char PermissionDecisionAutoBlocker::kPromptIgnoreCountKey[] =
"ignore_count";
// static
const char PermissionDecisionAutoBlocker::kPermissionDismissalEmbargoKey[] =
"dismissal_embargo_days";
// static
const char PermissionDecisionAutoBlocker::kPermissionIgnoreEmbargoKey[] =
"ignore_embargo_days";
// static
PermissionDecisionAutoBlocker* PermissionDecisionAutoBlocker::GetForProfile(
Profile* profile) {
return PermissionDecisionAutoBlocker::Factory::GetForProfile(profile);
}
// static
PermissionResult PermissionDecisionAutoBlocker::GetEmbargoResult(
HostContentSettingsMap* settings_map,
const GURL& request_origin,
ContentSettingsType permission,
base::Time current_time) {
DCHECK(settings_map);
std::unique_ptr<base::DictionaryValue> dict =
GetOriginAutoBlockerData(settings_map, request_origin);
base::Value* permission_dict = GetOrCreatePermissionDict(
dict.get(), PermissionUtil::GetPermissionString(permission));
if (IsUnderEmbargo(permission_dict, features::kBlockPromptsIfDismissedOften,
kPermissionDismissalEmbargoKey, current_time,
base::TimeDelta::FromDays(g_dismissal_embargo_days))) {
return PermissionResult(CONTENT_SETTING_BLOCK,
PermissionStatusSource::MULTIPLE_DISMISSALS);
}
if (IsUnderEmbargo(permission_dict, features::kBlockPromptsIfIgnoredOften,
kPermissionIgnoreEmbargoKey, current_time,
base::TimeDelta::FromDays(g_ignore_embargo_days))) {
return PermissionResult(CONTENT_SETTING_BLOCK,
PermissionStatusSource::MULTIPLE_IGNORES);
}
return PermissionResult(CONTENT_SETTING_ASK,
PermissionStatusSource::UNSPECIFIED);
}
// static
void PermissionDecisionAutoBlocker::UpdateFromVariations() {
std::string dismissals_before_block_value =
variations::GetVariationParamValueByFeature(
features::kBlockPromptsIfDismissedOften, kPromptDismissCountKey);
std::string ignores_before_block_value =
variations::GetVariationParamValueByFeature(
features::kBlockPromptsIfIgnoredOften, kPromptIgnoreCountKey);
std::string dismissal_embargo_days_value =
variations::GetVariationParamValueByFeature(
features::kBlockPromptsIfDismissedOften,
kPermissionDismissalEmbargoKey);
std::string ignore_embargo_days_value =
variations::GetVariationParamValueByFeature(
features::kBlockPromptsIfIgnoredOften, kPermissionIgnoreEmbargoKey);
// If converting the value fails, revert to the original value.
UpdateValueFromVariation(dismissals_before_block_value,
&g_dismissals_before_block,
kDefaultDismissalsBeforeBlock);
UpdateValueFromVariation(ignores_before_block_value, &g_ignores_before_block,
kDefaultIgnoresBeforeBlock);
UpdateValueFromVariation(dismissal_embargo_days_value,
&g_dismissal_embargo_days, kDefaultEmbargoDays);
UpdateValueFromVariation(ignore_embargo_days_value, &g_ignore_embargo_days,
kDefaultEmbargoDays);
}
PermissionResult PermissionDecisionAutoBlocker::GetEmbargoResult(
const GURL& request_origin,
ContentSettingsType permission) {
return GetEmbargoResult(
HostContentSettingsMapFactory::GetForProfile(profile_), request_origin,
permission, clock_->Now());
}
int PermissionDecisionAutoBlocker::GetDismissCount(
const GURL& url,
ContentSettingsType permission) {
return GetActionCount(url, permission, kPromptDismissCountKey, profile_);
}
int PermissionDecisionAutoBlocker::GetIgnoreCount(
const GURL& url,
ContentSettingsType permission) {
return GetActionCount(url, permission, kPromptIgnoreCountKey, profile_);
}
bool PermissionDecisionAutoBlocker::RecordDismissAndEmbargo(
const GURL& url,
ContentSettingsType permission) {
int current_dismissal_count = RecordActionInWebsiteSettings(
url, permission, kPromptDismissCountKey, profile_);
// TODO(dominickn): ideally we would have a method
// PermissionContextBase::ShouldEmbargoAfterRepeatedDismissals() to specify
// if a permission is opted in. This is difficult right now because:
// 1. PermissionQueueController needs to call this method at a point where it
// does not have a PermissionContextBase available
// 2. Not calling RecordDismissAndEmbargo means no repeated dismissal metrics
// are recorded
// For now, only plugins are explicitly opted out. We should think about how
// to make this nicer once PermissionQueueController is removed.
if (base::FeatureList::IsEnabled(features::kBlockPromptsIfDismissedOften) &&
permission != CONTENT_SETTINGS_TYPE_PLUGINS &&
current_dismissal_count >= g_dismissals_before_block) {
PlaceUnderEmbargo(url, permission, kPermissionDismissalEmbargoKey);
return true;
}
return false;
}
bool PermissionDecisionAutoBlocker::RecordIgnoreAndEmbargo(
const GURL& url,
ContentSettingsType permission) {
int current_ignore_count = RecordActionInWebsiteSettings(
url, permission, kPromptIgnoreCountKey, profile_);
if (base::FeatureList::IsEnabled(features::kBlockPromptsIfIgnoredOften) &&
permission != CONTENT_SETTINGS_TYPE_PLUGINS &&
current_ignore_count >= g_ignores_before_block) {
PlaceUnderEmbargo(url, permission, kPermissionIgnoreEmbargoKey);
return true;
}
return false;
}
void PermissionDecisionAutoBlocker::RemoveEmbargoByUrl(
const GURL& url,
ContentSettingsType permission) {
if (!PermissionUtil::IsPermission(permission))
return;
// Don't proceed if |permission| was not under embargo for |url|.
PermissionResult result = GetEmbargoResult(url, permission);
if (result.source != PermissionStatusSource::MULTIPLE_DISMISSALS)
return;
HostContentSettingsMap* map =
HostContentSettingsMapFactory::GetForProfile(profile_);
std::unique_ptr<base::DictionaryValue> dict =
GetOriginAutoBlockerData(map, url);
base::Value* permission_dict = GetOrCreatePermissionDict(
dict.get(), PermissionUtil::GetPermissionString(permission));
const bool dismissal_key_deleted =
permission_dict->RemoveKey(kPermissionDismissalEmbargoKey);
DCHECK(dismissal_key_deleted);
map->SetWebsiteSettingDefaultScope(
url, GURL(), CONTENT_SETTINGS_TYPE_PERMISSION_AUTOBLOCKER_DATA,
std::string(), std::move(dict));
}
void PermissionDecisionAutoBlocker::RemoveCountsByUrl(
base::Callback<bool(const GURL& url)> filter) {
HostContentSettingsMap* map =
HostContentSettingsMapFactory::GetForProfile(profile_);
std::unique_ptr<ContentSettingsForOneType> settings(
new ContentSettingsForOneType);
map->GetSettingsForOneType(CONTENT_SETTINGS_TYPE_PERMISSION_AUTOBLOCKER_DATA,
std::string(), settings.get());
for (const auto& site : *settings) {
GURL origin(site.primary_pattern.ToString());
if (origin.is_valid() && filter.Run(origin)) {
map->SetWebsiteSettingDefaultScope(
origin, GURL(), CONTENT_SETTINGS_TYPE_PERMISSION_AUTOBLOCKER_DATA,
std::string(), nullptr);
}
}
}
PermissionDecisionAutoBlocker::PermissionDecisionAutoBlocker(Profile* profile)
: profile_(profile), clock_(base::DefaultClock::GetInstance()) {}
PermissionDecisionAutoBlocker::~PermissionDecisionAutoBlocker() {}
void PermissionDecisionAutoBlocker::PlaceUnderEmbargo(
const GURL& request_origin,
ContentSettingsType permission,
const char* key) {
HostContentSettingsMap* map =
HostContentSettingsMapFactory::GetForProfile(profile_);
std::unique_ptr<base::DictionaryValue> dict =
GetOriginAutoBlockerData(map, request_origin);
base::Value* permission_dict = GetOrCreatePermissionDict(
dict.get(), PermissionUtil::GetPermissionString(permission));
permission_dict->SetKey(
key, base::Value(static_cast<double>(clock_->Now().ToInternalValue())));
map->SetWebsiteSettingDefaultScope(
request_origin, GURL(), CONTENT_SETTINGS_TYPE_PERMISSION_AUTOBLOCKER_DATA,
std::string(), std::move(dict));
}
void PermissionDecisionAutoBlocker::SetClockForTesting(base::Clock* clock) {
clock_ = clock;
}