File: abusive_notification_permissions_manager.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (340 lines) | stat: -rw-r--r-- 14,375 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
// 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/time/default_clock.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/prefs/pref_service.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace {

void UpdateNotificationPermission(HostContentSettingsMap* hcsm,
                                  GURL url,
                                  ContentSetting setting_value) {
  hcsm->SetContentSettingCustomScope(
      ContentSettingsPattern::FromURLNoWildcard(url),
      ContentSettingsPattern::Wildcard(), ContentSettingsType::NOTIFICATIONS,
      setting_value);
}

}  // 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;

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;
  }
  // 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);
  safety_hub_util::SetRevokedAbusiveNotificationPermission(hcsm_.get(), url,
                                                           /*is_ignored=*/true);
  // Set this back to false, so that revoked settings can be cleaned up if
  // necessary.
  is_abusive_site_revocation_running_ = false;
}

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;
  }
  // 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);
  safety_hub_util::SetRevokedAbusiveNotificationPermission(
      hcsm_.get(), url, /*is_ignored=*/false, constraints);
  // Set this back to false, so that revoked settings can be cleaned up if
  // necessary.
  is_abusive_site_revocation_running_ = false;
}

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::RestoreDeletedRevokedPermission(
    const ContentSettingsPattern& primary_pattern,
    content_settings::ContentSettingConstraints constraints) {
  safety_hub_util::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();
}

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) {
    UpdateNotificationPermission(hcsm_.get(), url,
                                 ContentSetting::CONTENT_SETTING_DEFAULT);
    content_settings::ContentSettingConstraints default_constraint(
        clock_->Now());
    default_constraint.set_lifetime(safety_hub_util::GetCleanUpThreshold());
    safety_hub_util::SetRevokedAbusiveNotificationPermission(
        hcsm_.get(), url, /*is_ignored=*/false, default_constraint);
    content_settings_uma_util::RecordContentSettingsHistogram(
        "Settings.SafetyHub.UnusedSitePermissionsModule.AutoRevoked2",
        ContentSettingsType::NOTIFICATIONS);
  }
  // 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();
  }
}