blob: 5e9c3d1b76a9ddf3c3b037a11f4b248ccb8f781f [file] [log] [blame] [edit]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/permissions/permission_decision_auto_blocker.h"
#include <map>
#include <memory>
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/test/gtest_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/test/test_permissions_client.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
namespace permissions {
namespace {
using PermissionStatus = blink::mojom::PermissionStatus;
bool FilterGoogle(const GURL& url) {
return url == "https://www.google.com/";
}
bool FilterAll(const GURL& url) {
return true;
}
} // namespace
class PermissionDecisionAutoBlockerUnitTest : public testing::Test {
protected:
PermissionDecisionAutoBlockerUnitTest() {
last_embargoed_status_ = false;
autoblocker()->SetClockForTesting(&clock_);
callback_was_run_ = false;
}
PermissionDecisionAutoBlocker* autoblocker() {
return PermissionsClient::Get()->GetPermissionDecisionAutoBlocker(
&browser_context_);
}
void SetLastEmbargoStatus(base::OnceClosure quit_closure, bool status) {
callback_was_run_ = true;
last_embargoed_status_ = status;
if (quit_closure) {
std::move(quit_closure).Run();
}
}
bool last_embargoed_status() { return last_embargoed_status_; }
bool callback_was_run() { return callback_was_run_; }
base::SimpleTestClock* clock() { return &clock_; }
private:
content::BrowserTaskEnvironment task_environment_;
base::SimpleTestClock clock_;
base::test::ScopedFeatureList feature_list_;
content::TestBrowserContext browser_context_;
TestPermissionsClient permissions_client_;
bool last_embargoed_status_;
bool callback_was_run_;
};
class MockObserver : public PermissionDecisionAutoBlocker::Observer {
public:
void OnEmbargoStarted(const GURL& origin,
ContentSettingsType content_setting) override {
callbacks_[origin].push_back(content_setting);
}
std::map<GURL, std::vector<ContentSettingsType>>& GetCallbacks() {
return callbacks_;
}
private:
std::map<GURL, std::vector<ContentSettingsType>> callbacks_;
};
// Check removing the the embargo for a single permission on a site works, and
// that it doesn't interfere with other embargoed permissions or the same
// permission embargoed on other sites.
TEST_F(PermissionDecisionAutoBlockerUnitTest, RemoveEmbargoAndResetCounts) {
GURL url1("https://www.google.com");
GURL url2("https://www.example.com");
// Record dismissals for location and notifications in |url1|.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::NOTIFICATIONS, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::NOTIFICATIONS, false));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::NOTIFICATIONS, false));
// Record dismissals for location in |url2|.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url2, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url2, ContentSettingsType::GEOLOCATION, false));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url2, ContentSettingsType::GEOLOCATION, false));
// Verify all dismissals recorded above resulted in embargo.
std::optional<content::PermissionResult> result =
autoblocker()->GetEmbargoResult(url1, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
result =
autoblocker()->GetEmbargoResult(url1, ContentSettingsType::NOTIFICATIONS);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
result =
autoblocker()->GetEmbargoResult(url2, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
// Remove the embargo on notifications. Verify it is no longer under embargo,
// but location still is.
autoblocker()->RemoveEmbargoAndResetCounts(
url1, ContentSettingsType::NOTIFICATIONS);
result =
autoblocker()->GetEmbargoResult(url1, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
result =
autoblocker()->GetEmbargoResult(url1, ContentSettingsType::NOTIFICATIONS);
// If not under embargo, GetEmbargoResult() returns std::nullopt.
EXPECT_FALSE(result.has_value());
// Verify |url2|'s embargo is still intact as well.
result =
autoblocker()->GetEmbargoResult(url2, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
}
// Test it does not take one more dismissal to re-trigger embargo after
// removing the embargo status for a site.
TEST_F(PermissionDecisionAutoBlockerUnitTest,
DismissAfterRemovingEmbargoByURL) {
GURL url("https://www.example.com");
// Record dismissals for location.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
// Verify location is under embargo.
std::optional<content::PermissionResult> result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
// Remove embargo and verify this is true.
autoblocker()->RemoveEmbargoAndResetCounts(url,
ContentSettingsType::GEOLOCATION);
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(result.has_value());
// Record another dismissal and verify location is not under embargo again.
autoblocker()->RecordDismissAndEmbargo(url, ContentSettingsType::GEOLOCATION,
false);
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(result.has_value());
}
TEST_F(PermissionDecisionAutoBlockerUnitTest,
NonembargoedOriginRemoveEmbargoCounts) {
GURL gurl_to_embargo("https://www.google.com");
// Make sure that an origin's Dismiss count is 0.
EXPECT_EQ(0, autoblocker()->GetDismissCount(
gurl_to_embargo, ContentSettingsType::GEOLOCATION));
// Dismiss the origin a few times but do not add under embargo.
autoblocker()->RecordDismissAndEmbargo(
gurl_to_embargo, ContentSettingsType::GEOLOCATION, false);
autoblocker()->RecordDismissAndEmbargo(
gurl_to_embargo, ContentSettingsType::GEOLOCATION, false);
EXPECT_EQ(2, autoblocker()->GetDismissCount(
gurl_to_embargo, ContentSettingsType::GEOLOCATION));
autoblocker()->RemoveEmbargoAndResetCounts(gurl_to_embargo,
ContentSettingsType::GEOLOCATION);
EXPECT_EQ(0, autoblocker()->GetDismissCount(
gurl_to_embargo, ContentSettingsType::GEOLOCATION));
autoblocker()->RecordIgnoreAndEmbargo(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX, false);
autoblocker()->RecordIgnoreAndEmbargo(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX, false);
EXPECT_EQ(2, autoblocker()->GetIgnoreCount(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX));
autoblocker()->RemoveEmbargoAndResetCounts(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX);
EXPECT_EQ(0, autoblocker()->GetIgnoreCount(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX));
}
TEST_F(PermissionDecisionAutoBlockerUnitTest, RemoveEmbargoCounts) {
GURL gurl_to_embargo("https://www.google.com");
// Add an origin under embargo for 2 dismissed and 1 ignored
// ContentSettingsType.
autoblocker()->RecordDismissAndEmbargo(
gurl_to_embargo, ContentSettingsType::GEOLOCATION, false);
autoblocker()->RecordDismissAndEmbargo(
gurl_to_embargo, ContentSettingsType::GEOLOCATION, false);
autoblocker()->RecordDismissAndEmbargo(
gurl_to_embargo, ContentSettingsType::GEOLOCATION, false);
EXPECT_EQ(3, autoblocker()->GetDismissCount(
gurl_to_embargo, ContentSettingsType::GEOLOCATION));
autoblocker()->RecordDismissAndEmbargo(
gurl_to_embargo, ContentSettingsType::NOTIFICATIONS, false);
autoblocker()->RecordDismissAndEmbargo(
gurl_to_embargo, ContentSettingsType::NOTIFICATIONS, false);
autoblocker()->RecordDismissAndEmbargo(
gurl_to_embargo, ContentSettingsType::NOTIFICATIONS, false);
EXPECT_EQ(3, autoblocker()->GetDismissCount(
gurl_to_embargo, ContentSettingsType::NOTIFICATIONS));
autoblocker()->RecordIgnoreAndEmbargo(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX, false);
autoblocker()->RecordIgnoreAndEmbargo(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX, false);
autoblocker()->RecordIgnoreAndEmbargo(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX, false);
autoblocker()->RecordIgnoreAndEmbargo(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX, false);
EXPECT_EQ(4, autoblocker()->GetIgnoreCount(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX));
autoblocker()->RemoveEmbargoAndResetCounts(gurl_to_embargo,
ContentSettingsType::GEOLOCATION);
// GEOLOCATION has been cleared, a dismiss count should be 0.
EXPECT_EQ(0, autoblocker()->GetDismissCount(
gurl_to_embargo, ContentSettingsType::GEOLOCATION));
// GEOLOCATION has been cleared, but other counts should be
// preservet.
EXPECT_EQ(3, autoblocker()->GetDismissCount(
gurl_to_embargo, ContentSettingsType::NOTIFICATIONS));
EXPECT_EQ(4, autoblocker()->GetIgnoreCount(gurl_to_embargo,
ContentSettingsType::MIDI_SYSEX));
}
TEST_F(PermissionDecisionAutoBlockerUnitTest, RemoveEmbargoAndResetCounts_All) {
GURL url1("https://www.google.com");
GURL url2("https://www.example.com");
// Record some dismissals.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(1, autoblocker()->GetDismissCount(
url1, ContentSettingsType::GEOLOCATION));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(2, autoblocker()->GetDismissCount(
url1, ContentSettingsType::GEOLOCATION));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(3, autoblocker()->GetDismissCount(
url1, ContentSettingsType::GEOLOCATION));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url2, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(1, autoblocker()->GetDismissCount(
url2, ContentSettingsType::GEOLOCATION));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::NOTIFICATIONS, false));
EXPECT_EQ(1, autoblocker()->GetDismissCount(
url1, ContentSettingsType::NOTIFICATIONS));
// Record some ignores.
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url1, ContentSettingsType::MIDI_SYSEX, false));
EXPECT_EQ(
1, autoblocker()->GetIgnoreCount(url1, ContentSettingsType::MIDI_SYSEX));
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url1, ContentSettingsType::DURABLE_STORAGE, false));
EXPECT_EQ(1, autoblocker()->GetIgnoreCount(
url1, ContentSettingsType::DURABLE_STORAGE));
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url2, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url2, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(
2, autoblocker()->GetIgnoreCount(url2, ContentSettingsType::GEOLOCATION));
autoblocker()->RemoveEmbargoAndResetCounts(
base::BindRepeating(&FilterGoogle));
// Expect that url1's actions are gone, but url2's remain.
EXPECT_EQ(0, autoblocker()->GetDismissCount(
url1, ContentSettingsType::GEOLOCATION));
EXPECT_EQ(0, autoblocker()->GetDismissCount(
url1, ContentSettingsType::NOTIFICATIONS));
EXPECT_EQ(
0, autoblocker()->GetIgnoreCount(url1, ContentSettingsType::MIDI_SYSEX));
EXPECT_EQ(0, autoblocker()->GetIgnoreCount(
url1, ContentSettingsType::DURABLE_STORAGE));
EXPECT_EQ(1, autoblocker()->GetDismissCount(
url2, ContentSettingsType::GEOLOCATION));
EXPECT_EQ(
2, autoblocker()->GetIgnoreCount(url2, ContentSettingsType::GEOLOCATION));
// Add some more actions.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(1, autoblocker()->GetDismissCount(
url1, ContentSettingsType::GEOLOCATION));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::NOTIFICATIONS, false));
EXPECT_EQ(1, autoblocker()->GetDismissCount(
url1, ContentSettingsType::NOTIFICATIONS));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url2, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(2, autoblocker()->GetDismissCount(
url2, ContentSettingsType::GEOLOCATION));
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(
1, autoblocker()->GetIgnoreCount(url1, ContentSettingsType::GEOLOCATION));
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url1, ContentSettingsType::NOTIFICATIONS, false));
EXPECT_EQ(1, autoblocker()->GetIgnoreCount(
url1, ContentSettingsType::NOTIFICATIONS));
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url1, ContentSettingsType::DURABLE_STORAGE, false));
EXPECT_EQ(1, autoblocker()->GetIgnoreCount(
url1, ContentSettingsType::DURABLE_STORAGE));
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url2, ContentSettingsType::MIDI_SYSEX, false));
EXPECT_EQ(
1, autoblocker()->GetIgnoreCount(url2, ContentSettingsType::MIDI_SYSEX));
// Remove everything and expect that it's all gone.
autoblocker()->RemoveEmbargoAndResetCounts(base::BindRepeating(&FilterAll));
EXPECT_EQ(0, autoblocker()->GetDismissCount(
url1, ContentSettingsType::GEOLOCATION));
EXPECT_EQ(0, autoblocker()->GetDismissCount(
url1, ContentSettingsType::NOTIFICATIONS));
EXPECT_EQ(0, autoblocker()->GetDismissCount(
url2, ContentSettingsType::GEOLOCATION));
EXPECT_EQ(
0, autoblocker()->GetIgnoreCount(url1, ContentSettingsType::GEOLOCATION));
EXPECT_EQ(0, autoblocker()->GetIgnoreCount(
url1, ContentSettingsType::NOTIFICATIONS));
EXPECT_EQ(
0, autoblocker()->GetIgnoreCount(url2, ContentSettingsType::GEOLOCATION));
EXPECT_EQ(0, autoblocker()->GetIgnoreCount(
url2, ContentSettingsType::DURABLE_STORAGE));
EXPECT_EQ(
0, autoblocker()->GetIgnoreCount(url2, ContentSettingsType::MIDI_SYSEX));
}
// Check that GetEmbargoedOrigins only returns origins where embargo is the
// effective permission enforcement mechanism.
TEST_F(PermissionDecisionAutoBlockerUnitTest, CheckEmbargoedOrigins) {
GURL url1("https://www.google.com");
GURL url2("https://www.google.com:8443");
std::vector<ContentSettingsType> content_types = {
ContentSettingsType::GEOLOCATION, ContentSettingsType::NOTIFICATIONS};
std::set<GURL> origins;
clock()->SetNow(base::Time::Now());
EXPECT_EQ(0UL, autoblocker()->GetEmbargoedOrigins(content_types).size());
// Place both origins under embargo and verify.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url1, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url2, ContentSettingsType::NOTIFICATIONS, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url2, ContentSettingsType::NOTIFICATIONS, false));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url2, ContentSettingsType::NOTIFICATIONS, false));
origins = autoblocker()->GetEmbargoedOrigins(content_types);
EXPECT_EQ(2UL, origins.size());
EXPECT_EQ(1UL, origins.count(url1));
EXPECT_EQ(1UL, origins.count(url2));
// Check no leakage between content types
origins =
autoblocker()->GetEmbargoedOrigins(ContentSettingsType::GEOLOCATION);
EXPECT_EQ(1UL, origins.count(url1));
origins =
autoblocker()->GetEmbargoedOrigins(ContentSettingsType::NOTIFICATIONS);
EXPECT_EQ(1UL, origins.count(url2));
// Remove an embargo and confirm it's removed from origins
autoblocker()->RemoveEmbargoAndResetCounts(url1,
ContentSettingsType::GEOLOCATION);
origins = autoblocker()->GetEmbargoedOrigins(content_types);
EXPECT_EQ(1UL, origins.size());
EXPECT_EQ(1UL, origins.count(url2));
// Expire the remaining embargo and confirm the origin is removed
clock()->Advance(base::Days(8));
origins = autoblocker()->GetEmbargoedOrigins(content_types);
EXPECT_EQ(0UL, origins.size());
}
// Check that GetEmbargoResult returns the correct value when the embargo is set
// and expires.
TEST_F(PermissionDecisionAutoBlockerUnitTest, CheckEmbargoStatus) {
GURL url("https://www.google.com");
clock()->SetNow(base::Time::Now());
// Check the default state.
std::optional<content::PermissionResult> result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(result.has_value());
// Place under embargo and verify.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
// Check that the origin is not under embargo for a different permission.
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::NOTIFICATIONS);
EXPECT_FALSE(result.has_value());
// Confirm embargo status during the embargo period.
clock()->Advance(base::Days(5));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
// Check embargo is lifted on expiry day. A small offset after the exact
// embargo expiration date has been added to account for any precision errors
// when removing the date stored as a double from the permission dictionary.
clock()->Advance(base::Hours(3 * 24 + 1));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(result.has_value());
// Check embargo is lifted well after the expiry day.
clock()->Advance(base::Days(1));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(result.has_value());
// Place under embargo again and verify the embargo status.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::NOTIFICATIONS, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::NOTIFICATIONS, false));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::NOTIFICATIONS, false));
clock()->Advance(base::Days(1));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::NOTIFICATIONS);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(result.has_value());
}
// Check that GetEmbargoStartTime returns the correct time for embargoes whether
// they are nonexistent, expired or active.
TEST_F(PermissionDecisionAutoBlockerUnitTest, CheckEmbargoStartTime) {
GURL url("https://www.google.com");
// The time recorded for embargoes will be stored as a double, which will
// cause aliasing to a limited set of base::Time values upon retrieval. We
// thus pick a base::Time for our test time that is part of this set via
// aliasing the current time by passing it through a double. This allows us
// to directly compare the test time and times retrieved from storage.
base::Time test_time = base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(static_cast<double>(
base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds())));
clock()->SetNow(test_time);
// Check the default non embargod state.
base::Time embargo_start_time =
autoblocker()->GetEmbargoStartTime(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(base::Time(), embargo_start_time);
// Ensure that dismissing less than the required number for an embargo
// does not record an embargo start time.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
embargo_start_time =
autoblocker()->GetEmbargoStartTime(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(base::Time(), embargo_start_time);
// Place site under geolocation dismissal embargo.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
// Confirm embargo is recorded as starting at the correct time.
embargo_start_time =
autoblocker()->GetEmbargoStartTime(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(test_time, embargo_start_time);
// Ensure moving clock while within embargo period does not affect embargo
// start time.
clock()->Advance(base::Days(5));
embargo_start_time =
autoblocker()->GetEmbargoStartTime(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(test_time, embargo_start_time);
// Move clock beyond embaro (plus a small offset for potential precision
// errors) and confirm start time is unaffected but embargo is suspended.
clock()->Advance(base::Hours(3 * 24 + 1));
embargo_start_time =
autoblocker()->GetEmbargoStartTime(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(test_time, embargo_start_time);
std::optional<content::PermissionResult> result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(result.has_value());
// Advance time, reinstate embargo and confirm that time is updated.
test_time += base::Days(9);
test_time = base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(static_cast<double>(
test_time.ToDeltaSinceWindowsEpoch().InMicroseconds())));
clock()->SetNow(test_time);
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
embargo_start_time =
autoblocker()->GetEmbargoStartTime(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(test_time, embargo_start_time);
// Advance time to expire dismiss embargo and create new embargo for ignoring.
test_time += base::Days(7);
test_time = base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(static_cast<double>(
test_time.ToDeltaSinceWindowsEpoch().InMicroseconds())));
clock()->SetNow(test_time);
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_TRUE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
// Confirm the most recent embargo is updated to match new ignore embargo.
embargo_start_time =
autoblocker()->GetEmbargoStartTime(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(test_time, embargo_start_time);
// Advance time, reinstate the dismiss embargo via a single action, and
// confirm that time is updated.
test_time += base::Days(1);
test_time = base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(static_cast<double>(
test_time.ToDeltaSinceWindowsEpoch().InMicroseconds())));
clock()->SetNow(test_time);
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
embargo_start_time =
autoblocker()->GetEmbargoStartTime(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(test_time, embargo_start_time);
// Remove records of dismiss and ignore embargoes and confirm start time
// reverts to default.
autoblocker()->RemoveEmbargoAndResetCounts(
base::BindRepeating(&FilterGoogle));
embargo_start_time =
autoblocker()->GetEmbargoStartTime(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(base::Time(), embargo_start_time);
}
// Tests the alternating pattern of the block on multiple dismiss behaviour. On
// N dismissals, the origin to be embargoed for the requested permission and
// automatically blocked. Each time the embargo is lifted, the site gets another
// chance to request the permission, but if it is again dismissed it is placed
// under embargo again and its permission requests blocked.
TEST_F(PermissionDecisionAutoBlockerUnitTest, TestDismissEmbargoBackoff) {
GURL url("https://www.google.com");
clock()->SetNow(base::Time::Now());
base::HistogramTester histograms;
// Record some dismisses.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
// A request with < 3 prior dismisses should not be automatically blocked.
std::optional<content::PermissionResult> result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(result.has_value());
// After the 3rd dismiss subsequent permission requests should be autoblocked.
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
// Accelerate time forward, check that the embargo status is lifted and the
// request won't be automatically blocked.
clock()->Advance(base::Days(8));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(result.has_value());
// Record another dismiss, subsequent requests should be autoblocked again.
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
// Accelerate time again, check embargo is lifted and another permission
// request is let through.
clock()->Advance(base::Days(8));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_FALSE(result.has_value());
// Record another dismiss, subsequent requests should be autoblocked again.
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::GEOLOCATION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
}
// Tests the alternating pattern of the block on multiple ignores behaviour.
TEST_F(PermissionDecisionAutoBlockerUnitTest, TestIgnoreEmbargoBackoff) {
GURL url("https://www.google.com");
clock()->SetNow(base::Time::Now());
base::HistogramTester histograms;
// Record some ignores.
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::MIDI_SYSEX, false));
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::MIDI_SYSEX, false));
// A request with < 4 prior ignores should not be automatically blocked.
std::optional<content::PermissionResult> result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::MIDI_SYSEX);
EXPECT_FALSE(result.has_value());
// After the 4th ignore subsequent permission requests should be autoblocked.
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::MIDI_SYSEX, false));
EXPECT_TRUE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::MIDI_SYSEX, false));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::MIDI_SYSEX);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_IGNORES, result->source);
// Accelerate time forward, check that the embargo status is lifted and the
// request won't be automatically blocked.
clock()->Advance(base::Days(8));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::MIDI_SYSEX);
EXPECT_FALSE(result.has_value());
// Record another dismiss, subsequent requests should be autoblocked again.
EXPECT_TRUE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::MIDI_SYSEX, false));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::MIDI_SYSEX);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_IGNORES, result->source);
// Accelerate time again, check embargo is lifted and another permission
// request is let through.
clock()->Advance(base::Days(8));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::MIDI_SYSEX);
EXPECT_FALSE(result.has_value());
// Record another dismiss, subsequent requests should be autoblocked again.
EXPECT_TRUE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::MIDI_SYSEX, false));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::MIDI_SYSEX);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_IGNORES, result->source);
}
// Test that quiet ui embargo has a different threshold for ignores.
TEST_F(PermissionDecisionAutoBlockerUnitTest, TestIgnoreEmbargoUsingQuietUi) {
GURL url("https://www.google.com");
clock()->SetNow(base::Time::Now());
// Check the default state.
std::optional<content::PermissionResult> result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::NOTIFICATIONS);
EXPECT_FALSE(result.has_value());
// One quiet ui ignore is not enough to trigger embargo.
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::NOTIFICATIONS, true));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::NOTIFICATIONS);
EXPECT_FALSE(result.has_value());
// Loud ui ignores are counted separately.
EXPECT_FALSE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::NOTIFICATIONS, false));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::NOTIFICATIONS);
EXPECT_FALSE(result.has_value());
// The second quiet ui ignore puts the url under embargo.
EXPECT_TRUE(autoblocker()->RecordIgnoreAndEmbargo(
url, ContentSettingsType::NOTIFICATIONS, true));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::NOTIFICATIONS);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_IGNORES, result->source);
}
// Test that quiet ui embargo has a different threshold for dismisses.
TEST_F(PermissionDecisionAutoBlockerUnitTest, TestDismissEmbargoUsingQuietUi) {
GURL url("https://www.google.com");
clock()->SetNow(base::Time::Now());
// Check the default state.
std::optional<content::PermissionResult> result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::NOTIFICATIONS);
EXPECT_FALSE(result.has_value());
// One loud ui dismiss does not trigger embargo.
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::NOTIFICATIONS, false));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::NOTIFICATIONS);
EXPECT_FALSE(result.has_value());
// One quiet ui dismiss puts the url under embargo.
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::NOTIFICATIONS, true));
result =
autoblocker()->GetEmbargoResult(url, ContentSettingsType::NOTIFICATIONS);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
}
namespace {
// Checks that embargo on federated identity permission is lifted only after the
// passed-in |time_delta| has elapsed.
void CheckFederatedIdentityApiEmbargoLiftedAfterTimeElapsing(
PermissionDecisionAutoBlocker* autoblocker,
base::SimpleTestClock* clock,
const GURL& url,
base::TimeDelta time_delta) {
ASSERT_LT(base::Minutes(1), time_delta);
clock->Advance(time_delta - base::Minutes(1));
std::optional<content::PermissionResult> result =
autoblocker->GetEmbargoResult(
url, ContentSettingsType::FEDERATED_IDENTITY_API);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::MULTIPLE_DISMISSALS,
result->source);
clock->Advance(base::Minutes(2));
result = autoblocker->GetEmbargoResult(
url, ContentSettingsType::FEDERATED_IDENTITY_API);
EXPECT_FALSE(result.has_value());
}
// Checks that embargo on federated identity auto re-authn permission is lifted
// only after the passed-in |time_delta| has elapsed.
void CheckFederatedIdentityAutoReauthnEmbargoLiftedAfterTimeElapsing(
PermissionDecisionAutoBlocker* autoblocker,
base::SimpleTestClock* clock,
const GURL& url,
base::TimeDelta time_delta) {
ASSERT_LT(base::Minutes(1), time_delta);
clock->Advance(time_delta - base::Minutes(1));
std::optional<content::PermissionResult> result =
autoblocker->GetEmbargoResult(
url, ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION);
EXPECT_EQ(PermissionStatus::DENIED, result->status);
EXPECT_EQ(content::PermissionStatusSource::RECENT_DISPLAY, result->source);
clock->Advance(base::Minutes(2));
result = autoblocker->GetEmbargoResult(
url, ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION);
EXPECT_FALSE(result.has_value());
}
} // namespace
TEST_F(PermissionDecisionAutoBlockerUnitTest,
TestDismissFederatedIdentityApiBackoff) {
GURL url("https://www.google.com");
clock()->SetNow(base::Time::Now());
std::optional<content::PermissionResult> result =
autoblocker()->GetEmbargoResult(
url, ContentSettingsType::FEDERATED_IDENTITY_API);
EXPECT_FALSE(result.has_value());
// 2 hour embargo for 1st dismissal
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::FEDERATED_IDENTITY_API, false));
CheckFederatedIdentityApiEmbargoLiftedAfterTimeElapsing(
autoblocker(), clock(), url, base::Hours(2));
// 1 day embargo for 2nd dismissal
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::FEDERATED_IDENTITY_API, false));
CheckFederatedIdentityApiEmbargoLiftedAfterTimeElapsing(
autoblocker(), clock(), url, base::Days(1));
// 7 day embargo for 3rd dismissal
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::FEDERATED_IDENTITY_API, false));
CheckFederatedIdentityApiEmbargoLiftedAfterTimeElapsing(
autoblocker(), clock(), url, base::Days(7));
// 28 day embargo for 4th dismissal (and all additional dismissals)
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::FEDERATED_IDENTITY_API, false));
CheckFederatedIdentityApiEmbargoLiftedAfterTimeElapsing(
autoblocker(), clock(), url, base::Days(28));
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::FEDERATED_IDENTITY_API, false));
CheckFederatedIdentityApiEmbargoLiftedAfterTimeElapsing(
autoblocker(), clock(), url, base::Days(28));
// Return to 2 hour embargo after
// PermissionDecisionAutoBlocker::RemoveEmbargoAndResetCounts()
autoblocker()->RemoveEmbargoAndResetCounts(
url, ContentSettingsType::FEDERATED_IDENTITY_API);
result = autoblocker()->GetEmbargoResult(
url, ContentSettingsType::FEDERATED_IDENTITY_API);
EXPECT_FALSE(result.has_value());
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::FEDERATED_IDENTITY_API, false));
CheckFederatedIdentityApiEmbargoLiftedAfterTimeElapsing(
autoblocker(), clock(), url, base::Hours(2));
}
TEST_F(PermissionDecisionAutoBlockerUnitTest,
TestLogoutFederatedIdentityAutoReauthnBackoff) {
GURL url("https://www.google.com");
clock()->SetNow(base::Time::Now());
std::optional<content::PermissionResult> result =
autoblocker()->GetEmbargoResult(
url, ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION);
EXPECT_FALSE(result.has_value());
// 10 minute embargo
EXPECT_TRUE(autoblocker()->RecordDisplayAndEmbargo(
url, ContentSettingsType::FEDERATED_IDENTITY_AUTO_REAUTHN_PERMISSION));
CheckFederatedIdentityAutoReauthnEmbargoLiftedAfterTimeElapsing(
autoblocker(), clock(), url, base::Minutes(10));
}
TEST_F(PermissionDecisionAutoBlockerUnitTest,
ObserverIsNotifiedWhenEmbargoStarts) {
GURL url("https://www.google.com");
MockObserver observer;
autoblocker()->AddObserver(&observer);
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(0u, observer.GetCallbacks().size());
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(0u, observer.GetCallbacks().size());
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(1u, observer.GetCallbacks().size());
EXPECT_EQ(url, observer.GetCallbacks().begin()->first);
EXPECT_EQ(ContentSettingsType::GEOLOCATION, observer.GetCallbacks()[url][0]);
autoblocker()->RemoveObserver(&observer);
}
TEST_F(PermissionDecisionAutoBlockerUnitTest,
RemovedObserverIsNotNotifiedWhenEmbargoStarts) {
GURL url("https://www.google.com");
MockObserver observer;
autoblocker()->AddObserver(&observer);
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(0u, observer.GetCallbacks().size());
EXPECT_FALSE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(0u, observer.GetCallbacks().size());
autoblocker()->RemoveObserver(&observer);
EXPECT_TRUE(autoblocker()->RecordDismissAndEmbargo(
url, ContentSettingsType::GEOLOCATION, false));
EXPECT_EQ(0u, observer.GetCallbacks().size());
}
} // namespace permissions