diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6541c19fffe1..51b12449a6bc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -69,6 +69,12 @@ components/weekly_storage/ @iefremov # Perf predictor components/brave_perf_predictor/ @iefremov +# Permissions +browser/permissions/**/*expiration* @goodov +browser/permissions/**/*lifetime* @goodov +components/permissions/**/*expiration* @goodov +components/permissions/**/*lifetime* @goodov + # Java patching build/android/bytecode/ @samartnik patches/*.java.patch @samartnik diff --git a/browser/ephemeral_storage/ephemeral_storage_tab_helper.cc b/browser/ephemeral_storage/ephemeral_storage_tab_helper.cc index 80bca6920332..278757b95844 100644 --- a/browser/ephemeral_storage/ephemeral_storage_tab_helper.cc +++ b/browser/ephemeral_storage/ephemeral_storage_tab_helper.cc @@ -86,7 +86,7 @@ void EphemeralStorageTabHelper::ReadyToCommitNavigation( } void EphemeralStorageTabHelper::CreateEphemeralStorageAreasForDomainAndURL( - std::string new_domain, + const std::string& new_domain, const GURL& new_url) { if (new_url.is_empty()) return; diff --git a/browser/ephemeral_storage/ephemeral_storage_tab_helper.h b/browser/ephemeral_storage/ephemeral_storage_tab_helper.h index 649cecc3b429..26048c6e8801 100644 --- a/browser/ephemeral_storage/ephemeral_storage_tab_helper.h +++ b/browser/ephemeral_storage/ephemeral_storage_tab_helper.h @@ -40,7 +40,7 @@ class EphemeralStorageTabHelper content::NavigationHandle* navigation_handle) override; private: - void CreateEphemeralStorageAreasForDomainAndURL(std::string new_domain, + void CreateEphemeralStorageAreasForDomainAndURL(const std::string& new_domain, const GURL& new_url); friend class content::WebContentsUserData; diff --git a/browser/permissions/permission_lifetime_manager_browsertest.cc b/browser/permissions/permission_lifetime_manager_browsertest.cc index f772a2c98755..d481f4b0370e 100644 --- a/browser/permissions/permission_lifetime_manager_browsertest.cc +++ b/browser/permissions/permission_lifetime_manager_browsertest.cc @@ -27,6 +27,7 @@ #include "chrome/test/base/ui_test_utils.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/content_settings/core/common/content_settings_types.h" +#include "components/network_session_configurator/common/network_switches.h" #include "components/permissions/features.h" #include "components/permissions/request_type.h" #include "components/prefs/pref_service.h" @@ -34,6 +35,7 @@ #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" +#include "net/base/features.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "url/gurl.h" @@ -49,22 +51,31 @@ const char kPreTestDataFileName[] = "pre_test_data"; class PermissionLifetimeManagerBrowserTest : public InProcessBrowserTest { public: - PermissionLifetimeManagerBrowserTest() { + PermissionLifetimeManagerBrowserTest() + : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { scoped_feature_list_.InitAndEnableFeature(features::kPermissionLifetime); } ~PermissionLifetimeManagerBrowserTest() override = default; + void SetUpCommandLine(base::CommandLine* command_line) override { + InProcessBrowserTest::SetUpCommandLine(command_line); + command_line->AppendSwitch(switches::kIgnoreCertificateErrors); + } + void SetUpOnMainThread() override { PermissionRequestManager* manager = GetPermissionRequestManager(); prompt_factory_.reset(new MockPermissionLifetimePromptFactory(manager)); host_resolver()->AddRule("*", "127.0.0.1"); - ASSERT_TRUE(embedded_test_server()->Start()); + https_server()->ServeFilesFromDirectory(GetChromeTestDataDir()); + ASSERT_TRUE(https_server()->Start()); } void TearDownOnMainThread() override { prompt_factory_.reset(); } + net::EmbeddedTestServer* https_server() { return &https_server_; } + PermissionRequestManager* GetPermissionRequestManager() { return PermissionRequestManager::FromWebContents( browser()->tab_strip_model()->GetActiveWebContents()); @@ -120,12 +131,13 @@ class PermissionLifetimeManagerBrowserTest : public InProcessBrowserTest { protected: base::test::ScopedFeatureList scoped_feature_list_; + net::test_server::EmbeddedTestServer https_server_; std::unique_ptr prompt_factory_; base::Value pre_test_data_{base::Value::Type::DICTIONARY}; }; IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerBrowserTest, ExpirationSmoke) { - const GURL& url = embedded_test_server()->GetURL("/empty.html"); + const GURL& url = https_server()->GetURL("/empty.html"); ui_test_utils::NavigateToURL(browser(), url); prompt_factory_->set_response_type( PermissionRequestManager::AutoResponseType::ACCEPT_ALL); @@ -169,7 +181,7 @@ IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerBrowserTest, ExpirationSmoke) { IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerBrowserTest, PRE_PermissionExpiredAfterRestart) { - const GURL& url = embedded_test_server()->GetURL("/empty.html"); + const GURL& url = https_server()->GetURL("/empty.html"); ui_test_utils::NavigateToURL(browser(), url); prompt_factory_->set_response_type( PermissionRequestManager::AutoResponseType::ACCEPT_ALL); @@ -227,7 +239,7 @@ IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerBrowserTest, IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerBrowserTest, ExpirationRemovedAfterManualReset) { - const GURL& url = embedded_test_server()->GetURL("/empty.html"); + const GURL& url = https_server()->GetURL("/empty.html"); ui_test_utils::NavigateToURL(browser(), url); prompt_factory_->set_response_type( PermissionRequestManager::AutoResponseType::ACCEPT_ALL); @@ -257,4 +269,175 @@ IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerBrowserTest, EXPECT_TRUE(GetExpirationsPrefValue()->DictEmpty()); } -} // namespace permissions \ No newline at end of file +class PermissionLifetimeManagerWithOriginMonitorBrowserTest + : public PermissionLifetimeManagerBrowserTest { + public: + PermissionLifetimeManagerWithOriginMonitorBrowserTest() { + scoped_feature_list_.InitAndEnableFeature( + net::features::kBraveEphemeralStorage); + } + + protected: + base::test::ScopedFeatureList scoped_feature_list_; +}; + +IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerWithOriginMonitorBrowserTest, + DomainPermissionReset) { + const GURL& url = https_server()->GetURL("host.com", "/empty.html"); + ui_test_utils::NavigateToURL(browser(), url); + prompt_factory_->set_response_type( + PermissionRequestManager::AutoResponseType::ACCEPT_ALL); + + EXPECT_CALL(*prompt_factory_, OnPermissionPromptCreated(_)) + .WillOnce(testing::Invoke([](MockPermissionLifetimePrompt* prompt) { + prompt->delegate()->Requests()[0]->SetLifetime(base::TimeDelta()); + })); + content::ExecuteScriptAsync( + GetActiveMainFrame(), + "navigator.geolocation.getCurrentPosition(function(){});"); + prompt_factory_->WaitForPermissionBubble(); + + EXPECT_EQ(1, prompt_factory_->show_count()); + EXPECT_FALSE(permission_lifetime_timer().IsRunning()); + EXPECT_FALSE(GetExpirationsPrefValue()->DictEmpty()); + + EXPECT_EQ(host_content_settings_map()->GetContentSetting( + url, url, ContentSettingsType::GEOLOCATION), + ContentSetting::CONTENT_SETTING_ALLOW); + + // Navigate to another domain. It should reset the permission. + const GURL& other_url = + https_server()->GetURL("other_host.com", "/empty.html"); + ui_test_utils::NavigateToURL(browser(), other_url); + EXPECT_EQ(host_content_settings_map()->GetContentSetting( + url, url, ContentSettingsType::GEOLOCATION), + ContentSetting::CONTENT_SETTING_ASK); + EXPECT_TRUE(GetExpirationsPrefValue()->DictEmpty()); +} + +IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerWithOriginMonitorBrowserTest, + FriendlyDomainPermissionKept) { + const GURL& url = https_server()->GetURL("example.com", "/empty.html"); + ui_test_utils::NavigateToURL(browser(), url); + prompt_factory_->set_response_type( + PermissionRequestManager::AutoResponseType::ACCEPT_ALL); + + EXPECT_CALL(*prompt_factory_, OnPermissionPromptCreated(_)) + .WillOnce(testing::Invoke([](MockPermissionLifetimePrompt* prompt) { + prompt->delegate()->Requests()[0]->SetLifetime(base::TimeDelta()); + })); + content::ExecuteScriptAsync( + GetActiveMainFrame(), + "navigator.geolocation.getCurrentPosition(function(){});"); + prompt_factory_->WaitForPermissionBubble(); + + EXPECT_EQ(1, prompt_factory_->show_count()); + EXPECT_FALSE(permission_lifetime_timer().IsRunning()); + EXPECT_FALSE(GetExpirationsPrefValue()->DictEmpty()); + + EXPECT_EQ(host_content_settings_map()->GetContentSetting( + url, url, ContentSettingsType::GEOLOCATION), + ContentSetting::CONTENT_SETTING_ALLOW); + + // Navigate to a subdomain, permission should be kept. + const GURL& sub_url = + https_server()->GetURL("sub.example.com", "/empty.html"); + ui_test_utils::NavigateToURL(browser(), sub_url); + EXPECT_EQ(host_content_settings_map()->GetContentSetting( + url, url, ContentSettingsType::GEOLOCATION), + ContentSetting::CONTENT_SETTING_ALLOW); + EXPECT_FALSE(GetExpirationsPrefValue()->DictEmpty()); + + // Navigate to another domain. It should reset the permission. + const GURL& other_url = + https_server()->GetURL("other_host.com", "/empty.html"); + ui_test_utils::NavigateToURL(browser(), other_url); + EXPECT_EQ(host_content_settings_map()->GetContentSetting( + url, url, ContentSettingsType::GEOLOCATION), + ContentSetting::CONTENT_SETTING_ASK); + EXPECT_TRUE(GetExpirationsPrefValue()->DictEmpty()); +} + +IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerWithOriginMonitorBrowserTest, + PublicSuffixListDomainPermissionReset) { + const GURL& url = https_server()->GetURL("user.github.io", "/empty.html"); + ui_test_utils::NavigateToURL(browser(), url); + prompt_factory_->set_response_type( + PermissionRequestManager::AutoResponseType::ACCEPT_ALL); + + EXPECT_CALL(*prompt_factory_, OnPermissionPromptCreated(_)) + .WillOnce(testing::Invoke([](MockPermissionLifetimePrompt* prompt) { + prompt->delegate()->Requests()[0]->SetLifetime(base::TimeDelta()); + })); + content::ExecuteScriptAsync( + GetActiveMainFrame(), + "navigator.geolocation.getCurrentPosition(function(){});"); + prompt_factory_->WaitForPermissionBubble(); + + EXPECT_EQ(1, prompt_factory_->show_count()); + EXPECT_FALSE(permission_lifetime_timer().IsRunning()); + EXPECT_FALSE(GetExpirationsPrefValue()->DictEmpty()); + + EXPECT_EQ(host_content_settings_map()->GetContentSetting( + url, url, ContentSettingsType::GEOLOCATION), + ContentSetting::CONTENT_SETTING_ALLOW); + + // Navigate to a subdomain, permission should be kept. + const GURL& sub_url = + https_server()->GetURL("sub.user.github.io", "/empty.html"); + ui_test_utils::NavigateToURL(browser(), sub_url); + EXPECT_EQ(host_content_settings_map()->GetContentSetting( + url, url, ContentSettingsType::GEOLOCATION), + ContentSetting::CONTENT_SETTING_ALLOW); + EXPECT_FALSE(GetExpirationsPrefValue()->DictEmpty()); + + // Navigate to another domain in PSL. It should reset the permission. + const GURL& other_url = + https_server()->GetURL("user2.github.io", "/empty.html"); + ui_test_utils::NavigateToURL(browser(), other_url); + EXPECT_EQ(host_content_settings_map()->GetContentSetting( + url, url, ContentSettingsType::GEOLOCATION), + ContentSetting::CONTENT_SETTING_ASK); + EXPECT_TRUE(GetExpirationsPrefValue()->DictEmpty()); +} + +IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerWithOriginMonitorBrowserTest, + PRE_DomainPermissionResetAfterRestart) { + const GURL& url = https_server()->GetURL("example.com", "/empty.html"); + ui_test_utils::NavigateToURL(browser(), url); + prompt_factory_->set_response_type( + PermissionRequestManager::AutoResponseType::ACCEPT_ALL); + + EXPECT_CALL(*prompt_factory_, OnPermissionPromptCreated(_)) + .WillOnce(testing::Invoke([](MockPermissionLifetimePrompt* prompt) { + prompt->delegate()->Requests()[0]->SetLifetime(base::TimeDelta()); + })); + content::ExecuteScriptAsync( + GetActiveMainFrame(), + "navigator.geolocation.getCurrentPosition(function(){});"); + prompt_factory_->WaitForPermissionBubble(); + + EXPECT_EQ(1, prompt_factory_->show_count()); + EXPECT_FALSE(permission_lifetime_timer().IsRunning()); + EXPECT_FALSE(GetExpirationsPrefValue()->DictEmpty()); + + EXPECT_EQ(host_content_settings_map()->GetContentSetting( + url, url, ContentSettingsType::GEOLOCATION), + ContentSetting::CONTENT_SETTING_ALLOW); + + pre_test_data_.SetStringKey("url", url.spec()); + WritePreTestData(); +} + +IN_PROC_BROWSER_TEST_F(PermissionLifetimeManagerWithOriginMonitorBrowserTest, + DomainPermissionResetAfterRestart) { + ReadPreTestData(); + const GURL url(*pre_test_data_.FindStringKey("url")); + + EXPECT_EQ(host_content_settings_map()->GetContentSetting( + url, url, ContentSettingsType::GEOLOCATION), + ContentSetting::CONTENT_SETTING_ASK); + EXPECT_TRUE(GetExpirationsPrefValue()->DictEmpty()); +} + +} // namespace permissions diff --git a/browser/permissions/permission_lifetime_manager_factory.cc b/browser/permissions/permission_lifetime_manager_factory.cc index 48de8dd136cd..4e5042275ca7 100644 --- a/browser/permissions/permission_lifetime_manager_factory.cc +++ b/browser/permissions/permission_lifetime_manager_factory.cc @@ -5,13 +5,18 @@ #include "brave/browser/permissions/permission_lifetime_manager_factory.h" +#include +#include + #include "brave/components/permissions/permission_lifetime_manager.h" +#include "brave/components/permissions/permission_origin_lifetime_monitor_impl.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/permissions/permission_manager_factory.h" #include "chrome/browser/profiles/incognito_helpers.h" #include "chrome/browser/profiles/profile.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/permissions/features.h" +#include "net/base/features.h" // static permissions::PermissionLifetimeManager* @@ -43,10 +48,18 @@ KeyedService* PermissionLifetimeManagerFactory::BuildServiceInstanceFor( permissions::features::kPermissionLifetime)) { return nullptr; } + std::unique_ptr + permission_origin_lifetime_monitor; + if (base::FeatureList::IsEnabled(net::features::kBraveEphemeralStorage)) { + permission_origin_lifetime_monitor = + std::make_unique( + context); + } auto* profile = Profile::FromBrowserContext(context); return new permissions::PermissionLifetimeManager( HostContentSettingsMapFactory::GetForProfile(context), - profile->IsOffTheRecord() ? nullptr : profile->GetPrefs()); + profile->IsOffTheRecord() ? nullptr : profile->GetPrefs(), + std::move(permission_origin_lifetime_monitor)); } bool PermissionLifetimeManagerFactory::ServiceIsCreatedWithBrowserContext() diff --git a/browser/permissions/permission_lifetime_manager_unittest.cc b/browser/permissions/permission_lifetime_manager_unittest.cc index 87055940cf16..55474d28d82c 100644 --- a/browser/permissions/permission_lifetime_manager_unittest.cc +++ b/browser/permissions/permission_lifetime_manager_unittest.cc @@ -25,10 +25,12 @@ #include "components/prefs/pref_service.h" #include "components/sync_preferences/testing_pref_service_syncable.h" #include "content/public/test/browser_task_environment.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using content_settings::WebsiteSettingsInfo; using content_settings::WebsiteSettingsRegistry; +using testing::_; namespace permissions { @@ -78,6 +80,35 @@ constexpr base::StringPiece kTwoTypesOneExpirationPrefValue = R"({ } })"; +class MockPermissionOriginLifetimeMonitor + : public PermissionOriginLifetimeMonitor { + public: + MockPermissionOriginLifetimeMonitor() { + ON_CALL(*this, SetOnPermissionOriginDestroyedCallback(_)) + .WillByDefault( + [this](base::RepeatingCallback callback) { + origin_destroyed_callback_ = std::move(callback); + }); + } + + MOCK_METHOD(void, + SetOnPermissionOriginDestroyedCallback, + (base::RepeatingCallback callback), + (override)); + MOCK_METHOD(std::string, + SubscribeToPermissionOriginDestruction, + (const GURL& requesting_origin), + (override)); + + void NotifyOriginDestroyed(const std::string& origin) { + ASSERT_TRUE(origin_destroyed_callback_); + origin_destroyed_callback_.Run(origin); + } + + private: + base::RepeatingCallback origin_destroyed_callback_; +}; + } // namespace class PermissionLifetimeManagerTest : public testing::Test { @@ -100,10 +131,16 @@ class PermissionLifetimeManagerTest : public testing::Test { PrefService* prefs() { return profile_.GetPrefs(); } + virtual std::unique_ptr + GetPermissionOriginLifetimeMonitor() { + return nullptr; + } + PermissionLifetimeManager* manager() { if (!manager_) { manager_ = std::make_unique( - host_content_settings_map_, prefs()); + host_content_settings_map_, prefs(), + GetPermissionOriginLifetimeMonitor()); } return manager_.get(); } @@ -516,4 +553,161 @@ TEST_F(PermissionLifetimeManagerTest, ClearAllExpiredAfterRestart) { ContentSetting::CONTENT_SETTING_DEFAULT); } +class PermissionLifetimeManagerWithOriginMonitorTest + : public PermissionLifetimeManagerTest { + public: + std::unique_ptr + GetPermissionOriginLifetimeMonitor() override { + auto monitor = std::make_unique(); + origin_lifetime_monitor_ = monitor.get(); + EXPECT_CALL(*origin_lifetime_monitor_, + SetOnPermissionOriginDestroyedCallback(_)); + return monitor; + } + + protected: + MockPermissionOriginLifetimeMonitor* origin_lifetime_monitor_ = nullptr; +}; + +TEST_F(PermissionLifetimeManagerWithOriginMonitorTest, + SetAndResetDomainPermission) { + // Create a manager with a mocked origin lifetime monitor. + ASSERT_TRUE(manager()); + auto request(CreateRequestAndAllowContentSetting( + kOrigin, ContentSettingsType::NOTIFICATIONS, base::TimeDelta())); + EXPECT_CALL(*origin_lifetime_monitor_, + SubscribeToPermissionOriginDestruction(kOrigin)) + .WillOnce(testing::Return(kOrigin.host())); + manager()->PermissionDecided(*request, kOrigin, kOrigin, + ContentSetting::CONTENT_SETTING_ALLOW, false); + EXPECT_FALSE(timer().IsRunning()); + + // Check data stored in prefs. + CheckExpirationsPref(FROM_HERE, kOneTypeOneExpirationPrefValue, + {"notifications", kOrigin.host(), kOrigin.spec()}); + + // Invalid host destroy shouldn't trigger any reset. + origin_lifetime_monitor_->NotifyOriginDestroyed("test.com"); + CheckExpirationsPref(FROM_HERE, kOneTypeOneExpirationPrefValue, + {"notifications", kOrigin.host(), kOrigin.spec()}); + + // Destroy origin, this should trigger a setting reset to default state. + origin_lifetime_monitor_->NotifyOriginDestroyed(kOrigin.host()); + ExpectContentSetting(FROM_HERE, kOrigin, ContentSettingsType::NOTIFICATIONS, + ContentSetting::CONTENT_SETTING_DEFAULT); + + // Prefs data should be empty. + CheckExpirationsPref(FROM_HERE, "{}"); +} + +TEST_F(PermissionLifetimeManagerWithOriginMonitorTest, + ResetAllDomainsAfterRestart) { + // Create a manager with a mocked origin lifetime monitor. + ASSERT_TRUE(manager()); + auto request(CreateRequestAndAllowContentSetting( + kOrigin, ContentSettingsType::NOTIFICATIONS, base::TimeDelta())); + auto request2(CreateRequestAndAllowContentSetting( + kOrigin2, ContentSettingsType::NOTIFICATIONS, base::TimeDelta())); + EXPECT_CALL(*origin_lifetime_monitor_, + SubscribeToPermissionOriginDestruction(kOrigin)) + .WillOnce(testing::Return(kOrigin.host())); + EXPECT_CALL(*origin_lifetime_monitor_, + SubscribeToPermissionOriginDestruction(kOrigin2)) + .WillOnce(testing::Return(kOrigin2.host())); + manager()->PermissionDecided(*request, kOrigin, kOrigin, + ContentSetting::CONTENT_SETTING_ALLOW, false); + manager()->PermissionDecided(*request2, kOrigin2, kOrigin2, + ContentSetting::CONTENT_SETTING_ALLOW, false); + EXPECT_FALSE(timer().IsRunning()); + + // Check data stored in prefs. + CheckExpirationsPref(FROM_HERE, kOneTypeTwoExpirationsPrefValue, + {"notifications", kOrigin.host(), kOrigin.spec(), + kOrigin2.host(), kOrigin2.spec()}); + + ResetManager(); + // This will create a new PermissionLifetimeManager instance. + ASSERT_TRUE(manager()); + + ExpectContentSetting(FROM_HERE, kOrigin, ContentSettingsType::NOTIFICATIONS, + ContentSetting::CONTENT_SETTING_DEFAULT); + ExpectContentSetting(FROM_HERE, kOrigin2, ContentSettingsType::NOTIFICATIONS, + ContentSetting::CONTENT_SETTING_DEFAULT); + + // Prefs data should be empty. + CheckExpirationsPref(FROM_HERE, "{}"); +} + +TEST_F(PermissionLifetimeManagerWithOriginMonitorTest, + TimeAndDomainKeyedPermissionsWorks) { + // Create a manager with a mocked origin lifetime monitor. + ASSERT_TRUE(manager()); + auto request(CreateRequestAndAllowContentSetting( + kOrigin, ContentSettingsType::NOTIFICATIONS, kLifetime)); + const base::Time expected_expiration_time = + base::Time::Now() + *request->GetLifetime(); + auto request2(CreateRequestAndAllowContentSetting( + kOrigin2, ContentSettingsType::NOTIFICATIONS, base::TimeDelta())); + EXPECT_CALL(*origin_lifetime_monitor_, + SubscribeToPermissionOriginDestruction(kOrigin)) + .Times(0); + EXPECT_CALL(*origin_lifetime_monitor_, + SubscribeToPermissionOriginDestruction(kOrigin2)) + .WillOnce(testing::Return(kOrigin2.host())); + manager()->PermissionDecided(*request, kOrigin, kOrigin, + ContentSetting::CONTENT_SETTING_ALLOW, false); + manager()->PermissionDecided(*request2, kOrigin2, kOrigin2, + ContentSetting::CONTENT_SETTING_ALLOW, false); + EXPECT_TRUE(timer().IsRunning()); + + // Check data stored in prefs. + CheckExpirationsPref( + FROM_HERE, kOneTypeTwoExpirationsPrefValue, + {"notifications", + std::to_string(expected_expiration_time.ToDeltaSinceWindowsEpoch() + .InMicroseconds()), + kOrigin.spec(), kOrigin2.host(), kOrigin2.spec()}); + + browser_task_environment_.FastForwardBy(*request->GetLifetime()); + ExpectContentSetting(FROM_HERE, kOrigin, ContentSettingsType::NOTIFICATIONS, + ContentSetting::CONTENT_SETTING_DEFAULT); + ExpectContentSetting(FROM_HERE, kOrigin2, ContentSettingsType::NOTIFICATIONS, + ContentSetting::CONTENT_SETTING_ALLOW); + + // Check data stored in prefs. + CheckExpirationsPref(FROM_HERE, kOneTypeOneExpirationPrefValue, + {"notifications", kOrigin2.host(), kOrigin2.spec()}); + + // Destroy origin, this should trigger a setting reset to default state. + origin_lifetime_monitor_->NotifyOriginDestroyed(kOrigin2.host()); + ExpectContentSetting(FROM_HERE, kOrigin2, ContentSettingsType::NOTIFICATIONS, + ContentSetting::CONTENT_SETTING_DEFAULT); + + // Prefs data should be empty. + CheckExpirationsPref(FROM_HERE, "{}"); +} + +TEST_F(PermissionLifetimeManagerWithOriginMonitorTest, + PermissionResetIfDomainKeyIsEmpty) { + // Create a manager with a mocked origin lifetime monitor. + ASSERT_TRUE(manager()); + auto request(CreateRequestAndAllowContentSetting( + kOrigin, ContentSettingsType::NOTIFICATIONS, base::TimeDelta())); + EXPECT_CALL(*origin_lifetime_monitor_, + SubscribeToPermissionOriginDestruction(kOrigin)) + .WillOnce(testing::Return(std::string())); + manager()->PermissionDecided(*request, kOrigin, kOrigin, + ContentSetting::CONTENT_SETTING_ALLOW, false); + + // Nothing should be stored in prefs. + CheckExpirationsPref(FROM_HERE, "{}"); + + // Permission should be reset on the next loop. + ExpectContentSetting(FROM_HERE, kOrigin, ContentSettingsType::NOTIFICATIONS, + ContentSetting::CONTENT_SETTING_ALLOW); + browser_task_environment_.RunUntilIdle(); + ExpectContentSetting(FROM_HERE, kOrigin, ContentSettingsType::NOTIFICATIONS, + ContentSetting::CONTENT_SETTING_DEFAULT); +} + } // namespace permissions diff --git a/chromium_src/chrome/browser/permissions/permission_manager_factory.cc b/chromium_src/chrome/browser/permissions/permission_manager_factory.cc index 8bf5fc78ecd7..e102e5a32bc0 100644 --- a/chromium_src/chrome/browser/permissions/permission_manager_factory.cc +++ b/chromium_src/chrome/browser/permissions/permission_manager_factory.cc @@ -34,4 +34,4 @@ KeyedService* PermissionManagerFactory::BuildServiceInstanceFor( } return new permissions::PermissionManager(profile, std::move(permission_contexts)); -} \ No newline at end of file +} diff --git a/chromium_src/content/browser/tld_ephemeral_lifetime.cc b/chromium_src/content/browser/tld_ephemeral_lifetime.cc index c1547c7db66c..c677fcc519bf 100644 --- a/chromium_src/content/browser/tld_ephemeral_lifetime.cc +++ b/chromium_src/content/browser/tld_ephemeral_lifetime.cc @@ -6,6 +6,7 @@ #include "content/public/browser/tld_ephemeral_lifetime.h" #include + #include "base/no_destructor.h" #include "services/network/public/mojom/cookie_manager.mojom.h" @@ -27,12 +28,13 @@ TLDEphemeralLifetimeMap& active_tld_storage_areas() { } // namespace -TLDEphemeralLifetime::TLDEphemeralLifetime(TLDEphemeralLifetimeKey key, +TLDEphemeralLifetime::TLDEphemeralLifetime(const TLDEphemeralLifetimeKey& key, StoragePartition* storage_partition) : key_(key), storage_partition_(storage_partition) { - DCHECK(active_tld_storage_areas().find(key) == + DCHECK(active_tld_storage_areas().find(key_) == active_tld_storage_areas().end()); - active_tld_storage_areas().emplace(key, weak_factory_.GetWeakPtr()); + DCHECK(storage_partition_); + active_tld_storage_areas().emplace(key_, weak_factory_.GetWeakPtr()); } TLDEphemeralLifetime::~TLDEphemeralLifetime() { @@ -41,27 +43,48 @@ TLDEphemeralLifetime::~TLDEphemeralLifetime() { storage_partition_->GetCookieManagerForBrowserProcess()->DeleteCookies( std::move(filter), base::NullCallback()); + if (!on_destroy_callbacks_.empty()) { + auto on_destroy_callbacks = std::move(on_destroy_callbacks_); + for (auto& callback : on_destroy_callbacks) { + std::move(callback).Run(key_.second); + } + } + active_tld_storage_areas().erase(key_); } -TLDEphemeralLifetime* TLDEphemeralLifetime::Get(BrowserContext* browser_context, - std::string storage_domain) { - TLDEphemeralLifetimeKey key = std::make_pair(browser_context, storage_domain); - auto it = active_tld_storage_areas().find(key); - DCHECK(it == active_tld_storage_areas().end() || it->second.get()); - return it != active_tld_storage_areas().end() ? it->second.get() : nullptr; +// static +TLDEphemeralLifetime* TLDEphemeralLifetime::Get( + BrowserContext* browser_context, + const std::string& storage_domain) { + const TLDEphemeralLifetimeKey key(browser_context, storage_domain); + return Get(key); } +// static scoped_refptr TLDEphemeralLifetime::GetOrCreate( BrowserContext* browser_context, StoragePartition* storage_partition, - std::string storage_domain) { - if (TLDEphemeralLifetime* existing = Get(browser_context, storage_domain)) { + const std::string& storage_domain) { + const TLDEphemeralLifetimeKey key(browser_context, storage_domain); + if (scoped_refptr existing = Get(key)) { return existing; } - TLDEphemeralLifetimeKey key = std::make_pair(browser_context, storage_domain); return base::MakeRefCounted(key, storage_partition); } +// static +TLDEphemeralLifetime* TLDEphemeralLifetime::Get( + const TLDEphemeralLifetimeKey& key) { + auto it = active_tld_storage_areas().find(key); + DCHECK(it == active_tld_storage_areas().end() || it->second.get()); + return it != active_tld_storage_areas().end() ? it->second.get() : nullptr; +} + +void TLDEphemeralLifetime::RegisterOnDestroyCallback( + OnDestroyCallback callback) { + on_destroy_callbacks_.push_back(std::move(callback)); +} + } // namespace content diff --git a/chromium_src/content/public/browser/tld_ephemeral_lifetime.h b/chromium_src/content/public/browser/tld_ephemeral_lifetime.h index c242fc452748..9802eb386dd0 100644 --- a/chromium_src/content/public/browser/tld_ephemeral_lifetime.h +++ b/chromium_src/content/public/browser/tld_ephemeral_lifetime.h @@ -8,6 +8,9 @@ #include #include +#include + +#include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "content/common/content_export.h" @@ -36,20 +39,29 @@ using TLDEphemeralLifetimeKey = class CONTENT_EXPORT TLDEphemeralLifetime : public base::RefCounted { public: - TLDEphemeralLifetime(TLDEphemeralLifetimeKey key, + using OnDestroyCallback = base::OnceCallback; + + TLDEphemeralLifetime(const TLDEphemeralLifetimeKey& key, StoragePartition* storage_partition); - static TLDEphemeralLifetime* Get(BrowserContext*, std::string storage_domain); + static TLDEphemeralLifetime* Get(BrowserContext* browser_context, + const std::string& storage_domain); static scoped_refptr GetOrCreate( BrowserContext* browser_context, StoragePartition* storage_partition, - std::string storage_domain); + const std::string& storage_domain); + + // Add a callback to a callback list to be called on destruction. + void RegisterOnDestroyCallback(OnDestroyCallback callback); private: friend class RefCounted; virtual ~TLDEphemeralLifetime(); + static TLDEphemeralLifetime* Get(const TLDEphemeralLifetimeKey& key); + TLDEphemeralLifetimeKey key_; StoragePartition* storage_partition_; + std::vector on_destroy_callbacks_; base::WeakPtrFactory weak_factory_{this}; }; diff --git a/components/permissions/DEPS b/components/permissions/DEPS index d47bdf490639..8cd9f4d2f82b 100644 --- a/components/permissions/DEPS +++ b/components/permissions/DEPS @@ -3,5 +3,6 @@ include_rules = [ "+components/keyed_service/core", "+components/permissions", "+components/pref_registry", + "+content/public/browser", "+services/preferences/public/cpp", ] diff --git a/components/permissions/permission_expiration_key.cc b/components/permissions/permission_expiration_key.cc new file mode 100644 index 000000000000..7bbae1765335 --- /dev/null +++ b/components/permissions/permission_expiration_key.cc @@ -0,0 +1,81 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/components/permissions/permission_expiration_key.h" + +#include + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" + +namespace permissions { + +namespace { + +base::Time ParseExpirationTime(const std::string& key_str) { + int64_t expiration_time = 0; + if (!base::StringToInt64(key_str, &expiration_time)) { + return base::Time(); + } + return base::Time::FromDeltaSinceWindowsEpoch( + base::TimeDelta::FromMicroseconds(expiration_time)); +} + +std::string ExpirationTimeToStr(base::Time expiration_time) { + return std::to_string( + expiration_time.ToDeltaSinceWindowsEpoch().InMicroseconds()); +} + +} // namespace + +PermissionExpirationKey::PermissionExpirationKey(base::Time time) + : time_(time) {} +PermissionExpirationKey::PermissionExpirationKey(std::string domain) + : time_(base::Time::Max()), domain_(std::move(domain)) { + DCHECK(!domain_.empty()); +} +PermissionExpirationKey::PermissionExpirationKey( + const PermissionExpirationKey&) = default; +PermissionExpirationKey& PermissionExpirationKey::operator=( + const PermissionExpirationKey&) = default; +PermissionExpirationKey::PermissionExpirationKey( + PermissionExpirationKey&&) noexcept = default; +PermissionExpirationKey& PermissionExpirationKey::operator=( + PermissionExpirationKey&&) noexcept = default; +PermissionExpirationKey::~PermissionExpirationKey() = default; + +// static +PermissionExpirationKey PermissionExpirationKey::FromString( + const std::string& key_str) { + auto expiration_time = ParseExpirationTime(key_str); + return !expiration_time.is_null() ? PermissionExpirationKey(expiration_time) + : PermissionExpirationKey(key_str); +} + +std::string PermissionExpirationKey::ToString() const { + return IsTimeKey() ? ExpirationTimeToStr(time_) : domain_; +} + +bool PermissionExpirationKey::operator<( + const PermissionExpirationKey& rhs) const { + auto tie = [](const PermissionExpirationKey& obj) { + return std::tie(obj.time_, obj.domain_); + }; + return tie(*this) < tie(rhs); +} + +bool PermissionExpirationKey::operator==( + const PermissionExpirationKey& rhs) const { + auto tie = [](const PermissionExpirationKey& obj) { + return std::tie(obj.time_, obj.domain_); + }; + return tie(*this) == tie(rhs); +} + +bool PermissionExpirationKey::IsTimeKey() const { + return domain_.empty(); +} + +} // namespace permissions diff --git a/components/permissions/permission_expiration_key.h b/components/permissions/permission_expiration_key.h new file mode 100644 index 000000000000..ce229d864d57 --- /dev/null +++ b/components/permissions/permission_expiration_key.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_PERMISSIONS_PERMISSION_EXPIRATION_KEY_H_ +#define BRAVE_COMPONENTS_PERMISSIONS_PERMISSION_EXPIRATION_KEY_H_ + +#include + +#include "base/time/time.h" + +namespace permissions { + +// Stores permission expiration key (an expiration time or a bound domain). +class PermissionExpirationKey { + public: + explicit PermissionExpirationKey(base::Time time); + explicit PermissionExpirationKey(std::string domain); + PermissionExpirationKey(const PermissionExpirationKey&); + PermissionExpirationKey& operator=(const PermissionExpirationKey&); + PermissionExpirationKey(PermissionExpirationKey&&) noexcept; + PermissionExpirationKey& operator=(PermissionExpirationKey&&) noexcept; + ~PermissionExpirationKey(); + + static PermissionExpirationKey FromString(const std::string& key_str); + std::string ToString() const; + + bool operator<(const PermissionExpirationKey&) const; + bool operator==(const PermissionExpirationKey&) const; + + bool IsTimeKey() const; + const base::Time& time() const { return time_; } + const std::string& domain() const { return domain_; } + + private: + base::Time time_; + std::string domain_; +}; + +} // namespace permissions + +#endif // BRAVE_COMPONENTS_PERMISSIONS_PERMISSION_EXPIRATION_KEY_H_ diff --git a/components/permissions/permission_expirations.cc b/components/permissions/permission_expirations.cc index 66fd548173cb..690600317885 100644 --- a/components/permissions/permission_expirations.cc +++ b/components/permissions/permission_expirations.cc @@ -6,6 +6,8 @@ #include "brave/components/permissions/permission_expirations.h" #include +#include +#include #include "base/stl_util.h" #include "base/strings/strcat.h" @@ -21,6 +23,8 @@ using content_settings::WebsiteSettingsInfo; using content_settings::WebsiteSettingsRegistry; +using prefs::DictionaryValueUpdate; +using prefs::ScopedDictionaryPrefUpdate; namespace permissions { @@ -30,20 +34,6 @@ namespace { constexpr base::StringPiece kRequestingOriginKey = "ro"; constexpr base::StringPiece kEmbeddingOriginKey = "eo"; -base::Time ParseExpirationTime(const std::string& time_str) { - int64_t expiration_time = 0; - if (!base::StringToInt64(time_str, &expiration_time)) { - return base::Time(); - } - return base::Time::FromDeltaSinceWindowsEpoch( - base::TimeDelta::FromMicroseconds(expiration_time)); -} - -std::string ExpirationTimeToStr(base::Time expiration_time) { - return std::to_string( - expiration_time.ToDeltaSinceWindowsEpoch().InMicroseconds()); -} - } // namespace // static @@ -64,11 +54,11 @@ PermissionExpirations::~PermissionExpirations() = default; void PermissionExpirations::AddExpiringPermission( ContentSettingsType content_type, - base::Time expiration_time, + PermissionExpirationKey expiration_key, PermissionOrigins permission_origins) { - expirations_[content_type][expiration_time].push_back( + expirations_[content_type][expiration_key].push_back( std::move(permission_origins)); - UpdateTimeExpirationsPref(content_type, {expiration_time}); + UpdateExpirationsPref(content_type, {expiration_key}); } bool PermissionExpirations::RemoveExpiringPermissions( @@ -79,14 +69,14 @@ bool PermissionExpirations::RemoveExpiringPermissions( return false; } - auto& time_expirations_map = expirations_it->second; - std::vector time_items_to_update_prefs; + auto& key_expirations_map = expirations_it->second; + std::vector expiration_keys_to_update_prefs; // Remove all elements for which |predicate| returned true. - for (auto time_expirations_it = time_expirations_map.begin(); - time_expirations_it != time_expirations_map.end();) { - const auto& expiration_time = time_expirations_it->first; - auto& expiring_permissions = time_expirations_it->second; + for (auto key_expirations_it = key_expirations_map.begin(); + key_expirations_it != key_expirations_map.end();) { + const auto& expiration_key = key_expirations_it->first; + auto& expiring_permissions = key_expirations_it->second; bool is_anything_removed = false; for (auto expiring_permission_it = expiring_permissions.begin(); @@ -102,86 +92,118 @@ bool PermissionExpirations::RemoveExpiringPermissions( // Track removed items to update prefs. if (is_anything_removed) { - time_items_to_update_prefs.push_back(expiration_time); + expiration_keys_to_update_prefs.push_back(expiration_key); } // Remove empty nested containers. if (expiring_permissions.empty()) { - time_expirations_it = time_expirations_map.erase(time_expirations_it); + key_expirations_it = key_expirations_map.erase(key_expirations_it); } else { - ++time_expirations_it; + ++key_expirations_it; } } // If nothing was removed then we're done here. - if (time_items_to_update_prefs.empty()) { + if (expiration_keys_to_update_prefs.empty()) { return false; } // Remove empty nested containers. - if (time_expirations_map.empty()) { + if (key_expirations_map.empty()) { expirations_.erase(expirations_it); } // Update prefs. - UpdateTimeExpirationsPref(content_type, time_items_to_update_prefs); + UpdateExpirationsPref(content_type, expiration_keys_to_update_prefs); return true; } PermissionExpirations::ExpiredPermissions PermissionExpirations::RemoveExpiredPermissions(base::Time current_time) { + return RemoveExpiredPermissionsImpl(base::BindRepeating( + [](const PermissionExpirationKey& expiration_key, + const PermissionExpirations::KeyExpirationsMap& key_expirations) { + return std::make_pair(key_expirations.begin(), + key_expirations.upper_bound(expiration_key)); + }, + PermissionExpirationKey(current_time))); +} + +PermissionExpirations::ExpiredPermissions +PermissionExpirations::RemoveExpiredPermissions(const std::string& domain) { + return RemoveExpiredPermissionsImpl(base::BindRepeating( + [](const PermissionExpirationKey& expiration_key, + const PermissionExpirations::KeyExpirationsMap& key_expirations) { + return key_expirations.equal_range(expiration_key); + }, + PermissionExpirationKey(domain))); +} + +PermissionExpirations::ExpiredPermissions +PermissionExpirations::RemoveAllDomainPermissions() { + return RemoveExpiredPermissionsImpl(base::BindRepeating( + [](const PermissionExpirationKey& expiration_key, + const PermissionExpirations::KeyExpirationsMap& key_expirations) { + return std::make_pair(key_expirations.upper_bound(expiration_key), + key_expirations.end()); + }, + PermissionExpirationKey(base::Time::Max()))); +} + +PermissionExpirations::ExpiredPermissions +PermissionExpirations::RemoveExpiredPermissionsImpl( + base::RepeatingCallback( + const KeyExpirationsMap&)> predicate) { ExpiredPermissions expired_permissions; // Enumerate all content types and remove all expired permissions. for (auto expirations_it = expirations_.begin(); expirations_it != expirations_.end();) { const auto content_type = expirations_it->first; - auto& time_expirations_map = expirations_it->second; - - std::vector time_items_to_clear_prefs; - auto time_expirations_it = time_expirations_map.begin(); - for (; time_expirations_it != time_expirations_map.end(); - ++time_expirations_it) { - const auto& expiration_time = time_expirations_it->first; - auto& expiring_permissions = time_expirations_it->second; - if (expiration_time > current_time) { - // If we encountered an expiration that is still active, then all next - // expirations will also be active (map is sorted). - break; - } + auto& key_expirations_map = expirations_it->second; + + std::vector expiration_keys_to_clear_prefs; + auto iterator_pair = predicate.Run(key_expirations_map); + auto key_expirations_begin_it = + base::ConstCastIterator(key_expirations_map, iterator_pair.first); + auto key_expirations_end_it = + base::ConstCastIterator(key_expirations_map, iterator_pair.second); + for (auto key_expirations_it = key_expirations_begin_it; + key_expirations_it != key_expirations_end_it; ++key_expirations_it) { + const auto& expiration_key = key_expirations_it->first; + auto& expiring_permissions = key_expirations_it->second; std::move(expiring_permissions.begin(), expiring_permissions.end(), std::back_inserter(expired_permissions[content_type])); - time_items_to_clear_prefs.push_back(expiration_time); + expiration_keys_to_clear_prefs.push_back(expiration_key); } - time_expirations_map.erase(time_expirations_map.begin(), - time_expirations_it); + key_expirations_map.erase(key_expirations_begin_it, key_expirations_end_it); // Remove empty nested containers. - if (time_expirations_map.empty()) { + if (key_expirations_map.empty()) { expirations_it = expirations_.erase(expirations_it); } else { ++expirations_it; } // Update prefs. - UpdateTimeExpirationsPref(content_type, time_items_to_clear_prefs); + UpdateExpirationsPref(content_type, expiration_keys_to_clear_prefs); } return expired_permissions; } -void PermissionExpirations::UpdateTimeExpirationsPref( +void PermissionExpirations::UpdateExpirationsPref( ContentSettingsType content_type, - const std::vector& time_items) { + const std::vector& expiration_keys) { if (!prefs_) { return; } // Use a scoped pref update to update only changed pref subkeys. - ::prefs::ScopedDictionaryPrefUpdate update( - prefs_, prefs::kPermissionLifetimeExpirations); - std::unique_ptr<::prefs::DictionaryValueUpdate> time_expirations_val = - update.Get(); - DCHECK(time_expirations_val); + ScopedDictionaryPrefUpdate update(prefs_, + prefs::kPermissionLifetimeExpirations); + std::unique_ptr key_expirations_val = update.Get(); + DCHECK(key_expirations_val); const std::string& content_type_name = WebsiteSettingsRegistry::GetInstance()->Get(content_type)->name(); @@ -189,25 +211,28 @@ void PermissionExpirations::UpdateTimeExpirationsPref( const auto& expirations_it = expirations_.find(content_type); if (expirations_it == expirations_.end()) { // Remove content type if it's absent in a runtime container. - time_expirations_val->RemovePath(content_type_name, nullptr); + key_expirations_val->RemovePath(content_type_name, nullptr); return; } - const auto& time_expirations_map = expirations_it->second; - for (const auto& time_to_update : time_items) { - const auto& time_expirations_it = time_expirations_map.find(time_to_update); - if (time_expirations_it == time_expirations_map.end() || - time_expirations_it->second.empty()) { - // Remove a time element if it's absent or empty in a runtime container. - time_expirations_val->RemovePath( - base::StrCat( - {content_type_name, ".", ExpirationTimeToStr(time_to_update)}), - nullptr); + const auto& key_expirations_map = expirations_it->second; + for (const auto& expiration_key : expiration_keys) { + const auto& key_expirations_it = key_expirations_map.find(expiration_key); + const std::string& key = expiration_key.ToString(); + if (key_expirations_it == key_expirations_map.end() || + key_expirations_it->second.empty()) { + // Remove a key element if it's absent or empty in a runtime container. + std::unique_ptr content_type_expirations_val; + if (key_expirations_val->GetDictionaryWithoutPathExpansion( + content_type_name, &content_type_expirations_val)) { + DCHECK(content_type_expirations_val); + content_type_expirations_val->RemoveWithoutPathExpansion(key, nullptr); + } } else { - // Update a time element if it's not empty in a runtime container. - time_expirations_val->SetPath( - {content_type_name, ExpirationTimeToStr(time_to_update)}, - ExpiringPermissionsToValue(time_expirations_it->second)); + // Update a key element if it's not empty in a runtime container. + key_expirations_val->SetPath( + {content_type_name, key}, + ExpiringPermissionsToValue(key_expirations_it->second)); } } } @@ -226,8 +251,8 @@ void PermissionExpirations::ReadExpirationsFromPrefs() { for (const auto& type_expirations_val : type_expirations_map_val->DictItems()) { const std::string& content_type_name = type_expirations_val.first; - const base::Value& time_expirations_map_val = type_expirations_val.second; - if (!time_expirations_map_val.is_dict()) { + const base::Value& key_expirations_map_val = type_expirations_val.second; + if (!key_expirations_map_val.is_dict()) { continue; } const WebsiteSettingsInfo* website_settings_info = @@ -236,37 +261,32 @@ void PermissionExpirations::ReadExpirationsFromPrefs() { invalid_content_type_names.push_back(content_type_name); continue; } - TimeExpirationsMap time_expirations_map; - for (const auto& time_expiring_permissions_val : - time_expirations_map_val.DictItems()) { - const std::string& time_str = time_expiring_permissions_val.first; - const base::Value& expiring_permissions_val = - time_expiring_permissions_val.second; - const base::Time expiration_time = ParseExpirationTime(time_str); - if (expiration_time.is_null()) { - continue; - } + KeyExpirationsMap key_expirations_map; + for (const auto& key_expirations_val : + key_expirations_map_val.DictItems()) { + const std::string& key_str = key_expirations_val.first; + const base::Value& expiring_permissions_val = key_expirations_val.second; ExpiringPermissions expiring_permissions = ParseExpiringPermissions(expiring_permissions_val); if (expiring_permissions.empty()) { continue; } - time_expirations_map.emplace(expiration_time, - std::move(expiring_permissions)); + key_expirations_map.emplace(PermissionExpirationKey::FromString(key_str), + std::move(expiring_permissions)); } - if (!time_expirations_map.empty()) { + if (!key_expirations_map.empty()) { expirations_.emplace(website_settings_info->type(), - std::move(time_expirations_map)); + std::move(key_expirations_map)); } } if (!invalid_content_type_names.empty()) { ::prefs::ScopedDictionaryPrefUpdate update( prefs_, prefs::kPermissionLifetimeExpirations); - std::unique_ptr<::prefs::DictionaryValueUpdate> time_expirations_val = + std::unique_ptr<::prefs::DictionaryValueUpdate> key_expirations_val = update.Get(); for (const auto& invalid_content_type_name : invalid_content_type_names) { - time_expirations_val->RemovePath(invalid_content_type_name, nullptr); + key_expirations_val->RemovePath(invalid_content_type_name, nullptr); } } } diff --git a/components/permissions/permission_expirations.h b/components/permissions/permission_expirations.h index 1df8a01ead4d..1c23b36841c3 100644 --- a/components/permissions/permission_expirations.h +++ b/components/permissions/permission_expirations.h @@ -7,10 +7,13 @@ #define BRAVE_COMPONENTS_PERMISSIONS_PERMISSION_EXPIRATIONS_H_ #include +#include +#include #include #include "base/callback.h" #include "base/containers/flat_set.h" +#include "brave/components/permissions/permission_expiration_key.h" #include "brave/components/permissions/permission_origins.h" #include "components/content_settings/core/common/content_settings.h" @@ -27,9 +30,10 @@ namespace permissions { class PermissionExpirations { public: using ExpiringPermissions = std::vector; - using TimeExpirationsMap = std::map; - using TypeTimeExpirationsMap = - base::flat_map; + using KeyExpirationsMap = + std::map; + using TypeKeyExpirationsMap = + base::flat_map; using ExpiredPermissions = base::flat_map; @@ -42,21 +46,32 @@ class PermissionExpirations { // Add expiring permission. void AddExpiringPermission(ContentSettingsType content_type, - base::Time expiration_time, + PermissionExpirationKey expiration_time, PermissionOrigins permission_origins); // Remove permission using |predicate|. Returns true if anything was removed. bool RemoveExpiringPermissions( ContentSettingsType content_type, base::RepeatingCallback predicate); - // Remove expired permissions with expiration_time > |current_time|. + // Remove expired permissions with expiration_time >= |current_time|. ExpiredPermissions RemoveExpiredPermissions(base::Time current_time); + // Remove expired permissions with exact |domain|. + ExpiredPermissions RemoveExpiredPermissions(const std::string& domain); + // Remove expired permissions with a domain as a key. + ExpiredPermissions RemoveAllDomainPermissions(); - const TypeTimeExpirationsMap& expirations() const { return expirations_; } + const TypeKeyExpirationsMap& expirations() const { return expirations_; } private: + // Remove expired permissions using |predicate|. + ExpiredPermissions RemoveExpiredPermissionsImpl( + base::RepeatingCallback( + const KeyExpirationsMap&)> predicate); + // Update value in prefs, |time_items| used to update only listed items. - void UpdateTimeExpirationsPref(ContentSettingsType content_type, - const std::vector& time_items); + void UpdateExpirationsPref( + ContentSettingsType content_type, + const std::vector& expiration_keys); void ReadExpirationsFromPrefs(); ExpiringPermissions ParseExpiringPermissions( @@ -67,7 +82,7 @@ class PermissionExpirations { PrefService* const prefs_ = nullptr; // Expirations data from prefs used at runtime. Kept in sync with prefs. - TypeTimeExpirationsMap expirations_; + TypeKeyExpirationsMap expirations_; }; } // namespace permissions diff --git a/components/permissions/permission_expirations_unittest.cc b/components/permissions/permission_expirations_unittest.cc index bccabb0e1f11..95629c29fd1d 100644 --- a/components/permissions/permission_expirations_unittest.cc +++ b/components/permissions/permission_expirations_unittest.cc @@ -6,6 +6,7 @@ #include "brave/components/permissions/permission_expirations.h" #include +#include #include "base/stl_util.h" #include "base/strings/strcat.h" @@ -45,6 +46,20 @@ constexpr base::StringPiece kOneTypeTwoExpirationsPrefValue = R"({ } })"; +constexpr base::StringPiece kOneTypeThreeExpirationsPrefValue = R"({ + "$1": { + "$2": [ + {"ro": "$3"} + ], + "$4": [ + {"ro": "$5"} + ], + "$6": [ + {"ro": "$7"} + ] + } +})"; + constexpr base::StringPiece kTwoTypesOneExpirationPrefValue = R"({ "$1": { "$2": [ @@ -89,7 +104,15 @@ class PermissionExpirationsTest : public testing::Test { void AddExpiringPermission(ContentSettingsType content_type, base::TimeDelta time_delta, const GURL& origin) { - expirations()->AddExpiringPermission(content_type, now_ + time_delta, + expirations()->AddExpiringPermission( + content_type, PermissionExpirationKey(now_ + time_delta), + PermissionOrigins(origin, origin)); + } + + void AddExpiringPermission(ContentSettingsType content_type, + const GURL& origin) { + expirations()->AddExpiringPermission(content_type, + PermissionExpirationKey(origin.host()), PermissionOrigins(origin, origin)); } @@ -105,9 +128,16 @@ class PermissionExpirationsTest : public testing::Test { pref_value_template, subst, nullptr))); } + static std::string TimeKey(const base::Time& time) { + return std::to_string(time.ToDeltaSinceWindowsEpoch().InMicroseconds()); + } + + static std::string DomainKey(const GURL& url) { return url.host(); } + protected: const GURL kOrigin{"https://example.com"}; - const GURL kOrigin2{"https://brave.com"}; + const GURL kOrigin2{"https://brave1.com"}; + const GURL kOrigin3{"https://brave2.com"}; const base::TimeDelta kLifetime{base::TimeDelta::FromSeconds(5)}; const base::TimeDelta kOneSecond{base::TimeDelta::FromSeconds(1)}; @@ -126,10 +156,7 @@ TEST_F(PermissionExpirationsTest, AddAndRemoveAfterExpiration) { // Check data stored in prefs. CheckExpirationsPref( FROM_HERE, kOneTypeOneExpirationPrefValue, - {"notifications", - std::to_string( - expiration_time.ToDeltaSinceWindowsEpoch().InMicroseconds()), - kOrigin.spec()}); + {"notifications", TimeKey(expiration_time), kOrigin.spec()}); auto removed = expirations()->RemoveExpiredPermissions(expiration_time); EXPECT_EQ(removed, PermissionExpirations::ExpiredPermissions( @@ -156,13 +183,8 @@ TEST_F(PermissionExpirationsTest, AddAndRemoveExpiring) { // Check data stored in prefs. CheckExpirationsPref( FROM_HERE, kOneTypeTwoExpirationsPrefValue, - {"notifications", - std::to_string( - expiration_time2.ToDeltaSinceWindowsEpoch().InMicroseconds()), - kOrigin2.spec(), - std::to_string( - expiration_time.ToDeltaSinceWindowsEpoch().InMicroseconds()), - kOrigin.spec()}); + {"notifications", TimeKey(expiration_time2), kOrigin2.spec(), + TimeKey(expiration_time), kOrigin.spec()}); EXPECT_TRUE(expirations()->RemoveExpiringPermissions( ContentSettingsType::NOTIFICATIONS, @@ -173,10 +195,7 @@ TEST_F(PermissionExpirationsTest, AddAndRemoveExpiring) { // Check data stored in prefs. CheckExpirationsPref( FROM_HERE, kOneTypeOneExpirationPrefValue, - {"notifications", - std::to_string( - expiration_time.ToDeltaSinceWindowsEpoch().InMicroseconds()), - kOrigin.spec()}); + {"notifications", TimeKey(expiration_time), kOrigin.spec()}); EXPECT_TRUE(expirations()->RemoveExpiringPermissions( ContentSettingsType::NOTIFICATIONS, @@ -202,13 +221,8 @@ TEST_F(PermissionExpirationsTest, RemoveExpiredDifferentTypes) { // Check data stored in prefs. CheckExpirationsPref( FROM_HERE, kTwoTypesOneExpirationPrefValue, - {"notifications", - std::to_string( - expiration_time.ToDeltaSinceWindowsEpoch().InMicroseconds()), - kOrigin.spec(), "geolocation", - std::to_string( - expiration_time.ToDeltaSinceWindowsEpoch().InMicroseconds()), - kOrigin.spec()}); + {"notifications", TimeKey(expiration_time), kOrigin.spec(), "geolocation", + TimeKey(expiration_time), kOrigin.spec()}); auto removed = expirations()->RemoveExpiredPermissions(expiration_time); EXPECT_EQ(removed, PermissionExpirations::ExpiredPermissions( @@ -247,4 +261,119 @@ TEST_F(PermissionExpirationsTest, ClearInvalidContentType) { CheckExpirationsPref(FROM_HERE, "{}"); } +TEST_F(PermissionExpirationsTest, AddRemoveDomainExpiration) { + AddExpiringPermission(ContentSettingsType::NOTIFICATIONS, kOrigin); + AddExpiringPermission(ContentSettingsType::GEOLOCATION, kOrigin2); + + // Check data stored in prefs. + CheckExpirationsPref(FROM_HERE, kTwoTypesOneExpirationPrefValue, + {"notifications", DomainKey(kOrigin), kOrigin.spec(), + "geolocation", DomainKey(kOrigin2), kOrigin2.spec()}); + + auto removed = expirations()->RemoveExpiredPermissions(kOrigin.host()); + EXPECT_EQ(removed, PermissionExpirations::ExpiredPermissions( + {{ContentSettingsType::NOTIFICATIONS, + {PermissionOrigins(kOrigin, kOrigin)}}})); + + CheckExpirationsPref(FROM_HERE, kOneTypeOneExpirationPrefValue, + {"geolocation", DomainKey(kOrigin2), kOrigin2.spec()}); + + removed = expirations()->RemoveExpiredPermissions(kOrigin2.host()); + EXPECT_EQ(removed, PermissionExpirations::ExpiredPermissions( + {{ContentSettingsType::GEOLOCATION, + {PermissionOrigins(kOrigin2, kOrigin2)}}})); + CheckExpirationsPref(FROM_HERE, "{}"); + + // Nothing should be removed in the end. + EXPECT_TRUE(expirations()->RemoveAllDomainPermissions().empty()); +} + +TEST_F(PermissionExpirationsTest, RemoveDomainThenTimeExpirations) { + const auto expiration_delta = base::TimeDelta::FromSeconds(10); + const auto expiration_time = now_ + expiration_delta; + AddExpiringPermission(ContentSettingsType::NOTIFICATIONS, expiration_delta, + kOrigin); + AddExpiringPermission(ContentSettingsType::GEOLOCATION, kOrigin2); + + // Check data stored in prefs. + CheckExpirationsPref( + FROM_HERE, kTwoTypesOneExpirationPrefValue, + {"notifications", TimeKey(expiration_time), kOrigin.spec(), "geolocation", + DomainKey(kOrigin2), kOrigin2.spec()}); + + auto removed = expirations()->RemoveExpiredPermissions(kOrigin2.host()); + EXPECT_EQ(removed, PermissionExpirations::ExpiredPermissions( + {{ContentSettingsType::GEOLOCATION, + {PermissionOrigins(kOrigin2, kOrigin2)}}})); + + CheckExpirationsPref( + FROM_HERE, kOneTypeOneExpirationPrefValue, + {"notifications", TimeKey(expiration_time), kOrigin.spec()}); + + removed = expirations()->RemoveExpiredPermissions(expiration_time); + EXPECT_EQ(removed, PermissionExpirations::ExpiredPermissions( + {{ContentSettingsType::NOTIFICATIONS, + {PermissionOrigins(kOrigin, kOrigin)}}})); + CheckExpirationsPref(FROM_HERE, "{}"); +} + +TEST_F(PermissionExpirationsTest, RemoveTimeThenDomainExpirations) { + const auto expiration_delta = base::TimeDelta::FromSeconds(10); + const auto expiration_time = now_ + expiration_delta; + AddExpiringPermission(ContentSettingsType::NOTIFICATIONS, expiration_delta, + kOrigin); + AddExpiringPermission(ContentSettingsType::GEOLOCATION, kOrigin2); + + // Check data stored in prefs. + CheckExpirationsPref( + FROM_HERE, kTwoTypesOneExpirationPrefValue, + {"notifications", TimeKey(expiration_time), kOrigin.spec(), "geolocation", + DomainKey(kOrigin2), kOrigin2.spec()}); + + auto removed = expirations()->RemoveExpiredPermissions(expiration_time); + EXPECT_EQ(removed, PermissionExpirations::ExpiredPermissions( + {{ContentSettingsType::NOTIFICATIONS, + {PermissionOrigins(kOrigin, kOrigin)}}})); + + CheckExpirationsPref(FROM_HERE, kOneTypeOneExpirationPrefValue, + {"geolocation", DomainKey(kOrigin2), kOrigin2.spec()}); + + removed = expirations()->RemoveExpiredPermissions(kOrigin2.host()); + EXPECT_EQ(removed, PermissionExpirations::ExpiredPermissions( + {{ContentSettingsType::GEOLOCATION, + {PermissionOrigins(kOrigin2, kOrigin2)}}})); + CheckExpirationsPref(FROM_HERE, "{}"); +} + +TEST_F(PermissionExpirationsTest, RemoveAllDomainExpirations) { + const auto expiration_delta = base::TimeDelta::FromSeconds(10); + const auto expiration_time = now_ + expiration_delta; + AddExpiringPermission(ContentSettingsType::NOTIFICATIONS, expiration_delta, + kOrigin); + AddExpiringPermission(ContentSettingsType::NOTIFICATIONS, kOrigin2); + AddExpiringPermission(ContentSettingsType::NOTIFICATIONS, kOrigin3); + + // Check data stored in prefs. + CheckExpirationsPref(FROM_HERE, kOneTypeThreeExpirationsPrefValue, + {"notifications", TimeKey(expiration_time), + kOrigin.spec(), DomainKey(kOrigin2), kOrigin2.spec(), + DomainKey(kOrigin3), kOrigin3.spec()}); + + auto removed = expirations()->RemoveAllDomainPermissions(); + EXPECT_EQ(removed, PermissionExpirations::ExpiredPermissions( + {{ContentSettingsType::NOTIFICATIONS, + {{PermissionOrigins(kOrigin2, kOrigin2)}, + PermissionOrigins(kOrigin3, kOrigin3)}}})); + + CheckExpirationsPref( + FROM_HERE, kOneTypeOneExpirationPrefValue, + {"notifications", TimeKey(expiration_time), kOrigin.spec()}); + + removed = expirations()->RemoveExpiredPermissions(expiration_time); + EXPECT_EQ(removed, PermissionExpirations::ExpiredPermissions( + {{ContentSettingsType::NOTIFICATIONS, + {PermissionOrigins(kOrigin, kOrigin)}}})); + CheckExpirationsPref(FROM_HERE, "{}"); +} + } // namespace permissions diff --git a/components/permissions/permission_lifetime_manager.cc b/components/permissions/permission_lifetime_manager.cc index 9b38e285c161..c4f3ef57e21b 100644 --- a/components/permissions/permission_lifetime_manager.cc +++ b/components/permissions/permission_lifetime_manager.cc @@ -6,9 +6,11 @@ #include "brave/components/permissions/permission_lifetime_manager.h" #include +#include #include "base/auto_reset.h" #include "base/stl_util.h" +#include "base/threading/sequenced_task_runner_handle.h" #include "brave/components/permissions/permission_lifetime_pref_names.h" #include "components/content_settings/core/browser/content_settings_registry.h" #include "components/content_settings/core/browser/content_settings_utils.h" @@ -34,9 +36,13 @@ void PermissionLifetimeManager::RegisterProfilePrefs( PermissionLifetimeManager::PermissionLifetimeManager( HostContentSettingsMap* host_content_settings_map, - PrefService* prefs) + PrefService* prefs, + std::unique_ptr + permission_origin_lifetime_monitor) : host_content_settings_map_(host_content_settings_map), prefs_(prefs), + permission_origin_lifetime_monitor_( + std::move(permission_origin_lifetime_monitor)), permission_expirations_(prefs_), expiration_timer_(std::make_unique()) { DCHECK(host_content_settings_map_); @@ -44,6 +50,14 @@ PermissionLifetimeManager::PermissionLifetimeManager( ResetExpiredPermissionsAndUpdateTimer(base::Time::Now()); + if (permission_origin_lifetime_monitor_) { + ResetAllDomainPermissions(); + permission_origin_lifetime_monitor_->SetOnPermissionOriginDestroyedCallback( + base::BindRepeating( + &PermissionLifetimeManager::OnPermissionOriginDestroyed, + base::Unretained(this))); + } + host_content_settings_map_observation_.Observe(host_content_settings_map_); } @@ -51,6 +65,7 @@ PermissionLifetimeManager::~PermissionLifetimeManager() {} void PermissionLifetimeManager::Shutdown() { host_content_settings_map_observation_.Reset(); + permission_origin_lifetime_monitor_.reset(); StopExpirationTimer(); } @@ -68,14 +83,13 @@ void PermissionLifetimeManager::PermissionDecided( } const auto& lifetime = permission_request.GetLifetime(); - if (!lifetime || *lifetime == base::TimeDelta()) { + if (!lifetime) { // If no lifetime is set, then we don't need to do anything here. return; } const ContentSettingsType content_type = permission_request.GetContentSettingsType(); - const base::Time expiration_time = base::Time::Now() + *lifetime; DVLOG(1) << "PermissionLifetimeManager::PermissionDecided" << "\ntype: " @@ -87,11 +101,32 @@ void PermissionLifetimeManager::PermissionDecided( << "\nlifetime: " << permission_request.GetLifetime()->InSeconds() << " seconds"; - permission_expirations_.AddExpiringPermission( - content_type, expiration_time, - PermissionOrigins(requesting_origin, embedding_origin)); - - UpdateExpirationTimer(); + if (*lifetime == base::TimeDelta()) { + DCHECK(permission_origin_lifetime_monitor_); + std::string key = + permission_origin_lifetime_monitor_ + ->SubscribeToPermissionOriginDestruction(requesting_origin); + if (key.empty()) { + // There is no any active origin with this key, so reset the permission + // right away. PostTask is required because at this point the permission + // is not stored in HostContentSettingsMap yet. + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&PermissionLifetimeManager::ResetPermission, + weak_ptr_factory_.GetWeakPtr(), content_type, + requesting_origin, embedding_origin)); + return; + } + permission_expirations_.AddExpiringPermission( + content_type, PermissionExpirationKey(std::move(key)), + PermissionOrigins(requesting_origin, embedding_origin)); + } else { + const base::Time expiration_time = base::Time::Now() + *lifetime; + permission_expirations_.AddExpiringPermission( + content_type, PermissionExpirationKey(expiration_time), + PermissionOrigins(requesting_origin, embedding_origin)); + UpdateExpirationTimer(); + } } void PermissionLifetimeManager::OnContentSettingChanged( @@ -154,12 +189,13 @@ void PermissionLifetimeManager::RestartExpirationTimerForTesting() { void PermissionLifetimeManager::UpdateExpirationTimer() { base::Time nearest_expiration_time(base::Time::Max()); for (const auto& type_expirations : permission_expirations_.expirations()) { - const auto& time_expirations_map = type_expirations.second; - if (time_expirations_map.empty()) { + const auto& key_expirations_map = type_expirations.second; + if (key_expirations_map.empty() || + !key_expirations_map.begin()->first.IsTimeKey()) { continue; } - nearest_expiration_time = - std::min(nearest_expiration_time, time_expirations_map.begin()->first); + nearest_expiration_time = std::min( + nearest_expiration_time, key_expirations_map.begin()->first.time()); } if (nearest_expiration_time == base::Time::Max()) { @@ -200,13 +236,47 @@ void PermissionLifetimeManager::ResetExpiredPermissionsAndUpdateTimer( const auto& content_type = expired_permissions.first; const auto& expiring_permissions = expired_permissions.second; for (const auto& expiring_permission : expiring_permissions) { - host_content_settings_map_->SetContentSettingDefaultScope( - expiring_permission.requesting_origin(), - expiring_permission.embedding_origin(), content_type, - CONTENT_SETTING_DEFAULT); + ResetPermission(content_type, expiring_permission.requesting_origin(), + expiring_permission.embedding_origin()); } } UpdateExpirationTimer(); } +void PermissionLifetimeManager::OnPermissionOriginDestroyed( + const std::string& origin_key) { + base::AutoReset auto_reset(&is_currently_removing_permissions_, true); + for (const auto& expired_permissions : + permission_expirations_.RemoveExpiredPermissions(origin_key)) { + const auto& content_type = expired_permissions.first; + const auto& expiring_permissions = expired_permissions.second; + for (const auto& expiring_permission : expiring_permissions) { + ResetPermission(content_type, expiring_permission.requesting_origin(), + expiring_permission.embedding_origin()); + } + } +} + +void PermissionLifetimeManager::ResetAllDomainPermissions() { + base::AutoReset auto_reset(&is_currently_removing_permissions_, true); + for (const auto& expired_permissions : + permission_expirations_.RemoveAllDomainPermissions()) { + const auto& content_type = expired_permissions.first; + const auto& expiring_permissions = expired_permissions.second; + for (const auto& expiring_permission : expiring_permissions) { + ResetPermission(content_type, expiring_permission.requesting_origin(), + expiring_permission.embedding_origin()); + } + } +} + +void PermissionLifetimeManager::ResetPermission( + ContentSettingsType content_type, + const GURL& requesting_origin, + const GURL& embedding_origin) { + host_content_settings_map_->SetContentSettingDefaultScope( + requesting_origin, embedding_origin, content_type, + CONTENT_SETTING_DEFAULT); +} + } // namespace permissions diff --git a/components/permissions/permission_lifetime_manager.h b/components/permissions/permission_lifetime_manager.h index a9ee17b56edb..f3da4cb179cb 100644 --- a/components/permissions/permission_lifetime_manager.h +++ b/components/permissions/permission_lifetime_manager.h @@ -8,10 +8,13 @@ #include #include +#include +#include "base/memory/weak_ptr.h" #include "base/scoped_observation.h" #include "base/util/timer/wall_clock_timer.h" #include "brave/components/permissions/permission_expirations.h" +#include "brave/components/permissions/permission_origin_lifetime_monitor.h" #include "components/content_settings/core/browser/content_settings_observer.h" #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/content_settings/core/common/content_settings.h" @@ -35,7 +38,9 @@ class PermissionLifetimeManager : public KeyedService, static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); PermissionLifetimeManager(HostContentSettingsMap* host_content_settings_map, - PrefService* prefs); + PrefService* prefs, + std::unique_ptr + permission_origin_lifetime_monitor); PermissionLifetimeManager(const PermissionLifetimeManager&) = delete; PermissionLifetimeManager& operator=(const PermissionLifetimeManager&) = delete; @@ -71,9 +76,18 @@ class PermissionLifetimeManager : public KeyedService, void OnExpirationTimer(); void ResetExpiredPermissionsAndUpdateTimer( base::Time current_expiration_time); + // Resets permission, updates pref. + void OnPermissionOriginDestroyed(const std::string& origin_key); + void ResetAllDomainPermissions(); + void ResetPermission(ContentSettingsType content_type, + const GURL& requesting_origin, + const GURL& embedding_origin); HostContentSettingsMap* const host_content_settings_map_ = nullptr; PrefService* const prefs_ = nullptr; + // Origin lifetime monitor enables origin-based permission lifetime support. + std::unique_ptr + permission_origin_lifetime_monitor_; // Expirations data from prefs used at runtime. Kept in sync with prefs. PermissionExpirations permission_expirations_; // WallClockTimer to reset permissions properly even if a machine was put in @@ -89,6 +103,8 @@ class PermissionLifetimeManager : public KeyedService, // Flag to ignore notifications from HostContentSettingsMap when a permission // reset is in progress. bool is_currently_removing_permissions_ = false; + + base::WeakPtrFactory weak_ptr_factory_{this}; }; } // namespace permissions diff --git a/components/permissions/permission_lifetime_pref_names.h b/components/permissions/permission_lifetime_pref_names.h index 014f6c7cf05b..a85980d18048 100644 --- a/components/permissions/permission_lifetime_pref_names.h +++ b/components/permissions/permission_lifetime_pref_names.h @@ -12,7 +12,8 @@ namespace prefs { // General pref for all permission lifetime logic. constexpr char kPermissionLifetimeRoot[] = "permission_lifetime"; // Expiration pref to store currently expiring permissions. -constexpr char kPermissionLifetimeExpirations[] = "permission_lifetime.expirations"; +constexpr char kPermissionLifetimeExpirations[] = + "permission_lifetime.expirations"; } // namespace prefs } // namespace permissions diff --git a/components/permissions/permission_lifetime_utils.cc b/components/permissions/permission_lifetime_utils.cc index dc763b9e5041..b8d79b8b0544 100644 --- a/components/permissions/permission_lifetime_utils.cc +++ b/components/permissions/permission_lifetime_utils.cc @@ -5,6 +5,9 @@ #include "brave/components/permissions/permission_lifetime_utils.h" +#include +#include + #include "base/command_line.h" #include "base/optional.h" #include "base/strings/string_number_conversions.h" @@ -12,6 +15,7 @@ #include "base/strings/utf_string_conversions.h" #include "components/grit/brave_components_strings.h" #include "components/permissions/features.h" +#include "net/base/features.h" #include "ui/base/l10n/l10n_util.h" namespace permissions { @@ -42,17 +46,15 @@ base::Optional GetTestSecondsOption() { std::vector CreatePermissionLifetimeOptions() { std::vector options; - const size_t kOptionsCount = 3; + const size_t kOptionsCount = 4; options.reserve(kOptionsCount); - // TODO(https://github.com/brave/brave-browser/issues/14126): Add support for - // "until page is closed" lifetime. -#if 0 - options.emplace_back(PermissionLifetimeOption( - l10n_util::GetStringUTF16( - IDS_PERMISSIONS_BUBBLE_UNTIL_PAGE_CLOSE_LIFETIME_OPTION), - base::TimeDelta())); -#endif // 0 + if (base::FeatureList::IsEnabled(net::features::kBraveEphemeralStorage)) { + options.emplace_back(PermissionLifetimeOption( + l10n_util::GetStringUTF16( + IDS_PERMISSIONS_BUBBLE_UNTIL_PAGE_CLOSE_LIFETIME_OPTION), + base::TimeDelta())); + } options.emplace_back(PermissionLifetimeOption( l10n_util::GetStringUTF16( IDS_PERMISSIONS_BUBBLE_24_HOURS_LIFETIME_OPTION), @@ -63,7 +65,7 @@ std::vector CreatePermissionLifetimeOptions() { options.emplace_back(PermissionLifetimeOption( l10n_util::GetStringUTF16(IDS_PERMISSIONS_BUBBLE_FOREVER_LIFETIME_OPTION), base::nullopt)); - DCHECK_EQ(options.size(), kOptionsCount); + DCHECK_LE(options.size(), kOptionsCount); // This is strictly for manual testing. if (auto test_seconds_option = GetTestSecondsOption()) { diff --git a/components/permissions/permission_origin_lifetime_monitor.h b/components/permissions/permission_origin_lifetime_monitor.h new file mode 100644 index 000000000000..04d3efbe0a9e --- /dev/null +++ b/components/permissions/permission_origin_lifetime_monitor.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_PERMISSIONS_PERMISSION_ORIGIN_LIFETIME_MONITOR_H_ +#define BRAVE_COMPONENTS_PERMISSIONS_PERMISSION_ORIGIN_LIFETIME_MONITOR_H_ + +#include + +#include "base/callback.h" +#include "url/gurl.h" + +namespace permissions { + +// Helper to support an origin-based permission lifetime logic. +class PermissionOriginLifetimeMonitor { + public: + virtual ~PermissionOriginLifetimeMonitor() = default; + + // Set a callback to call when a permission origin is destroyed. + // Callback will receive a string returned by + // |SubscribeToPermissionOriginDestruction|. + virtual void SetOnPermissionOriginDestroyedCallback( + base::RepeatingCallback callback) = 0; + + // Subscribe to a permission origin destruction. Returns an ephemeral storage + // domain or an empty string if a storage partition for |requesting_origin| + // doesn't exist. Returned string will be used in a callback. + virtual std::string SubscribeToPermissionOriginDestruction( + const GURL& requesting_origin) = 0; +}; + +} // namespace permissions + +#endif // BRAVE_COMPONENTS_PERMISSIONS_PERMISSION_ORIGIN_LIFETIME_MONITOR_H_ diff --git a/components/permissions/permission_origin_lifetime_monitor_impl.cc b/components/permissions/permission_origin_lifetime_monitor_impl.cc new file mode 100644 index 000000000000..9a80ee078d5e --- /dev/null +++ b/components/permissions/permission_origin_lifetime_monitor_impl.cc @@ -0,0 +1,66 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/components/permissions/permission_origin_lifetime_monitor_impl.h" + +#include + +#include "base/stl_util.h" +#include "content/public/browser/tld_ephemeral_lifetime.h" +#include "net/base/features.h" +#include "net/base/url_util.h" + +namespace permissions { + +PermissionOriginLifetimeMonitorImpl::PermissionOriginLifetimeMonitorImpl( + content::BrowserContext* browser_context) + : browser_context_(browser_context) { + DCHECK(browser_context_); + DCHECK(base::FeatureList::IsEnabled(net::features::kBraveEphemeralStorage)); +} + +PermissionOriginLifetimeMonitorImpl::~PermissionOriginLifetimeMonitorImpl() = + default; + +void PermissionOriginLifetimeMonitorImpl:: + SetOnPermissionOriginDestroyedCallback( + base::RepeatingCallback callback) { + permission_destroyed_callback_ = std::move(callback); +} + +std::string +PermissionOriginLifetimeMonitorImpl::SubscribeToPermissionOriginDestruction( + const GURL& requesting_origin) { + DCHECK(permission_destroyed_callback_); + std::string storage_domain = + net::URLToEphemeralStorageDomain(requesting_origin); + auto* tld_ephemeral_lifetime = + content::TLDEphemeralLifetime::Get(browser_context_, storage_domain); + if (!tld_ephemeral_lifetime) { + DCHECK(!base::Contains(active_subscriptions_, storage_domain)); + // If an ephemeral lifetime object doesn't exist, treat a permission origin + // as an already destroyed one. + return std::string(); + } + + if (!base::Contains(active_subscriptions_, storage_domain)) { + tld_ephemeral_lifetime->RegisterOnDestroyCallback(base::BindOnce( + &PermissionOriginLifetimeMonitorImpl::OnEphemeralTLDDestroyed, + weak_ptr_factory_.GetWeakPtr())); + active_subscriptions_.insert(storage_domain); + } + return storage_domain; +} + +void PermissionOriginLifetimeMonitorImpl::OnEphemeralTLDDestroyed( + const std::string& storage_domain) { + DCHECK(base::Contains(active_subscriptions_, storage_domain)); + active_subscriptions_.erase(storage_domain); + if (permission_destroyed_callback_) { + permission_destroyed_callback_.Run(storage_domain); + } +} + +} // namespace permissions diff --git a/components/permissions/permission_origin_lifetime_monitor_impl.h b/components/permissions/permission_origin_lifetime_monitor_impl.h new file mode 100644 index 000000000000..66a43c5af134 --- /dev/null +++ b/components/permissions/permission_origin_lifetime_monitor_impl.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2021 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_PERMISSIONS_PERMISSION_ORIGIN_LIFETIME_MONITOR_IMPL_H_ +#define BRAVE_COMPONENTS_PERMISSIONS_PERMISSION_ORIGIN_LIFETIME_MONITOR_IMPL_H_ + +#include + +#include "base/containers/flat_set.h" +#include "base/memory/weak_ptr.h" +#include "brave/components/permissions/permission_origin_lifetime_monitor.h" + +namespace content { +class BrowserContext; +} // namespace content + +namespace permissions { + +// Uses TLDEphemeralLifetime to observe a permission origin destruction. +class PermissionOriginLifetimeMonitorImpl + : public PermissionOriginLifetimeMonitor { + public: + explicit PermissionOriginLifetimeMonitorImpl( + content::BrowserContext* browser_context); + PermissionOriginLifetimeMonitorImpl( + const PermissionOriginLifetimeMonitorImpl&) = delete; + PermissionOriginLifetimeMonitorImpl& operator=( + const PermissionOriginLifetimeMonitorImpl&) = delete; + ~PermissionOriginLifetimeMonitorImpl() override; + + void SetOnPermissionOriginDestroyedCallback( + base::RepeatingCallback callback) override; + std::string SubscribeToPermissionOriginDestruction( + const GURL& requesting_origin) override; + + private: + void OnEphemeralTLDDestroyed(const std::string& storage_domain); + + content::BrowserContext* const browser_context_ = nullptr; + + base::RepeatingCallback + permission_destroyed_callback_; + base::flat_set active_subscriptions_; + + base::WeakPtrFactory weak_ptr_factory_{ + this}; +}; + +} // namespace permissions + +#endif // BRAVE_COMPONENTS_PERMISSIONS_PERMISSION_ORIGIN_LIFETIME_MONITOR_IMPL_H_ diff --git a/components/permissions/sources.gni b/components/permissions/sources.gni index 5c125f7aad12..b46788cd6689 100644 --- a/components/permissions/sources.gni +++ b/components/permissions/sources.gni @@ -1,4 +1,6 @@ brave_components_permissions_sources = [ + "//brave/components/permissions/permission_expiration_key.cc", + "//brave/components/permissions/permission_expiration_key.h", "//brave/components/permissions/permission_expirations.cc", "//brave/components/permissions/permission_expirations.h", "//brave/components/permissions/permission_lifetime_manager.cc", @@ -8,6 +10,9 @@ brave_components_permissions_sources = [ "//brave/components/permissions/permission_lifetime_pref_names.h", "//brave/components/permissions/permission_lifetime_utils.cc", "//brave/components/permissions/permission_lifetime_utils.h", + "//brave/components/permissions/permission_origin_lifetime_monitor.h", + "//brave/components/permissions/permission_origin_lifetime_monitor_impl.cc", + "//brave/components/permissions/permission_origin_lifetime_monitor_impl.h", "//brave/components/permissions/permission_origins.cc", "//brave/components/permissions/permission_origins.h", ]