blob: 11da418a7eb91d40a36c72d6eee1dc1656748626 [file] [log] [blame]
// Copyright 2023 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/content_settings/one_time_permission_provider.h"
#include <memory>
#include <set>
#include "base/power_monitor/power_monitor.h"
#include "base/ranges/algorithm.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/permissions/one_time_permissions_tracker.h"
#include "chrome/browser/permissions/one_time_permissions_tracker_factory.h"
#include "chrome/browser/permissions/one_time_permissions_tracker_observer.h"
#include "components/content_settings/core/browser/content_settings_registry.h"
#include "components/content_settings/core/browser/content_settings_rule.h"
#include "components/content_settings/core/browser/user_modifiable_provider.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_constraints.h"
#include "components/content_settings/core/common/content_settings_metadata.h"
#include "components/content_settings/core/common/content_settings_partition_key.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/content_settings/core/common/features.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
#include "url/gurl.h"
OneTimePermissionProvider::OneTimePermissionProvider(
OneTimePermissionsTracker* one_time_permissions_tracker)
: one_time_permissions_tracker_(one_time_permissions_tracker),
clock_(base::DefaultClock::GetInstance()) {
one_time_permissions_tracker_->AddObserver(this);
// The PowerMonitor is initialized in content_main_runner_impl.cc before the
// main function for the browser process is run (which initializes the HCSM).
// For this reason, the PowerMonitor is always initialized before the observer
// is added here.
base::PowerMonitor::AddPowerSuspendObserver(this);
}
OneTimePermissionProvider::~OneTimePermissionProvider() {
base::PowerMonitor::RemovePowerSuspendObserver(this);
}
// TODO(b/307193732): handle the PartitionKey in all relevant methods, including
// when we call NotifyObservers().
std::unique_ptr<content_settings::RuleIterator>
OneTimePermissionProvider::GetRuleIterator(
ContentSettingsType content_type,
bool incognito,
const content_settings::PartitionKey& partition_key) const {
if (!permissions::PermissionUtil::CanPermissionBeAllowedOnce(content_type)) {
return nullptr;
}
return value_map_.GetRuleIterator(content_type);
}
std::unique_ptr<content_settings::Rule> OneTimePermissionProvider::GetRule(
const GURL& primary_url,
const GURL& secondary_url,
ContentSettingsType content_type,
bool off_the_record,
const content_settings::PartitionKey& partition_key) const {
if (!permissions::PermissionUtil::CanPermissionBeAllowedOnce(content_type)) {
return nullptr;
}
base::AutoLock auto_lock(value_map_.GetLock());
return value_map_.GetRule(primary_url, secondary_url, content_type);
}
bool OneTimePermissionProvider::SetWebsiteSetting(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_settings_type,
base::Value&& value,
const content_settings::ContentSettingConstraints& constraints,
const content_settings::PartitionKey& partition_key) {
if (!permissions::PermissionUtil::CanPermissionBeAllowedOnce(
content_settings_type)) {
return false;
}
if (!content_settings::ContentSettingsRegistry::GetInstance()->Get(
content_settings_type)) {
// Object permissions cannot be mapped to a ContentSetting and thus cannot
// be handled by this provider.
return false;
}
auto content_setting = content_settings::ValueToContentSetting(value);
// For transitions to block and resetting to default, clear the grant and let
// the pref provider handle the permission as usual.
if (content_setting == CONTENT_SETTING_DEFAULT ||
content_setting == CONTENT_SETTING_BLOCK) {
base::AutoLock lock(value_map_.GetLock());
auto* previous_one_time_grant = value_map_.GetValue(
primary_pattern.ToRepresentativeUrl(),
secondary_pattern.ToRepresentativeUrl(), content_settings_type);
if (!previous_one_time_grant) {
// If there was no grant, it means that this content setting is
// already being handled by the pref provider.
return false;
}
value_map_.DeleteValue(primary_pattern, secondary_pattern,
content_settings_type);
permissions::PermissionUmaUtil::RecordOneTimePermissionEvent(
content_settings_type,
permissions::OneTimePermissionEvent::REVOKED_MANUALLY);
return false;
}
if (constraints.session_model() != content_settings::SessionModel::OneTime) {
if (content_setting == CONTENT_SETTING_ALLOW) {
// Transition from Allow once to Allow. Delete setting and let the pref
// provider handle it.
base::AutoLock lock(value_map_.GetLock());
value_map_.DeleteValue(primary_pattern, secondary_pattern,
content_settings_type);
}
return false;
}
DCHECK_EQ(content_settings::ValueToContentSetting(value),
CONTENT_SETTING_ALLOW);
base::Time now = clock_->Now();
content_settings::RuleMetaData metadata;
metadata.set_session_model(content_settings::SessionModel::OneTime);
metadata.set_last_modified(now);
if (base::FeatureList::IsEnabled(
content_settings::features::kActiveContentSettingExpiry)) {
metadata.SetExpirationAndLifetime(now + constraints.lifetime(),
constraints.lifetime());
}
permissions::PermissionUmaUtil::RecordOneTimePermissionEvent(
content_settings_type,
permissions::OneTimePermissionEvent::GRANTED_ONE_TIME);
base::AutoLock lock(value_map_.GetLock());
value_map_.SetValue(primary_pattern, secondary_pattern, content_settings_type,
std::move(value), metadata);
// We need to handle transitions from Allow to Allow Once gracefully.
// In that case we add the Allow Once setting in this provider, but also
// have to clear the Allow setting in the pref provider. By returning false
// here, we let the control flow trickle down to the pref provider.
return false;
}
bool OneTimePermissionProvider::UpdateLastUsedTime(
const GURL& primary_url,
const GURL& secondary_url,
ContentSettingsType content_type,
const base::Time time,
const content_settings::PartitionKey& partition_key) {
// Last used time is not tracked for one-time permissions.
return false;
}
bool OneTimePermissionProvider::ResetLastVisitTime(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
const content_settings::PartitionKey& partition_key) {
// LastVisit time is not currently tracked for one-time permissions.
return false;
}
bool OneTimePermissionProvider::UpdateLastVisitTime(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
const content_settings::PartitionKey& partition_key) {
// LastVisit time is not tracked for one-time permissions.
return false;
}
std::optional<base::TimeDelta> OneTimePermissionProvider::RenewContentSetting(
const GURL& primary_url,
const GURL& secondary_url,
ContentSettingsType type,
std::optional<ContentSetting> setting_to_match,
const content_settings::PartitionKey& partition_key) {
// Setting renewal is not supported for one-time permissions.
return std::nullopt;
}
void OneTimePermissionProvider::ClearAllContentSettingsRules(
ContentSettingsType content_type,
const content_settings::PartitionKey& partition_key) {
if (permissions::PermissionUtil::CanPermissionBeAllowedOnce(content_type)) {
return;
}
base::AutoLock lock(value_map_.GetLock());
value_map_.DeleteValues(content_type);
}
void OneTimePermissionProvider::ShutdownOnUIThread() {
RemoveAllObservers();
}
void OneTimePermissionProvider::SetClockForTesting(base::Clock* clock) {
clock_ = clock;
}
void OneTimePermissionProvider::ExpireWebsiteSetting(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_settings_type,
const content_settings::PartitionKey& partition_key) {
// Custom scope because NotifyObservers also requires value_map_'s exclusive
// lock.
{
base::AutoLock lock(value_map_.GetLock());
if (value_map_.GetValue(primary_pattern.ToRepresentativeUrl(),
secondary_pattern.ToRepresentativeUrl(),
content_settings_type) == nullptr) {
return;
}
value_map_.DeleteValue(primary_pattern, secondary_pattern,
content_settings_type);
}
permissions::PermissionUmaUtil::RecordOneTimePermissionEvent(
content_settings_type,
permissions::OneTimePermissionEvent::EXPIRED_AFTER_MAXIMUM_LIFETIME);
NotifyObservers(primary_pattern, secondary_pattern, content_settings_type,
/*partition_key=*/nullptr);
}
void OneTimePermissionProvider::OnSuspend() {
std::vector<ContentSettingEntry> patterns_to_delete;
auto* registry = content_settings::ContentSettingsRegistry::GetInstance();
for (const auto* info : *registry) {
auto setting_type = info->website_settings_info()->type();
if (permissions::PermissionUtil::CanPermissionBeAllowedOnce(setting_type)) {
std::unique_ptr<content_settings::RuleIterator> rule_iterator(
value_map_.GetRuleIterator(setting_type));
while (rule_iterator && rule_iterator->HasNext()) {
auto rule = rule_iterator->Next();
patterns_to_delete.emplace_back(setting_type, rule->primary_pattern,
rule->secondary_pattern);
permissions::PermissionUmaUtil::RecordOneTimePermissionEvent(
setting_type,
permissions::OneTimePermissionEvent::EXPIRED_ON_SUSPEND);
}
}
}
DeleteEntriesAndNotify(patterns_to_delete);
}
// All tabs with the given origin have either been closed or navigated away
// from. We remove all permissions associated with the origin.
void OneTimePermissionProvider::OnLastPageFromOriginClosed(
const url::Origin& origin) {
for (auto setting_type : {ContentSettingsType::GEOLOCATION,
ContentSettingsType::MEDIASTREAM_CAMERA,
ContentSettingsType::MEDIASTREAM_MIC}) {
DeleteEntriesMatchingGURL(
setting_type, origin.GetURL(),
permissions::OneTimePermissionEvent::ALL_TABS_CLOSED_OR_DISCARDED);
}
}
// All tabs with the given origin have either been in the background for a
// certain time or not used for a certain time. This situation currently only
// expires geolocation. We remove the geolocation permission associated with
// the origin.
void OneTimePermissionProvider::OnAllTabsInBackgroundTimerExpired(
const url::Origin& origin,
const OneTimePermissionsTrackerObserver::BackgroundExpiryType&
expiry_type) {
switch (expiry_type) {
case BackgroundExpiryType::kTimeout:
DeleteEntriesMatchingGURL(
ContentSettingsType::GEOLOCATION, origin.GetURL(),
permissions::OneTimePermissionEvent::EXPIRED_IN_BACKGROUND);
return;
case BackgroundExpiryType::kLongTimeout:
return;
}
}
// All tabs to the origin have not shown a tab indicator for video for a
// certain time and have been in the background. We remove the camera
// permission associated with the origin.
void OneTimePermissionProvider::OnCapturingVideoExpired(
const url::Origin& origin) {
DeleteEntriesMatchingGURL(
ContentSettingsType::MEDIASTREAM_CAMERA, origin.GetURL(),
permissions::OneTimePermissionEvent::EXPIRED_IN_BACKGROUND);
}
// All tabs to the origin have not shown a tab indicator for microphone access
// for a certain time and have been in the background. We remove the
// microphone permission associated with the origin.
void OneTimePermissionProvider::OnCapturingAudioExpired(
const url::Origin& origin) {
DeleteEntriesMatchingGURL(
ContentSettingsType::MEDIASTREAM_MIC, origin.GetURL(),
permissions::OneTimePermissionEvent::EXPIRED_IN_BACKGROUND);
}
void OneTimePermissionProvider::DeleteEntriesAndNotify(
const std::vector<OneTimePermissionProvider::ContentSettingEntry>&
entries_to_delete) {
{
base::AutoLock lock(value_map_.GetLock());
for (const auto& pattern : entries_to_delete) {
value_map_.DeleteValue(pattern.primary_pattern, pattern.secondary_pattern,
pattern.type);
// In all the observable `OneTimePermissionsTrackerObserver` events
// which are emitted from the tracker, the tracker knows for which
// origins and content settings it can halt bookkeeping. However, the
// tracker isn't aware of externally deleted content settings. To
// prevent it from triggering observers for an already deleted content
// setting, we need to inform it about the deletion here (and only
// here).
one_time_permissions_tracker_->CleanupStateForExpiredContentSetting(
pattern.type, pattern.primary_pattern, pattern.secondary_pattern);
}
}
for (const auto& pattern : entries_to_delete) {
NotifyObservers(pattern.primary_pattern, pattern.secondary_pattern,
pattern.type, /*partition_key=*/nullptr);
}
}
void OneTimePermissionProvider::DeleteEntriesMatchingGURL(
ContentSettingsType content_setting_type,
const GURL& origin_gurl,
permissions::OneTimePermissionEvent trigger_event) {
std::vector<ContentSettingEntry> patterns_to_delete;
std::unique_ptr<content_settings::RuleIterator> rule_iterator(
value_map_.GetRuleIterator(content_setting_type));
while (rule_iterator && rule_iterator->HasNext()) {
auto rule = rule_iterator->Next();
if (rule->primary_pattern.Matches(origin_gurl) &&
rule->secondary_pattern.Matches(origin_gurl)) {
patterns_to_delete.emplace_back(
content_setting_type, rule->primary_pattern, rule->secondary_pattern);
permissions::PermissionUmaUtil::RecordOneTimePermissionEvent(
content_setting_type, trigger_event);
}
}
rule_iterator.reset();
DeleteEntriesAndNotify(patterns_to_delete);
}
void OneTimePermissionProvider::OnShutdown() {
if (one_time_permissions_tracker_) {
one_time_permissions_tracker_->RemoveObserver(this);
one_time_permissions_tracker_ = nullptr;
}
}