// Copyright 2017 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/android/search_permissions/search_permissions_service.h"
#include <memory>
#include <utility>
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_decision_auto_blocker.h"
#include "components/permissions/permission_uma_util.h"
#include "components/prefs/pref_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
namespace {
const char kGoogleURL[] = "https://www.google.com/";
// The test delegate is used to mock out search-engine related functionality.
class TestSearchEngineDelegate
: public SearchPermissionsService::SearchEngineDelegate {
public:
TestSearchEngineDelegate()
: dse_origin_(url::Origin::Create(GURL(kGoogleURL))) {}
std::u16string GetDSEName() override {
if (dse_origin_.host().find("google") != std::string::npos)
return u"Google";
return u"Example";
}
url::Origin GetDSEOrigin() override { return dse_origin_; }
void set_dse_origin(const std::string& dse_origin) {
dse_origin_ = url::Origin::Create(GURL(dse_origin));
}
private:
url::Origin dse_origin_;
};
} // namespace
class SearchPermissionsServiceTest : public testing::Test {
public:
void SetUp() override {
profile_ = std::make_unique<TestingProfile>();
// Because notification channel settings aren't tied to the profile,
// they will persist across tests. We need to make sure they're clean
// here.
ClearContentSettings(ContentSettingsType::NOTIFICATIONS);
auto test_delegate = std::make_unique<TestSearchEngineDelegate>();
test_delegate_ = test_delegate.get();
GetService()->SetSearchEngineDelegateForTest(std::move(test_delegate));
ReinitializeService(true /* clear_pref */);
}
void TearDown() override {
test_delegate_ = nullptr;
// Because notification channel settings aren't tied to the profile, they
// will persist across tests. We need to make sure they're reset here.
ClearContentSettings(ContentSettingsType::NOTIFICATIONS);
profile_.reset();
}
void ClearContentSettings(ContentSettingsType type) {
SetContentSetting(kGoogleURL, type, CONTENT_SETTING_DEFAULT);
}
TestingProfile* profile() { return profile_.get(); }
TestSearchEngineDelegate* test_delegate() { return test_delegate_; }
SearchPermissionsService* GetService() {
return SearchPermissionsService::Factory::GetForBrowserContext(profile());
}
void SetContentSetting(const std::string& origin_string,
ContentSettingsType type,
ContentSetting setting) {
GURL url(origin_string);
HostContentSettingsMap* hcsm =
HostContentSettingsMapFactory::GetForProfile(profile());
// Clear a setting before setting it. This is needed because in general
// notifications settings can't be changed from ALLOW<->BLOCK on Android O+.
// We need to change the setting from ALLOW->BLOCK in one case, where the
// previous DSE had permission blocked but the new DSE we're changing to has
// permission allowed. Thus this works around that restriction.
// WARNING: This is a special case and in general notification settings
// should never be changed between ALLOW<->BLOCK on Android. Do not copy
// this code. Check with the notifications team if you need to do something
// like this.
hcsm->SetContentSettingDefaultScope(url, url, type,
CONTENT_SETTING_DEFAULT);
hcsm->SetContentSettingDefaultScope(url, url, type, setting);
}
ContentSetting GetContentSetting(const std::string& origin_string,
ContentSettingsType type) {
GURL url(origin_string);
return HostContentSettingsMapFactory::GetForProfile(profile())
->GetContentSetting(url, url, type);
}
// Simulates the initialization that happens when recreating the service. If
// |clear_pref| is true, then it simulates the first time the service is ever
// created.
void ReinitializeService(bool clear_pref) {
if (clear_pref) {
profile()->GetPrefs()->ClearPref(prefs::kDSEPermissionsSettings);
profile()->GetPrefs()->ClearPref(prefs::kDSEWasDisabledByPolicy);
}
GetService()->InitializeSettingsIfNeeded();
}
void SetDSEPref(ContentSetting setting) {
GetService()->SetDSEPrefForTesting(setting, setting);
}
private:
std::unique_ptr<TestingProfile> profile_;
content::BrowserTaskEnvironment task_environment_;
// This is owned by the SearchPermissionsService which is owned by the
// profile.
raw_ptr<TestSearchEngineDelegate> test_delegate_;
};
// As soon as the SearchPermissionsService is initialized, the DSE permissions
// are reverted.
TEST_F(SearchPermissionsServiceTest, DSEPermissionsAreReverted) {
constexpr struct {
ContentSetting stored_pref_setting;
ContentSetting current_setting;
ContentSetting expected_setting_after_autogrant_reverted;
} kTests[] = {
{CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW},
{CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK},
{CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK},
};
for (const auto& test : kTests) {
for (const auto type : {ContentSettingsType::NOTIFICATIONS,
ContentSettingsType::GEOLOCATION}) {
ClearContentSettings(type);
SetDSEPref(test.stored_pref_setting);
SetContentSetting(kGoogleURL, type, test.current_setting);
// Initialize DSE and verify the expected setting.
ReinitializeService(false /* clear_pref */);
EXPECT_EQ(test.expected_setting_after_autogrant_reverted,
GetContentSetting(kGoogleURL, type));
}
}
}
// Tests permission revert in the scenario in which the permission is disabled
// by default but the DSE origin is allowed.
TEST_F(SearchPermissionsServiceTest, PermissionDisabledByDefault) {
constexpr struct {
ContentSetting stored_pref_setting;
ContentSetting expected_setting;
} kTests[] = {
{CONTENT_SETTING_ASK, CONTENT_SETTING_BLOCK},
{CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW},
{CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK},
};
HostContentSettingsMap* hcsm =
HostContentSettingsMapFactory::GetForProfile(profile());
for (const auto& test : kTests) {
for (const auto type : {ContentSettingsType::GEOLOCATION,
ContentSettingsType::NOTIFICATIONS}) {
ClearContentSettings(type);
hcsm->SetDefaultContentSetting(type, CONTENT_SETTING_BLOCK);
SetContentSetting(kGoogleURL, type, CONTENT_SETTING_ALLOW);
SetDSEPref(test.stored_pref_setting);
EXPECT_EQ(CONTENT_SETTING_ALLOW, GetContentSetting(kGoogleURL, type));
// After revert the DSE origin should now have the expected setting.
ReinitializeService(false /* clear_pref */);
EXPECT_EQ(test.expected_setting, GetContentSetting(kGoogleURL, type));
}
}
}
// Test that the appropriate UMA metrics have been recorded when the DSE is
// disabled.
TEST_F(SearchPermissionsServiceTest,
MetricsAndPrefsAreRecordedWhenAutoDSEPermissionReverted) {
constexpr struct {
ContentSetting initial_setting;
ContentSetting updated_setting;
permissions::AutoDSEPermissionRevertTransition expected_transition;
} kTests[] = {
{CONTENT_SETTING_ASK, CONTENT_SETTING_ALLOW,
permissions::AutoDSEPermissionRevertTransition::NO_DECISION_ASK},
{CONTENT_SETTING_ALLOW, CONTENT_SETTING_ALLOW,
permissions::AutoDSEPermissionRevertTransition::PRESERVE_ALLOW},
{CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW,
permissions::AutoDSEPermissionRevertTransition::CONFLICT_ASK},
{CONTENT_SETTING_ASK, CONTENT_SETTING_BLOCK,
permissions::AutoDSEPermissionRevertTransition::PRESERVE_BLOCK_ASK},
{CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK,
permissions::AutoDSEPermissionRevertTransition::PRESERVE_BLOCK_ALLOW},
{CONTENT_SETTING_BLOCK, CONTENT_SETTING_BLOCK,
permissions::AutoDSEPermissionRevertTransition::PRESERVE_BLOCK_BLOCK},
};
for (const auto& test : kTests) {
for (const auto& type : {ContentSettingsType::NOTIFICATIONS,
ContentSettingsType::GEOLOCATION}) {
// Notifications can not be set to ASK on Android as notification channels
// explicitly rely on the state being only BLOCK/ALLOW/DEFAULT.
if (test.initial_setting == CONTENT_SETTING_ASK &&
type == ContentSettingsType::NOTIFICATIONS) {
continue;
}
// Simulate an initial autogranted permission state.
ClearContentSettings(type);
SetDSEPref(test.initial_setting);
SetContentSetting(kGoogleURL, type, test.updated_setting);
// Initialize the service which should revert the autogranted permissions.
{
base::HistogramTester histograms;
ReinitializeService(false /* clear_pref */);
// Test that the expected samples are recorded in histograms.
for (auto sample = static_cast<int>(
permissions::AutoDSEPermissionRevertTransition::
NO_DECISION_ASK);
sample <
static_cast<int>(
permissions::AutoDSEPermissionRevertTransition::kMaxValue);
++sample) {
std::string histogram =
"Permissions.DSE.AutoPermissionRevertTransition.";
histogram += type == ContentSettingsType::NOTIFICATIONS
? "Notifications"
: "Geolocation";
histograms.ExpectBucketCount(
histogram, sample,
static_cast<int>(test.expected_transition) == sample ? 1 : 0);
}
}
}
}
}
// Records DSE origin settings whenever the service is initialized.
TEST_F(SearchPermissionsServiceTest, DSEEffectiveSettingMetric) {
base::HistogramTester histograms;
ClearContentSettings(ContentSettingsType::NOTIFICATIONS);
ClearContentSettings(ContentSettingsType::GEOLOCATION);
ReinitializeService(true /* clear_pref */);
histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Notifications",
CONTENT_SETTING_ASK, 1);
histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Geolocation",
CONTENT_SETTING_ASK, 1);
SetContentSetting(kGoogleURL, ContentSettingsType::NOTIFICATIONS,
CONTENT_SETTING_BLOCK);
SetContentSetting(kGoogleURL, ContentSettingsType::GEOLOCATION,
CONTENT_SETTING_ALLOW);
ReinitializeService(false /* clear_pref */);
histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Notifications",
CONTENT_SETTING_ASK, 1);
histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Notifications",
CONTENT_SETTING_BLOCK, 1);
histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Geolocation",
CONTENT_SETTING_ASK, 1);
histograms.ExpectBucketCount("Permissions.DSE.EffectiveSetting.Geolocation",
CONTENT_SETTING_ALLOW, 1);
}