blob: 2a23cce62b3f8effdb6e6dcbcbcd45989381f6ab [file] [log] [blame] [edit]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/permissions/object_permission_context_base.h"
#include <string_view>
#include <utility>
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/observer_list.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/values.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/permissions/features.h"
#include "url/origin.h"
namespace permissions {
const char kObjectListKey[] = "chosen-objects";
ObjectPermissionContextBase::ObjectPermissionContextBase(
ContentSettingsType guard_content_settings_type,
ContentSettingsType data_content_settings_type,
HostContentSettingsMap* host_content_settings_map)
: guard_content_settings_type_(guard_content_settings_type),
data_content_settings_type_(data_content_settings_type),
host_content_settings_map_(host_content_settings_map) {
DCHECK(host_content_settings_map_);
}
ObjectPermissionContextBase::ObjectPermissionContextBase(
ContentSettingsType data_content_settings_type,
HostContentSettingsMap* host_content_settings_map)
: guard_content_settings_type_(std::nullopt),
data_content_settings_type_(data_content_settings_type),
host_content_settings_map_(host_content_settings_map) {
DCHECK(host_content_settings_map_);
}
ObjectPermissionContextBase::~ObjectPermissionContextBase() {
FlushScheduledSaveSettingsCalls();
}
ObjectPermissionContextBase::Object::Object(
const url::Origin& origin,
base::Value::Dict value,
content_settings::SettingSource source,
bool incognito)
: origin(origin.GetURL()),
value(std::move(value)),
source(source),
incognito(incognito) {}
ObjectPermissionContextBase::Object::Object(
const url::Origin& origin,
base::Value value,
content_settings::SettingSource source,
bool incognito)
: Object(origin, std::move(value.GetDict()), source, incognito) {}
ObjectPermissionContextBase::Object::~Object() = default;
std::unique_ptr<ObjectPermissionContextBase::Object>
ObjectPermissionContextBase::Object::Clone() {
return std::make_unique<Object>(url::Origin::Create(origin), value.Clone(),
source, incognito);
}
void ObjectPermissionContextBase::PermissionObserver::OnObjectPermissionChanged(
std::optional<ContentSettingsType> guard_content_settings_type,
ContentSettingsType data_content_settings_type) {}
void ObjectPermissionContextBase::PermissionObserver::OnPermissionRevoked(
const url::Origin& origin) {}
void ObjectPermissionContextBase::AddObserver(PermissionObserver* observer) {
permission_observer_list_.AddObserver(observer);
}
void ObjectPermissionContextBase::RemoveObserver(PermissionObserver* observer) {
permission_observer_list_.RemoveObserver(observer);
}
bool ObjectPermissionContextBase::CanRequestObjectPermission(
const url::Origin& origin) const {
if (!guard_content_settings_type_)
return true;
ContentSetting content_setting =
host_content_settings_map_->GetContentSetting(
origin.GetURL(), GURL(), *guard_content_settings_type_);
return content_setting == CONTENT_SETTING_ASK;
}
std::unique_ptr<ObjectPermissionContextBase::Object>
ObjectPermissionContextBase::GetGrantedObject(const url::Origin& origin,
std::string_view key) {
if (!CanRequestObjectPermission(origin))
return nullptr;
const auto& origin_objects_it = objects().find(origin);
if (origin_objects_it == objects().end())
return nullptr;
const auto& object_it = origin_objects_it->second.find(std::string(key));
if (object_it == origin_objects_it->second.end())
return nullptr;
return object_it->second->Clone();
}
std::vector<std::unique_ptr<ObjectPermissionContextBase::Object>>
ObjectPermissionContextBase::GetGrantedObjects(const url::Origin& origin) {
if (!CanRequestObjectPermission(origin))
return {};
const auto& origin_objects_it = objects().find(origin);
if (origin_objects_it == objects().end())
return {};
std::vector<std::unique_ptr<Object>> results;
for (const auto& object : origin_objects_it->second)
results.push_back(object.second->Clone());
return results;
}
std::set<url::Origin> ObjectPermissionContextBase::GetOriginsWithGrants() {
std::set<url::Origin> origins_with_grants;
for (const auto& objects_entry : objects()) {
url::Origin objects_entry_url = objects_entry.first;
if (!CanRequestObjectPermission(objects_entry_url)) {
continue;
}
origins_with_grants.insert(objects_entry_url);
}
return origins_with_grants;
}
std::vector<std::unique_ptr<ObjectPermissionContextBase::Object>>
ObjectPermissionContextBase::GetAllGrantedObjects() {
std::vector<std::unique_ptr<Object>> results;
for (const auto& objects_entry : objects()) {
if (!CanRequestObjectPermission(objects_entry.first)) {
continue;
}
for (const auto& object : objects_entry.second) {
results.push_back(object.second->Clone());
}
}
return results;
}
void ObjectPermissionContextBase::GrantObjectPermission(
const url::Origin& origin,
base::Value::Dict object) {
DCHECK(IsValidObject(object));
const std::string key = GetKeyForObject(object);
objects()[origin][key] = std::make_unique<Object>(
origin, std::move(object), content_settings::SettingSource::kUser,
host_content_settings_map_->IsOffTheRecord());
ScheduleSaveWebsiteSetting(origin);
NotifyPermissionChanged();
}
void ObjectPermissionContextBase::UpdateObjectPermission(
const url::Origin& origin,
const base::Value::Dict& old_object,
base::Value::Dict new_object) {
auto origin_objects_it = objects().find(origin);
if (origin_objects_it == objects().end()) {
return;
}
std::string key = GetKeyForObject(old_object);
auto object_it = origin_objects_it->second.find(key);
if (object_it == origin_objects_it->second.end()) {
return;
}
origin_objects_it->second.erase(object_it);
key = GetKeyForObject(new_object);
DCHECK(!base::Contains(origin_objects_it->second, key));
GrantObjectPermission(origin, std::move(new_object));
}
void ObjectPermissionContextBase::RevokeObjectPermission(
const url::Origin& origin,
const base::Value::Dict& object) {
DCHECK(IsValidObject(object));
RevokeObjectPermission(origin, GetKeyForObject(object));
}
void ObjectPermissionContextBase::RevokeObjectPermission(
const url::Origin& origin,
std::string_view key) {
auto origin_objects_it = objects().find(origin);
if (origin_objects_it == objects().end()) {
return;
}
auto object_it = origin_objects_it->second.find(std::string(key));
if (object_it == origin_objects_it->second.end()) {
return;
}
origin_objects_it->second.erase(object_it);
if (!origin_objects_it->second.size()) {
objects().erase(origin_objects_it);
}
ScheduleSaveWebsiteSetting(origin);
NotifyPermissionRevoked(origin);
}
bool ObjectPermissionContextBase::RevokeObjectPermissions(
const url::Origin& origin) {
auto origin_objects_it = objects().find(origin);
if (origin_objects_it == objects().end()) {
return false;
}
origin_objects_it->second.clear();
ScheduleSaveWebsiteSetting(origin);
NotifyPermissionRevoked(origin);
return true;
}
void ObjectPermissionContextBase::FlushScheduledSaveSettingsCalls() {
// Persist any pending object updates that did not have the chance to be
// persisted yet.
while (!origins_with_scheduled_save_settings_calls_.empty()) {
const url::Origin origin =
*(origins_with_scheduled_save_settings_calls_.begin());
SaveWebsiteSetting(origin);
}
}
bool ObjectPermissionContextBase::IsOffTheRecord() {
return host_content_settings_map_->IsOffTheRecord();
}
void ObjectPermissionContextBase::NotifyPermissionChanged() {
for (auto& observer : permission_observer_list_) {
observer.OnObjectPermissionChanged(guard_content_settings_type_,
data_content_settings_type_);
}
}
void ObjectPermissionContextBase::NotifyPermissionRevoked(
const url::Origin& origin) {
for (auto& observer : permission_observer_list_) {
observer.OnObjectPermissionChanged(guard_content_settings_type_,
data_content_settings_type_);
observer.OnPermissionRevoked(origin);
}
}
base::Value::Dict ObjectPermissionContextBase::GetWebsiteSetting(
const url::Origin& origin,
content_settings::SettingInfo* info) {
base::Value value = host_content_settings_map_->GetWebsiteSetting(
origin.GetURL(), GURL(), data_content_settings_type_, info);
if (!value.is_dict()) {
return base::Value::Dict();
}
return std::move(value.GetDict());
}
void ObjectPermissionContextBase::SaveWebsiteSetting(
const url::Origin& origin) {
auto scheduled_save_it =
origins_with_scheduled_save_settings_calls_.find(origin);
if (scheduled_save_it == origins_with_scheduled_save_settings_calls_.end()) {
// Another scheduled `SaveWebsiteSetting` call has handled this origin
// already.
return;
}
origins_with_scheduled_save_settings_calls_.erase(scheduled_save_it);
auto origin_objects_it = objects().find(origin);
if (origin_objects_it == objects().end()) {
host_content_settings_map_->SetWebsiteSettingDefaultScope(
origin.GetURL(), GURL(), data_content_settings_type_, base::Value());
return;
}
base::Value::List objects_list;
for (const auto& object : origin_objects_it->second) {
objects_list.Append(object.second->value.Clone());
}
base::Value::Dict website_setting_value;
website_setting_value.Set(kObjectListKey, std::move(objects_list));
content_settings::ContentSettingConstraints constraints;
#if !BUILDFLAG(IS_ANDROID)
if (base::FeatureList::IsEnabled(
features::kRecordChooserPermissionLastVisitedTimestamps)) {
if (content_settings::CanTrackLastVisit(data_content_settings_type_)) {
constraints.set_track_last_visit_for_autoexpiration(true);
}
}
#endif // !BUILDFLAG(IS_ANDROID)
host_content_settings_map_->SetWebsiteSettingDefaultScope(
origin.GetURL(), GURL(), data_content_settings_type_,
base::Value(std::move(website_setting_value)), constraints);
}
void ObjectPermissionContextBase::ScheduleSaveWebsiteSetting(
const url::Origin& origin) {
bool success =
origins_with_scheduled_save_settings_calls_.insert(origin).second;
if (!success) {
// There is already a scheduled `SaveWebsiteSetting` call, no need to
// schedule another.
return;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ObjectPermissionContextBase::SaveWebsiteSetting,
weak_factory_.GetWeakPtr(), origin));
}
std::vector<std::unique_ptr<ObjectPermissionContextBase::Object>>
ObjectPermissionContextBase::GetWebsiteSettingObjects() {
std::vector<std::unique_ptr<Object>> results;
for (const ContentSettingPatternSource& content_setting :
host_content_settings_map_->GetSettingsForOneType(
data_content_settings_type_)) {
// Old settings used the (requesting,embedding) pair whereas the new
// settings simply use (embedding, *). The migration logic in
// HostContentSettingsMap::MigrateSettingsPrecedingPermissionDelegationActivation
// ensures that there is no way for leftover old settings to make us pick
// the wrong pattern here.
GURL origin_url(content_setting.primary_pattern.ToString());
if (!origin_url.is_valid()) {
continue;
}
const auto origin = url::Origin::Create(origin_url);
if (!CanRequestObjectPermission(origin)) {
continue;
}
content_settings::SettingInfo info;
base::Value::Dict setting = GetWebsiteSetting(origin, &info);
base::Value::List* objects = setting.FindList(kObjectListKey);
if (!objects) {
continue;
}
for (auto& object : *objects) {
if (!IsValidObject(object.GetDict())) {
continue;
}
results.push_back(std::make_unique<Object>(
origin, std::move(object), info.source, content_setting.incognito));
}
}
return results;
}
void ObjectPermissionContextBase::LoadWebsiteSettingsIntoObjects() {
auto loaded_objects = GetWebsiteSettingObjects();
for (auto& object : loaded_objects) {
objects_[url::Origin::Create(object->origin)].emplace(
GetKeyForObject(object->value), std::move(object));
}
}
ObjectPermissionContextBase::ObjectMap& ObjectPermissionContextBase::objects() {
if (!objects_initialized_) {
LoadWebsiteSettingsIntoObjects();
objects_initialized_ = true;
}
return objects_;
}
} // namespace permissions