blob: 296b269c1fbbb2d8d435b03ab661f22f1737d3aa [file] [log] [blame]
// Copyright 2012 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/extensions/api/preference/preference_api.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <optional>
#include <utility>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/pref_mapping.h"
#include "chrome/browser/extensions/pref_transformer_interface.h"
#include "chrome/browser/extensions/preference/preference_helpers.h"
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/privacy_sandbox/privacy_sandbox_features.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "extensions/browser/api/content_settings/content_settings_service.h"
#include "extensions/browser/extension_function_registry.h"
#include "extensions/browser/extension_pref_value_map.h"
#include "extensions/browser/extension_pref_value_map_factory.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_prefs_helper.h"
#include "extensions/browser/extension_system_provider.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/pref_names.h"
#include "extensions/common/api/types.h"
#include "extensions/common/constants.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permissions_data.h"
#include "media/media_buildflags.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
using extensions::mojom::APIPermissionID;
namespace extensions {
namespace {
using extensions::api::types::ChromeSettingScope;
constexpr char kConversionErrorMessage[] =
"Internal error: Stored value for preference '*' cannot be converted "
"properly.";
constexpr char kPermissionErrorMessage[] =
"You do not have permission to access the preference '*'. "
"Be sure to declare in your manifest what permissions you need.";
constexpr char kIncognitoKey[] = "incognito";
constexpr char kScopeKey[] = "scope";
constexpr char kIncognitoSpecific[] = "incognitoSpecific";
constexpr char kLevelOfControl[] = "levelOfControl";
constexpr char kValue[] = "value";
bool StringToScope(const std::string& s, ChromeSettingScope& scope) {
scope = extensions::api::types::ParseChromeSettingScope(s);
return scope != ChromeSettingScope::kNone;
}
} // namespace
PreferenceEventRouter::PreferenceEventRouter(Profile* profile)
: profile_(profile) {
registrar_.Init(profile_->GetPrefs());
for (const auto& pref : PrefMapping::GetMappings()) {
registrar_.Add(
pref.browser_pref,
base::BindRepeating(&PreferenceEventRouter::OnPrefChanged,
base::Unretained(this), registrar_.prefs()));
}
DCHECK(!profile_->IsOffTheRecord());
observed_profiles_.AddObservation(profile_.get());
if (profile->HasPrimaryOTRProfile())
OnOffTheRecordProfileCreated(
profile->GetPrimaryOTRProfile(/*create_if_needed=*/true));
else
ObserveOffTheRecordPrefs(profile->GetReadOnlyOffTheRecordPrefs());
}
PreferenceEventRouter::~PreferenceEventRouter() = default;
void PreferenceEventRouter::OnPrefChanged(PrefService* pref_service,
const std::string& browser_pref) {
bool incognito = (pref_service != profile_->GetPrefs());
std::string event_name;
APIPermissionID permission = APIPermissionID::kInvalid;
bool rv = PrefMapping::GetInstance()->FindEventForBrowserPref(
browser_pref, &event_name, &permission);
DCHECK(rv);
base::Value::List args;
const PrefService::Preference* pref =
pref_service->FindPreference(browser_pref);
CHECK(pref);
PrefTransformerInterface* transformer =
PrefMapping::GetInstance()->FindTransformerForBrowserPref(browser_pref);
std::optional<base::Value> transformed_value =
transformer->BrowserToExtensionPref(*pref->GetValue(), incognito);
if (!transformed_value) {
LOG(ERROR) << ErrorUtils::FormatErrorMessage(kConversionErrorMessage,
pref->name());
return;
}
base::Value::Dict dict;
dict.Set(kValue, std::move(*transformed_value));
if (incognito) {
ExtensionPrefs* ep = ExtensionPrefs::Get(profile_);
dict.Set(kIncognitoSpecific, ep->HasIncognitoPrefValue(browser_pref));
}
args.Append(std::move(dict));
// TODO(kalman): Have a histogram value for each pref type.
// This isn't so important for the current use case of these
// histograms, which is to track which event types are waking up event
// pages, or which are delivered to persistent background pages. Simply
// "a setting changed" is enough detail for that. However if we try to
// use these histograms for any fine-grained logic (like removing the
// string event name altogether), or if we discover this event is
// firing a lot and want to understand that better, then this will need
// to change.
events::HistogramValue histogram_value =
events::TYPES_CHROME_SETTING_ON_CHANGE;
extensions::preference_helpers::DispatchEventToExtensions(
profile_, histogram_value, event_name, std::move(args), permission,
incognito, browser_pref);
}
void PreferenceEventRouter::OnOffTheRecordProfileCreated(
Profile* off_the_record) {
observed_profiles_.AddObservation(off_the_record);
ObserveOffTheRecordPrefs(off_the_record->GetPrefs());
}
void PreferenceEventRouter::OnProfileWillBeDestroyed(Profile* profile) {
observed_profiles_.RemoveObservation(profile);
if (profile->IsOffTheRecord()) {
// The real PrefService is about to be destroyed so we must make sure we
// get the "dummy" one.
ObserveOffTheRecordPrefs(profile_->GetReadOnlyOffTheRecordPrefs());
}
}
void PreferenceEventRouter::ObserveOffTheRecordPrefs(PrefService* prefs) {
incognito_registrar_ = std::make_unique<PrefChangeRegistrar>();
incognito_registrar_->Init(prefs);
for (const auto& pref : PrefMapping::GetMappings()) {
incognito_registrar_->Add(
pref.browser_pref,
base::BindRepeating(&PreferenceEventRouter::OnPrefChanged,
base::Unretained(this),
incognito_registrar_->prefs()));
}
}
PreferenceAPI::PreferenceAPI(content::BrowserContext* context)
: profile_(Profile::FromBrowserContext(context)) {
// Preferences.
ExtensionFunctionRegistry& registry =
ExtensionFunctionRegistry::GetInstance();
registry.RegisterFunction<GetPreferenceFunction>();
registry.RegisterFunction<SetPreferenceFunction>();
registry.RegisterFunction<ClearPreferenceFunction>();
PrefMapping* pref_mapping = PrefMapping::GetInstance();
for (const auto& pref : PrefMapping::GetMappings()) {
std::string event_name;
APIPermissionID permission = APIPermissionID::kInvalid;
bool rv = pref_mapping->FindEventForBrowserPref(pref.browser_pref,
&event_name, &permission);
DCHECK(rv);
EventRouter::Get(profile_)->RegisterObserver(this, event_name);
}
content_settings_store()->AddObserver(this);
}
PreferenceAPI::~PreferenceAPI() = default;
void PreferenceAPI::Shutdown() {
EventRouter::Get(profile_)->UnregisterObserver(this);
if (!ExtensionPrefs::Get(profile_)->extensions_disabled())
ClearIncognitoSessionOnlyContentSettings();
content_settings_store()->RemoveObserver(this);
}
static base::LazyInstance<BrowserContextKeyedAPIFactory<PreferenceAPI>>::
DestructorAtExit g_preference_api_factory = LAZY_INSTANCE_INITIALIZER;
// static
BrowserContextKeyedAPIFactory<PreferenceAPI>*
PreferenceAPI::GetFactoryInstance() {
return g_preference_api_factory.Pointer();
}
// static
PreferenceAPI* PreferenceAPI::Get(content::BrowserContext* context) {
return BrowserContextKeyedAPIFactory<PreferenceAPI>::Get(context);
}
void PreferenceAPI::OnListenerAdded(const EventListenerInfo& details) {
EnsurePreferenceEventRouterCreated();
EventRouter::Get(profile_)->UnregisterObserver(this);
}
void PreferenceAPI::EnsurePreferenceEventRouterCreated() {
if (!preference_event_router_) {
preference_event_router_ =
std::make_unique<PreferenceEventRouter>(profile_);
}
}
void PreferenceAPI::OnContentSettingChanged(const ExtensionId& extension_id,
bool incognito) {
if (incognito) {
ExtensionPrefs::Get(profile_)->UpdateExtensionPref(
extension_id, pref_names::kPrefIncognitoContentSettings,
base::Value(content_settings_store()->GetSettingsForExtension(
extension_id, ChromeSettingScope::kIncognitoPersistent)));
} else {
ExtensionPrefs::Get(profile_)->UpdateExtensionPref(
extension_id, pref_names::kPrefContentSettings,
base::Value(content_settings_store()->GetSettingsForExtension(
extension_id, ChromeSettingScope::kRegular)));
}
}
void PreferenceAPI::ClearIncognitoSessionOnlyContentSettings() {
for (const auto& id : ExtensionPrefs::Get(profile_)->GetExtensions()) {
content_settings_store()->ClearContentSettingsForExtension(
id, ChromeSettingScope::kIncognitoSessionOnly);
}
}
scoped_refptr<ContentSettingsStore> PreferenceAPI::content_settings_store() {
return ContentSettingsService::Get(profile_)->content_settings_store();
}
template <>
void
BrowserContextKeyedAPIFactory<PreferenceAPI>::DeclareFactoryDependencies() {
DependsOn(ContentSettingsService::GetFactoryInstance());
DependsOn(ExtensionPrefsFactory::GetInstance());
DependsOn(ExtensionPrefValueMapFactory::GetInstance());
DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
}
PreferenceFunction::~PreferenceFunction() = default;
GetPreferenceFunction::~GetPreferenceFunction() = default;
ExtensionFunction::ResponseAction GetPreferenceFunction::Run() {
EXTENSION_FUNCTION_VALIDATE(args().size() >= 2);
EXTENSION_FUNCTION_VALIDATE(args()[0].is_string());
EXTENSION_FUNCTION_VALIDATE(args()[1].is_dict());
const std::string& pref_key = args()[0].GetString();
const base::Value& details = args()[1];
bool incognito = false;
if (std::optional<bool> result = details.GetDict().FindBool(kIncognitoKey)) {
incognito = *result;
}
// Check incognito access.
if (incognito) {
// Extensions are only allowed to modify incognito preferences if they are
// enabled in incognito. If the calling browser context is off the record,
// then the extension must be allowed to run incognito. Otherwise, this
// could be a spanning mode extension, and we need to check its incognito
// access.
if (!browser_context()->IsOffTheRecord() &&
!include_incognito_information()) {
return RespondNow(Error(extension_misc::kIncognitoErrorMessage));
}
}
// Obtain and check read/write permission for pref.
std::string browser_pref;
APIPermissionID read_permission = APIPermissionID::kInvalid;
APIPermissionID write_permission = APIPermissionID::kInvalid;
EXTENSION_FUNCTION_VALIDATE(
PrefMapping::GetInstance()->FindBrowserPrefForExtensionPref(
pref_key, &browser_pref, &read_permission, &write_permission));
if (!extension()->permissions_data()->HasAPIPermission(read_permission))
return RespondNow(Error(kPermissionErrorMessage, pref_key));
Profile* profile = Profile::FromBrowserContext(browser_context());
PrefService* prefs =
extensions::preference_helpers::GetProfilePrefService(profile, incognito);
const PrefService::Preference* pref = prefs->FindPreference(browser_pref);
CHECK(pref);
// Retrieve level of control.
std::string level_of_control =
extensions::preference_helpers::GetLevelOfControl(
profile, extension_id(), browser_pref, incognito);
base::Value::Dict result;
ProduceGetResult(&result, pref->GetValue(), level_of_control, browser_pref,
incognito);
return RespondNow(WithArguments(std::move(result)));
}
void GetPreferenceFunction::ProduceGetResult(
base::Value::Dict* result,
const base::Value* pref_value,
const std::string& level_of_control,
const std::string& browser_pref,
bool incognito) {
PrefTransformerInterface* transformer =
PrefMapping::GetInstance()->FindTransformerForBrowserPref(browser_pref);
std::optional<base::Value> transformed_value =
transformer->BrowserToExtensionPref(*pref_value, incognito);
if (!transformed_value) {
// TODO(devlin): Can this happen? When? Should it be an error, or a bad
// message?
LOG(ERROR) << ErrorUtils::FormatErrorMessage(kConversionErrorMessage,
browser_pref);
return;
}
result->Set(kValue, std::move(*transformed_value));
result->Set(kLevelOfControl, level_of_control);
// Retrieve incognito status.
if (incognito) {
ExtensionPrefs* ep = ExtensionPrefs::Get(browser_context());
result->Set(kIncognitoSpecific, ep->HasIncognitoPrefValue(browser_pref));
}
}
SetPreferenceFunction::~SetPreferenceFunction() = default;
ExtensionFunction::ResponseAction SetPreferenceFunction::Run() {
EXTENSION_FUNCTION_VALIDATE(args().size() >= 2);
EXTENSION_FUNCTION_VALIDATE(args()[0].is_string());
EXTENSION_FUNCTION_VALIDATE(args()[1].is_dict());
std::string pref_key = args()[0].GetString();
const base::Value::Dict& details = args()[1].GetDict();
const base::Value* value = details.Find(kValue);
EXTENSION_FUNCTION_VALIDATE(value);
ChromeSettingScope scope = ChromeSettingScope::kRegular;
if (const std::string* scope_str = details.FindString(kScopeKey)) {
EXTENSION_FUNCTION_VALIDATE(StringToScope(*scope_str, scope));
}
// Check incognito scope.
bool incognito = scope == ChromeSettingScope::kIncognitoPersistent ||
scope == ChromeSettingScope::kIncognitoSessionOnly;
if (incognito) {
// Regular profiles can't access incognito unless
// include_incognito_information is true.
if (!browser_context()->IsOffTheRecord() &&
!include_incognito_information())
return RespondNow(Error(extension_misc::kIncognitoErrorMessage));
} else if (browser_context()->IsOffTheRecord()) {
// If the browser_context associated with this ExtensionFunction is off the
// record, it must have come from the incognito process for a split-mode
// extension (spanning mode extensions only run in the on-the-record
// process). The incognito profile of a split-mode extension should never be
// able to modify the on-the-record profile, so error out.
return RespondNow(
Error("Can't modify regular settings from an incognito context."));
}
Profile* profile = Profile::FromBrowserContext(browser_context());
if (scope == ChromeSettingScope::kIncognitoSessionOnly &&
!profile->HasPrimaryOTRProfile()) {
return RespondNow(Error(extension_misc::kIncognitoSessionOnlyErrorMessage));
}
// Obtain pref.
std::string browser_pref;
APIPermissionID read_permission = APIPermissionID::kInvalid;
APIPermissionID write_permission = APIPermissionID::kInvalid;
EXTENSION_FUNCTION_VALIDATE(
PrefMapping::GetInstance()->FindBrowserPrefForExtensionPref(
pref_key, &browser_pref, &read_permission, &write_permission));
if (!extension()->permissions_data()->HasAPIPermission(write_permission))
return RespondNow(Error(kPermissionErrorMessage, pref_key));
// If 3PCs are globally blocked in incognito re-allowing them is not
// supported, so error out.
if (incognito && browser_pref == prefs::kCookieControlsMode &&
value->GetBool() &&
base::FeatureList::IsEnabled(
privacy_sandbox::kAlwaysBlock3pcsIncognito)) {
return RespondNow(
Error(extension_misc::kCookiesAllowedIncognitoErrorMessage));
}
ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context());
const PrefService::Preference* pref =
prefs->pref_service()->FindPreference(browser_pref);
CHECK(pref);
// Validate new value.
PrefTransformerInterface* transformer =
PrefMapping::GetInstance()->FindTransformerForBrowserPref(browser_pref);
std::string error;
bool bad_message = false;
std::optional<base::Value> browser_pref_value =
transformer->ExtensionToBrowserPref(*value, error, bad_message);
if (!browser_pref_value) {
EXTENSION_FUNCTION_VALIDATE(!bad_message);
return RespondNow(Error(std::move(error)));
}
EXTENSION_FUNCTION_VALIDATE(browser_pref_value->type() == pref->GetType());
// Validate also that the stored value can be converted back by the
// transformer.
std::optional<base::Value> extension_pref_value =
transformer->BrowserToExtensionPref(*browser_pref_value, incognito);
EXTENSION_FUNCTION_VALIDATE(extension_pref_value);
auto* prefs_helper = ExtensionPrefsHelper::Get(browser_context());
// Set the new Autofill prefs if the extension sets the deprecated pref in
// order to maintain backward compatibility in the extensions preference API.
// TODO(crbug.com/40587768): Remove this once the deprecated pref is retired.
if (autofill::prefs::kAutofillEnabledDeprecated == browser_pref) {
// |SetExtensionControlledPref| takes ownership of the base::Value pointer.
prefs_helper->SetExtensionControlledPref(
extension_id(), autofill::prefs::kAutofillCreditCardEnabled, scope,
base::Value(browser_pref_value->GetBool()));
prefs_helper->SetExtensionControlledPref(
extension_id(), autofill::prefs::kAutofillProfileEnabled, scope,
base::Value(browser_pref_value->GetBool()));
}
// Whenever an extension takes control of the |kSafeBrowsingEnabled|
// preference, it must also set |kSafeBrowsingEnhanced| to false.
// See crbug.com/1064722 for more background.
//
// TODO(crbug.com/40681445): Consider extending
// chrome.privacy.services.safeBrowsingEnabled to a three-state enum.
if (prefs::kSafeBrowsingEnabled == browser_pref) {
prefs_helper->SetExtensionControlledPref(extension_id(),
prefs::kSafeBrowsingEnhanced,
scope, base::Value(false));
}
prefs_helper->SetExtensionControlledPref(extension_id(), browser_pref, scope,
browser_pref_value->Clone());
return RespondNow(NoArguments());
}
ClearPreferenceFunction::~ClearPreferenceFunction() = default;
ExtensionFunction::ResponseAction ClearPreferenceFunction::Run() {
EXTENSION_FUNCTION_VALIDATE(args().size() >= 2);
EXTENSION_FUNCTION_VALIDATE(args()[0].is_string());
EXTENSION_FUNCTION_VALIDATE(args()[1].is_dict());
std::string pref_key = args()[0].GetString();
const base::Value::Dict& details = args()[1].GetDict();
ChromeSettingScope scope = ChromeSettingScope::kRegular;
if (const std::string* scope_str = details.FindString(kScopeKey)) {
EXTENSION_FUNCTION_VALIDATE(StringToScope(*scope_str, scope));
}
// Check incognito scope.
bool incognito = scope == ChromeSettingScope::kIncognitoPersistent ||
scope == ChromeSettingScope::kIncognitoSessionOnly;
if (incognito) {
// We don't check incognito permissions here, as an extension should be
// always allowed to clear its own settings.
} else if (browser_context()->IsOffTheRecord()) {
// Incognito profiles can't access regular mode ever, they only exist in
// split mode.
return RespondNow(
Error("Can't modify regular settings from an incognito context."));
}
std::string browser_pref;
APIPermissionID read_permission = APIPermissionID::kInvalid;
APIPermissionID write_permission = APIPermissionID::kInvalid;
EXTENSION_FUNCTION_VALIDATE(
PrefMapping::GetInstance()->FindBrowserPrefForExtensionPref(
pref_key, &browser_pref, &read_permission, &write_permission));
if (!extension()->permissions_data()->HasAPIPermission(write_permission))
return RespondNow(Error(kPermissionErrorMessage, pref_key));
auto* prefs_helper = ExtensionPrefsHelper::Get(browser_context());
prefs_helper->RemoveExtensionControlledPref(extension_id(), browser_pref,
scope);
// Whenever an extension clears the |kSafeBrowsingEnabled| preference,
// it must also clear |kSafeBrowsingEnhanced|. See crbug.com/1064722 for
// more background.
//
// TODO(crbug.com/40681445): Consider extending
// chrome.privacy.services.safeBrowsingEnabled to a three-state enum.
if (prefs::kSafeBrowsingEnabled == browser_pref) {
prefs_helper->RemoveExtensionControlledPref(
extension_id(), prefs::kSafeBrowsingEnhanced, scope);
}
return RespondNow(NoArguments());
}
} // namespace extensions