// 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 "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/smart_card/smart_card_reader_tracker.h"
#include "chrome/browser/smart_card/smart_card_reader_tracker_factory.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::InSequence;
using testing::StrictMock;
namespace {
constexpr char kDummyReader[] = "dummy reader";
class FakeOneTimePermissionsTracker : public OneTimePermissionsTracker {
public:
using OneTimePermissionsTracker::NotifyLastPageFromOriginClosed;
};
class FakeSmartCardReaderTracker : public SmartCardReaderTracker {
public:
void Start(Observer* observer, StartCallback callback) override {
observers_.AddObserverIfMissing(observer);
std::vector<ReaderInfo> reader_list;
reader_list.reserve(info_map_.size());
for (const auto& [_, info] : info_map_) {
reader_list.push_back(info);
}
std::move(callback).Run(std::move(reader_list));
}
void Stop(Observer* observer) override {
observers_.RemoveObserver(observer);
}
void AddReader(ReaderInfo info) {
CHECK(!info_map_.contains(info.name));
std::string name = info.name;
info_map_.emplace(std::move(name), std::move(info));
}
void AddReaderWithCard(const std::string& name) {
ReaderInfo info;
info.name = name;
info.present = true;
info.event_count = 1;
AddReader(std::move(info));
}
void RemoveCard(const std::string& reader_name) {
auto it = info_map_.find(reader_name);
CHECK(it != info_map_.end());
ReaderInfo& info = it->second;
CHECK(info.present);
CHECK(!info.empty);
info.present = false;
info.empty = true;
++info.event_count;
observers_.NotifyReaderChanged(info);
}
void RemoveReader(const std::string& reader_name) {
auto it = info_map_.find(reader_name);
CHECK(it != info_map_.end());
info_map_.erase(it);
observers_.NotifyReaderRemoved(reader_name);
}
bool HasObservers() const { return !observers_.empty(); }
private:
ObserverList observers_;
std::map<std::string, ReaderInfo> info_map_;
};
} // namespace
class SmartCardPermissionContextTest : public testing::Test {
protected:
void SetUp() override {
SmartCardReaderTrackerFactory::GetInstance()->SetTestingFactory(
&profile_,
base::BindRepeating(
&SmartCardPermissionContextTest::CreateFakeSmartCardReaderTracker,
base::Unretained(this)));
OneTimePermissionsTrackerFactory::GetInstance()->SetTestingFactory(
&profile_, base::BindRepeating(&SmartCardPermissionContextTest::
CreateFakeOneTimePermissionsTracker,
base::Unretained(this)));
}
bool HasReaderPermission(SmartCardPermissionContext& context,
const url::Origin& origin,
const std::string& reader_name) {
return context.HasReaderPermission(origin, reader_name);
}
void GrantEphemeralReaderPermission(SmartCardPermissionContext& context,
const url::Origin& origin,
const std::string& reader_name) {
return context.GrantEphemeralReaderPermission(origin, reader_name);
}
void GrantPersistentReaderPermission(SmartCardPermissionContext& context,
const url::Origin& origin,
const std::string& reader_name) {
return context.GrantPersistentReaderPermission(origin, reader_name);
}
std::unique_ptr<KeyedService> CreateFakeSmartCardReaderTracker(
content::BrowserContext* context) {
CHECK_EQ(context, &profile_);
return std::make_unique<StrictMock<FakeSmartCardReaderTracker>>();
}
std::unique_ptr<KeyedService> CreateFakeOneTimePermissionsTracker(
content::BrowserContext* context) {
CHECK_EQ(context, &profile_);
return std::make_unique<FakeOneTimePermissionsTracker>();
}
content::BrowserTaskEnvironment task_environment_;
TestingProfile profile_;
};
TEST_F(SmartCardPermissionContextTest, GrantEphemeralReaderPermission) {
auto& reader_tracker = static_cast<FakeSmartCardReaderTracker&>(
SmartCardReaderTrackerFactory::GetForProfile(profile_));
reader_tracker.AddReaderWithCard(kDummyReader);
auto foo_origin = url::Origin::Create(GURL("https://foo.com/"));
SmartCardPermissionContext permission_context(&profile_);
EXPECT_FALSE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
EXPECT_FALSE(reader_tracker.HasObservers());
GrantEphemeralReaderPermission(permission_context, foo_origin, kDummyReader);
EXPECT_TRUE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
EXPECT_TRUE(reader_tracker.HasObservers());
}
TEST_F(SmartCardPermissionContextTest,
RevokeEphemeralPermissionWhenCardRemoved) {
auto& reader_tracker = static_cast<FakeSmartCardReaderTracker&>(
SmartCardReaderTrackerFactory::GetForProfile(profile_));
reader_tracker.AddReaderWithCard(kDummyReader);
auto foo_origin = url::Origin::Create(GURL("https://foo.com/"));
SmartCardPermissionContext permission_context(&profile_);
GrantEphemeralReaderPermission(permission_context, foo_origin, kDummyReader);
ASSERT_TRUE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
ASSERT_TRUE(reader_tracker.HasObservers());
reader_tracker.RemoveCard(kDummyReader);
// The ephemeral permission should have been revoked.
EXPECT_FALSE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
EXPECT_FALSE(reader_tracker.HasObservers());
}
TEST_F(SmartCardPermissionContextTest,
RevokeEphemeralPermissionWhenReaderRemoved) {
auto& reader_tracker = static_cast<FakeSmartCardReaderTracker&>(
SmartCardReaderTrackerFactory::GetForProfile(profile_));
reader_tracker.AddReaderWithCard(kDummyReader);
auto foo_origin = url::Origin::Create(GURL("https://foo.com/"));
SmartCardPermissionContext permission_context(&profile_);
GrantEphemeralReaderPermission(permission_context, foo_origin, kDummyReader);
ASSERT_TRUE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
ASSERT_TRUE(reader_tracker.HasObservers());
reader_tracker.RemoveReader(kDummyReader);
// The ephemeral permission should have been revoked.
EXPECT_FALSE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
EXPECT_FALSE(reader_tracker.HasObservers());
}
TEST_F(SmartCardPermissionContextTest, RevokeEphemeralPermissionWhenAppClosed) {
auto* one_time_tracker = static_cast<FakeOneTimePermissionsTracker*>(
OneTimePermissionsTrackerFactory::GetForBrowserContext(&profile_));
auto foo_origin = url::Origin::Create(GURL("https://foo.com/"));
SmartCardPermissionContext permission_context(&profile_);
GrantEphemeralReaderPermission(permission_context, foo_origin, kDummyReader);
ASSERT_TRUE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
one_time_tracker->NotifyLastPageFromOriginClosed(foo_origin);
// The ephemeral permission should have been revoked.
EXPECT_FALSE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
}
// Covers callers of this method such as the PowerSuspendObserver.
TEST_F(SmartCardPermissionContextTest, RevokeEphemeralPermissions) {
auto foo_origin = url::Origin::Create(GURL("https://foo.com/"));
SmartCardPermissionContext permission_context(&profile_);
GrantEphemeralReaderPermission(permission_context, foo_origin, kDummyReader);
ASSERT_TRUE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
permission_context.RevokeEphemeralPermissions();
EXPECT_FALSE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
}
TEST_F(SmartCardPermissionContextTest, Blocked) {
auto foo_origin = url::Origin::Create(GURL("https://foo.com/"));
SmartCardPermissionContext permission_context(&profile_);
GrantPersistentReaderPermission(permission_context, foo_origin, kDummyReader);
EXPECT_TRUE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
auto* settings_map = HostContentSettingsMapFactory::GetForProfile(&profile_);
settings_map->SetContentSettingDefaultScope(
foo_origin.GetURL(), GURL(), ContentSettingsType::SMART_CARD_GUARD,
ContentSetting::CONTENT_SETTING_BLOCK);
EXPECT_FALSE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
settings_map->SetContentSettingDefaultScope(
foo_origin.GetURL(), GURL(), ContentSettingsType::SMART_CARD_GUARD,
ContentSetting::CONTENT_SETTING_DEFAULT);
EXPECT_TRUE(
HasReaderPermission(permission_context, foo_origin, kDummyReader));
permission_context.RevokeObjectPermissions(foo_origin);
}