blob: bec89e3f7fea199978a7c88641fe5c4671a2755c [file] [log] [blame]
// Copyright 2025 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/revoked_permissions_result.h"
#include <list>
#include <memory>
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/ui/safety_hub/abusive_notification_permissions_manager.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/features.h"
#include "components/safe_browsing/core/common/features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using ::testing::Field;
using ::testing::Optional;
using ::testing::UnorderedElementsAre;
const char url1[] = "https://example1.com:443";
const char url2[] = "https://example2.com:443";
const char url3[] = "https://example3.com:443";
const ContentSettingsType geolocation_type = ContentSettingsType::GEOLOCATION;
const ContentSettingsType mediastream_type =
ContentSettingsType::MEDIASTREAM_CAMERA;
const ContentSettingsType notifications_type =
ContentSettingsType::NOTIFICATIONS;
const ContentSettingsType chooser_type =
ContentSettingsType::FILE_SYSTEM_ACCESS_CHOOSER_DATA;
std::set<ContentSettingsType> abusive_permission_types({notifications_type});
std::set<ContentSettingsType> unused_permission_types({geolocation_type,
chooser_type});
std::set<ContentSettingsType> abusive_and_unused_permission_types(
{notifications_type, geolocation_type, chooser_type});
PermissionsData CreatePermissionsData(
ContentSettingsPattern& primary_pattern,
std::set<ContentSettingsType>& permission_types) {
PermissionsData permissions_data;
permissions_data.primary_pattern = primary_pattern;
permissions_data.permission_types = permission_types;
return permissions_data;
}
} // namespace
// TODO(crbug.com/399056993): Clean-up feature flags and remove
// parameterization.
class RevokedPermissionsResultTest
: public ChromeRenderViewHostTestHarness,
public testing::WithParamInterface<
std::tuple</*should_setup_abusive_notification_sites*/ bool,
/*should_setup_unused_sites*/ bool,
/*should_setup_disruptive_sites*/ bool>> {
public:
RevokedPermissionsResultTest() {
std::vector<base::test::FeatureRef> enabled_features;
enabled_features.push_back(
content_settings::features::kSafetyCheckUnusedSitePermissions);
enabled_features.push_back(
content_settings::features::
kSafetyCheckUnusedSitePermissionsForSupportedChooserPermissions);
if (ShouldSetupDisruptiveSites()) {
enabled_features.push_back(
features::kSafetyHubDisruptiveNotificationRevocation);
}
feature_list_.InitWithFeatures(
/*enabled_features=*/enabled_features,
/*disabled_features=*/{});
}
// There are two variations of the test: where safe browsing is enabled and
// disabled. The former should allow abusive notifications to be revoked and
// the latter should not. However, other permission revocations are not gated
// by the safe browsing setting.
bool ShouldSetupSafeBrowsing() { return get<0>(GetParam()); }
bool ShouldSetupUnusedSites() { return get<1>(GetParam()); }
bool ShouldSetupDisruptiveSites() { return get<2>(GetParam()); }
void AddRevokedPermissionToResult(
RevokedPermissionsResult* result,
std::set<ContentSettingsType> permission_types,
std::string url) {
auto origin = ContentSettingsPattern::FromString(url);
result->AddRevokedPermission(
CreatePermissionsData(origin, permission_types));
}
bool IsUrlInRevokedSettings(std::list<PermissionsData> permissions_data,
std::string url) {
// TODO(crbug.com/40250875): Replace the below with a lambda method and
// base::Contains.
std::string url_pattern =
ContentSettingsPattern::FromURLNoWildcard(GURL(url)).ToString();
for (const auto& permission : permissions_data) {
if (permission.primary_pattern.ToString() == url ||
permission.primary_pattern.ToString() == url_pattern) {
return true;
}
}
return false;
}
base::test::ScopedFeatureList feature_list_;
};
TEST_P(RevokedPermissionsResultTest, ResultToFromDict) {
auto result = std::make_unique<RevokedPermissionsResult>();
// This is necessary for revoked abusive notification permissions, since
// checking URLs is asynchronous.
base::RunLoop().RunUntilIdle();
if (ShouldSetupUnusedSites()) {
AddRevokedPermissionToResult(result.get(), unused_permission_types, url1);
if (ShouldSetupSafeBrowsing()) {
AddRevokedPermissionToResult(result.get(),
abusive_and_unused_permission_types, url2);
} else {
AddRevokedPermissionToResult(result.get(), unused_permission_types, url2);
}
}
if (ShouldSetupSafeBrowsing()) {
if (!ShouldSetupUnusedSites()) {
AddRevokedPermissionToResult(result.get(), abusive_permission_types,
url2);
}
AddRevokedPermissionToResult(result.get(), abusive_permission_types, url3);
}
if (ShouldSetupUnusedSites() && ShouldSetupSafeBrowsing()) {
EXPECT_EQ(3U, result->GetRevokedPermissions().size());
EXPECT_EQ(ContentSettingsPattern::FromString(url1),
result->GetRevokedPermissions().front().primary_pattern);
EXPECT_TRUE(IsUrlInRevokedSettings(result->GetRevokedPermissions(), url1));
EXPECT_TRUE(IsUrlInRevokedSettings(result->GetRevokedPermissions(), url2));
EXPECT_TRUE(IsUrlInRevokedSettings(result->GetRevokedPermissions(), url3));
} else if (ShouldSetupUnusedSites()) {
EXPECT_EQ(2U, result->GetRevokedPermissions().size());
EXPECT_TRUE(IsUrlInRevokedSettings(result->GetRevokedPermissions(), url1));
EXPECT_TRUE(IsUrlInRevokedSettings(result->GetRevokedPermissions(), url2));
} else if (ShouldSetupSafeBrowsing()) {
EXPECT_EQ(2U, result->GetRevokedPermissions().size());
EXPECT_TRUE(IsUrlInRevokedSettings(result->GetRevokedPermissions(), url2));
EXPECT_TRUE(IsUrlInRevokedSettings(result->GetRevokedPermissions(), url3));
}
// When converting to dict, the values of the revoked permissions should be
// correctly converted to base::Value.
base::Value::Dict dict = result->ToDictValue();
auto* revoked_origins_list = dict.FindList(kRevokedPermissionsResultKey);
if (ShouldSetupUnusedSites() && ShouldSetupSafeBrowsing()) {
EXPECT_THAT(*revoked_origins_list, UnorderedElementsAre(url1, url2, url3));
} else if (ShouldSetupUnusedSites()) {
EXPECT_THAT(*revoked_origins_list, UnorderedElementsAre(url1, url2));
} else if (ShouldSetupSafeBrowsing()) {
EXPECT_THAT(*revoked_origins_list, UnorderedElementsAre(url2, url3));
}
}
TEST_P(RevokedPermissionsResultTest, ResultGetRevokedOrigins) {
auto result = std::make_unique<RevokedPermissionsResult>();
EXPECT_EQ(0U, result->GetRevokedOrigins().size());
AddRevokedPermissionToResult(result.get(), unused_permission_types, url1);
EXPECT_EQ(1U, result->GetRevokedOrigins().size());
EXPECT_EQ(ContentSettingsPattern::FromString(url1),
*result->GetRevokedOrigins().begin());
AddRevokedPermissionToResult(result.get(), unused_permission_types, url2);
EXPECT_EQ(2U, result->GetRevokedOrigins().size());
EXPECT_TRUE(result->GetRevokedOrigins().contains(
ContentSettingsPattern::FromString(url1)));
EXPECT_TRUE(result->GetRevokedOrigins().contains(
ContentSettingsPattern::FromString(url2)));
// Adding another permission type to `url2` does not change the size of the
// revoked origin list.
std::set<ContentSettingsType> permission_types({mediastream_type});
auto origin = ContentSettingsPattern::FromString(url2);
result->AddRevokedPermission(CreatePermissionsData(origin, permission_types));
EXPECT_EQ(2U, result->GetRevokedOrigins().size());
}
TEST_P(RevokedPermissionsResultTest, ResultIsTriggerForMenuNotification) {
auto result = std::make_unique<RevokedPermissionsResult>();
EXPECT_FALSE(result->IsTriggerForMenuNotification());
AddRevokedPermissionToResult(result.get(), unused_permission_types, url1);
EXPECT_TRUE(result->IsTriggerForMenuNotification());
}
TEST_P(RevokedPermissionsResultTest, ResultWarrantsNewMenuNotification) {
auto old_result = std::make_unique<RevokedPermissionsResult>();
auto new_result = std::make_unique<RevokedPermissionsResult>();
EXPECT_FALSE(
new_result->WarrantsNewMenuNotification(old_result->ToDictValue()));
// origin1 revoked in new, but not in old -> warrants notification
AddRevokedPermissionToResult(new_result.get(), unused_permission_types, url1);
EXPECT_TRUE(
new_result->WarrantsNewMenuNotification(old_result->ToDictValue()));
// origin1 in both new and old -> no notification
AddRevokedPermissionToResult(old_result.get(), unused_permission_types, url1);
EXPECT_FALSE(
new_result->WarrantsNewMenuNotification(old_result->ToDictValue()));
// origin1 in both, origin2 in new -> warrants notification
AddRevokedPermissionToResult(new_result.get(), unused_permission_types, url2);
EXPECT_TRUE(
new_result->WarrantsNewMenuNotification(old_result->ToDictValue()));
// origin1 and origin2 in both new and old -> no notification
AddRevokedPermissionToResult(old_result.get(), unused_permission_types, url2);
EXPECT_FALSE(
new_result->WarrantsNewMenuNotification(old_result->ToDictValue()));
}
INSTANTIATE_TEST_SUITE_P(
All,
RevokedPermissionsResultTest,
testing::Combine(
/*should_setup_abusive_notification_sites=*/testing::Bool(),
/*should_setup_unused_sites=*/testing::Bool(),
/*should_setup_disruptive_sites=*/testing::Bool()));