chromium/chrome/browser/android/search_permissions/search_permissions_service_unittest.cc

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