blob: 7a02cd7c7aea637266e36ec82afd8cf4fe1712a2 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/api/storage/storage_api.h"
#include <stddef.h>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/api/storage/session_storage_manager.h"
#include "extensions/browser/api/storage/storage_frontend.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/quota_service.h"
#include "extensions/common/api/storage.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/feature_channel.h"
using value_store::ValueStore;
namespace extensions {
// Concrete settings functions
namespace {
constexpr PrefMap kPrefSessionStorageAccessLevel = {
"storage_session_access_level", PrefType::kInteger,
PrefScope::kExtensionSpecific};
// Returns a vector of any strings within the given list.
std::vector<std::string> GetKeysFromList(const base::Value& list) {
DCHECK(list.is_list());
std::vector<std::string> keys;
keys.reserve(list.GetList().size());
for (const auto& value : list.GetList()) {
auto* as_string = value.GetIfString();
if (as_string)
keys.push_back(*as_string);
}
return keys;
}
// Returns a vector of keys within the given dict.
std::vector<std::string> GetKeysFromDict(const base::Value& dict) {
DCHECK(dict.is_dict());
std::vector<std::string> keys;
keys.reserve(dict.DictSize());
for (auto value : dict.DictItems()) {
keys.push_back(value.first);
}
return keys;
}
// Converts a map to a Value::Type::DICTIONARY.
base::Value MapAsValueDict(
const std::map<std::string, const base::Value*>& values) {
base::Value dict(base::Value::Type::DICTIONARY);
for (const auto& value : values)
dict.SetKey(value.first, value.second->Clone());
return dict;
}
// Creates quota heuristics for settings modification.
void GetModificationQuotaLimitHeuristics(QuotaLimitHeuristics* heuristics) {
// See storage.json for the current value of these limits.
QuotaLimitHeuristic::Config short_limit_config = {
api::storage::sync::MAX_WRITE_OPERATIONS_PER_MINUTE, base::Minutes(1)};
QuotaLimitHeuristic::Config long_limit_config = {
api::storage::sync::MAX_WRITE_OPERATIONS_PER_HOUR, base::Hours(1)};
heuristics->push_back(std::make_unique<QuotaService::TimedLimit>(
short_limit_config,
std::make_unique<QuotaLimitHeuristic::SingletonBucketMapper>(),
"MAX_WRITE_OPERATIONS_PER_MINUTE"));
heuristics->push_back(std::make_unique<QuotaService::TimedLimit>(
long_limit_config,
std::make_unique<QuotaLimitHeuristic::SingletonBucketMapper>(),
"MAX_WRITE_OPERATIONS_PER_HOUR"));
}
// Returns a nested dictionary Value converted from a ValueChange.
base::Value ValueChangeToValue(
std::vector<SessionStorageManager::ValueChange> changes) {
base::Value changes_value(base::Value::Type::DICTIONARY);
for (auto& change : changes) {
base::Value change_value(base::Value::Type::DICTIONARY);
if (change.old_value.has_value())
change_value.SetKey("oldValue", std::move(change.old_value.value()));
if (change.new_value)
change_value.SetKey("newValue", change.new_value->Clone());
changes_value.SetKey(change.key, std::move(change_value));
}
return changes_value;
}
} // namespace
// SettingsFunction
SettingsFunction::SettingsFunction() = default;
SettingsFunction::~SettingsFunction() = default;
bool SettingsFunction::ShouldSkipQuotaLimiting() const {
// Only apply quota if this is for sync storage.
if (args().empty() || !args()[0].is_string()) {
// This should be EXTENSION_FUNCTION_VALIDATE(false) but there is no way
// to signify that from this function. It will be caught in Run().
return false;
}
const std::string& storage_area_string = args()[0].GetString();
return StorageAreaFromString(storage_area_string) !=
StorageAreaNamespace::kSync;
}
ExtensionFunction::ResponseAction SettingsFunction::Run() {
EXTENSION_FUNCTION_VALIDATE(args().size() >= 1);
EXTENSION_FUNCTION_VALIDATE(args()[0].is_string());
// Not a ref since we remove the underlying value after.
std::string storage_area_string = args()[0].GetString();
mutable_args().erase(args().begin());
storage_area_ = StorageAreaFromString(storage_area_string);
EXTENSION_FUNCTION_VALIDATE(storage_area_ != StorageAreaNamespace::kInvalid);
// Session is the only storage area that does not use ValueStore, and will
// return synchronously.
if (storage_area_ == StorageAreaNamespace::kSession) {
// Currently only `session` can restrict the storage access. This call will
// be moved after the other storage areas allow it.
if (!IsAccessToStorageAllowed()) {
return RespondNow(
Error("Access to storage is not allowed from this context."));
}
return RespondNow(RunInSession());
}
// All other StorageAreas use ValueStore with settings_namespace, and will
// return asynchronously if successful.
settings_namespace_ = StorageAreaToSettingsNamespace(storage_area_);
EXTENSION_FUNCTION_VALIDATE(settings_namespace_ !=
settings_namespace::INVALID);
if (extension()->is_login_screen_extension() &&
storage_area_ != StorageAreaNamespace::kManaged) {
// Login screen extensions are not allowed to use local/sync storage for
// security reasons (see crbug.com/978443).
return RespondNow(Error(base::StringPrintf(
"\"%s\" is not available for login screen extensions",
storage_area_string.c_str())));
}
StorageFrontend* frontend = StorageFrontend::Get(browser_context());
if (!frontend->IsStorageEnabled(settings_namespace_)) {
return RespondNow(Error(
base::StringPrintf("\"%s\" is not available in this instance of Chrome",
storage_area_string.c_str())));
}
observers_ = frontend->GetObservers();
frontend->RunWithStorage(
extension(), settings_namespace_,
base::BindOnce(&SettingsFunction::AsyncRunWithStorage, this));
return RespondLater();
}
void SettingsFunction::AsyncRunWithStorage(ValueStore* storage) {
ResponseValue response = RunWithStorage(storage);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&SettingsFunction::Respond, this, std::move(response)));
}
ExtensionFunction::ResponseValue SettingsFunction::UseReadResult(
ValueStore::ReadResult result) {
TRACE_EVENT2("browser", "SettingsFunction::UseReadResult", "extension_id",
extension_id(), "namespace", storage_area_);
if (!result.status().ok())
return Error(result.status().message);
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->Swap(&result.settings());
return OneArgument(base::Value::FromUniquePtrValue(std::move(dict)));
}
ExtensionFunction::ResponseValue SettingsFunction::UseWriteResult(
ValueStore::WriteResult result) {
TRACE_EVENT2("browser", "SettingsFunction::UseWriteResult", "extension_id",
extension_id(), "namespace", storage_area_);
if (!result.status().ok())
return Error(result.status().message);
if (!result.changes().empty()) {
observers_->Notify(
FROM_HERE, &SettingsObserver::OnSettingsChanged, extension_id(),
storage_area_,
value_store::ValueStoreChange::ToValue(result.PassChanges()));
}
return NoArguments();
}
void SettingsFunction::OnSessionSettingsChanged(
std::vector<SessionStorageManager::ValueChange> changes) {
if (!changes.empty()) {
scoped_refptr<SettingsObserverList> observers =
StorageFrontend::Get(browser_context())->GetObservers();
observers->Notify(FROM_HERE, &SettingsObserver::OnSettingsChanged,
extension_id(), storage_area_,
ValueChangeToValue(std::move(changes)));
}
}
bool SettingsFunction::IsAccessToStorageAllowed() {
ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context());
// Default access level is only secure contexts.
int access_level = api::storage::ACCESS_LEVEL_TRUSTED_CONTEXTS;
prefs->ReadPrefAsInteger(extension()->id(), kPrefSessionStorageAccessLevel,
&access_level);
// Only a blessed extension context is considered trusted.
if (access_level == api::storage::ACCESS_LEVEL_TRUSTED_CONTEXTS)
return source_context_type() == Feature::BLESSED_EXTENSION_CONTEXT;
// All contexts are allowed.
DCHECK_EQ(api::storage::ACCESS_LEVEL_TRUSTED_AND_UNTRUSTED_CONTEXTS,
access_level);
return true;
}
ExtensionFunction::ResponseValue StorageStorageAreaGetFunction::RunWithStorage(
ValueStore* storage) {
TRACE_EVENT1("browser", "StorageStorageAreaGetFunction::RunWithStorage",
"extension_id", extension_id());
if (args().empty())
return BadMessage();
const base::Value& input = args()[0];
switch (input.type()) {
case base::Value::Type::NONE:
return UseReadResult(storage->Get());
case base::Value::Type::STRING:
return UseReadResult(storage->Get(input.GetString()));
case base::Value::Type::LIST:
return UseReadResult(storage->Get(GetKeysFromList(input)));
case base::Value::Type::DICTIONARY: {
ValueStore::ReadResult result = storage->Get(GetKeysFromDict(input));
if (!result.status().ok()) {
return UseReadResult(std::move(result));
}
std::unique_ptr<base::DictionaryValue> with_default_values =
base::Value::AsDictionaryValue(input).CreateDeepCopy();
with_default_values->MergeDictionary(&result.settings());
return UseReadResult(ValueStore::ReadResult(
std::move(with_default_values), result.PassStatus()));
}
default:
return BadMessage();
}
}
ExtensionFunction::ResponseValue StorageStorageAreaGetFunction::RunInSession() {
if (args().empty())
return BadMessage();
base::Value& input = mutable_args()[0];
base::Value value_dict(base::Value::Type::DICTIONARY);
SessionStorageManager* session_manager =
SessionStorageManager::GetForBrowserContext(browser_context());
switch (input.type()) {
case base::Value::Type::NONE:
value_dict = MapAsValueDict(session_manager->GetAll(extension_id()));
break;
case base::Value::Type::STRING:
value_dict = MapAsValueDict(session_manager->Get(
extension_id(), std::vector<std::string>(1, input.GetString())));
break;
case base::Value::Type::LIST:
value_dict = MapAsValueDict(
session_manager->Get(extension_id(), GetKeysFromList(input)));
break;
case base::Value::Type::DICTIONARY: {
std::map<std::string, const base::Value*> values =
session_manager->Get(extension_id(), GetKeysFromDict(input));
for (auto default_value : input.DictItems()) {
auto value_it = values.find(default_value.first);
value_dict.SetKey(default_value.first,
value_it != values.end()
? value_it->second->Clone()
: std::move(default_value.second));
}
break;
}
default:
return BadMessage();
}
return OneArgument(std::move(value_dict));
}
ExtensionFunction::ResponseValue
StorageStorageAreaGetBytesInUseFunction::RunWithStorage(ValueStore* storage) {
TRACE_EVENT1("browser",
"StorageStorageAreaGetBytesInUseFunction::RunWithStorage",
"extension_id", extension_id());
if (args().empty())
return BadMessage();
const base::Value& input = args()[0];
size_t bytes_in_use = 0;
switch (input.type()) {
case base::Value::Type::NONE:
bytes_in_use = storage->GetBytesInUse();
break;
case base::Value::Type::STRING:
bytes_in_use = storage->GetBytesInUse(input.GetString());
break;
case base::Value::Type::LIST:
bytes_in_use = storage->GetBytesInUse(GetKeysFromList(input));
break;
default:
return BadMessage();
}
return OneArgument(base::Value(static_cast<int>(bytes_in_use)));
}
ExtensionFunction::ResponseValue
StorageStorageAreaGetBytesInUseFunction::RunInSession() {
if (args().empty())
return BadMessage();
const base::Value& input = args()[0];
size_t bytes_in_use = 0;
SessionStorageManager* session_manager =
SessionStorageManager::GetForBrowserContext(browser_context());
switch (input.type()) {
case base::Value::Type::NONE:
bytes_in_use = session_manager->GetTotalBytesInUse(extension_id());
break;
case base::Value::Type::STRING:
bytes_in_use = session_manager->GetBytesInUse(
extension_id(), std::vector<std::string>(1, input.GetString()));
break;
case base::Value::Type::LIST:
bytes_in_use = session_manager->GetBytesInUse(extension_id(),
GetKeysFromList(input));
break;
default:
return BadMessage();
}
// Checked cast should not overflow since `bytes_in_use` is guaranteed to be a
// small number, due to the quota limits we have in place for in-memory
// storage
return OneArgument(base::Value(base::checked_cast<int>(bytes_in_use)));
}
ExtensionFunction::ResponseValue StorageStorageAreaSetFunction::RunWithStorage(
ValueStore* storage) {
TRACE_EVENT1("browser", "StorageStorageAreaSetFunction::RunWithStorage",
"extension_id", extension_id());
if (args().empty() || !args()[0].is_dict())
return BadMessage();
const base::DictionaryValue& input =
base::Value::AsDictionaryValue(args()[0]);
return UseWriteResult(storage->Set(ValueStore::DEFAULTS, input));
}
ExtensionFunction::ResponseValue StorageStorageAreaSetFunction::RunInSession() {
// Retrieve and delete input from `args_` since they will be moved to storage.
if (args().empty() || !args()[0].is_dict())
return BadMessage();
base::Value input = std::move(mutable_args()[0]);
mutable_args().erase(args().begin());
std::map<std::string, base::Value> values;
for (auto item : input.DictItems()) {
values.emplace(std::move(item.first), std::move(item.second));
}
std::vector<SessionStorageManager::ValueChange> changes;
bool result = SessionStorageManager::GetForBrowserContext(browser_context())
->Set(extension_id(), std::move(values), changes);
if (!result) {
// TODO(crbug.com/1185226): Add API test that triggers this behavior.
return Error(
"Session storage quota bytes exceeded. Values were not stored.");
}
OnSessionSettingsChanged(std::move(changes));
return NoArguments();
}
void StorageStorageAreaSetFunction::GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const {
GetModificationQuotaLimitHeuristics(heuristics);
}
ExtensionFunction::ResponseValue
StorageStorageAreaRemoveFunction::RunWithStorage(ValueStore* storage) {
TRACE_EVENT1("browser", "StorageStorageAreaRemoveFunction::RunWithStorage",
"extension_id", extension_id());
if (args().empty())
return BadMessage();
const base::Value& input = args()[0];
switch (input.type()) {
case base::Value::Type::STRING:
return UseWriteResult(storage->Remove(input.GetString()));
case base::Value::Type::LIST:
return UseWriteResult(storage->Remove(GetKeysFromList(input)));
default:
return BadMessage();
}
}
ExtensionFunction::ResponseValue
StorageStorageAreaRemoveFunction::RunInSession() {
if (args().empty())
return BadMessage();
const base::Value& input = args()[0];
SessionStorageManager* session_manager =
SessionStorageManager::GetForBrowserContext(browser_context());
std::vector<SessionStorageManager::ValueChange> changes;
switch (input.type()) {
case base::Value::Type::STRING:
session_manager->Remove(extension_id(), input.GetString(), changes);
break;
case base::Value::Type::LIST:
session_manager->Remove(extension_id(), GetKeysFromList(input), changes);
break;
default:
return BadMessage();
}
OnSessionSettingsChanged(std::move(changes));
return NoArguments();
}
void StorageStorageAreaRemoveFunction::GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const {
GetModificationQuotaLimitHeuristics(heuristics);
}
ExtensionFunction::ResponseValue
StorageStorageAreaClearFunction::RunWithStorage(ValueStore* storage) {
TRACE_EVENT1("browser", "StorageStorageAreaClearFunction::RunWithStorage",
"extension_id", extension_id());
return UseWriteResult(storage->Clear());
}
ExtensionFunction::ResponseValue
StorageStorageAreaClearFunction::RunInSession() {
std::vector<SessionStorageManager::ValueChange> changes;
SessionStorageManager::GetForBrowserContext(browser_context())
->Clear(extension_id(), changes);
OnSessionSettingsChanged(std::move(changes));
return NoArguments();
}
void StorageStorageAreaClearFunction::GetQuotaLimitHeuristics(
QuotaLimitHeuristics* heuristics) const {
GetModificationQuotaLimitHeuristics(heuristics);
}
ExtensionFunction::ResponseValue
StorageStorageAreaSetAccessLevelFunction::RunWithStorage(ValueStore* storage) {
// Not supported. Should return error.
return Error("This StorageArea is not available for setting access level");
}
ExtensionFunction::ResponseValue
StorageStorageAreaSetAccessLevelFunction::RunInSession() {
if (source_context_type() != Feature::BLESSED_EXTENSION_CONTEXT)
return Error("Context cannot set the storage access level");
std::unique_ptr<api::storage::StorageArea::SetAccessLevel::Params> params(
api::storage::StorageArea::SetAccessLevel::Params::Create(args()));
if (!params)
return BadMessage();
// The parsing code ensures `access_level` is sane.
DCHECK(params->access_options.access_level ==
api::storage::ACCESS_LEVEL_TRUSTED_CONTEXTS ||
params->access_options.access_level ==
api::storage::ACCESS_LEVEL_TRUSTED_AND_UNTRUSTED_CONTEXTS);
ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context());
prefs->SetIntegerPref(extension_id(), kPrefSessionStorageAccessLevel,
params->access_options.access_level);
return NoArguments();
}
} // namespace extensions