blob: 20fc44be9ef92b5df5ebc74b2e93bf55495eaaa9 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.h"
#include <utility>
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/time/default_clock.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/engagement/site_engagement_service_factory.h"
#include "chrome/browser/permissions/notifications_engagement_service_factory.h"
#include "chrome/browser/permissions/permission_revocation_request.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.h"
#include "chrome/browser/ui/safety_hub/safety_hub_constants.h"
#include "chrome/browser/ui/safety_hub/safety_hub_prefs.h"
#include "chrome/browser/ui/safety_hub/safety_hub_util.h"
#include "components/content_settings/core/browser/content_settings_uma_util.h"
#include "components/content_settings/core/common/features.h"
#include "components/permissions/notifications_engagement_service.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/content/browser/notification_content_detection/notification_content_detection_constants.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/core/browser/safe_browsing_metrics_collector.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "content/public/browser/browser_thread.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
// Histogram names.
constexpr char kAbusiveNotificationPermissionRevocationHistogram[] =
"Settings.SafetyHub.AbusiveNotificationPermissionRevocation";
constexpr char kPermissionChangedHistogramSuffix[] = "PermissionChanged";
void UpdateNotificationPermission(HostContentSettingsMap* hcsm,
GURL url,
ContentSetting setting_value) {
hcsm->SetContentSettingCustomScope(
ContentSettingsPattern::FromURLNoWildcard(url),
ContentSettingsPattern::Wildcard(), ContentSettingsType::NOTIFICATIONS,
setting_value);
}
void RecordAbusiveNotificationPermissionChangedHistogram(
bool is_ignored,
safe_browsing::NotificationRevocationSource revocation_source,
ContentSetting setting_value) {
std::string_view revoke_status = is_ignored ? "Ignored" : "Revoked";
std::string_view source_str;
switch (revocation_source) {
case safe_browsing::NotificationRevocationSource::
kSocialEngineeringBlocklist:
source_str = "SocialEngineeringBlocklist";
break;
case safe_browsing::NotificationRevocationSource::
kManualSafeBrowsingRevocation:
source_str = "ManualSafeBrowsingRevocation";
break;
case safe_browsing::NotificationRevocationSource::
kSuspiciousContentAutoRevocation:
source_str = "SuspiciousContentAutoRevocation";
break;
default:
source_str = "Unknown";
}
base::UmaHistogramEnumeration(
base::StrCat({kAbusiveNotificationPermissionRevocationHistogram, ".",
source_str, ".", revoke_status, ".",
kPermissionChangedHistogramSuffix}),
setting_value, ContentSetting::CONTENT_SETTING_NUM_SETTINGS);
}
// Convert string representation for storing in
// `REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS` into
// `NotificationRevocationSource`.
safe_browsing::NotificationRevocationSource GetNotificationRevocationSource(
std::string source_str) {
if (source_str == kSocialEngineeringBlocklistStr) {
return safe_browsing::NotificationRevocationSource::
kSocialEngineeringBlocklist;
}
if (source_str == kManualSafeBrowsingRevocationStr) {
return safe_browsing::NotificationRevocationSource::
kManualSafeBrowsingRevocation;
}
if (source_str == kSuspiciousContentAutoRevocationStr) {
return safe_browsing::NotificationRevocationSource::
kSuspiciousContentAutoRevocation;
}
// Only `kSocialEngineeringBlocklist` and `kManualSafeBrowsingRevocation` are
// stored in `REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS`, other type of
// `NotificationRevocationSource` should never be the reason for abusive
// notification revocation.
return safe_browsing::NotificationRevocationSource::kUnknown;
}
// Return true if users has chosen to see original content of warned
// suspicious notification.
bool HasShowOriginalSuspiciousNotification(HostContentSettingsMap* hcsm,
GURL url) {
DCHECK(hcsm);
DCHECK(url.is_valid());
base::Value stored_value(hcsm->GetWebsiteSetting(
url, url, ContentSettingsType::SUSPICIOUS_NOTIFICATION_SHOW_ORIGINAL));
if (stored_value.is_none()) {
return false;
}
DCHECK(stored_value.is_dict());
DCHECK(stored_value.GetDict().contains(
safe_browsing::kSuspiciousNotificationShowOriginalKey));
return stored_value.GetDict()
.FindBool(safe_browsing::kSuspiciousNotificationShowOriginalKey)
.value_or(false);
}
// Return true if the url should be considered for suspicious content
// auto-revocation. This includes checking if the notification is enabled, no
// other abusive revocation has been done, user engagement with the site is
// sufficiently low, and user have not interacted with warning or re-granting
// permission for `url` previously.
bool ShouldCheckSuspiciousContentRevocationThreshold(
Profile* profile,
HostContentSettingsMap* hcsm,
GURL url) {
DCHECK(hcsm);
DCHECK(url.is_valid());
// Notification permission for the site is not enabled.
if (hcsm->GetContentSetting(url, url, ContentSettingsType::NOTIFICATIONS) !=
ContentSetting::CONTENT_SETTING_ALLOW) {
return false;
}
// Notification permission has already been revoked for other abusive reasons.
if (safety_hub_util::IsUrlRevokedAbusiveNotification(hcsm, url)) {
return false;
}
// The user has previously re-granted revoked notification permission.
if (safety_hub_util::IsAbusiveNotificationRevocationIgnored(hcsm, url) ||
DisruptiveNotificationPermissionsManager::
IsUrlIgnoredForRevokedDisruptiveNotification(hcsm, url)) {
return false;
}
// The user interacts with suspicious notification warning to show original
// content.
if (HasShowOriginalSuspiciousNotification(hcsm, url)) {
return false;
}
// Check site engagement score.
auto* site_engagement_service =
site_engagement::SiteEngagementServiceFactory::GetForProfile(profile);
if (!site_engagement_service) {
return false;
}
double engagement_score = site_engagement_service->GetScore(url);
// The user engagement with the site is higher than revocation threshold.
if (engagement_score >=
safe_browsing::kAutoRevokeSuspiciousNotificationEngagementScoreCutOff
.Get()) {
return false;
}
return true;
}
} // namespace
AbusiveNotificationPermissionsManager::AbusiveNotificationPermissionsManager(
scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> database_manager,
scoped_refptr<HostContentSettingsMap> hcsm,
PrefService* pref_service)
: database_manager_(database_manager),
hcsm_(hcsm),
pref_service_(pref_service),
safe_browsing_check_delay_(kCheckUrlTimeoutMs) {}
AbusiveNotificationPermissionsManager::
~AbusiveNotificationPermissionsManager() = default;
// static
void AbusiveNotificationPermissionsManager::
ExecuteAbusiveNotificationAutoRevocation(
HostContentSettingsMap* hcsm,
GURL url,
safe_browsing::NotificationRevocationSource revocation_source,
const raw_ptr<const base::Clock> clock) {
UpdateNotificationPermission(hcsm, url,
ContentSetting::CONTENT_SETTING_DEFAULT);
// Set the default constraint to the current time and lifetime defined by
// the clean up threshold. Use this to set the expiration time of the
// revocation permission.
content_settings::ContentSettingConstraints default_constraint(clock->Now());
default_constraint.set_lifetime(safety_hub_util::GetCleanUpThreshold());
SetRevokedAbusiveNotificationPermission(
hcsm, url, /*is_ignored=*/false, revocation_source, default_constraint);
content_settings_uma_util::RecordContentSettingsHistogram(
"Settings.SafetyHub.UnusedSitePermissionsModule.AutoRevoked2",
ContentSettingsType::NOTIFICATIONS);
}
// static
void AbusiveNotificationPermissionsManager::
SetRevokedAbusiveNotificationPermission(
HostContentSettingsMap* hcsm,
GURL url,
bool is_ignored,
safe_browsing::NotificationRevocationSource revocation_source,
const content_settings::ContentSettingConstraints& constraints) {
DCHECK(url.is_valid());
// If the `url` should be ignore during future auto revocation, then the
// constraint should not expire. If the lifetime is zero, then the setting
// does not expire.
if (is_ignored) {
DCHECK(constraints.lifetime().is_zero());
DCHECK(constraints.expiration() == base::Time());
PermissionRevocationRequest::ExemptOriginFromFutureRevocations(hcsm, url);
} else {
PermissionRevocationRequest::UndoExemptOriginFromFutureRevocations(hcsm,
url);
}
base::Value::Dict revoked_value;
revoked_value.Set(
safety_hub::kRevokedStatusDictKeyStr,
is_ignored ? safety_hub::kIgnoreStr : safety_hub::kRevokeStr);
std::optional<std::string> source_str =
GetRevocationSourceString(revocation_source);
if (source_str) {
revoked_value.Set(kAbusiveRevocationSourceKeyStr, source_str.value());
}
hcsm->SetWebsiteSettingCustomScope(
ContentSettingsPattern::FromURLNoWildcard(url),
ContentSettingsPattern::Wildcard(),
ContentSettingsType::REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS,
base::Value(std::move(revoked_value)), constraints);
}
// static
// `SetRevokedAbusiveNotificationPermission`, preserving `revocation_source` if
// it exists. This method should be used for re-grant, undo of the re-grant, or
// other scenarios where there may be an existing revocation entry for the url.
void AbusiveNotificationPermissionsManager::
SetRevokedAbusiveNotificationPermission(
HostContentSettingsMap* hcsm,
GURL url,
bool is_ignored,
const content_settings::ContentSettingConstraints& constraints) {
safe_browsing::NotificationRevocationSource revocation_source =
GetRevokedAbusiveNotificationRevocationSource(hcsm, url);
SetRevokedAbusiveNotificationPermission(hcsm, url, is_ignored,
revocation_source, constraints);
}
// static
bool AbusiveNotificationPermissionsManager::
MaybeRevokeSuspiciousNotificationPermission(Profile* profile, GURL url) {
DCHECK(base::FeatureList::IsEnabled(
safe_browsing::kAutoRevokeSuspiciousNotification));
auto* hcsm = HostContentSettingsMapFactory::GetForProfile(profile);
if (!url.is_valid() || !hcsm) {
return false;
}
if (!ShouldCheckSuspiciousContentRevocationThreshold(profile, hcsm, url)) {
return false;
}
// Check suspicious notification count.
base::Value stored_value = hcsm->GetWebsiteSetting(
url, url, ContentSettingsType::NOTIFICATION_INTERACTIONS);
if (stored_value.is_none() || !stored_value.is_dict()) {
return false;
}
int suspicious_notification_count = permissions::
NotificationsEngagementService::GetSuspiciousNotificationCountForPeriod(
stored_value.GetDict(),
safe_browsing::kAutoRevokeSuspiciousNotificationLookBackPeriod.Get());
// The `suspicious_notification_count` value represents how many suspicious
// notification warnings have been sent to the user prior to the current. This
// method is called when a notification should be replaced by a warning,
// revoking permissions if this notification warning would put the count over
// the threshold for revocation.
if (suspicious_notification_count <
safe_browsing::kAutoRevokeSuspiciousNotificationMinNotificationCount
.Get()) {
return false;
}
ExecuteAbusiveNotificationAutoRevocation(
hcsm, url,
safe_browsing::NotificationRevocationSource::
kSuspiciousContentAutoRevocation,
base::DefaultClock::GetInstance());
base::UmaHistogramEnumeration("SafeBrowsing.NotificationRevocationSource",
safe_browsing::NotificationRevocationSource::
kSuspiciousContentAutoRevocation);
return true;
}
// static
safe_browsing::NotificationRevocationSource
AbusiveNotificationPermissionsManager::
GetRevokedAbusiveNotificationRevocationSource(HostContentSettingsMap* hcsm,
GURL setting_url) {
DCHECK(setting_url.is_valid());
base::Value stored_value =
safety_hub_util::GetRevokedAbusiveNotificationPermissionsSettingValue(
hcsm, setting_url);
if (stored_value.is_none()) {
return safe_browsing::NotificationRevocationSource::kUnknown;
}
const std::string* source_str =
stored_value.GetDict().FindString(kAbusiveRevocationSourceKeyStr);
if (!source_str) {
return safe_browsing::NotificationRevocationSource::kUnknown;
}
return GetNotificationRevocationSource(*source_str);
}
// static
bool AbusiveNotificationPermissionsManager::IsUrlRevokedDueToSuspiciousContent(
HostContentSettingsMap* hcsm,
GURL setting_url) {
DCHECK(setting_url.is_valid());
return GetRevokedAbusiveNotificationRevocationSource(hcsm, setting_url) ==
safe_browsing::NotificationRevocationSource::
kSuspiciousContentAutoRevocation;
}
void AbusiveNotificationPermissionsManager::
CheckNotificationPermissionOrigins() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!database_manager_) {
return;
}
ResetSafeBrowsingCheckHelpers();
// Keep track of blocklist check count for logging histogram below.
int blocklist_check_counter = 0;
auto notification_permission_settings =
hcsm_->GetSettingsForOneType(ContentSettingsType::NOTIFICATIONS);
for (const auto& setting : notification_permission_settings) {
// Ignore origins where the permission is not CONTENT_SETTING_ALLOW or the
// user previously bypassed a warning or re-allowed the permission in Safety
// Hub.
if (ShouldCheckOrigin(setting)) {
// Since we are dealing with permissions for specific origins, and there
// are no wildcard values in the pattern, it is safe to convert between
// ContentSettingsPattern, string, and URL types.
GURL setting_url = GURL(setting.primary_pattern.ToString());
PerformSafeBrowsingChecks(setting_url);
blocklist_check_counter += 1;
}
}
base::UmaHistogramCounts100(safety_hub::kBlocklistCheckCountHistogramName,
blocklist_check_counter);
}
void AbusiveNotificationPermissionsManager::
RegrantPermissionForOriginIfNecessary(const GURL& url) {
// If the user decides to regrant permissions for `url`, check if it has
// revoked abusive notification permissions. If so, allow notification
// permissions and ignore the `url` from future auto-revocation.
if (!safety_hub_util::IsUrlRevokedAbusiveNotification(hcsm_.get(), url)) {
return;
}
safe_browsing::NotificationRevocationSource revocation_source =
GetRevokedAbusiveNotificationRevocationSource(hcsm_.get(), url);
// Set this to true to prevent removal of revoked setting values.
is_abusive_site_revocation_running_ = true;
UpdateNotificationPermission(hcsm_.get(), url,
ContentSetting::CONTENT_SETTING_ALLOW);
SetRevokedAbusiveNotificationPermission(hcsm_.get(), url,
/*is_ignored=*/true,
revocation_source);
// Set this back to false, so that revoked settings can be cleaned up if
// necessary.
is_abusive_site_revocation_running_ = false;
LogAbusiveNotificationPermissionRevocationUKM(
url, AbusiveNotificationPermissionsInteractions::kAllowAgain,
revocation_source);
}
void AbusiveNotificationPermissionsManager::
UndoRegrantPermissionForOriginIfNecessary(
const GURL& url,
std::set<ContentSettingsType> permission_types,
content_settings::ContentSettingConstraints constraints) {
// The user has decided to undo the regranted permission revocation for `url`.
// Only update the `NOTIFICATIONS` and
// `REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS` settings if the url had revoked
// notification permissions.
if (!permission_types.contains(ContentSettingsType::NOTIFICATIONS)) {
return;
}
base::Value stored_value(hcsm_->GetWebsiteSetting(
url, url, ContentSettingsType::REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS));
if (stored_value.is_none()) {
return;
}
safe_browsing::NotificationRevocationSource revocation_source =
GetRevokedAbusiveNotificationRevocationSource(hcsm_.get(), url);
// Set this to true to prevent removal of revoked setting values.
is_abusive_site_revocation_running_ = true;
UpdateNotificationPermission(hcsm_.get(), url,
ContentSetting::CONTENT_SETTING_DEFAULT);
SetRevokedAbusiveNotificationPermission(hcsm_.get(), url,
/*is_ignored=*/false,
revocation_source, constraints);
// Set this back to false, so that revoked settings can be cleaned up if
// necessary.
is_abusive_site_revocation_running_ = false;
LogAbusiveNotificationPermissionRevocationUKM(
url, AbusiveNotificationPermissionsInteractions::kUndoAllowAgain,
revocation_source);
}
void AbusiveNotificationPermissionsManager::ClearRevokedPermissionsList() {
ContentSettingsForOneType revoked_permissions =
safety_hub_util::GetRevokedAbusiveNotificationPermissions(hcsm_.get());
for (const auto& revoked_permission : revoked_permissions) {
DeletePatternFromRevokedAbusiveNotificationList(
revoked_permission.primary_pattern,
revoked_permission.secondary_pattern);
}
}
void AbusiveNotificationPermissionsManager::
DeletePatternFromRevokedAbusiveNotificationList(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern) {
hcsm_->SetWebsiteSettingCustomScope(
primary_pattern, secondary_pattern,
ContentSettingsType::REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS, {});
}
void AbusiveNotificationPermissionsManager::OnPermissionChanged(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern) {
GURL setting_url = primary_pattern.ToRepresentativeUrl();
if (!setting_url.is_valid()) {
return;
}
base::Value stored_value =
safety_hub_util::GetRevokedAbusiveNotificationPermissionsSettingValue(
hcsm_.get(), setting_url);
if (stored_value.is_none()) {
// This permission change is unrelated to abusive revocation; do nothing.
return;
}
bool is_ignored = safety_hub_util::IsAbusiveNotificationRevocationIgnored(
hcsm_.get(), setting_url);
safe_browsing::NotificationRevocationSource revocation_source =
GetRevokedAbusiveNotificationRevocationSource(hcsm_.get(), setting_url);
ContentSetting new_setting = hcsm_->GetContentSetting(
setting_url, secondary_pattern.ToRepresentativeUrl(),
ContentSettingsType::NOTIFICATIONS);
RecordAbusiveNotificationPermissionChangedHistogram(
is_ignored, revocation_source, new_setting);
// If the user re-granted notification permission revoked due to suspicious
// content, set revocation entry to ignore so the permission will not be
// revoked again.
if (new_setting == CONTENT_SETTING_ALLOW &&
revocation_source == safe_browsing::NotificationRevocationSource::
kSuspiciousContentAutoRevocation) {
SetRevokedAbusiveNotificationPermission(hcsm_.get(), setting_url,
/*is_ignored=*/true);
// Return without deleting abusive revocation entry.
return;
}
// Delete entry from abusive notification list as we assume the user is taking
// an active decision on the revocation. Note removal of entry with revoked
// status "ignored" will result in notification being auto-revoked again once
// criteria are met.
DeletePatternFromRevokedAbusiveNotificationList(primary_pattern,
secondary_pattern);
}
void AbusiveNotificationPermissionsManager::RestoreDeletedRevokedPermission(
const ContentSettingsPattern& primary_pattern,
content_settings::ContentSettingConstraints constraints) {
SetRevokedAbusiveNotificationPermission(hcsm_.get(),
primary_pattern.ToRepresentativeUrl(),
/*is_ignored=*/false, constraints);
}
const base::Clock* AbusiveNotificationPermissionsManager::GetClock() {
if (clock_for_testing_) {
return clock_for_testing_;
}
return base::DefaultClock::GetInstance();
}
bool AbusiveNotificationPermissionsManager::IsRevocationRunning() {
return is_abusive_site_revocation_running_ ||
!safe_browsing_request_clients_.empty();
}
void AbusiveNotificationPermissionsManager::
LogAbusiveNotificationPermissionRevocationUKM(
const GURL& origin,
AbusiveNotificationPermissionsInteractions interaction,
safe_browsing::NotificationRevocationSource revocation_source) {
ukm::SourceId source_id = ukm::UkmRecorder::GetSourceIdForNotificationEvent(
base::PassKey<AbusiveNotificationPermissionsManager>(), origin);
ukm::builders::SafetyHub_AbusiveNotificationPermissionRevocation_Interactions(
source_id)
.SetInteractionType(static_cast<int>(interaction))
.SetRevocationSource(static_cast<int>(revocation_source))
.Record(ukm::UkmRecorder::Get());
}
AbusiveNotificationPermissionsManager::SafeBrowsingCheckClient::
SafeBrowsingCheckClient(
base::PassKey<safe_browsing::SafeBrowsingDatabaseManager::Client>
pass_key,
safe_browsing::SafeBrowsingDatabaseManager* database_manager,
raw_ptr<std::map<SafeBrowsingCheckClient*,
std::unique_ptr<SafeBrowsingCheckClient>>>
safe_browsing_request_clients,
raw_ptr<HostContentSettingsMap> hcsm,
PrefService* pref_service,
GURL url,
int safe_browsing_check_delay,
const base::Clock* clock)
: safe_browsing::SafeBrowsingDatabaseManager::Client(std::move(pass_key)),
database_manager_(database_manager),
safe_browsing_request_clients_(safe_browsing_request_clients),
hcsm_(hcsm),
pref_service_(pref_service),
url_(url),
safe_browsing_check_delay_(safe_browsing_check_delay),
clock_(clock) {}
AbusiveNotificationPermissionsManager::SafeBrowsingCheckClient::
~SafeBrowsingCheckClient() {
if (timer_.IsRunning()) {
DCHECK(database_manager_);
database_manager_->CancelCheck(this);
timer_.Stop();
}
}
void AbusiveNotificationPermissionsManager::SafeBrowsingCheckClient::
CheckSocialEngineeringBlocklist() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Set a timer to fail open, i.e. call it "allowlisted", if the full
// check takes too long.
auto timeout_callback =
base::BindOnce(&AbusiveNotificationPermissionsManager::
SafeBrowsingCheckClient::OnCheckBlocklistTimeout,
weak_factory_.GetWeakPtr());
// Start a timer to abort the check if it takes too long.
timer_.Start(FROM_HERE, base::Milliseconds(safe_browsing_check_delay_),
std::move(timeout_callback));
// Check the phishing blocklist, since this is where we'll find blocklisted
// abusive notification sites.
DCHECK(database_manager_);
bool is_safe_synchronously = database_manager_->CheckBrowseUrl(
url_,
safe_browsing::CreateSBThreatTypeSet(
{safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_PHISHING}),
this, safe_browsing::CheckBrowseUrlType::kHashDatabase);
// If we can synchronously determine that the URL is safe, stop the timer to
// avoid `OnCheckBlocklistTimeout` from being called, since
// `OnCheckBrowseUrlResult` won't be called.
if (is_safe_synchronously) {
timer_.Stop();
safe_browsing_request_clients_->erase(this);
// The previous line results in deleting this object.
// No further access to the object's attributes is permitted here.
}
}
void AbusiveNotificationPermissionsManager::SafeBrowsingCheckClient::
OnCheckBrowseUrlResult(const GURL& url,
safe_browsing::SBThreatType threat_type,
const safe_browsing::ThreatMetadata& metadata) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Stop the timer to avoid `OnCheckBlocklistTimeout` from being called, since
// we got a blocklist check result in time.
timer_.Stop();
if (threat_type == safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_PHISHING) {
ExecuteAbusiveNotificationAutoRevocation(
hcsm_.get(), url,
safe_browsing::NotificationRevocationSource::
kSocialEngineeringBlocklist,
clock_);
safe_browsing::SafeBrowsingMetricsCollector::
LogSafeBrowsingNotificationRevocationSourceHistogram(
safe_browsing::NotificationRevocationSource::
kSocialEngineeringBlocklist);
}
// Update user pref that stores the time of the last successful blocklist
// check.
if (pref_service_) {
base::TimeDelta delta_since_unix_epoch =
base::Time::Now() - base::Time::UnixEpoch();
pref_service_->SetInt64(
safety_hub_prefs::
kLastTimeInMsAbusiveNotificationBlocklistCheckCompleted,
delta_since_unix_epoch.InMilliseconds());
}
safe_browsing_request_clients_->erase(this);
// The previous line results in deleting this object.
// No further access to the object's attributes is permitted here.
}
void AbusiveNotificationPermissionsManager::SafeBrowsingCheckClient::
OnCheckBlocklistTimeout() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(database_manager_);
database_manager_->CancelCheck(this);
safe_browsing_request_clients_->erase(this);
// The previous line results in deleting this object.
// No further access to the object's attributes is permitted here.
}
void AbusiveNotificationPermissionsManager::PerformSafeBrowsingChecks(
GURL url) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(database_manager_);
auto new_sb_check = std::make_unique<SafeBrowsingCheckClient>(
safe_browsing::SafeBrowsingDatabaseManager::Client::GetPassKey(),
database_manager_.get(), &safe_browsing_request_clients_, hcsm_.get(),
pref_service_, url, safe_browsing_check_delay_, GetClock());
auto new_sb_check_ptr = new_sb_check.get();
safe_browsing_request_clients_[new_sb_check_ptr] = std::move(new_sb_check);
new_sb_check_ptr->CheckSocialEngineeringBlocklist();
}
bool AbusiveNotificationPermissionsManager::ShouldCheckOrigin(
const ContentSettingPatternSource& setting) const {
DCHECK(hcsm_);
// Skip wildcard patterns that don't belong to a single origin.
if (!setting.primary_pattern.MatchesSingleOrigin()) {
return false;
}
// Skip checks when they've already been performed within the last 24 hours.
if (pref_service_) {
base::TimeDelta delta_since_unix_epoch =
base::Time::Now() - base::Time::UnixEpoch();
base::TimeDelta last_check_time =
base::Milliseconds(pref_service_->GetInt64(
safety_hub_prefs::
kLastTimeInMsAbusiveNotificationBlocklistCheckCompleted));
// If a previous check has occurred and was within the last day, skip
// checks.
if (last_check_time > base::Milliseconds(0) &&
delta_since_unix_epoch - last_check_time < base::Days(1)) {
return false;
}
}
if (setting.setting_value == CONTENT_SETTING_ALLOW) {
// Secondary pattern should be wildcard for notification permissions. If
// not, the permission should be ignored.
if (setting.secondary_pattern != ContentSettingsPattern::Wildcard()) {
return false;
}
// If the url is not valid, do not check the origin.
GURL setting_url = setting.primary_pattern.ToRepresentativeUrl();
if (!setting_url.is_valid()) {
return false;
}
// If the url does not have a REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS
// setting value, we should check the origin.
base::Value stored_value =
safety_hub_util::GetRevokedAbusiveNotificationPermissionsSettingValue(
hcsm_.get(), setting_url);
if (stored_value.is_none()) {
return true;
}
// If the url has a REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS setting value
// and the NOTIFICATIONS permission is set to CONTENT_SETTING_ALLOW, then
// the user chose to ignore the origin for future revocations so the setting
// value should specify ignore.
DCHECK(safety_hub_util::IsAbusiveNotificationRevocationIgnored(
hcsm_.get(), setting.primary_pattern.ToRepresentativeUrl()));
return false;
}
return false;
}
void AbusiveNotificationPermissionsManager::ResetSafeBrowsingCheckHelpers() {
if (!safe_browsing_request_clients_.empty()) {
safe_browsing_request_clients_.clear();
}
}
// static
std::optional<std::string>
AbusiveNotificationPermissionsManager::GetRevocationSourceString(
safe_browsing::NotificationRevocationSource source) {
switch (source) {
case safe_browsing::NotificationRevocationSource::
kSocialEngineeringBlocklist:
return kSocialEngineeringBlocklistStr;
case safe_browsing::NotificationRevocationSource::
kManualSafeBrowsingRevocation:
return kManualSafeBrowsingRevocationStr;
case safe_browsing::NotificationRevocationSource::
kSuspiciousContentAutoRevocation:
return kSuspiciousContentAutoRevocationStr;
// Other revocation sources are not stored in
// REVOKED_ABUSIVE_NOTIFICATION_PERMISSIONS setting.
default:
return std::nullopt;
}
}