| // 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 "base/bind.h" |
| #include "base/callback.h" |
| #include "base/feature_list.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/utf_string_conversions.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/browser/profiles/profile.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/browser/search_engines/ui_thread_search_terms_data.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/content_settings/core/browser/content_settings_utils.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "components/permissions/features.h" |
| #include "components/permissions/permission_decision_auto_blocker.h" |
| #include "components/permissions/permission_uma_util.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/search_engines/template_url.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "url/gurl.h" |
| #include "url/url_constants.h" |
| |
| namespace { |
| |
| const char kDSENameKey[] = "dse_name"; |
| const char kDSEOriginKey[] = "dse_origin"; |
| const char kDSEGeolocationSettingKey[] = "geolocation_setting_to_restore"; |
| const char kDSENotificationsSettingKey[] = "notifications_setting_to_restore"; |
| |
| // Default implementation of SearchEngineDelegate that is used for production |
| // code. |
| class SearchEngineDelegateImpl |
| : public SearchPermissionsService::SearchEngineDelegate { |
| public: |
| explicit SearchEngineDelegateImpl(Profile* profile) |
| : profile_(profile), |
| template_url_service_( |
| TemplateURLServiceFactory::GetForProfile(profile_)) {} |
| |
| std::u16string GetDSEName() override { |
| if (template_url_service_) { |
| const TemplateURL* template_url = |
| template_url_service_->GetDefaultSearchProvider(); |
| if (template_url) |
| return template_url->short_name(); |
| } |
| |
| return std::u16string(); |
| } |
| |
| url::Origin GetDSEOrigin() override { |
| if (template_url_service_) { |
| const TemplateURL* template_url = |
| template_url_service_->GetDefaultSearchProvider(); |
| if (template_url) { |
| GURL search_url = template_url->GenerateSearchURL( |
| template_url_service_->search_terms_data()); |
| return url::Origin::Create(search_url); |
| } |
| } |
| |
| return url::Origin(); |
| } |
| |
| private: |
| raw_ptr<Profile> profile_; |
| |
| // Will be null in unittests. |
| raw_ptr<TemplateURLService> template_url_service_; |
| }; |
| |
| } // namespace |
| |
| struct SearchPermissionsService::PrefValue { |
| std::u16string dse_name; |
| std::string dse_origin; |
| ContentSetting geolocation_setting_to_restore; |
| ContentSetting notifications_setting_to_restore; |
| }; |
| |
| // static |
| SearchPermissionsService* |
| SearchPermissionsService::Factory::GetForBrowserContext( |
| content::BrowserContext* context) { |
| return static_cast<SearchPermissionsService*>( |
| GetInstance()->GetServiceForBrowserContext(context, true)); |
| } |
| |
| // static |
| SearchPermissionsService::Factory* |
| SearchPermissionsService::Factory::GetInstance() { |
| return base::Singleton<SearchPermissionsService::Factory>::get(); |
| } |
| |
| SearchPermissionsService::Factory::Factory() |
| : ProfileKeyedServiceFactory("SearchPermissionsService") { |
| DependsOn(HostContentSettingsMapFactory::GetInstance()); |
| DependsOn(TemplateURLServiceFactory::GetInstance()); |
| } |
| |
| SearchPermissionsService::Factory::~Factory() {} |
| |
| bool SearchPermissionsService::Factory::ServiceIsCreatedWithBrowserContext() |
| const { |
| return true; |
| } |
| |
| KeyedService* SearchPermissionsService::Factory::BuildServiceInstanceFor( |
| content::BrowserContext* context) const { |
| return new SearchPermissionsService(Profile::FromBrowserContext(context)); |
| } |
| |
| void SearchPermissionsService::Factory::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterDictionaryPref(prefs::kDSEGeolocationSettingDeprecated); |
| registry->RegisterDictionaryPref(prefs::kDSEPermissionsSettings); |
| registry->RegisterBooleanPref(prefs::kDSEWasDisabledByPolicy, false); |
| } |
| |
| SearchPermissionsService::SearchPermissionsService(Profile* profile) |
| : profile_(profile), |
| pref_service_(profile_->GetPrefs()), |
| host_content_settings_map_( |
| HostContentSettingsMapFactory::GetForProfile(profile_)) { |
| // This class should never be constructed in incognito. |
| DCHECK(!profile_->IsOffTheRecord()); |
| |
| delegate_ = std::make_unique<SearchEngineDelegateImpl>(profile_); |
| |
| InitializeSettingsIfNeeded(); |
| } |
| |
| bool SearchPermissionsService::IsDseOrigin(const url::Origin& origin) { |
| return origin.scheme() == url::kHttpsScheme && |
| origin.IsSameOriginWith(delegate_->GetDSEOrigin()); |
| } |
| |
| void SearchPermissionsService::Shutdown() { |
| delegate_.reset(); |
| } |
| |
| SearchPermissionsService::~SearchPermissionsService() {} |
| |
| ContentSetting SearchPermissionsService::RestoreOldSettingAndReturnPrevious( |
| const GURL& dse_origin, |
| ContentSettingsType type, |
| ContentSetting setting_to_restore, |
| bool preserve_block_setting) { |
| // Read the current value of the old DSE. This is the DSE setting that we want |
| // to try to apply to the new DSE origin. |
| ContentSetting dse_setting = GetContentSetting(dse_origin, type); |
| |
| // The user's setting should never be ASK while an origin is the DSE. There |
| // should be no way for the user to reset the DSE content setting to ASK. |
| if (dse_setting == CONTENT_SETTING_ASK) { |
| // The style guide suggests not to handle cases which are invalid code paths |
| // however in this case there are security risks to state being invalid so |
| // we ensure the dse_setting is reverted to BLOCK. |
| dse_setting = CONTENT_SETTING_BLOCK; |
| } |
| |
| // If `preserve_block_setting` is set we don't restore a "BLOCK" setting. |
| if (dse_setting == CONTENT_SETTING_BLOCK && preserve_block_setting) |
| setting_to_restore = CONTENT_SETTING_BLOCK; |
| |
| // Restore the setting for the old origin. If the user has changed the setting |
| // since the origin became the DSE, we reset the setting so the user will be |
| // prompted. |
| if (setting_to_restore != dse_setting) |
| setting_to_restore = CONTENT_SETTING_ASK; |
| SetContentSetting(dse_origin, type, setting_to_restore); |
| |
| return dse_setting; |
| } |
| |
| void SearchPermissionsService::InitializeSettingsIfNeeded() { |
| GURL dse_origin = delegate_->GetDSEOrigin().GetURL(); |
| |
| // `dse_origin` can be invalid in tests or if the DSE is disabled by policy. |
| // If that's the case or if `RevertDSEAutomaticPermissions` is enabled, we |
| // restore the old settings and erase the pref. |
| const bool disabled_by_policy = !dse_origin.is_valid(); |
| if (pref_service_->HasPrefPath(prefs::kDSEPermissionsSettings)) { |
| if (disabled_by_policy) |
| pref_service_->SetBoolean(prefs::kDSEWasDisabledByPolicy, true); |
| |
| PrefValue pref = GetDSEPref(); |
| GURL old_dse_origin(pref.dse_origin); |
| |
| ContentSetting effective_setting = RestoreOldSettingAndReturnPrevious( |
| old_dse_origin, ContentSettingsType::GEOLOCATION, |
| pref.geolocation_setting_to_restore, !disabled_by_policy); |
| if (!disabled_by_policy) { |
| RecordAutoDSEPermissionReverted(ContentSettingsType::GEOLOCATION, |
| pref.geolocation_setting_to_restore, |
| effective_setting, dse_origin); |
| } |
| |
| if (pref.notifications_setting_to_restore != CONTENT_SETTING_DEFAULT) { |
| effective_setting = RestoreOldSettingAndReturnPrevious( |
| old_dse_origin, ContentSettingsType::NOTIFICATIONS, |
| pref.notifications_setting_to_restore, !disabled_by_policy); |
| if (!disabled_by_policy) { |
| RecordAutoDSEPermissionReverted(ContentSettingsType::NOTIFICATIONS, |
| pref.notifications_setting_to_restore, |
| effective_setting, dse_origin); |
| } |
| } |
| pref_service_->ClearPref(prefs::kDSEPermissionsSettings); |
| } |
| |
| RecordEffectiveDSEOriginPermissions(); |
| } |
| |
| SearchPermissionsService::PrefValue SearchPermissionsService::GetDSEPref() { |
| const base::Value::Dict& dict = |
| pref_service_->GetDict(prefs::kDSEPermissionsSettings); |
| |
| PrefValue pref; |
| const std::string* dse_name = dict.FindString(kDSENameKey); |
| const std::string* dse_origin = dict.FindString(kDSEOriginKey); |
| absl::optional<int> geolocation_setting_to_restore = |
| dict.FindInt(kDSEGeolocationSettingKey); |
| absl::optional<int> notifications_setting_to_restore = |
| dict.FindInt(kDSENotificationsSettingKey); |
| |
| if (dse_name && dse_origin && geolocation_setting_to_restore && |
| notifications_setting_to_restore) { |
| pref.dse_name = base::UTF8ToUTF16(*dse_name); |
| pref.dse_origin = *dse_origin; |
| pref.geolocation_setting_to_restore = |
| IntToContentSetting(*geolocation_setting_to_restore); |
| pref.notifications_setting_to_restore = |
| IntToContentSetting(*notifications_setting_to_restore); |
| } |
| |
| return pref; |
| } |
| |
| ContentSetting SearchPermissionsService::GetContentSetting( |
| const GURL& origin, |
| ContentSettingsType type) { |
| return host_content_settings_map_->GetUserModifiableContentSetting( |
| origin, origin, type); |
| } |
| |
| void SearchPermissionsService::SetContentSetting(const GURL& origin, |
| ContentSettingsType type, |
| ContentSetting setting) { |
| // 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. |
| host_content_settings_map_->SetContentSettingDefaultScope( |
| origin, origin, type, CONTENT_SETTING_DEFAULT); |
| |
| // If we're restoring an ASK setting, it really implies that we should delete |
| // the user-defined setting to fall back to the default. |
| if (setting == CONTENT_SETTING_ASK) |
| return; // We deleted the setting above already. |
| |
| host_content_settings_map_->SetContentSettingDefaultScope(origin, origin, |
| type, setting); |
| } |
| |
| void SearchPermissionsService::SetSearchEngineDelegateForTest( |
| std::unique_ptr<SearchEngineDelegate> delegate) { |
| delegate_ = std::move(delegate); |
| } |
| |
| void SearchPermissionsService::RecordAutoDSEPermissionReverted( |
| ContentSettingsType permission_type, |
| ContentSetting backed_up_setting, |
| ContentSetting effective_setting, |
| const GURL& origin) { |
| ContentSetting end_state_setting = GetContentSetting(origin, permission_type); |
| permissions::PermissionUmaUtil::RecordAutoDSEPermissionReverted( |
| permission_type, backed_up_setting, effective_setting, end_state_setting); |
| } |
| |
| void SearchPermissionsService::RecordEffectiveDSEOriginPermissions() { |
| GURL dse_origin = delegate_->GetDSEOrigin().GetURL(); |
| if (!dse_origin.is_valid()) |
| return; |
| |
| permissions::PermissionUmaUtil::RecordDSEEffectiveSetting( |
| ContentSettingsType::NOTIFICATIONS, |
| GetContentSetting(dse_origin, ContentSettingsType::NOTIFICATIONS)); |
| |
| permissions::PermissionUmaUtil::RecordDSEEffectiveSetting( |
| ContentSettingsType::GEOLOCATION, |
| GetContentSetting(dse_origin, ContentSettingsType::GEOLOCATION)); |
| } |
| |
| void SearchPermissionsService::SetDSEPrefForTesting( |
| ContentSetting geolocation_setting_to_restore, |
| ContentSetting notifications_setting_to_restore) { |
| base::DictionaryValue dict; |
| dict.SetStringKey(kDSENameKey, delegate_->GetDSEName()); |
| dict.SetStringKey(kDSEOriginKey, delegate_->GetDSEOrigin().GetURL().spec()); |
| dict.SetIntKey(kDSEGeolocationSettingKey, geolocation_setting_to_restore); |
| dict.SetIntKey(kDSENotificationsSettingKey, notifications_setting_to_restore); |
| pref_service_->Set(prefs::kDSEPermissionsSettings, dict); |
| } |