blob: ba0f8926a81c25a810b71ca4ec6e79450f2aea22 [file] [log] [blame]
// Copyright 2018 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/notifications/persistent_notification_handler.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/thread_pool.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/notifications/metrics/mock_notification_metrics_logger.h"
#include "chrome/browser/notifications/metrics/notification_metrics_logger_factory.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/notification_permission_context.h"
#include "chrome/browser/notifications/platform_notification_service_factory.h"
#include "chrome/browser/notifications/platform_notification_service_impl.h"
#include "chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/safe_browsing/notification_content_detection/mock_notification_content_detection_service.h"
#include "chrome/browser/safe_browsing/notification_content_detection/notification_content_detection_service_factory.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/optimization_guide/core/model_quality/test_model_quality_logs_uploader_service.h"
#include "components/safe_browsing/content/browser/notification_content_detection/notification_content_detection_constants.h"
#include "components/safe_browsing/content/browser/notification_content_detection/notification_content_detection_service.h"
#include "components/safe_browsing/content/browser/notification_content_detection/test_model_observer_tracker.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/notification_database_data.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/permission_result.h"
#include "content/public/browser/platform_notification_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/storage_partition_config.h"
#include "content/public/common/persistent_notification_status.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_permission_manager.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/notifications/notification_resources.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
#include "third_party/blink/public/mojom/site_engagement/site_engagement.mojom.h"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/safe_browsing/android/notification_content_detection_manager_android.h"
#endif // BUILDFLAG(IS_ANDROID)
using ::testing::_;
using ::testing::Return;
using PermissionStatus = blink::mojom::PermissionStatus;
MATCHER_P(PermissionTypeMatcher, id, "") {
return ::testing::Matches(::testing::Eq(id))(
blink::PermissionDescriptorToPermissionType(arg));
}
namespace {
const char kExampleNotificationId[] = "example_notification_id";
const char kExampleOrigin[] = "https://example.com";
class TestingProfileWithPermissionManager : public TestingProfile {
public:
TestingProfileWithPermissionManager()
: permission_manager_(
std::make_unique<
testing::NiceMock<content::MockPermissionManager>>()) {}
TestingProfileWithPermissionManager(
const TestingProfileWithPermissionManager&) = delete;
TestingProfileWithPermissionManager& operator=(
const TestingProfileWithPermissionManager&) = delete;
~TestingProfileWithPermissionManager() override = default;
// Sets the notification permission status to |permission_status|.
void SetNotificationPermissionStatus(PermissionStatus permission_status) {
ON_CALL(
*permission_manager_,
GetPermissionResultForOriginWithoutContext(
PermissionTypeMatcher(blink::PermissionType::NOTIFICATIONS), _, _))
.WillByDefault(Return(content::PermissionResult(
permission_status, content::PermissionStatusSource::UNSPECIFIED)));
}
// TestingProfile overrides:
content::PermissionControllerDelegate* GetPermissionControllerDelegate()
override {
return permission_manager_.get();
}
private:
std::unique_ptr<content::MockPermissionManager> permission_manager_;
};
} // namespace
class PersistentNotificationHandlerTest : public ::testing::Test {
public:
PersistentNotificationHandlerTest()
: profile_(std::make_unique<TestingProfileWithPermissionManager>()),
display_service_tester_(profile_.get()),
origin_(kExampleOrigin) {}
PersistentNotificationHandlerTest(const PersistentNotificationHandlerTest&) =
delete;
PersistentNotificationHandlerTest& operator=(
const PersistentNotificationHandlerTest&) = delete;
~PersistentNotificationHandlerTest() override = default;
// ::testing::Test overrides:
void SetUp() override {
scoped_feature_list_.InitWithFeatures(
{}, {safe_browsing::kShowWarningsForSuspiciousNotifications});
HistoryServiceFactory::GetInstance()->SetTestingFactory(
profile_.get(), HistoryServiceFactory::GetDefaultFactory());
mock_logger_ = static_cast<MockNotificationMetricsLogger*>(
NotificationMetricsLoggerFactory::GetInstance()
->SetTestingFactoryAndUse(
profile_.get(),
base::BindRepeating(
&MockNotificationMetricsLogger::FactoryForTests)));
PlatformNotificationServiceFactory::GetForProfile(profile_.get())
->ClearClosedNotificationsForTesting();
}
void TearDown() override {
mock_logger_ = nullptr;
profile_.reset();
}
protected:
base::test::ScopedFeatureList scoped_feature_list_;
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfileWithPermissionManager> profile_;
NotificationDisplayServiceTester display_service_tester_;
// The origin for which these tests are being run.
GURL origin_;
// Owned by the |profile_| as a keyed service.
raw_ptr<MockNotificationMetricsLogger> mock_logger_ = nullptr;
};
TEST_F(PersistentNotificationHandlerTest, OnClick_WithoutPermission) {
EXPECT_CALL(*mock_logger_, LogPersistentNotificationClickWithoutPermission());
profile_->SetNotificationPermissionStatus(PermissionStatus::DENIED);
std::unique_ptr<NotificationHandler> handler =
std::make_unique<PersistentNotificationHandler>();
handler->OnClick(profile_.get(), origin_, kExampleNotificationId,
std::nullopt /* action_index */, std::nullopt /* reply */,
base::DoNothing());
}
TEST_F(PersistentNotificationHandlerTest,
OnClick_CloseUnactionableNotifications) {
// Show a notification for a particular origin.
{
base::RunLoop run_loop;
display_service_tester_.SetNotificationAddedClosure(run_loop.QuitClosure());
EXPECT_CALL(*mock_logger_, LogPersistentNotificationShown());
PlatformNotificationServiceFactory::GetForProfile(profile_.get())
->DisplayPersistentNotification(
kExampleNotificationId, origin_ /* service_worker_scope */, origin_,
blink::PlatformNotificationData(), blink::NotificationResources());
run_loop.Run();
}
ASSERT_TRUE(display_service_tester_.GetNotification(kExampleNotificationId));
// Revoke permission for any origin to display notifications.
profile_->SetNotificationPermissionStatus(PermissionStatus::DENIED);
// Now simulate a click on the notification. It should be automatically closed
// by the PersistentNotificationHandler.
{
EXPECT_CALL(*mock_logger_,
LogPersistentNotificationClickWithoutPermission());
display_service_tester_.SimulateClick(
NotificationHandler::Type::WEB_PERSISTENT, kExampleNotificationId,
std::nullopt /* action_index */, std::nullopt /* reply */);
}
EXPECT_FALSE(display_service_tester_.GetNotification(kExampleNotificationId));
}
TEST_F(PersistentNotificationHandlerTest, OnClose_ByUser) {
EXPECT_CALL(*mock_logger_, LogPersistentNotificationClosedByUser());
std::unique_ptr<NotificationHandler> handler =
std::make_unique<PersistentNotificationHandler>();
handler->OnClose(profile_.get(), origin_, kExampleNotificationId,
/* by_user= */ true, base::DoNothing());
}
TEST_F(PersistentNotificationHandlerTest, OnClose_Programmatically) {
EXPECT_CALL(*mock_logger_, LogPersistentNotificationClosedProgrammatically());
std::unique_ptr<NotificationHandler> handler =
std::make_unique<PersistentNotificationHandler>();
handler->OnClose(profile_.get(), origin_, kExampleNotificationId,
/* by_user= */ false, base::DoNothing());
}
TEST_F(PersistentNotificationHandlerTest, DisableNotifications) {
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
std::unique_ptr<NotificationPermissionContext> permission_context =
std::make_unique<NotificationPermissionContext>(profile_.get());
ASSERT_EQ(permission_context
->GetPermissionStatus(
content::PermissionDescriptorUtil::
CreatePermissionDescriptorForPermissionType(
blink::PermissionType::NOTIFICATIONS),
nullptr /* render_frame_host */, origin_, origin_)
.status,
PermissionStatus::ASK);
// Set `ARE_SUSPICIOUS_NOTIFICATIONS_ALLOWLISTED_BY_USER` to true for
// `origin_`.
auto* hcsm = HostContentSettingsMapFactory::GetForProfile(profile_.get());
hcsm->SetWebsiteSettingCustomScope(
ContentSettingsPattern::FromURLNoWildcard(origin_),
ContentSettingsPattern::Wildcard(),
ContentSettingsType::ARE_SUSPICIOUS_NOTIFICATIONS_ALLOWLISTED_BY_USER,
base::Value(base::Value::Dict().Set(
safe_browsing::kIsAllowlistedByUserKey, true)));
// Set non-null `SUSPICIOUS_NOTIFICATION_IDS` value.
base::Value::List suspicious_notification_ids;
suspicious_notification_ids.Append("1");
suspicious_notification_ids.Append("2");
base::Value::Dict suspicious_notification_id_dict;
suspicious_notification_id_dict.Set("suspicious-notification-ids",
std::move(suspicious_notification_ids));
hcsm->SetWebsiteSettingDefaultScope(
origin_, GURL(), ContentSettingsType::SUSPICIOUS_NOTIFICATION_IDS,
base::Value(suspicious_notification_id_dict.Clone()));
std::unique_ptr<NotificationHandler> handler =
std::make_unique<PersistentNotificationHandler>();
#if BUILDFLAG(IS_ANDROID)
handler->OnShowOriginalNotification(origin_, "dummy-notification-id",
profile_.get());
task_environment_.RunUntilIdle();
#endif
handler->DisableNotifications(
profile_.get(), origin_,
/*notification_id=*/"non-suspicious-notification-id");
// Disabling the permission should set
// `ARE_SUSPICIOUS_NOTIFICATIONS_ALLOWLISTED_BY_USER` to false.
content_settings::SettingInfo info;
base::Value value = hcsm->GetWebsiteSetting(
origin_, origin_,
ContentSettingsType::ARE_SUSPICIOUS_NOTIFICATIONS_ALLOWLISTED_BY_USER,
&info);
EXPECT_EQ(
false,
value.GetDict().FindBool(safe_browsing::kIsAllowlistedByUserKey).value());
#if BUILDFLAG(IS_ANDROID)
PermissionStatus kExpectedDisabledStatus = PermissionStatus::ASK;
#else
PermissionStatus kExpectedDisabledStatus = PermissionStatus::DENIED;
#endif
ASSERT_EQ(permission_context
->GetPermissionStatus(
content::PermissionDescriptorUtil::
CreatePermissionDescriptorForPermissionType(
blink::PermissionType::NOTIFICATIONS),
nullptr /* render_frame_host */, origin_, origin_)
.status,
kExpectedDisabledStatus);
// Note: we expect that this UKM does not record the unsubscribe
// event, since the unsubscribe occurred for on a notification that did not
// show a warning.
auto ukm_entries = test_ukm_recorder.GetEntriesByName(
ukm::builders::SuspiciousNotificationInteraction::kEntryName);
#if BUILDFLAG(IS_ANDROID)
ASSERT_EQ(1u, ukm_entries.size());
test_ukm_recorder.ExpectEntryMetric(
ukm_entries[0], "SuspiciousInteractionType",
static_cast<int>(
safe_browsing::SuspiciousNotificationWarningInteractions::
kShowOriginalNotification));
// No suspicious score has been previously stored in the database, so UKM
// should not log a suspicious score.
EXPECT_FALSE(
test_ukm_recorder.EntryHasMetric(ukm_entries[0], "SuspiciousScore"));
#else
EXPECT_EQ(0u, ukm_entries.size());
#endif
}
#if BUILDFLAG(IS_ANDROID)
TEST_F(PersistentNotificationHandlerTest,
DisableNotificationAfterWarningLogsUKM) {
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
std::unique_ptr<NotificationPermissionContext> permission_context =
std::make_unique<NotificationPermissionContext>(profile_.get());
// Set `SUSPICIOUS_NOTIFICATION_IDS` value.
std::string suspicious_id = "suspicious_id";
base::Value::List suspicious_notification_ids;
suspicious_notification_ids.Append(suspicious_id);
base::Value::Dict suspicious_notification_id_dict;
suspicious_notification_id_dict.Set("suspicious-notification-ids",
std::move(suspicious_notification_ids));
auto* hcsm = HostContentSettingsMapFactory::GetForProfile(profile_.get());
hcsm->SetWebsiteSettingDefaultScope(
origin_, GURL(), ContentSettingsType::SUSPICIOUS_NOTIFICATION_IDS,
base::Value(suspicious_notification_id_dict.Clone()));
std::unique_ptr<NotificationHandler> handler =
std::make_unique<PersistentNotificationHandler>();
handler->DisableNotifications(profile_.get(), origin_, suspicious_id);
task_environment_.RunUntilIdle();
// Disabling notifications after a warning was shown should log the UKM.
auto ukm_entries = test_ukm_recorder.GetEntriesByName(
ukm::builders::SuspiciousNotificationInteraction::kEntryName);
ASSERT_EQ(1u, ukm_entries.size());
test_ukm_recorder.ExpectEntryMetric(
ukm_entries[0], "SuspiciousInteractionType",
static_cast<int>(
safe_browsing::SuspiciousNotificationWarningInteractions::
kUnsubscribe));
// No suspicious score has been previously stored in the database, so UKM
// should not log a suspicious score.
EXPECT_FALSE(
test_ukm_recorder.EntryHasMetric(ukm_entries[0], "SuspiciousScore"));
}
#endif
class PersistentNotificationHandlerWithNotificationContentDetection
: public PersistentNotificationHandlerTest,
public testing::WithParamInterface<bool> {
public:
void SetUp() override {
if (IsSafeBrowsingEnabled()) {
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kSafeBrowsingEnabled, std::make_unique<base::Value>(true));
} else {
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kSafeBrowsingEnabled, std::make_unique<base::Value>(false));
}
mock_notification_content_detection_service_ = static_cast<
safe_browsing::MockNotificationContentDetectionService*>(
safe_browsing::NotificationContentDetectionServiceFactory::GetInstance()
->SetTestingFactoryAndUse(
profile_.get(),
base::BindRepeating(
&safe_browsing::MockNotificationContentDetectionService::
FactoryForTests,
&model_observer_tracker_,
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock()}))));
}
void TearDown() override {
mock_notification_content_detection_service_ = nullptr;
PersistentNotificationHandlerTest::TearDown();
}
bool IsSafeBrowsingEnabled() { return GetParam(); }
protected:
raw_ptr<safe_browsing::MockNotificationContentDetectionService>
mock_notification_content_detection_service_ = nullptr;
safe_browsing::TestModelObserverTracker model_observer_tracker_;
};
INSTANTIATE_TEST_SUITE_P(
,
PersistentNotificationHandlerWithNotificationContentDetection,
testing::Bool());
TEST_P(PersistentNotificationHandlerWithNotificationContentDetection,
PerformNotificationContentDetectionWhenEnabled) {
base::RunLoop run_loop;
display_service_tester_.SetNotificationAddedClosure(run_loop.QuitClosure());
int expected_number_of_calls = 0;
if (IsSafeBrowsingEnabled()) {
expected_number_of_calls = 1;
}
EXPECT_CALL(*mock_notification_content_detection_service_,
MaybeCheckNotificationContentDetectionModel(_, _, _, _))
.Times(expected_number_of_calls);
PlatformNotificationServiceFactory::GetForProfile(profile_.get())
->DisplayPersistentNotification(
kExampleNotificationId, origin_ /* service_worker_scope */, origin_,
blink::PlatformNotificationData(), blink::NotificationResources());
if (!IsSafeBrowsingEnabled()) {
run_loop.Run();
}
}
class
PersistentNotificationHandlerWithNotificationContentDetectionAndLoggingTest
: public PersistentNotificationHandlerTest {
public:
PersistentNotificationHandlerWithNotificationContentDetectionAndLoggingTest()
: manager_(TestingBrowserProcess::GetGlobal()) {}
void SetUp() override {
ASSERT_TRUE(manager_.SetUp());
testing_profile_ = manager_.CreateTestingProfile("foo");
mock_optimization_guide_keyed_service_ = static_cast<
MockOptimizationGuideKeyedService*>(
OptimizationGuideKeyedServiceFactory::GetInstance()
->SetTestingFactoryAndUse(
testing_profile_,
base::BindRepeating([](content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return std::make_unique<MockOptimizationGuideKeyedService>();
})));
auto logs_uploader = std::make_unique<
optimization_guide::TestModelQualityLogsUploaderService>(
TestingBrowserProcess::GetGlobal()->local_state());
mock_optimization_guide_keyed_service_
->SetModelQualityLogsUploaderServiceForTesting(
std::move(logs_uploader));
}
void TearDown() override { PersistentNotificationHandlerTest::TearDown(); }
TestingProfile* profile() { return testing_profile_; }
optimization_guide::TestModelQualityLogsUploaderService* logs_uploader() {
return static_cast<
optimization_guide::TestModelQualityLogsUploaderService*>(
mock_optimization_guide_keyed_service_
->GetModelQualityLogsUploaderService());
}
const std::vector<
std::unique_ptr<optimization_guide::proto::LogAiDataRequest>>&
uploaded_logs() {
return logs_uploader()->uploaded_logs();
}
content::PlatformNotificationContext* GetPlatformNotificationContext(
GURL origin) {
return testing_profile_->GetStoragePartitionForUrl(origin)
->GetPlatformNotificationContext();
}
void WriteNotificationDataAndMetadataToDatabase(bool is_on_global_cache_list,
bool is_allowlisted_by_user,
double suspicious_score) {
// Store notification data in `NotificationDatabase`.
const int64_t kFakeServiceWorkerRegistrationId = 42;
int notification_id = 1;
GURL origin(kExampleOrigin);
content::NotificationDatabaseData notification_database_data;
notification_database_data.origin = origin;
GetPlatformNotificationContext(origin)->WriteNotificationData(
notification_id, kFakeServiceWorkerRegistrationId, origin,
notification_database_data, base::DoNothing());
task_environment_.RunUntilIdle();
// Store metadata in `NotificationDatabase`.
std::string notification_id_str =
"p#" + origin.spec() + "#0" + base::NumberToString(notification_id);
std::string serialized_metadata =
"{\"" +
std::string(safe_browsing::kMetadataIsOriginAllowlistedByUserKey) +
"\":" + (is_allowlisted_by_user ? "true" : "false") + ",\"" +
std::string(safe_browsing::kMetadataIsOriginOnGlobalCacheListKey) +
"\":" + (is_on_global_cache_list ? "true" : "false") + ",\"" +
std::string(safe_browsing::kMetadataSuspiciousScoreKey) +
"\":" + base::NumberToString(suspicious_score) + "}";
GetPlatformNotificationContext(origin)->WriteNotificationMetadata(
notification_id_str, origin,
safe_browsing::kNotificationContentDetectionMetadataDictionaryKey,
serialized_metadata, base::DoNothing());
}
private:
TestingProfileManager manager_;
raw_ptr<TestingProfile> testing_profile_;
raw_ptr<MockOptimizationGuideKeyedService>
mock_optimization_guide_keyed_service_;
};
TEST_F(
PersistentNotificationHandlerWithNotificationContentDetectionAndLoggingTest,
ReportNotificationAsSafe) {
bool is_url_on_allowlist = true;
bool did_user_always_allow_url = false;
double suspicious_score = 70.0;
WriteNotificationDataAndMetadataToDatabase(
is_url_on_allowlist, did_user_always_allow_url, suspicious_score);
int notification_id = 1;
GURL origin(origin_);
std::string notification_id_str =
"p#" + origin.spec() + "#0" + base::NumberToString(notification_id);
base::test::TestFuture<void> log_uploaded_signal;
logs_uploader()->WaitForLogUpload(log_uploaded_signal.GetCallback());
std::unique_ptr<NotificationHandler> handler =
std::make_unique<PersistentNotificationHandler>();
handler->ReportNotificationAsSafe(notification_id_str, origin_, profile());
// Check the MQLS log.
ASSERT_TRUE(log_uploaded_signal.Wait());
const auto& logs = uploaded_logs();
ASSERT_EQ(1u, logs.size());
auto* const notification_content_detection =
logs[0]->mutable_notification_content_detection();
ASSERT_TRUE(notification_content_detection->has_request());
ASSERT_TRUE(notification_content_detection->has_response());
ASSERT_TRUE(notification_content_detection->has_quality());
ASSERT_EQ(
origin.spec(),
notification_content_detection->request().notification_contents().url());
ASSERT_EQ(suspicious_score,
notification_content_detection->response().suspicious_score());
ASSERT_TRUE(notification_content_detection->quality().is_url_on_allowlist());
ASSERT_FALSE(
notification_content_detection->quality().did_user_always_allow_url());
ASSERT_TRUE(
notification_content_detection->quality().was_user_shown_warning());
ASSERT_FALSE(
notification_content_detection->quality().did_user_unsubscribe());
ASSERT_EQ(optimization_guide::proto::SiteEngagementScore::
SITE_ENGAGEMENT_SCORE_NONE,
notification_content_detection->quality().site_engagement_score());
}
TEST_F(
PersistentNotificationHandlerWithNotificationContentDetectionAndLoggingTest,
ReportWarnedNotificationAsSpam) {
bool is_url_on_allowlist = true;
bool did_user_always_allow_url = false;
double suspicious_score = 70.0;
WriteNotificationDataAndMetadataToDatabase(
is_url_on_allowlist, did_user_always_allow_url, suspicious_score);
int notification_id = 1;
GURL origin(origin_);
std::string notification_id_str =
"p#" + origin.spec() + "#0" + base::NumberToString(notification_id);
base::test::TestFuture<void> log_uploaded_signal;
logs_uploader()->WaitForLogUpload(log_uploaded_signal.GetCallback());
std::unique_ptr<NotificationHandler> handler =
std::make_unique<PersistentNotificationHandler>();
handler->ReportWarnedNotificationAsSpam(notification_id_str, origin_,
profile());
// Check the MQLS log.
ASSERT_TRUE(log_uploaded_signal.Wait());
const auto& logs = uploaded_logs();
ASSERT_EQ(1u, logs.size());
auto* const notification_content_detection =
logs[0]->mutable_notification_content_detection();
ASSERT_TRUE(notification_content_detection->has_request());
ASSERT_TRUE(notification_content_detection->has_response());
ASSERT_TRUE(notification_content_detection->has_quality());
ASSERT_EQ(
origin.spec(),
notification_content_detection->request().notification_contents().url());
ASSERT_EQ(suspicious_score,
notification_content_detection->response().suspicious_score());
ASSERT_TRUE(notification_content_detection->quality().is_url_on_allowlist());
ASSERT_FALSE(
notification_content_detection->quality().did_user_always_allow_url());
ASSERT_TRUE(
notification_content_detection->quality().was_user_shown_warning());
ASSERT_TRUE(notification_content_detection->quality().did_user_unsubscribe());
ASSERT_EQ(optimization_guide::proto::SiteEngagementScore::
SITE_ENGAGEMENT_SCORE_NONE,
notification_content_detection->quality().site_engagement_score());
}
TEST_F(
PersistentNotificationHandlerWithNotificationContentDetectionAndLoggingTest,
ReportUnwarnedNotificationAsSpam) {
bool is_url_on_allowlist = true;
bool did_user_always_allow_url = false;
double suspicious_score = 70.0;
WriteNotificationDataAndMetadataToDatabase(
is_url_on_allowlist, did_user_always_allow_url, suspicious_score);
int notification_id = 1;
GURL origin(origin_);
std::string notification_id_str =
"p#" + origin.spec() + "#0" + base::NumberToString(notification_id);
base::test::TestFuture<void> log_uploaded_signal;
logs_uploader()->WaitForLogUpload(log_uploaded_signal.GetCallback());
std::unique_ptr<NotificationHandler> handler =
std::make_unique<PersistentNotificationHandler>();
handler->ReportUnwarnedNotificationAsSpam(notification_id_str, origin_,
profile());
// Check the MQLS log.
ASSERT_TRUE(log_uploaded_signal.Wait());
const auto& logs = uploaded_logs();
ASSERT_EQ(1u, logs.size());
auto* const notification_content_detection =
logs[0]->mutable_notification_content_detection();
ASSERT_TRUE(notification_content_detection->has_request());
ASSERT_TRUE(notification_content_detection->has_response());
ASSERT_TRUE(notification_content_detection->has_quality());
ASSERT_EQ(
origin.spec(),
notification_content_detection->request().notification_contents().url());
ASSERT_EQ(suspicious_score,
notification_content_detection->response().suspicious_score());
ASSERT_TRUE(notification_content_detection->quality().is_url_on_allowlist());
ASSERT_FALSE(
notification_content_detection->quality().did_user_always_allow_url());
ASSERT_FALSE(
notification_content_detection->quality().was_user_shown_warning());
ASSERT_TRUE(notification_content_detection->quality().did_user_unsubscribe());
ASSERT_EQ(optimization_guide::proto::SiteEngagementScore::
SITE_ENGAGEMENT_SCORE_NONE,
notification_content_detection->quality().site_engagement_score());
}
class
PersistentNotificationHandlerWithNotificationContentDetectionLowLoggingRateTest
: public PersistentNotificationHandlerWithNotificationContentDetectionAndLoggingTest {
void SetUp() override {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
safe_browsing::kReportNotificationContentDetectionData,
{{"ReportNotificationContentDetectionDataRate", "0"}});
PersistentNotificationHandlerWithNotificationContentDetectionAndLoggingTest::
SetUp();
}
};
TEST_F(
PersistentNotificationHandlerWithNotificationContentDetectionLowLoggingRateTest,
NoReportSent) {
bool is_url_on_allowlist = true;
bool did_user_always_allow_url = false;
double suspicious_score = 70.0;
WriteNotificationDataAndMetadataToDatabase(
is_url_on_allowlist, did_user_always_allow_url, suspicious_score);
int notification_id = 1;
GURL origin(origin_);
std::string notification_id_str =
"p#" + origin.spec() + "#0" + base::NumberToString(notification_id);
base::test::TestFuture<void> log_uploaded_signal;
logs_uploader()->WaitForLogUpload(log_uploaded_signal.GetCallback());
std::unique_ptr<NotificationHandler> handler =
std::make_unique<PersistentNotificationHandler>();
handler->ReportNotificationAsSafe(notification_id_str, origin_, profile());
// Check no MQLS logs.
const auto& logs = uploaded_logs();
ASSERT_EQ(0u, logs.size());
}
#if BUILDFLAG(IS_ANDROID)
TEST_F(
PersistentNotificationHandlerWithNotificationContentDetectionLowLoggingRateTest,
LogSuspiciousNotificationInteractionWithSuspiciousScore) {
ukm::TestAutoSetUkmRecorder test_ukm_recorder;
double suspicious_score = 70.0;
WriteNotificationDataAndMetadataToDatabase(
/*is_url_on_allowlist=*/true, /*did_user_always_allow_url=*/false,
suspicious_score);
int notification_id = 1;
GURL origin(origin_);
std::string notification_id_str =
"p#" + origin.spec() + "#0" + base::NumberToString(notification_id);
base::test::TestFuture<void> log_uploaded_signal;
logs_uploader()->WaitForLogUpload(log_uploaded_signal.GetCallback());
std::unique_ptr<NotificationHandler> handler =
std::make_unique<PersistentNotificationHandler>();
handler->OnShowOriginalNotification(origin_, notification_id_str, profile());
task_environment_.RunUntilIdle();
// Check UKM is logged with suspicious score.
auto ukm_entries = test_ukm_recorder.GetEntriesByName(
ukm::builders::SuspiciousNotificationInteraction::kEntryName);
ASSERT_EQ(1u, ukm_entries.size());
test_ukm_recorder.ExpectEntryMetric(
ukm_entries[0], "SuspiciousInteractionType",
static_cast<int>(
safe_browsing::SuspiciousNotificationWarningInteractions::
kShowOriginalNotification));
test_ukm_recorder.ExpectEntryMetric(ukm_entries[0], "SuspiciousScore",
suspicious_score);
}
#endif