blob: 01da62b5b08614d3cd36e5c3c1029d680605aa63 [file] [log] [blame]
// Copyright 2024 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/smart_card/smart_card_permission_context.h"
#include <algorithm>
#include <iterator>
#include <vector>
#include "base/check.h"
#include "base/check_deref.h"
#include "base/check_op.h"
#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_observer.h"
#include "base/scoped_observation.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/values.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.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 "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/smart_card/smart_card_histograms.h"
#include "chrome/browser/smart_card/smart_card_reader_tracker.h"
#include "chrome/browser/smart_card/smart_card_reader_tracker_factory.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/permissions/permission_context_base.h"
#include "components/permissions/permission_decision_auto_blocker.h"
#include "components/permissions/permission_request_manager.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/features.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
constexpr char kReaderNameKey[] = "reader-name";
template <typename StringType>
static base::Value::Dict ReaderNameToValue(const StringType& reader_name) {
base::Value::Dict value;
value.Set(kReaderNameKey, reader_name);
return value;
}
} // anonymous namespace
class SmartCardPermissionContext::OneTimeObserver
: public OneTimePermissionsTrackerObserver {
public:
explicit OneTimeObserver(SmartCardPermissionContext& permission_context)
: permission_context_(permission_context) {
observation_.Observe(OneTimePermissionsTrackerFactory::GetForBrowserContext(
&permission_context.profile_.get()));
}
void OnLastPageFromOriginClosed(const url::Origin& origin) override {
permission_context_->RevokeEphemeralPermissionsForOrigin(origin);
RecordSmartCardOneTimePermissionExpiryReason(
SmartCardOneTimePermissionExpiryReason::
kSmartCardPermissionExpiredLastWindowClosed);
}
void OnAllTabsInBackgroundTimerExpired(
const url::Origin& origin,
const BackgroundExpiryType& expiry_type) override {
if (expiry_type == BackgroundExpiryType::kTimeout) {
permission_context_->RevokeEphemeralPermissionsForOrigin(origin);
// Record histogram even if no permissions were revoked - for the purpose
// of a dry run.
RecordSmartCardOneTimePermissionExpiryReason(
SmartCardOneTimePermissionExpiryReason::
kSmartCardPermissionExpiredAllWindowsInTheBackgroundTimeout);
}
}
private:
base::ScopedObservation<OneTimePermissionsTracker,
OneTimePermissionsTrackerObserver>
observation_{this};
base::raw_ref<SmartCardPermissionContext> permission_context_;
};
class SmartCardPermissionContext::PowerSuspendObserver
: public base::PowerSuspendObserver {
public:
explicit PowerSuspendObserver(SmartCardPermissionContext& permission_context)
: permission_context_(permission_context) {
base::PowerMonitor::GetInstance()->AddPowerSuspendObserver(this);
}
~PowerSuspendObserver() override {
base::PowerMonitor::GetInstance()->RemovePowerSuspendObserver(this);
}
void OnSuspend() override {
permission_context_->RevokeEphemeralPermissions();
RecordSmartCardOneTimePermissionExpiryReason(
SmartCardOneTimePermissionExpiryReason::
kSmartCardPermissionExpiredSystemSuspended);
}
private:
base::raw_ref<SmartCardPermissionContext> permission_context_;
};
class SmartCardPermissionContext::ReaderObserver
: public SmartCardReaderTracker::Observer {
public:
explicit ReaderObserver(SmartCardPermissionContext& permission_context)
: permission_context_(permission_context) {}
void Reset(std::vector<SmartCardReaderTracker::ReaderInfo> info_list) {
known_info_map_.clear();
for (SmartCardReaderTracker::ReaderInfo& info : info_list) {
std::string name = info.name;
known_info_map_.emplace(std::move(name), std::move(info));
}
}
void OnReaderRemoved(const std::string& reader_name) override {
known_info_map_.erase(reader_name);
permission_context_->RevokeEphemeralPermissionsForReader(reader_name);
RecordSmartCardOneTimePermissionExpiryReason(
SmartCardOneTimePermissionExpiryReason::
kSmartCardPermissionExpiredReaderRemoved);
}
void OnReaderChanged(
const SmartCardReaderTracker::ReaderInfo& reader_info) override {
// Compare the new reader state with its previous one to find out whether
// its card has been removed. If so, notify the PermissionContext.
auto it = known_info_map_.find(reader_info.name);
if (it == known_info_map_.end()) {
known_info_map_.emplace(reader_info.name, reader_info);
return;
}
SmartCardReaderTracker::ReaderInfo& known_info = it->second;
// Either the card was removed or there was both a removal and an insertion
// in the meantime (meaning that reader_info.present could still be true).
// Note that event_count is not supported in all platforms.
const bool card_removed =
known_info.present &&
(!reader_info.present ||
(known_info.event_count < reader_info.event_count));
// Update known_info.
known_info = reader_info;
if (card_removed) {
permission_context_->RevokeEphemeralPermissionsForReader(
reader_info.name);
RecordSmartCardOneTimePermissionExpiryReason(
SmartCardOneTimePermissionExpiryReason::
kSmartCardPermissionExpiredCardRemoved);
}
}
std::map<std::string, SmartCardReaderTracker::ReaderInfo> known_info_map_;
base::raw_ref<SmartCardPermissionContext> permission_context_;
};
SmartCardPermissionContext::SmartCardPermissionContext(Profile* profile)
: ObjectPermissionContextBase(
ContentSettingsType::SMART_CARD_GUARD,
ContentSettingsType::SMART_CARD_DATA,
HostContentSettingsMapFactory::GetForProfile(profile)),
reader_observer_(std::make_unique<ReaderObserver>(*this)),
profile_(*profile),
weak_ptr_factory_(this) {
permission_observation_.Observe(this);
}
SmartCardPermissionContext::~SmartCardPermissionContext() = default;
std::string SmartCardPermissionContext::GetKeyForObject(
const base::Value::Dict& object) {
if (!IsValidObject(object)) {
return std::string();
}
return *object.FindString(kReaderNameKey);
}
bool SmartCardPermissionContext::HasReaderPermission(
content::RenderFrameHost& render_frame_host,
const std::string& reader_name) {
return HasReaderPermission(
render_frame_host.GetMainFrame()->GetLastCommittedOrigin(), reader_name);
}
bool SmartCardPermissionContext::HasReaderPermission(
const url::Origin& origin,
const std::string& reader_name) {
if (!CanRequestObjectPermission(origin)) {
return IsAllowlistedByPolicy(origin);
}
return ephemeral_grants_with_expiry_[origin].contains(reader_name) ||
HasPersistentReaderPermission(origin, reader_name);
}
bool SmartCardPermissionContext::IsAllowlistedByPolicy(
const url::Origin& origin) const {
if (!guard_content_settings_type_) {
return false;
}
content_settings::SettingInfo setting_info;
auto content_setting =
HostContentSettingsMapFactory::GetForProfile(&profile_.get())
->GetContentSetting(origin.GetURL(), GURL(),
ContentSettingsType::SMART_CARD_GUARD,
&setting_info);
return setting_info.source == content_settings::SettingSource::kPolicy &&
content_setting == CONTENT_SETTING_ALLOW;
}
bool SmartCardPermissionContext::CanRequestObjectPermission(
const url::Origin& origin) const {
CHECK(guard_content_settings_type_);
if (CHECK_DEREF(
PermissionDecisionAutoBlockerFactory::GetForProfile(&profile_.get()))
.IsEmbargoed(origin.GetURL(), *guard_content_settings_type_)) {
return false;
}
return ObjectPermissionContextBase::CanRequestObjectPermission(origin);
}
void SmartCardPermissionContext::RequestReaderPermisssion(
content::RenderFrameHost& render_frame_host,
const std::string& reader_name,
RequestReaderPermissionCallback callback) {
const url::Origin& origin =
render_frame_host.GetMainFrame()->GetLastCommittedOrigin();
auto* web_contents =
content::WebContents::FromRenderFrameHost(&render_frame_host);
permissions::PermissionRequestManager* permission_request_manager =
permissions::PermissionRequestManager::FromWebContents(web_contents);
if (!permission_request_manager) {
LOG(ERROR) << "Cannot request permission: no PermissionRequestManager";
std::move(callback).Run(false);
return;
}
if (!CanRequestObjectPermission(origin)) {
std::move(callback).Run(IsAllowlistedByPolicy(origin));
return;
}
auto permission_request = std::make_unique<SmartCardPermissionRequest>(
origin, reader_name,
base::BindOnce(&SmartCardPermissionContext::OnPermissionRequestDecided,
weak_ptr_factory_.GetWeakPtr(), origin, reader_name,
std::move(callback)));
permission_request_manager->AddRequest(&render_frame_host,
std::move(permission_request));
}
void SmartCardPermissionContext::GrantEphemeralReaderPermission(
const url::Origin& origin,
const std::string& reader_name) {
CHECK(!HasReaderPermission(origin, reader_name));
ephemeral_grants_with_expiry_[origin].emplace(
reader_name,
base::Time::Now() + permissions::kOneTimePermissionMaximumLifetime);
if (!power_suspend_observer_) {
power_suspend_observer_ = std::make_unique<PowerSuspendObserver>(*this);
}
if (!one_time_observer_) {
one_time_observer_ = std::make_unique<OneTimeObserver>(*this);
}
GetReaderTracker().Start(
reader_observer_.get(),
base::BindOnce(&SmartCardPermissionContext::OnTrackingStarted,
weak_ptr_factory_.GetWeakPtr()));
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SmartCardPermissionContext::
RevokeEphemeralPermissionIfLongTimeoutOccured,
weak_ptr_factory_.GetWeakPtr(), origin, reader_name),
permissions::kOneTimePermissionMaximumLifetime);
}
void SmartCardPermissionContext::GrantPersistentReaderPermission(
const url::Origin& origin,
const std::string& reader_name) {
CHECK(!HasReaderPermission(origin, reader_name));
GrantObjectPermission(origin, ReaderNameToValue(reader_name));
}
bool SmartCardPermissionContext::IsValidObject(
const base::Value::Dict& object) {
if (object.size() != 1) {
return false;
}
const std::string* reader_name = object.FindString(kReaderNameKey);
return reader_name && !reader_name->empty();
}
std::u16string SmartCardPermissionContext::GetObjectDisplayName(
const base::Value::Dict& object) {
const std::string* reader_name = object.FindString(kReaderNameKey);
CHECK(reader_name);
return base::UTF8ToUTF16(*reader_name);
}
bool SmartCardPermissionContext::HasPersistentReaderPermission(
const url::Origin& origin,
const std::string& reader_name) {
for (const auto& object :
ObjectPermissionContextBase::GetGrantedObjects(origin)) {
const base::Value::Dict& reader_value = object->value;
// Objects provided by the parent class can be assumed valid.
CHECK(IsValidObject(reader_value));
if (reader_name != *reader_value.FindString(kReaderNameKey)) {
continue;
}
return true;
}
return false;
}
void SmartCardPermissionContext::RevokeEphemeralPermissionsForReader(
const std::string& reader_name) {
for (auto it = ephemeral_grants_with_expiry_.begin();
it != ephemeral_grants_with_expiry_.end();) {
auto& [origin, reader_map] = *it;
if (reader_map.erase(reader_name)) {
NotifyPermissionRevoked(origin);
}
if (reader_map.empty()) {
it = ephemeral_grants_with_expiry_.erase(it);
} else {
++it;
}
}
if (ephemeral_grants_with_expiry_.empty()) {
StopObserving();
}
}
void SmartCardPermissionContext::RevokeEphemeralPermissionsForOrigin(
const url::Origin& origin) {
ephemeral_grants_with_expiry_.erase(origin);
if (ephemeral_grants_with_expiry_.empty()) {
StopObserving();
}
NotifyPermissionRevoked(origin);
}
void SmartCardPermissionContext::RevokeEphemeralPermissions() {
if (ephemeral_grants_with_expiry_.empty()) {
return;
}
std::set<url::Origin> revoked_origins;
std::ranges::transform(ephemeral_grants_with_expiry_,
std::inserter(revoked_origins, revoked_origins.end()),
[](const auto& pair) { return pair.first; });
ephemeral_grants_with_expiry_.clear();
StopObserving();
for (const auto& origin : revoked_origins) {
NotifyPermissionRevoked(origin);
}
}
void SmartCardPermissionContext::RevokeAllPermissions() {
for (auto& origin : GetOriginsWithGrants()) {
RevokeObjectPermissions(origin);
}
RevokeEphemeralPermissions();
}
void SmartCardPermissionContext::OnTrackingStarted(
std::optional<std::vector<SmartCardReaderTracker::ReaderInfo>> info_list) {
if (!info_list) {
// PC/SC failed on us.
LOG(ERROR) << "Failed to start reader tracking.";
return;
}
reader_observer_->Reset(std::move(*info_list));
}
void SmartCardPermissionContext::StopObserving() {
GetReaderTracker().Stop(reader_observer_.get());
one_time_observer_.reset();
power_suspend_observer_.reset();
}
SmartCardReaderTracker& SmartCardPermissionContext::GetReaderTracker() const {
return SmartCardReaderTrackerFactory::GetForProfile(*profile_);
}
void SmartCardPermissionContext::OnPermissionRequestDecided(
const url::Origin& origin,
const std::string& reader_name,
RequestReaderPermissionCallback callback,
SmartCardPermissionRequest::Result result) {
switch (result) {
case SmartCardPermissionRequest::Result::kAllowOnce:
GrantEphemeralReaderPermission(origin, reader_name);
std::move(callback).Run(true);
break;
case SmartCardPermissionRequest::Result::kAllowAlways:
GrantPersistentReaderPermission(origin, reader_name);
std::move(callback).Run(true);
break;
case SmartCardPermissionRequest::Result::kDontAllow:
std::move(callback).Run(false);
break;
}
}
std::vector<std::unique_ptr<SmartCardPermissionContext::Object>>
SmartCardPermissionContext::GetGrantedObjects(const url::Origin& origin) {
std::vector<std::unique_ptr<Object>> objects =
ObjectPermissionContextBase::GetGrantedObjects(origin);
if (IsAllowlistedByPolicy(origin)) {
objects.push_back(std::make_unique<Object>(
origin,
ReaderNameToValue(l10n_util::GetStringUTF16(
IDS_SMART_CARD_POLICY_DESCRIPTION_FOR_ANY_DEVICE)),
content_settings::SettingSource::kPolicy, IsOffTheRecord()));
}
return objects;
}
void SmartCardPermissionContext::OnPermissionRevoked(
const url::Origin& origin) {
permission_observers_.Notify(
&content::SmartCardDelegate::PermissionObserver::OnPermissionRevoked,
origin);
}
void SmartCardPermissionContext::AddObserver(
content::SmartCardDelegate::PermissionObserver* observer) {
permission_observers_.AddObserver(observer);
}
void SmartCardPermissionContext::RemoveObserver(
content::SmartCardDelegate::PermissionObserver* observer) {
permission_observers_.RemoveObserver(observer);
}
void SmartCardPermissionContext::RevokeEphemeralPermissionIfLongTimeoutOccured(
const url::Origin& origin,
const std::string& reader_name) {
auto it_origin = ephemeral_grants_with_expiry_.find(origin);
if (it_origin == ephemeral_grants_with_expiry_.end()) {
return;
}
auto& reader_map = it_origin->second;
auto it_reader = reader_map.find(reader_name);
if (it_reader == reader_map.end()) {
return;
}
if (base::Time::Now() >= it_reader->second) {
reader_map.erase(it_reader);
if (reader_map.empty()) {
ephemeral_grants_with_expiry_.erase(it_origin);
}
NotifyPermissionRevoked(origin);
RecordSmartCardOneTimePermissionExpiryReason(
SmartCardOneTimePermissionExpiryReason::
kSmartCardPermissionExpiredMaxLifetimeReached);
}
}
std::vector<std::unique_ptr<SmartCardPermissionContext::Object>>
SmartCardPermissionContext::GetAllGrantedObjects() {
auto objects = ObjectPermissionContextBase::GetAllGrantedObjects();
const auto& allowlisted_origins = profile_->GetPrefs()->GetList(
prefs::kManagedSmartCardConnectAllowedForUrls);
for (const auto& allowlisted_origin : allowlisted_origins) {
CHECK(allowlisted_origin.is_string());
GURL url = GURL(allowlisted_origin.GetString());
CHECK(url.is_valid());
objects.push_back(std::make_unique<Object>(
url::Origin::Create(url),
ReaderNameToValue(l10n_util::GetStringUTF16(
IDS_SMART_CARD_POLICY_DESCRIPTION_FOR_ANY_DEVICE)),
content_settings::SettingSource::kPolicy, IsOffTheRecord()));
}
return objects;
}